def finalize_options(self): if self.bdist_dir is None: bdist_base = self.get_finalized_command("bdist").bdist_base self.bdist_dir = os.path.join(bdist_base, "xar") if self.dist_dir is None: script_name = os.path.expanduser(self.distribution.script_name) package_dir = os.path.dirname(os.path.realpath(script_name)) self.dist_dir = os.path.join(package_dir, "dist") if self.console_scripts is not None: self.console_scripts = self.console_scripts.strip().split(",") self.sqopts = xar_util.SquashfsOptions() if self.xar_compression_algorithm is not None: self.sqopts.compression_algorithm = self.xar_compression_algorithm else: self.sqopts.compression_algorithm = "gzip" if self.xar_block_size is not None: self.sqopts.block_size = self.xar_block_size if self.xar_zstd_level is not None: self.sqopts.zstd_level = self.xar_zstd_level self.xar_outputs = [] self.working_set = pkg_resources.WorkingSet(sys.path) self.installer = None if self.download: bdist_pip = os.path.join(self.bdist_dir, "downloads") mkpath(bdist_pip) self.installer = pip_installer.PipInstaller( bdist_pip, self.working_set, log)
def setUp(self): self.xar_builder = xar_builder.XarBuilder() self.sqopts = xar_util.SquashfsOptions() self.sqopts.compression_algorithm = "gzip" self.src = xar_util.StagingDirectory() # A set of files for testing self.files = { "executable.sh": ("executable.sh", "#!echo executable", "w", 0o755), "lib.so": ("lib.so", b"binary source", "wb", 0o644), "source.txt": ("source.txt", "text source", "w", 0o644), "subdir/source.txt": ("subdir/source.txt", "subdir", "w", 0o644), } for filename, data, mode, permissions in self.files.values(): self.src.write(data, filename, mode, permissions) self.assertEqual( xar_test_helpers.mode(self.src.absolute(filename)), permissions)
def build(self, filename, squashfs_options=None): """ Actually build the XAR. Freezes the XarBuilder if not already frozen. Writes the XAR to `filename`. Uses `xar_util.SquashfsOptions` `squashfs_options` to construct the XAR. Finally calls :func:`delete`. The XarBuilder is no longer usable after this call. """ if squashfs_options is None: squashfs_options = xar_util.SquashfsOptions() if not self._frozen: self.freeze() xarfiles = {} base_name, xar_ext = os.path.splitext(filename) # Build the dependent XARs for ext, destination in self._partition_dest: ext_filename = base_name + ext + xar_ext with tempfile.NamedTemporaryFile(delete=False) as tf: xarfiles[ext] = (ext_filename, tf.name) self._build_staging_dir( destination.staging, xarfiles[ext][1], BORING_SHEBANG, {}, squashfs_options, ) # Build the main XAR with tempfile.NamedTemporaryFile(delete=False) as tf: tmp_xar = tf.name xar_header = self._build_xar_header(xarfiles) self._build_staging_dir(self._staging, tmp_xar, self._shebang, xar_header, squashfs_options) # Move the results into place shutil.move(tmp_xar, filename) for ext_filename, tmp_filename in xarfiles.values(): shutil.move(tmp_filename, ext_filename) # Make the output executable if necessary if self._executable is not None: os.chmod(filename, 0o755) self.delete()
def main(): logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s %(message)s") p = argparse.ArgumentParser() p.add_argument("--output", required=True, help="Output XAR file.") # XAR options p.add_argument( "--xar-exec", help="Path to xarexec, which must be present to run the XAR file.", default="/usr/bin/env xarexec_fuse", ) p.add_argument( "--xar-mount-root", default=None, help="Where the XAR file will mount by default.", ) p.add_argument( "--xar-compression-algorithm", default="gzip", help="Compression algorithm for the XAR file.", ) p.add_argument( "--xar-block-size", default=256 * 1024, help="Block size used when compressing the XAR file.", ) p.add_argument( "--xar-zstd-level", default=16, help="Default zstd level when zstd compression is used.", ) group = p.add_mutually_exclusive_group(required=True) # Python options group.add_argument( "--python", help="Make an executable python XAR from the given" "directory or zip archive.", ) p.add_argument( "--python-interpreter", default=None, help="Python interpreter for building and running the XAR. " "If not set and constructing from a zip archive we try " "to extract the shebang to get the Python interpreter. " "Otherwise we default to 'python'.", ) p.add_argument( "--python-entry-point", default=None, help="MODULE[:FUNCTION]" "The entry point for the python XAR. If unset, we look " "for a __main__ module in the XAR and use that.", ) # Raw options group.add_argument("--raw", help="Make a raw xar from a directory") p.add_argument( "--raw-executable", default=None, help="Executable invoked once the XAR is mounted. It must " "be a path relative to the XAR root. If unset the XAR " "is not executable.", ) opts = p.parse_args() squashfs_options = xar_util.SquashfsOptions() squashfs_options.compression_algorithm = opts.xar_compression_algorithm squashfs_options.block_size = opts.xar_block_size squashfs_options.zstd_level = opts.xar_zstd_level if opts.python: xar = xar_builder.PythonXarBuilder(opts.xar_exec, opts.xar_mount_root) interpreter = opts.python_interpreter entry_point = opts.python_entry_point # Either copy the directory or extract the archive. # Infer interpreter and entry_point if unset. if os.path.isdir(opts.python): xar.add_directory(opts.python) entry_point = entry_point or xar_util.get_python_main(opts.python) else: z_interpreter, z_entry_point = py_util.extract_python_archive_info( opts.python) with zipfile.ZipFile(opts.python) as zf: interpreter = interpreter or z_interpreter entry_point = entry_point or z_entry_point xar.add_zipfile(zf) if entry_point is None: raise XarArgumentError( "Python entry point not set and no __main__") xar.set_interpreter(interpreter) xar.set_entry_point(entry_point) elif opts.raw: xar = xar_builder.XarBuilder(opts.xar_exec, opts.xar_mount_root) xar.add_directory(opts.raw) if opts.raw_executable is not None: xar.set_executable(opts.raw_executable) else: raise ValueError("Unexpected value") xar.build(opts.output, squashfs_options) return 0