Building Python on macOS with LTO, Up-To-Date SQLite, and GUI Compatibilitychain link icon indicating a link to a heading

I use pyenv to build, install, and manage multiple versions of Python on my system at once. At some point recently, I needed to link my compiled Python against a newer version of SQLite than was shipped with the version of macOS that I was using at the time.

This need caused me to look through the available options in CPython’s ./configure script, and I noticed a few that I thought would be good to enable, including --enable-optimizations (who doesn’t want optimizations?), --with-lto (Link-Time Optimization, even more optimizations!), and --enable-ipv6 (why not?). The --enable-framework option was also new to me, and some research showed that a Framework build was what was necessary to have the tkinter module and Matplotlib’s GUIs work on Mac, so I decided to figure out how to make that happen as well.

After a good amount of time spent fussing with various environment variables and compiler arguments, this is the script I came up with, pyenv-env.sh:

#!/usr/bin/env sh
set -x

sdk_path="$(xcode-select --print-path)"
sqlite_path="$(brew --prefix sqlite)"
tcl_tk_path="$(brew --prefix tcl-tk)"
llvm_path="$(brew --prefix llvm)"

export PYTHON_CONFIGURE_OPTS="--enable-optimizations --enable-ipv6 --with-lto --enable-framework"
export MAKE_INSTALL_OPTS="PYTHONAPPSDIR=${HOME}/Applications/\$(PYTHONFRAMEWORK)-\$(VERSION)"
export PKG_CONFIG_PATH="${sqlite_path}/lib/pkgconfig:${tcl_tk_path}/lib/pkgconfig"
export CPPFLAGS="-I${sdk_path}/SDKs/MacOSX.sdk/usr/include -I${sqlite_path}/include -I${tcl_tk_path}/include"
export CFLAGS="-I${sdk_path}/usr/include -I${sqlite_path}/include -I${tcl_tk_path}/include"
export LDFLAGS="-L${sqlite_path}/lib -L${tcl_tk_path}/lib"
export LLVM_AR="${llvm_path}/bin/llvm-ar"

pyenv "$@"

There’s a few things going on here:

So, using this script will require SQLite, Tcl Tk, and LLVM installed from Homebrew.

I can then invoke this like ./pyenv-env.sh install 3.9.0 and all the variables will be set and pyenv will execute as desired.

There is one bit of weirdness with this: because it creates a Framework build, the various paths may not be what you expect:

Basically, this is a “Macification” of all the paths, which could mess up things like shell configuration files that expect things to be in certain places. This shouldn’t be hard to fix, though, and a small price to pay if the benefits of the Framework build are worth it to you.