Py2Native Compile custom Python into native machine code to protect proprietary code

← All posts

2026-09-16

How to Compile Python to a Native Binary Without Cython Syntax

How to Compile Python to a Native Binary Without Cython Syntax
python compiler cython code protection open source

How to Compile Python to a Native Binary Without Cython Syntax

You need to ship a Python application without handing over readable source code. You could learn Cython, write .pyx and .pxd files, configure build steps, and debug C extension issues. That is the hard way.

Py2Native gives you the shortcut: write plain Python, run one uv run py2native build command, and get a native executable or shared library. Cython still runs under the hood, but Py2Native hides all of it. You never write Cython syntax, never create .pyx files, and never touch a manual build pipeline.

By the end of this guide, you will take an existing Python project and compile it into a distributable native binary—without changing your Python code into Cython.

Prerequisites for Compiling Python with Py2Native

Before you start, make sure you have:

  • CPython 3.11–3.15, including free-threaded 3.14t and 3.15t
  • A platform C compiler and linker:
    • Windows: MSVC
    • Linux: GCC
    • macOS: Clang
  • Internet access, because Py2Native downloads Python distributions and libraries when needed
  • uv installed, since all Py2Native commands start with uv run py2native

Supported targets are Windows 8+, manylinux2014/musl Linux, and macOS on x86-64 or ARM64 CPUs.

Check your environment quickly:

uv --version
python --version

If uv is not installed, install it first from the uv project. Py2Native then runs as a normal uv-managed package.

Step-by-Step: Compile Your Python Code to a Native Binary

Step 1: Install Py2Native with uv

You can install Py2Native as a standalone tool:

uv tool install py2native

For project-local builds, add it to the project that uv manages:

uv add py2native

From this point on, all examples use uv run py2native so the command runs in the exact project environment.

Step 2: Prepare a plain Python project

Py2Native compiles the Python you already have. There is no project restructure, no type annotation pass, and no Cython boilerplate.

Example project:

myapp/
├── main.py
└── helpers.py

helpers.py:

def format_amount(value: float) -> str:
    return f"${value:.2f}"

main.py:

from helpers import format_amount

def main():
    print(format_amount(19.99))

if __name__ == "__main__":
    main()

Both files are ordinary Python.

Step 3: Build a native executable

From the project directory, run:

uv run py2native build main.py *.py

Py2Native expands the *.py glob, transpiles every matched Python file to C, compiles the C to object files, and links them into a native executable. The first argument, main.py, is the entry point.

The generated executable is a native binary. On Windows, expect an .exe; on Linux and macOS, expect an ELF or Mach-O binary. The command prints the exact output path after linking.

Step 4: Build a shared library instead

If you want to distribute a package that other Python code can import, use --library:

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

This creates a shared library with a platform-specific extension:

  • .pyd on Windows
  • .so on Linux
  • .dylib on macOS

To package that library as a wheel:

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

The wheel contains the compiled shared library plus an __init__.py meta-path finder and a __main__.py entry point. Those two files are required: __init__.py bridges Python’s import system to the native library, and __main__.py enables python -m <package>.

Step 5: Create a self-contained deployment

For a deployment directory that includes your binary and its dependencies, use --embed:

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

Py2Native creates a uv-managed deployment directory. Your own code is compiled; third-party libraries remain as Python packages and are handled by the embedded environment. This is useful when you want to hand off a directory instead of a single executable.

Step 6: Embed Pro license verification

If you have Py2Native Pro, you can embed license verification directly into the binary. Start by generating an EC P-256 keypair:

uv run py2native keygen private.pem public.pem

Sign a JWT payload with the private key:

uv run py2native sign --private private.pem payload.json license.dat

Then build with the license and public key:

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

The Pro plugin bakes the public key and the signature verification code into the executable. Only the public key is stored in the binary; the private key stays offline.

With Pro, you do not write the verifier by hand. The build generates the bootstrap module and its interface. The generated _p2n_bootstrap.pxd contract is:

from _p2n_bootstrap cimport _runtime_verify_es256_jwt

cdef _runtime_verify_es256_jwt(token, expected_iss=*, expected_aud=*)

The runtime check uses an ES256 JWT verifier with expected claims:

_runtime_verify_es256_jwt(token, expected_iss="RSJ Software GmbH", expected_aud="TimestampGIT")

In the compiled code, the call assigns the result to a variable:

license = _runtime_verify_es256_jwt(licenseString, expected_iss="RSJ Software GmbH", expected_aud="TimestampGIT")

You pass the license token at runtime. The verification logic is compiled into the binary, not shipped as readable Python.

Verifying the Compilation Worked

First, run the executable. If your main module is main.py, the generated binary is typically named main or main.exe:

./main

For a library build, import the package from Python. Replace <package_name> with the package name you normally import:

python -m <package_name>

The compiled library should behave like the original Python code.

You can also confirm that the readable Python definition is gone:

strings ./main | grep "def format_amount"

If that command returns nothing, the original function definition is not present as readable Python source. This is a sanity check, not a security proof, but it confirms that Py2Native moved your logic into native code.

Third-party imports should continue to work normally. For example, if your project imports requests, the embedded environment or the host Python environment supplies it at runtime. Py2Native does not compile third-party libraries; it compiles your own source files.

Troubleshooting Common Issues

Missing C compiler

If the build fails with a compiler error, install the required toolchain:

# Windows: install Visual Studio Build Tools with "Desktop development with C++"
# Linux:
sudo apt install build-essential
# macOS:
xcode-select --install

Py2Native needs a working C compiler because it transpiles Python to C and then compiles that C.

Duplicate module names

Py2Native requires unique module names across your source files. If two source files define modules with identical names, the build can fail or produce conflicting symbols. Rename one of the modules before rebuilding.

Monkey-patching is still possible

Compiling your code does not prevent all runtime modification. If your application or a third-party library monkey-patches your classes or functions, those dynamic changes can still happen at runtime because Python’s object model remains active.

LGPL libraries remain replaceable

Py2Native leaves third-party libraries as Python source. This means LGPL-licensed dependencies remain replaceable by the end user, which keeps you compliant with LGPL obligations.

Platform-specific output issues

On Windows, compiled extensions use .pyd naming. On Linux, --wheel builds can trigger auditwheel repair for manylinux compliance. If you see wheel repair warnings on Linux, review the auditwheel output and ensure your build environment matches your target manylinux profile.

FAQ: Compiling Python to Native Binary with Py2Native

Q: Does Py2Native require me to write Cython code?

A: No. Py2Native uses Cython internally, but you write plain Python. There are no .pyx files to maintain, no type annotations required, and no manual cythonize step.

Q: Can I compile Python to a native binary without a C compiler?

A: No. A C compiler is required because Py2Native transpiles Python to C and then compiles it. On Windows you need MSVC, on Linux GCC, and on macOS Clang.

Q: Does Py2Native compile third-party libraries?

A: No. Py2Native only compiles your own Python source files. Third-party libraries remain as Python packages and are imported normally at runtime.

Q: How does Py2Native Pro license verification work?

A: Py2Native Pro embeds a public key and signature verification code into the executable. You call _runtime_verify_es256_jwt with your license token and expected issuer and audience to validate a JWT license.

Conclusion: Protect Your Python Code with Zero-Config Compilation

Py2Native turns plain Python into native machine code without asking you to learn Cython, maintain .pyx files, or manage a manual build process. You write Python, run uv run py2native build, and get a binary or shared library ready to distribute.

Start with the open-source Community edition from Py2Native. If you need signed license verification baked into your executable, upgrade to Pro and add the license flags shown above.

Related posts

EU label: AI-generated content