Rust Projects

PyOxidizer uses Rust projects to build binaries embedding Python.

If you just have a standalone configuration file (such as when running pyoxidizer init-config-file), a temporary Rust project will be created as part of building binaries and the existence of Rust should be largely invisible (except for the output from building the Rust project).

If you use pyoxidizer init-rust-project to initialize a PyOxidizer application, the Rust project exists side-by-side with the PyOxidizer configuration file and can be modified like any other Rust project.

Either way, the PyOxidizer configuration file works alongside Rust to build binaries.

Layout

Generated Rust projects all have a similar layout:

$ find pyapp -type f | grep -v .git
Cargo.toml
build.rs
pyoxidizer.bzl
src/main.rs

The Cargo.toml file is the configuration file for the Rust project. Read more in the official Cargo documentation. The magic lines in this file to enable PyOxidizer are the following:

[package]
build = "build.rs"

[dependencies]
pyembed = ...

These lines declare a dependency on the pyembed package, which holds the smarts for embedding Python in a binary.

In addition, the build = "build.rs" tells runs a script that hooks up the output of the pyembed crate with this project.

Next let’s look at src/main.rs. If you aren’t familiar with Rust projects, the src/main.rs file is the default location for the source file implementing an executable. If we open that file, we see a fn main() { line, which declares the main function for our executable. The file is relatively straightforward. We import some symbols from the pyembed crate. We then construct a config object, use that to construct a Python interpreter, then we run the interpreter and pass its exit code to exit(). Succinctly, we instantiate and run an embedded Python interpreter. That’s our executable.

The pyoxidizer.bzl is our auto-generated PyOxidizer configuration file.

Build Artifacts for pyembed

The pyembed crate needs to reference special artifacts as part of its build process in order to compile a Python interpreter into a binary.

These special artifacts are ultimately generated by PyOxidizer. How exactly can vary.

By default, these special artifacts are generated by the pyembed crate’s build.rs build script. Unless behavior is overridden via environment variables, this program runs pyoxidizer run-build-script to generate these special artifacts. That command will resolve the default build script target in the found PyOxidizer configuration file.

The special build artifacts are generated by resolving a configuration file target returning a PythonEmbeddedData instance. In the auto-generated configuration file, the embedded target returns such a type.

Cargo Features to Control Building

The pyembed crate and generated Rust projects share a set of build-mode-* Cargo feature flags to control how build artifacts are created and consumed.

The features are described in the following sections.

build-mode-standalone

Do not attempt to invoke pyoxidizer or find artifacts it would have built. It is possible to build the pyembed crate in this mode if the rust-cpython and python3-sys crates can find a Python interpreter. But, the pyembed crate may not be usable or work in the way you want it to.

This mode is intended to be used for performing quick testing on the pyembed crate. It is quite possible that linking errors will occur in this mode unless you take additional actions to point Cargo at appropriate libraries.

build-mode-pyoxidizer-exe

A pyoxidizer executable will be run to generate build artifacts.

The path to this executable can be defined via the PYOXIDIZER_EXE environment variable. Otherwise PATH will be used.

At build time, pyoxidizer run-build-script will be run. A PyOxidizer configuration file will be discovered using the heuristics described at Finding Configuration Files. OUT_DIR will be set if running from cargo, so a pyoxidizer.bzl next to the main Rust project being built should be found and used.

pyoxidizer run-build-script will resolve the default build script target by default. To override which target should be resolved, specify the target name via the PYOXIDIZER_BUILD_TARGET environment variable. e.g.:

$ PYOXIDIZER_BUILD_TARGET=build-artifacts cargo build

build-mode-prebuilt-artifacts

This mode tells the build script to reuse artifacts that were already built. (Perhaps you called pyoxidizer build or pyoxidizer run-build-script outside the context of a normal cargo build.)

In this mode, the build script will look for artifacts in the directory specified by PYOXIDIZER_ARTIFACT_DIR if set, falling back to OUT_DIR. This directory must have a cargo_metadata.txt file, which will be printed to stdout by the build script to tell Cargo how to link a Python library.