package
Create a deployable artifact.
The package
goal creates an artifact that can be deployed or distributed.
The exact type of artifact depends on the type of target the goal is invoked on.
You can run pants package ::
to build all artifacts in your project. Pants will filter to only the relevant targets.
Because Pants understands the dependencies of your code, and the dependencies of those dependencies, the generated artifact will only include the exact code needed for your package to work. This results in smaller, more focused packages.
You can depend on a package target in a python_test
/ python_tests
target through the runtime_package_dependencies
field. Pants will run the equivalent of pants package
beforehand and copy the built artifact into the test's chroot, allowing you to test things like that the artifact has the correct files present and that it's executable.
This allows you to test your packaging pipeline by simply running pants test ::
, without needing custom integration test scripts.
See test for more information.
Check out our blog Streamline Docker Builds to read about how you can combine these package
formats with Pants's Docker support. Also see our Docker docs
Creating a PEX file from a pex_binary
target
Running package
on a pex_binary
target will create an executable PEX file.
The PEX file will contain all the code needed to run the binary, namely:
- All Python code and resources the binary transitively depends on.
- The resolved 3rd-party Python dependencies (sdists and wheels) of all targets the binary transitively depends on.
The PEX metadata will include:
- The entry point or console script specified by the
pex_binary
target, if any. - The intersection of all interpreter constraints applicable to the code in the Pex. See Interpreter compatibility.
You can also tweak many options, such as the execution_mode
option to optimize for faster initial runs vs. subsequent runs. Run pants help pex_binary
.
The entry_point
and script
fields
The entry_point
and script
fields set the behavior for what happens when you run ./dist/my_app.pex
, such as if it runs a particular script or launches an app.
Usually, you'll want to use entry_point
, which lets you specify a module and optionally a function to execute, such as project.my_app:main
. This is especially useful when you want to run first-party code.
script
is useful when you want to directly run a third-party dependency that sets console_scripts
in its distribution. This allows you to, for example, set script="black"
to create black.pex
that behaves like if you had pip install
ed black
and then run black
in your shell:
❯ ./dist/black.pex --version
python -m black, 23.1.0 (compiled: yes)
You can also leave off both fields, which will cause ./dist/my_app.pex
to launch a Python interpreter with all the relevant code and dependencies loaded.
❯ ./dist/my_app.pex
Python 3.9.6 (default, Jun 28 2021, 19:24:41)
[Clang 12.0.5 (clang-1205.0.22.9)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
If you use the entry_point
field, Pants will use dependency inference, which you can confirm by running pants dependencies path/to:app
. Otherwise, you must manually add to the dependencies
field.
entry_point
with a file name
You can specify a file name, which Pants will convert into a well-formed entry point. Like with the source
/ sources
field, file paths are relative to the BUILD file, rather than the build root.
# The default `sources` field will include `main.py`.
python_sources(name="lib")
# Pants will convert the entry point to `helloworld.main`.
pex_binary(
name="app",
entry_point="main.py",
)
# You can also specify the function to run.
pex_binary(
name="app_with_func",
entry_point="main.py:my_func",
)
This approach has the added benefit that you can use file arguments, e.g. pants package helloworld/main.py
, rather than needing to use target addresses like pants package helloworld:app
.
Explicit entry_point
You can directly specify the entry point in the format path.to.module
or path.to.module:my_func
. This allows you to use an entry point for a third-party requirement or the Python standard library.
# The default `sources` field will include `main.py`.
python_sources(name="lib")
pex_binary(
name="app",
entry_point="helloworld.main",
)
# You can also specify the function to run.
pex_binary(
name="app_with_func",
entry_point="helloworld.main:my_func",
)
# You can specify third-party requirements and the std lib.
pex_binary(
name="3rdparty_app",
entry_point="bandit:main",
)
Unlike using entry_point
with a file name, this does not work with file arguments; you must use the target address, like pants package helloworld:app
.
script
You can set the script
to any console_script
or script exposed by your third-party requirements.
python_requirement(name="black_req", requirements=["black==23.1.0"])
pex_binary(
name="black_bin",
script="black",
dependencies=[":black_req"],
)
You must explicitly add the dependencies you'd like to the dependencies
field.
This does not work with file arguments; you must use the target address, like pants package helloworld:black_bin
.
Injecting command-line arguments and environment variables
You can use the inject_args
and inject_env
fields to "freeze" command-line arguments and environment variables into the PEX file. This can save you from having to create shim files around generic binaries. For example:
python_requirement(name="gunicorn", requirements=["gunicorn==20.1.0"])
pex_binary(
name="myservice_bin",
script="gunicorn",
args=["myproduct.myservice.wsgi:app", "--name=myservice"],
env={"MY_ENV_VAR=1"},
dependencies=[":gunicorn"],
)
If your code's requirements include distributions that include native code, then the resulting PEX file will only run on the platform it was built on.
However, if all native code requirements are available as wheels for the target platform, then you can cross-build a PEX file on a different source platform by specifying the platforms
field on the pex_binary
, e.g. platforms=["linux-x86_64-cp-37-cp37m", "macosx_10_15_x86_64-cp-38-cp38"]
.
.pex
file with unzip
Because a .pex
file is simply a ZIP file, you can use the Unix tool unzip
to inspect the contents. For example, run unzip -l dist/app.pex
to see all file members.
resource
instead of file
file
and files
targets will not be included in the built PEX because filesystem APIs like open()
would not load them as expected. Instead, use the resource
and resources
target or wrap your pex_binary
in an archive
target. See Assets and archives for further explanation.
Examples
❯ pants package helloworld/main.py
17:36:42 [INFO] Wrote dist/helloworld/helloworld.pex
We can also build the same Pex by using the address of the pex_binary
target, as described here.
❯ pants package helloworld:app
17:36:42 [INFO] Wrote dist/helloworld/helloworld.pex
pex_binaries
target generator
If you have several scripts in the same directory, it can be convenient to use the pex_binaries
target generator, which will generate one pex_binary
target per entry in the entry_points
field:
# The default `sources` will include all our source files.
python_sources(name="lib")
pex_binaries(
name="binaries",
entry_points=[
"app1.py",
"app2.py",
"app3.py:my_func",
],
overrides={
"app2.py:my_func": {"execution_mode": "venv"},
},
)
Use pants peek path/to/dir:
to inspect the generated pex_binary
targets.
Create a setuptools distribution
Running package
on a python_distribution
target will create a standard setuptools-style Python distribution, such as an sdist or a wheel. See Building Distributions for details.
Create a zip
or tar
file
See Resources and archives for how to create a zip or tar file with built binaries and/or loose files in it by using the archive
target.
This is often useful when you want to create a PEX binary using the pex_binary
target, and bundle it with some loose config files.
Create an AWS Lambda
See AWS Lambda for how to build a zip file that works with AWS Lambda.
Create a Google Cloud Function
See Google Cloud Functions for how to build a zip file that works with Google Cloud Functions.
Create a PyOxidizer binary
See PyOxidizer for how to distribute your code as a binary, like PEX, but with the Python interpreter included.