Skip to content

Instantly share code, notes, and snippets.

@tsibley
Created May 11, 2022 16:47
Show Gist options
  • Select an option

  • Save tsibley/bfcf8ccbac1a32f76b529a47de1d6fe6 to your computer and use it in GitHub Desktop.

Select an option

Save tsibley/bfcf8ccbac1a32f76b529a47de1d6fe6 to your computer and use it in GitHub Desktop.
# This file defines how PyOxidizer application building and packaging is
# performed. See PyOxidizer's documentation at
# https://pyoxidizer.readthedocs.io/en/stable/ for details of this
# configuration file format.
# Define how to bundle a Python interpreter + our Python code + shared
# libraries + other resources into an executable and adjacent filesystem "lib/"
# tree.
def make_exe():
# Obtain the default PythonDistribution for our build target. We link this
# distribution into our produced executable and extract the Python standard
# library from it.
python_dist = default_python_distribution()
# This function creates a `PythonPackagingPolicy` instance, which
# influences how executables are built and how resources are added to the
# executable. You can customize the default behavior by assigning to
# attributes and calling functions.
packaging_policy = python_dist.make_python_packaging_policy()
# Emit both classified resources (PythonModuleSource, etc) and unclassified
# "File" resources, but only include the former in packaging by default.
# Allow "File" resource to be explicitly added on a case-by-case basis.
# See also our exe_resource_policy_decision().
packaging_policy.file_scanner_classify_files = True
packaging_policy.file_scanner_emit_files = True
packaging_policy.include_classified_resources = True
packaging_policy.include_file_resources = False
packaging_policy.allow_files = True
# Embed included resources in the executable by default when possible (e.g.
# pure Python modules and data files). When not possible (e.g. compiled
# extension modules) place them on the filesystem in a "lib/" directory
# adjacent to the executable.
packaging_policy.resources_location = "in-memory"
packaging_policy.resources_location_fallback = "filesystem-relative:lib"
# Invoke a function to make additional packaging decisions for each emitted
# resource.
packaging_policy.register_resource_callback(exe_resource_policy_decision)
# Configuration of the embedded Python interpreter.
python_config = python_dist.make_python_interpreter_config()
python_config.run_module = "nextstrain.cli"
# Produce a PythonExecutable from a Python distribution, embedded
# resources, and other options. The returned object represents the
# standalone executable that will be built.
exe = python_dist.to_python_executable(
name = "nextstrain",
packaging_policy = packaging_policy,
config = python_config)
# Invoke `pip install` with our Python distribution to install a single
# package. `pip_install()` returns objects representing installed files.
# `add_python_resources()` adds these objects to the binary, with a load
# location as defined by the packaging policy's resource location
# attributes.
exe.add_python_resources(exe.pip_install(["nextstrain-cli==3.2.0"]))
return exe
def exe_resource_policy_decision(policy, resource):
# Some pure Python packages use __file__ to locate their resources (instead
# of the importlib APIs) and thus cannot be embedded. Locate the modules
# and data resources of these packages on the filesystem as well.
pkgs_requiring_file = ["botocore", "boto3", "docutils.parsers.rst", "docutils.writers"]
if type(resource) == "PythonModuleSource":
if resource.name in pkgs_requiring_file or any([resource.name.startswith(p + ".") for p in pkgs_requiring_file]):
resource.add_location = "filesystem-relative:lib"
if type(resource) in ("PythonPackageResource", "PythonPackageDistributionResource"):
if resource.package in pkgs_requiring_file or any([resource.package.startswith(p + ".") for p in pkgs_requiring_file]):
resource.add_location = "filesystem-relative:lib"
# We ignore most "unclassified" Files (above) since our config discovers
# and emits *both* classified (PythonModuleSource, etc) and unclassified
# resources (File) and we prefer the former. However, a libffi shared
# object that ships with the Linux wheel for cffi doesn't get classified
# and thus must be caught here as a plain File.
if type(resource) == "File":
if resource.path.startswith("cffi.libs/libffi"):
print("Adding " + resource.path + " to bundle")
resource.add_include = True
resource.add_location = "filesystem-relative:lib"
# Materialize all the installation artifacts for the executable + external
# resources into a directory.
def make_installation_artifacts(exe):
files = FileManifest()
files.add_python_resource(".", exe)
return files
register_target("exe", make_exe)
register_target("installation-artifacts", make_installation_artifacts, depends=["exe"], default=True)
resolve_targets()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment