Portability of Binaries Built with PyOxidizer

Binary portability refers to the property that a binary built in machine/environment X is able to run on machine/environment Y. In other words, you’ve achieved binary portability if you are able to copy a binary to another machine and run it without modifications.

It is exceptionally difficult to achieve high levels of binary portability for various reasons.

PyOxidizer is capable of building binaries that are highly portable. However, the steps for doing so can be nuanced and vary substantially by operating system and target platform. This document attempts to capture the various steps and caveats involved.

Important

Please create issues at https://github.com/indygreg/PyOxidizer/issues when documentation on this page is inaccurate or lacks critical details.

Using pyoxidizer analyze For Assessing Binary Portability

The pyoxidizer analyze command can be used to analyze the contents of executables and libraries. It can be used as a PyOxidizer-specific tool for assessing the portability of built binaries.

For example, for ELF binaries (the binary format used on Linux), this command will list all shared library dependencies and analyze glibc symbol versions and print out which Linux distribution versions it thinks the binary is compatible with.

Note

pyoxidizer analyze is not yet feature complete on all platforms.

Python Distribution Versus Built Application Portability

PyOxidizer ships with specially built Python distributions that are highly portable. See Available Python Distributions for the full list of these distributions and Binary Portability of Distributions for details on the portability of these Python distributions.

Generally speaking, you don’t have to worry about the portability of the Python distributions because the distributions tend to just work.

Important

The machine and environment you use to run pyoxidizer has critical implications for the portability of built binaries.

When you use PyOxidizer to produce a new binary (an executable or library), you are compiling new code and linking it in an environment that is different from the specialized environment used to build the built-in Python distributions. This means that the binary portability of your built binary is effectively defined by the environment pyoxidizer was run from.

Windows

The built-in Python distributions have a run-time dependency on various DLLs. All 3rd party DLLs (OpenSSL, SQLite3, etc) required by Python extensions are provided by the built-in distributions.

Many DLL dependencies should be present in any Windows installation.

The Python distributions also have a dependency on the Visual Studio C++ Runtime. You will need to distribute a copy of vcruntimeXXX.dll alongside your binary or trigger the install of the Visual Stdio C++ Redistributable in your application installer so the dependency is managed at the system level. (Installing the redistributable via an installer is preferred.)

There is also currently a dependency on the Universal C Runtime (UCRT).

PyOxidizer will eventually make producing Windows installers from packaged applications turnkey (#279). Until that time arrives, see the Microsoft documentation on deployment considerations for Windows binaries. The Dependency Walker tool is also useful for analyzing DLL dependencies.

Windows binaries tend to be highly portable by default. If you follow Microsoft’s guidelines and install all required DLLs, you should be set.

macOS

The built-in Python distributions are built with MACOSX_DEPLOYMENT_TARGET=10.9, so they should be compatible with macOS versions 10.9 and newer.

The Python distribution has dependencies against a handful of system libraries and frameworks. These frameworks should be present on all macOS installations.

From your build environment, you may want to also ensure MACOSX_DEPLOYMENT_TARGET is set to ensure references to newer macOS SDK features aren’t present.

Apple’s Xcode documentation has various guides useful for further consideration.

Linux

Linux is the most difficult platform to tackle for binary portability. There’s a strongly held attitude that binaries should be managed as packages by the operating system and these packages are built in such a way that the package manager handles all the details for you. If you stray from the paved road and choose not to use the package manager provided by your operating system with the package sources configured by default, things get very challenging very quickly.

The best way to produce a portable Linux binary is to produce a fully statically-linked binary. There are no shared libraries to worry about and generally speaking these binaries just work. See Building Fully Statically Linked Binaries on Linux for more.

If you produce a dynamic binary with library dependencies, things are complicated.

Nearly every binary built on Linux will require linking against libc and will require a symbol provided by glibc. glibc versions it symbols. And when the linker resolves those symbols at link time, it usually uses the version of glibc being linked against. For example, if you link on a machine with glibc 2.19, the symbol versions in the produced binary will be against version 2.19 and the binary will load against glibc versions >=2.19. But if you link on a machine with glibc 2.29, symbol versions are against version 2.29 and you can only load against versions >= 2.29.

This means that to ensure maximum portability, you want to link against old glibc symbol versions. While it is possible to use old symbol versions when a more modern glibc is present, the path of least resistance is to build in an environment that has an older glibc.

The built-in Linux distributions use Debian 8 (Jessie) as their build environment. So a Debian 8 build environment is a good candidate to build on. Ubuntu 14.04, OpenSUSE 13.2, OpenSUSE 42.1, RHEL/CentOS 7, and Fedora 21 (glibc 2.20) are also good candidates for build environments.

Of course, if you are producing distribution-specific binaries and/or control installation (so e.g. dependencies are installed automatically), this matters less to you.

Again, the pyoxidizer analyze command can be very useful for inspecting binaries for portability and alerting you to any potential issues.