← All posts
How to Compile Python to a Native Executable: The Py2Native Guide

How to Compile Python to a Native Executable: The Py2Native Guide

python compiler cython code protection open source

How to Compile Python to a Native Executable: The Py2Native Guide

If you are shipping proprietary Python code — a licensed algorithm, an internal tool, or a commercial desktop app — handing out readable .py files is often not an option. The task in this guide is concrete: take your normal Python application and compile your custom Python code into a native executable so the source is no longer shipped as plain text.

Py2Native is the zero-config path to that result. It uses Cython under the hood, but hides the .pyx, .pxd, and manual cythonize workflow completely. You write plain Python, run one build command, and get a native binary. Third-party libraries stay as normal Python source and work as-is with minimal or no configuration.

By the end of this guide you will have:

This is not the hard way of hand-maintaining Cython build files. Py2Native handles the Python-to-C-to-object-to-link pipeline for you.

Prerequisites

Before building, make sure you have:

Verify your toolchain before starting. For example:

uv --version
gcc --version   # or clang --version, or cl.exe on Windows

Step-by-Step: Compiling Your Python Code

Step 1: Set up a uv-managed project with Py2Native

Py2Native uses uv for project and dependency management. Never use pip with Py2Native. Start from a fresh project:

uv init protected-app
cd protected-app
uv add --dev py2native

Confirm the CLI is available:

uv run py2native --help

For one-off builds outside a project, you can also install it as a standalone tool with uv tool install py2native, but the project-based uv run py2native flow is the primary path.

Step 2: Write your Python application as usual

There is no special packaging step. Keep your code as plain .py files:

protected-app/
├── main.py
└── myapp/
    ├── __init__.py
    ├── core.py
    └── utils.py

Example entry point:

# main.py
from myapp.core import run

if __name__ == "__main__":
    run()

Example module:

# myapp/core.py
def run():
    print("Hello from compiled native code")

Step 3: Build a native executable

Run the build command from the project root:

uv run py2native build main.py myapp/*.py

Py2Native expands the glob patterns, compiles the custom Python sources, compiles the generated C, and links the native executable. The output filename is platform-specific — for example, main.exe on Windows or main on Linux/macOS.

If you are building a Windows GUI application, add --no-console:

uv run py2native build main.py myapp/*.py --no-console

Third-party libraries that you import remain as Python source and continue to work.

Step 4: Build a shared library with --library

If you want an importable compiled module instead of a standalone executable, use --library:

uv run py2native build main.py myapp/*.py --library

This produces a shared library — .pyd on Windows, .so on Linux, or .dylib on macOS — containing your compiled modules. Library mode is useful when you want to distribute a native extension that behaves like an importable Python package.

Step 5: Package a wheel with --wheel

To distribute the compiled library as a Python package, combine --library with --wheel:

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

The generated wheel contains:

Both __init__.py and __main__.py are required for the import bridge and runnable entry point.

Step 6: Create an embedded deployment directory with --embed

For a self-contained deployment directory managed by uv, add --embed:

uv run py2native build main.py myapp/*.py --embed deploy/

This creates a deploy/ directory with the necessary Python runtime, dependencies, and compiled executable. It is useful when you want to ship a controlled environment without requiring the target machine to install Python.

Step 7: Add license verification with Py2Native Pro

The open-source core is MIT licensed. Py2Native Pro adds closed-source license verification on top of the same build workflow.

Pro adds these commands:

uv run py2native keygen private.pem public.pem

This generates an EC P-256 keypair.

The Pro plugin also provides a generated declaration file that exposes the license verification routine. Include that .pxd file in your source tree, then call the verification logic from your Python code with the license key:

# py2nativepro/license.pxd — generated by Py2Native Pro
cdef public bint verify_license(const char* license_key)

In your entry point, load the license file and require verification before running your app:

# main.py
from py2nativepro.license import verify_license

with open("license.dat", "rb") as f:
    license_key = f.read()

if not verify_license(license_key):
    raise SystemExit("Invalid or missing license")

from myapp.core import run

if __name__ == "__main__":
    run()

Sign a JSON payload into a license file:

uv run py2native sign --private private.pem '{"sub":"customer-123","exp":2030-01-01T00:00:00Z}' license.dat

Build with that license file and the public key:

uv run py2native build main.py myapp/*.py --license license.dat --public public.pem

Only the public key is stored in the executable. The signature verification code is compiled into the binary, so the license check does not depend on third-party verification libraries. Your private key never ships.

Verifying the Compilation

After the build, confirm the output exists. For a native executable build, check the project build directory for the platform-specific binary:

./build/main        # Linux/macOS
build\main.exe      # Windows

Run it and compare the behavior to the original Python script:

./build/main
# Hello from compiled native code

For library mode, import the package from Python and call a function:

uv run python -m myapp

If you built a wheel, install it in a clean environment and import the module normally.

For Pro license verification, test both paths:

./build/main
# Invalid or missing license

Then place a valid license.dat where the application expects it and run again:

./build/main
# Hello from compiled native code

Troubleshooting Common Issues

Missing C compiler

If the build fails during C compilation, make sure your platform toolchain is installed and available on PATH.

Module name conflicts

Py2Native does not support modules with identical names across source files. If you have two files with the same module name in different directories, rename one or adjust the source globs.

LGPL libraries

Third-party libraries remain as Python source. LGPL libraries therefore remain replaceable by the end user. Keep that in mind for compliance-sensitive distributions.

Monkey-patching is still possible

Because third-party libraries are not compiled, monkey-patching those libraries is still possible at runtime. Custom code compiled by Py2Native is protected as native machine code.

__init__.py is skipped during compilation

__init__.py is treated as a namespace marker, not compilable code. That is expected. In library mode, __main__.py is also excluded from sources because the wheel builder generates it.

FAQ

Does Py2Native compile third-party libraries to native code?

No. Py2Native only compiles your custom Python code. Third-party libraries remain as Python source and work as-is with minimal or no configuration.

Can I use Py2Native to create a Python extension module instead of an executable?

Yes. Use the --library flag to build a shared library (.pyd, .so, or .dylib) that can be imported like a regular Python module. When packaged as a wheel, the output includes __init__.py and __main__.py to bridge the import system.

How does license verification work in Py2Native Pro?

Py2Native Pro includes a plugin that bakes signature verification code and a public key into the executable. You generate an EC P-256 keypair, sign a JWT payload with the private key, and pass the license file to the build. The executable verifies the license at runtime.

Is Py2Native compatible with free-threaded Python?

Yes. Py2Native supports CPython 3.11–3.15, including free-threaded builds 3.14t and 3.15t.

Conclusion

Py2Native compiles your custom Python code into native machine code with a single uv run py2native build command. You write plain Python, keep third-party dependencies as they are, and get a protected native binary — no manual Cython build steps required.

The core compiler is open source under the MIT license. If you need commercial license enforcement, the Pro plugin layers JWT verification and signature checking on top, with the public key and verification code baked into your executable.

Start with the open-source core at Py2Native, and run your first build from a uv-managed project.

EU label: AI-generated content