How to Compile Python to a Native Executable: The Py2Native Guide
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:
- A native executable built from your
.pyfiles. - Optional library, wheel, or embedded deployment output.
- A working Pro license-check flow, if you need commercial licensing.
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:
- CPython 3.11–3.15, including free-threaded builds
3.14tand3.15t. - A platform C compiler/linker:
- Windows: MSVC
- Linux: GCC
- macOS: Clang
- A supported OS:
- Windows 8+
- manylinux2014 or musl Linux
- macOS
- x86-64 or ARM64 CPU.
- Internet access — Py2Native downloads Python distributions and libraries during builds.
- uv 0.11.8 or later — Py2Native is always invoked through
uv run py2native.
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:
- A single compiled shared library with your modules.
__init__.py, which bridges Python’s import system to the native library.__main__.py, which enablespython -m myapp.
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.
- Windows: install MSVC Build Tools.
- Linux: install GCC.
- macOS: install Xcode Command Line Tools or Clang.
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.