Py2Native Python to native compiler

← All posts

2026-09-01

Automate Python Compilation with uv and Py2Native

Automate Python Compilation with uv and Py2Native
python compiler cython code protection open source

Automate Python Compilation with uv and Py2Native

Every release, the same manual sequence happens: switch to the right Python version, rebuild the environment, remember the exact py2native build flags, run the build, and hope you did not ship a stale binary. If those steps live in your shell history or a team wiki, they are already overdue for automation.

This guide shows how to turn Python compilation into a repeatable, one-command process with uv and Py2Native. You write plain Python, run uv run py2native build, and get a native binary or shared library without maintaining Cython configuration by hand.

Why Automate Python Compilation?

Manual release builds fail in boring, predictable ways. A developer forgets to rebuild after a final patch. A CI runner has a different compiler flag than a local workstation. A junior engineer skips the --embed step and ships a folder that depends on the source checkout. The common root cause is that the build process is implicit, scattered, and human-driven.

Automating Python compilation removes the repetitive task. Instead of copying commands between machines, you encode them once in a version-controlled script or CI workflow. Every build then uses the same Python version, the same Py2Native invocation, and the same output layout. That gives you consistent artifacts, a complete build log, and a clear pass/fail signal on every commit.

The hard way is to maintain raw Cython files, write .pyx and .pxd modules, manage cythonize steps, and write custom C-extension build scripts. Py2Native takes a different path: it handles the Cython-to-C-to-native pipeline internally and leaves your application code as plain Python. If you want the full comparison, see Cython vs Py2Native: Which Is Better for Protecting Python Code?.

One-Time Setup for Automated Builds

Start by creating a uv-managed project and adding Py2Native as a dependency.

uv init myapp
cd myapp
uv add py2native

Your pyproject.toml will then include Py2Native in the project dependencies. A minimal example looks like this:

[project]
name = "myapp"
version = "0.1.0"
requires-python = ">=3.11,<3.16"
dependencies = ["py2native"]

You do not need to create a separate virtual environment for compilation. Py2Native manages a pristine Python environment automatically, so the compilation step does not pick up unrelated packages from your development environment.

Verify that the CLI is available through uv:

uv run py2native --help

From now on, every build command starts with uv run py2native, which keeps the compiler tied to the project and avoids global tool drift.

Pro license setup, if needed

If you use the Pro plugin, generate an EC P-256 keypair and sign a JWT payload for your license file:

uv run py2native keygen private.pem public.pem
uv run py2native sign --private private.pem '{"customer":"acme","exp":"2027-01-01"}' license.dat

You can inspect the claims during setup:

uv run py2native show --public public.pem license.dat

Store the generated license file where your pipeline can access it, but keep the private key out of version control and CI logs.

Building the Automated Pipeline

The core automation is a single Py2Native build command. Use shell globs to compile all of your custom Python files in one pass.

shopt -s globstar
uv run py2native build main.py src/**/*.py --embed dist/myapp

This compiles the called-out modules into a native artifact and creates an embedded deployment directory at dist/myapp. The --embed option produces an uv-managed deployment that does not require your original Python source files to run.

If you distribute a package instead of a standalone executable, switch to library mode and build a wheel:

uv run py2native build main.py src/**/*.py --library --wheel dist/wheel

In library mode, the generated wheel contains the compiled shared library plus the required __init__.py and __main__.py files. Those two generated files bridge Python’s import system to the native library and provide the python -m mypkg entry point.

A CI/CD workflow

Any CI system that can run shell commands can run Py2Native. The following GitHub Actions workflow installs uv, syncs dependencies, compiles the sources, and uploads the embedded output as an artifact.

name: Build native

on:
  push:
    tags: ["v*"]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install uv
        run: |
          curl -LsSf https://astral.sh/uv/install.sh | sh
          echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"

      - name: Sync dependencies
        run: uv sync --locked

      - name: Compile native binary
        run: |
          shopt -s globstar
          uv run py2native build main.py src/**/*.py --embed dist/myapp

      - uses: actions/upload-artifact@v4
        with:
          name: native-app
          path: dist/myapp

The same pattern works in GitLab CI, Jenkins, or a scheduled cron job. The important part is that the build command is stored in the pipeline, not in a developer’s terminal history.

For a Pro build that verifies a license during compilation, add the --license and --public flags:

uv run py2native build main.py src/**/*.py \
  --embed dist/myapp \
  --license license.dat \
  --public public.pem

The Pro plugin bakes the signature verification code and the public key into the executable, so the build fails if a valid license is not present.

Monitoring and Failure Handling

A repeatable build is only useful when failures are loud and actionable. Capture the full build log in CI so compiler and linker errors survive after the job finishes:

set -e
shopt -s globstar

if ! uv run py2native build main.py src/**/*.py --wheel dist/wheel 2>&1 | tee build.log; then
  echo "Py2Native build failed; see build.log"
  exit 1
fi

Py2Native uses the platform C compiler and linker, so the usual failure classes are:

  • Missing C compiler: install MSVC on Windows, GCC on Linux, or Clang on macOS.
  • Incompatible Python version: Py2Native supports CPython 3.11 through 3.15, including free-threaded 3.14t and 3.15t. Match your CI Python version to the version used for linking.
  • Duplicate module names: two source files cannot expose modules with identical names, so keep your package structure clean.
  • Cython or linking errors: these appear in the captured log as compiler/linker output; fixing the source or platform flags usually resolves them.

Use exit codes and CI status checks to fail the job automatically. For transient failures, re-run the job before debugging deeply. uv’s dependency cache speeds up repeated uv sync calls, and committing uv.lock to version control keeps dependency resolution reproducible across local and CI builds.

Best Practices for Automated Python Compilation

  • Keep module boundaries explicit. Py2Native cannot compile modules with identical names across source files. A clear package layout reduces collisions and makes glob patterns easier to maintain.
  • Trigger builds from version control tags. Tags give each native binary a source revision, which is valuable when customers report issues against a specific build.
  • Store build metadata with the artifact. Include the commit SHA, pipeline run number, or tag in the artifact name so you can trace a compiled binary back to its source.
  • Automate Pro license verification carefully. Keep the .pxd declaration supplied by py2nativepro in your source tree, and call the compiled verification function with the path to your license file at application startup. Because the public key and verification code are baked into the binary, only the public key is stored in the executable, and no third-party crypto library is required at runtime.
  • Update Py2Native and uv regularly. Compiler improvements and security patches arrive through normal dependency updates. Re-run the pipeline on a schedule so release builds do not silently rely on stale tooling.
  • Test compiled binaries in a clean environment. After building, run the output on a runner or container that does not have your source checkout. If the binary fails there, it was never truly self-contained.

FAQ

Can I automate Py2Native builds in any CI/CD system?

Yes. Py2Native works with any CI/CD system that supports running shell commands. Use uv run py2native build in your pipeline script, and ensure the required C compiler and Python version are available.

Does Py2Native require a separate build step for third-party libraries?

No. Third-party libraries are left as Python source and are not compiled. Py2Native only compiles your custom Python code, so you do not need to configure or rebuild dependencies.

How do I handle license verification in an automated build?

With the Pro plugin, you can generate a license key and embed the public key in your executable. During the build, pass the --license flag to require a valid license, and call the verification function in your code to check the license at runtime.

What if my build fails due to a missing C compiler?

Ensure your CI environment has the platform C compiler installed: MSVC on Windows, GCC on Linux, or Clang on macOS. Py2Native will detect and use the appropriate compiler automatically.

Conclusion

Automating Python compilation with uv and Py2Native turns a fragile manual release step into a one-command build. You encode the compiler invocation once, let CI run it on every relevant change, and know immediately whether the resulting native binary or wheel is valid. The source code stays protected without maintaining a custom Cython toolchain.

Learn more about Py2Native and make your next build script a single uv run py2native build command.

Related posts

EU label: AI-generated content