Behavior Quirks¶
Backspace Key Doesn’t work in Python REPL¶
If you attempt to run python
and the backspace key doesn’t
erase characters or the arrow keys don’t work as expected, this
is because the executable can’t find the terminfo database.
A telltale sign of this is the Python REPL printing the following on startup:
Cannot read termcap database;
using dumb terminal settings.
When you type a special key like the backspace key, this is
registered as a key press. There is special software (typically
readline
or libedit
) that most interactive programs use
that intercepts these special key presses and converts them into
special behavior, such as moving the cursor back instead of
forward. But because computer environments are different,
there needs to be some definition of how these special
behaviors are performed. This is the terminfo database.
When readline
and libedit
are compiled, there is
typically a hard-coded set of search locations for the
terminfo database baked into the built library. And when
you build a program (like Python) locally, you link against
readline
or libedit
and get these default locations
for free.
Because python-build-standalone Python distributions compile
and use their own version of libedit
and because the build
environment is different from your machine, the default search
locations for the terminfo database built into binaries
distributed with this project may point to a path that doesn’t
exist. The terminfo database cannot be located and libedit
does not know how to convert special key presses to special behavior.
The solution to this is to set an environment variable with the location of the terminfo database.
If running a Debian based Linux distribution (including Ubuntu):
$ TERMINFO_DIRS=/etc/terminfo:/lib/terminfo:/usr/share/terminfo
If running a RedHat based Linux distribution:
$ TERMINFO_DIRS=/etc/terminfo:/usr/share/terminfo
If running macOS:
$ TERMINFO_DIRS=/usr/share/terminfo
e.g.:
$ TERMINFO_DIRS=/etc/terminfo:/lib/terminfo:/usr/share/terminfo install/bin/python3.9
The macOS distributions built with this project should automatically
use the terminfo database in /usr/share/terminfo
. Please file
a bug report if the macOS distributions do not behave as expected.
Starting in the first release after 20240107, the Linux distributions are
configured to automatically use the terminfo database in /etc/terminfo
,
/lib/terminfo
, and /usr/share/terminfo
.
Also starting in the first release after 20240107, the terminfo database
is distributed in the share/terminfo
directory (../../share/terminfo
relative to the bin/python3
executable) in Linux distributions. Note
that ncurses and derived libraries don’t know how to find this directory
since they are configured to use absolute paths to the terminfo database
and the absolute path of the Python distribution is obviously not known
at build time! So actually using this bundled terminfo database will
require custom code setting TERMINFO_DIRS
before
ncurses/libedit/readline are loaded.
Tcl/tk Support Files¶
Python functionality using tcl/tk (such as the tkinter
or turtle
modules) requires loading .tcl
support files from the filesystem.
If these support files cannot be found, you’ll get an error like
_tkinter.TclError: Can't find a usable init.tcl in the following
directories:
.
Distributions produced from this project contain tcl/tk support files.
The paths to these files in the extracted distribution are advertised
in the PYTHON.json
file.
When tcl is initialized by Python, Python and tcl attempt to locate the
.tcl
support files. If the tcl<X.Y>/init.tcl
file cannot be found,
an error occurs.
But the mechanism for finding the .tcl
files varies by platform.
On all platforms, if the TCL_LIBRARY
environment variable is set,
it will be used to locate the .tcl
support files. This environment
variable is processed by tcl itself and is documented at
https://wiki.tcl-lang.org/page/TCL_LIBRARY.
On Windows, CPython will attempt to locate the .tcl
support files in
well-defined directories. The C code performs the equivalent of the
following:
import os
import sys
def get_tcl_path():
# e.g. sys.prefix/tcl/tcl8.6
p = os.path.join(sys.prefix, "tcl", "tcl<X.Y>")
if os.path.exists(p):
return p
return None
If Python’s code can find the support files in the well-defined location,
it calls into the tcl C API and defines the tcl_library
variable to the
found path.
The most robust way to ensure Python/tcl can find the .tcl
support files
is to define TCL_LIBRARY
to the path to the .tcl
files present in
the extracted Python distribution. It is possible to define this environment
variable from within Python. But it must be done before running any Python
code in the tkinter
module. The following example should work on Linux
and macOS distributions:
import os
import sys
os.environ["TCL_LIBRARY"] = os.path.join(os.path.dirname(sys.executable), "..", "lib", "tcl8.6")
import turtle
If you don’t set TCL_LIBRARY
on Linux and macOS, the default search
mechanics implemented by Tcl are used. These may pick up .tcl
files from
a location outside the Python distribution. This may just work. This may
fail fast. Or it could result in undefined behavior. For best results,
forcefully point Tcl at the .tcl
files from the Python distribution
produced by this project.
On Windows, explicitly setting TCL_LIBRARY
is not required as the
default install layout of this project’s Python distributions allows CPython’s
filesystem probing code to find the .tcl
files. As long as the
files from python/install/tcl
are present (in a tcl
directory
under the directory where the python.exe
is), things should just work.
For reference, PyOxidizer’s approach to this problem is to copy all the
.tcl
files from the Python distribution into an install location. At
run time, the TCL_LIBRARY
environment variable is set from within
the process before the Python interpreter is initialized. This ensures the
.tcl
files from the Python distribution are used.
No tix on macOS¶
macOS distributions do not contain tix tcl support files. This means that
tkinter.tix
module functionality will likely break at run-time. The
module will import fine. But attempting to instantiate a tkinter.tix.Tk
instance or otherwise attempt to run tix tcl files will result in a run-time
error.
tkinter.tix
has been deprecated since Python 3.6 and the official Python
macOS installers do not ship the tix support files. So this project behaves
similarly to the official CPython distributions.
No pip.exe
on Windows¶
The Windows distributions have pip
installed however no Scripts/pip.exe
,
Scripts/pip3.exe
, and Scripts/pipX.Y.exe
files are provided because
the way these executables are built isn’t portable. (It might be possible to
change how these are built to make them portable.)
To use pip, run python.exe -m pip
. (It is generally a best practice to
invoke pip via python -m pip
on all platforms so you can be explicit
about the python
executable that pip uses.)
Windows Static Distributions are Extremely Brittle¶
This project produces statically linked CPython distributions for Windows.
Building these distributions requires extensive patching of CPython’s build system. There are many aspects of CPython, the standard library, and 3rd party libraries that make assumptions that things will be built as dynamic libraries and break in these static builds.
Here is a list of known problems:
Most Windows extension modules link against
pythonXY.dll
(e.g.python39.dll
) orpython3.dll
and will fail to load on the static distributions. Extension modules will need to be explicitly recompiled against the static distribution.There is no supported platform tag for Windows static distributions and therefore there is no supported way to distribute binary wheels targeting the Python static distributions.
Aspects of OpenSSL (and therefore Python’s
ssl
module) don’t work when OpenSSL is compiled/linked statically. You will get opaque run-time errors.
It is highly recommended to extensively test your application against the static Windows distributions to ensure it works.
Linking Static Library on macOS¶
Python 3.9+ makes use of the __builtin_available()
compiler feature.
This functionality requires a symbol from libclang_rt
, which may not
be linked by default. Failure to link against libclang_rt
could result
in a linker error due to an undefined symbol ___isOSVersionAtLeast
.
To work around this linker failure, link against the static library
libclang_rt.<platform>.a
present in the Clang installation. e.g.
libclang_rt.osx.a
. You can find this library by invoking
clang --print-search-dirs
and looking in the lib/darwin
directory
under the printed libraries
directory. An example path is
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/12.0.0/lib/darwin/libclang_rt.osx.a
.
A copy of the libclang_rt.<platform>.a
from the Clang used to build
the distribution is included in the archive. However, it isn’t annotated
in PYTHON.json
because we’re unsure if using the file with another
build/version of Clang is supported. Use at your own risk.
See https://jonnyzzz.com/blog/2018/06/05/link-error-2/ and https://jonnyzzz.com/blog/2018/06/13/link-error-3/ for more on this topic.
Use of libedit
on Linux¶
Python 3.10+ Linux distributions link against libedit
(as opposed to
readline
) by default, as libedit
is supported on 3.10+ outside of
macOS.
Most Python builds on Linux will link against readline
because readline
is the dominant library on Linux.
Some functionality may behave subtly differently as a result of our choice
to link libedit
by default. (We choose libedit
by default to
avoid GPL licensing requirements of readline
.)
Static Linking of musl libc Prevents Extension Module Library Loading¶
Our musl libc linked Linux builds link musl libc statically and the resulting binaries are completely static and don’t have any external dependencies.
Due to how Linux/ELF works, a static/non-dynamic binary cannot call
dlopen()
and therefore it cannot load shared library based Python
extension modules (.so
based extension modules). This significantly
limits the utility of these Python distributions. (If you want to use
additional extension modules you can use the build artifacts in the
distributions to construct a new libpython
with the additional
extension modules configured as builtin extension modules.)
Another consequence of statically linking musl libc is that our musl distributions aren’t compatible with PEP 656. PEP 656 stipulates that Python and extension modules are linked against a dynamic musl. This is what you’ll find in Alpine Linux, for example.
See https://github.com/indygreg/python-build-standalone/issues/86 for a tracking issue to improve the state of musl distributions.
Static Linking of libX11
/ Incompatibility with PyQt on Linux¶
The _tkinter
Python extension module in the Python standard library
statically links against libX11
, libxcb
, and libXau
on Linux.
In addition, the _tkinter
extension module is statically linked into
libpython
and isn’t a standalone shared library file. This effectively
means that all these X11 libraries are statically linked into the main
Python interpreter.
On typical builds of Python on Linux, _tkinter
will link against
external shared libraries. e.g.:
$ ldd /usr/lib/python3.9/lib-dynload/_tkinter.cpython-39-x86_64-linux-gnu.so
linux-vdso.so.1 (0x00007fff3be9d000)
libBLT.2.5.so.8.6 => /lib/libBLT.2.5.so.8.6 (0x00007fdb6a6f8000)
libtk8.6.so => /lib/x86_64-linux-gnu/libtk8.6.so (0x00007fdb6a584000)
libtcl8.6.so => /lib/x86_64-linux-gnu/libtcl8.6.so (0x00007fdb6a3c1000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdb6a1d5000)
libX11.so.6 => /lib/x86_64-linux-gnu/libX11.so.6 (0x00007fdb6a097000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fdb69f49000)
libXft.so.2 => /lib/x86_64-linux-gnu/libXft.so.2 (0x00007fdb69f2e000)
libfontconfig.so.1 => /lib/x86_64-linux-gnu/libfontconfig.so.1 (0x00007fdb69ee6000)
libXss.so.1 => /lib/x86_64-linux-gnu/libXss.so.1 (0x00007fdb69ee1000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fdb69eda000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fdb69ebe000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fdb69e9c000)
/lib64/ld-linux-x86-64.so.2 (0x00007fdb6a892000)
libxcb.so.1 => /lib/x86_64-linux-gnu/libxcb.so.1 (0x00007fdb69e70000)
libfreetype.so.6 => /lib/x86_64-linux-gnu/libfreetype.so.6 (0x00007fdb69dad000)
libXrender.so.1 => /lib/x86_64-linux-gnu/libXrender.so.1 (0x00007fdb69da0000)
libexpat.so.1 => /lib/x86_64-linux-gnu/libexpat.so.1 (0x00007fdb69d71000)
libuuid.so.1 => /lib/x86_64-linux-gnu/libuuid.so.1 (0x00007fdb69d68000)
libXext.so.6 => /lib/x86_64-linux-gnu/libXext.so.6 (0x00007fdb69d53000)
libXau.so.6 => /lib/x86_64-linux-gnu/libXau.so.6 (0x00007fdb69d4b000)
libXdmcp.so.6 => /lib/x86_64-linux-gnu/libXdmcp.so.6 (0x00007fdb69d43000)
libpng16.so.16 => /lib/x86_64-linux-gnu/libpng16.so.16 (0x00007fdb69d08000)
libbrotlidec.so.1 => /lib/x86_64-linux-gnu/libbrotlidec.so.1 (0x00007fdb69cfa000)
libbsd.so.0 => /lib/x86_64-linux-gnu/libbsd.so.0 (0x00007fdb69ce2000)
libbrotlicommon.so.1 => /lib/x86_64-linux-gnu/libbrotlicommon.so.1 (0x00007fdb69cbd000)
libmd.so.0 => /lib/x86_64-linux-gnu/libmd.so.0 (0x00007fdb69cb0000)
The static linking of libX11
and other libraries can cause problems when
3rd party Python extension modules also loading similar libraries are also
loaded into the process. For example, extension modules associated with PyQt
are known to link against a shared libX11.so.6
. If multiple versions of
libX11
are loaded into the same process, run-time crashes / segfaults can
occur. See e.g. https://github.com/indygreg/python-build-standalone/issues/95.
The conceptual workaround is to not statically link libX11
and similar
libraries into libpython
. However, this requires re-linking a custom
libpython
without _tkinter
. It is possible to do this with the object
files included in the distributions. But there isn’t a turnkey way to do this.
And you can’t easily remove _tkinter
and its symbols from the pre-built
and ready-to-use Python install included in this project’s distribution
artifacts.
Missing libcrypt.so.1
¶
Linux distributions in the 20230507 release and earlier had a hard dependency
on libcrypt.so.1
due to static linking of the _crypt
extension module,
which imports it.
Presence of libcrypt.so.1
is mandated as part of the Linux Standard Base
Core Specification and therefore should be present in Linux environments
conforming to this specification. Most Linux distributions historically
attempted to conform to this specification.
In 2022, various Linux distributions stopped shipping libcrypt.so.1
(it appears glibc is ceasing to provide this functionality and Linux
distributions aren’t backfilling libcrypt.so.1
in the base install
to remain compatible with the Linux Standard Base Core Specification).
In reaction to Linux distributions no longer providing libcrypt.so.1
by
default, we changed the configuration of the _crypt
extension module so
it is compiled/distributed as a standalone shared library and not compiled
into libpython. This means a missing libcrypt.so.1
is only relevant if
the Python interpreter imports the crypt
/ _crypt
modules.
If you are using an older release of this project with a hard dependency
on libcrypt.so.1
and don’t want to upgrade, you can instruct end-users
to install a libxcrypt-compat
(or comparable) package to provide the
missing libcrypt.so.1
.
See https://github.com/indygreg/python-build-standalone/issues/113 and https://github.com/indygreg/python-build-standalone/issues/173 for additional context on this matter.
References to Build-Time Paths¶
The built Python distribution captures some absolute paths and other build-time configuration in a handful of files:
In a
_sysconfigdata_*.py
file in the standard library. e.g.lib/python3.10/_sysconfigdata__linux_x86_64-linux-gnu.py
.In a
Makefile
under aconfig-*
directory in the standard library. e.g.lib/python3.10/config-3.10-x86_64-linux-gnu/Makefile
.In
pkgconfig
files. e.g.lib/pkgconfig/python3.pc
.In
python*-config
files. e.g.bin/python3.10-config
.In
PYTHON.json
(mostly reflected values from_sysconfigdata_*.py
.
Each of these serves a different use case. But the general theme is various
aspects of the Python distribution attempt to capture how Python was built.
The most common use of these values is to facilitate compiling or linking
other software against this Python build. For example, the _sysconfigdata*
module is loaded by the sysconfig
module. sysconfig
in turn is used by packaging tools like setuptools
and pip
to figure out how to invoke a compiler for e.g. compiling C
extensions from source.
On Linux, our distributions are built in containers. The container has a
custom build of Clang in a custom filesystem location. And Python is
installed to the prefix /install
. So you may see references to
/install
in Linux distributions.
On macOS, most distributions are built from GitHub Actions runners. They
use a specific macOS SDK. So you may see references to SDK paths that don’t
exist on your machine. e.g.
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk
.
On Windows, builds are performed from a temporary directory. So you may see references to temporary directories in Windows distributions.
The existence of hard-coded paths in our produced distributions can confuse consumers of these values and break common workflows, like compiling C extensions.
We don’t currently have a great idea for how to solve this problem. We
can’t hardcode values that will work on every machine because every machine
has different filesystem layouts. For example, if we hardcode gcc
as
the compiler, someone with only clang
installed will complain. And
we certainly don’t know where end-users will extract their Python
distribution to!
To solve this problem requires executing dynamic code after extracting our custom distributions in order to patch these hardcoded values into conformance with the new machine. We’re unsure how to actually do this because figuring out what values to set is essentially equivalent to reinventing autoconf / configure! Perhaps we could implement something that works in common system layouts (e.g. hardcoded defaults for common distros like Debian/Ubuntu and RedHat).
Until we have a better solution here, just understand that anything looking
at sysconfig
could resolve non-existent paths or names of binaries that
don’t exist on the current machine.
Starting with the Linux and macOS distributions released in 2024, we do normalize some values in these files at build time. Normalizations include:
Removing compiler flags that are non-portable.
Removing references to build paths (e.g.
/tools
on Linux).
If there is a build time normalization that you think should be performed to make distributions more portable, please file a GitHub issue.