Packaging an Application from a PyPI Package¶
In this section, we’ll show how to package the pyflakes program using a published PyPI package. (Pyflakes is a Python linter.)
First, let’s create an empty project:
$ pyoxidizer init-config-file pyflakes
Next, we need to edit the configuration file to tell
PyOxidizer about pyflakes. Open the pyflakes/pyoxidizer.bzl
file in your
favorite editor.
Find the make_exe()
function. This function returns a
PythonExecutable instance which defines
a standalone executable containing Python. This function is a registered
target, which is a named entity that can be individually built or run.
By returning a PythonExecutable
instance, this function/target is saying
build an executable containing Python.
The PythonExecutable
type holds all state needed to package and run
a Python interpreter. This includes low-level interpreter configuration
settings to which Python resources (like source and bytecode modules)
are embedded in that executable binary. This type exposes an
add_in_memory_python_resources()
method which adds an iterable of objects representing Python resources to the
set of embedded resources.
Elsewhere in this function, the dist
variable holds an instance of
PythonDistribution. This type
represents a Python distribution, which is a fancy way of saying
an implementation of Python. In addition to defining the files
constituting that distribution, a PythonDistribution
exposes
methods for performing Python packaging. One of those methods is
pip_install(),
which invokes pip install
using that Python distribution.
To add a new Python package to our executable, we call
dist.pip_install()
then add the results to our PythonExecutable
instance. This is done like so:
exe.add_in_memory_python_resources(dist.pip_install(["pyflakes==2.1.1"]))
The inner call to dist.pip_install()
will effectively run
pip install pyflakes==2.1.1
and collect a set of installed
Python resources (like module sources and bytecode data) and return
that as an iterable data structure. The exe.add_in_memory_python_resources()
call will then embed these resources in the built executable binary.
Next, we tell PyOxidizer to run pyflakes
when the interpreter is executed:
run_eval="from pyflakes.api import main; main()",
This says to effectively run the Python code
eval(from pyflakes.api import main; main())
when the embedded interpreter
starts.
The new make_exe()
function should look something like the following (with
comments removed for brevity):
def make_exe():
dist = default_python_distribution()
config = PythonInterpreterConfig(
run_eval="from pyflakes.api import main; main()",
)
exe = dist.to_python_executable(
name="pyflakes",
config=config,
extension_module_filter="all",
include_sources=True,
include_resources=False,
include_test=False,
)
exe.add_in_memory_python_resources(dist.pip_install(["pyflakes==2.1.1"]))
return exe
With the configuration changes made, we can build and run a pyflakes
native executable:
# From outside the ``pyflakes`` directory
$ pyoxidizer run --path /path/to/pyflakes/project -- /path/to/python/file/to/analyze
# From inside the ``pyflakes`` directory
$ pyoxidizer run -- /path/to/python/file/to/analyze
# Or if you prefer the Rust native tools
$ cargo run -- /path/to/python/file/to/analyze
By default, pyflakes
analyzes Python source code passed to it via
stdin.