# Building Python 3.11 This article focuses on building CPython for common Linux server operating systems. These scripts will be tested in fresh Docker container. I'll install dependencies from package manager if possible, or I'll try compile myself if that makes sense. The dependencies doesn't change much between these CPython versions, so Python 3.12 and 3.10 may have the exact same process. (Obviously the link and the filename in the script must be changed.) All examples, unless `--prefix` is set, uses `altinstall` target for installing Python onto the system, which will install CPython into `/usr/local` by default and will not affect default `python` or `python3` command. ## Operating Systems | Distro | Note | | --- | --- | | [Debian 10+, Ubuntu 18.04+](#debian-10-ubuntu-1804) | (For Ubuntu 18.04) Additional info required by pkgconfig. | | [CentOS 7](#centos-7) | Additional info required by pkgconfig.
CentOS uses tk 8.5, while Python 3.10 and later uses tk 8.6 by default. | | [Rocky Linux 8](#rocky-linux-8) | | | [Rocky Linux 9](#rocky-linux-9) | | | [openSUSE Leap 15](#opensuse-leap-15) | | | [openSUSE Leap 42](#opensuse-leap-42) | Building OpenSSL 1.1.1 or later version is required. | | [Alpine Linux 3.18](#alpine-linux-318) | | ## Debian 10+, Ubuntu 18.04+ ### Normal Install Tested in [debian:buster](https://hub.docker.com/_/debian) and [ubuntu:18.04](https://hub.docker.com/_/ubuntu). ```bash #!/bin/bash PYTHON_VERSION=3.12.0 TZ=Etc/UTC source /etc/os-release ln -s /usr/share/zoneinfo/$TZ /etc/localtime echo $TZ > /etc/timezone apt update echo "" | apt upgrade -y echo "" | apt install -y libncurses-dev libbz2-dev libgdbm-dev liblzma-dev libssl-dev tk-dev \ uuid-dev libreadline-dev libsqlite3-dev libffi-dev gcc make automake \ wget libgdbm-dev libgdbm-compat-dev wget https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz tar -xvf Python-${PYTHON_VERSION}.tgz cd Python-${PYTHON_VERSION} if [ "$ID" == "ubuntu" ] && [ "$VERSION_ID" == "18.04" ]; then # For some reason, both tk-dev and tcl-dev doesn't include their pkgconfig files. # Although I can make a PR to the Python repo, it just doesn't make any sense for creating an # PR for an unsupported verison of Ubuntu. Hence I just post it here. mkdir -p Misc/ubuntu18.04 cat << EOF > Misc/ubuntu18.04/tcl.pc # tcl pkg-config source file prefix=/usr exec_prefix=/usr libdir=/usr/lib/$(dpkg-architecture -q DEB_BUILD_GNU_TYPE) includedir=/usr/include/tcl8.6 Name: Tool Command Language Description: Tcl is a powerful, easy-to-learn dynamic programming language, suitable for a wide range of uses. URL: http://www.tcl.tk/ Version: 8.6 Requires.private: zlib >= 1.2.3 Libs: -L\${libdir} -ltcl8.6 -ltclstub8.6 Libs.private: -ldl -lz -lpthread -lm Cflags: -I\${includedir} EOF cat << EOF > Misc/ubuntu18.04/tk.pc # tk pkg-config source file prefix=/usr exec_prefix=/usr libdir=/usr/lib/$(dpkg-architecture -q DEB_BUILD_GNU_TYPE) includedir=/usr/include/tcl8.6 Name: The Tk Toolkit Description: Tk is a cross-platform graphical user interface toolkit, the standard GUI not only for Tcl, but for many other dynamic languages as well. URL: https://www.tcl-lang.org/ Version: 8.6 Requires: tcl >= 8.6 Libs: -L\${libdir} -ltk8.6 -ltkstub8.6 Libs.private: -lXft -lfontconfig -lfreetype -lfontconfig -lX11 -lXss -lXext Cflags: -I\${includedir} EOF export PKG_CONFIG_PATH=$(pwd)/Misc/ubuntu18.04 fi ./configure --enable-loadable-sqlite-extensions make -j$(nproc) make altinstall ``` Testing (Before `make altinstall`) ```bash #!/bin/bash apt install -y locales locales-all gdb g++ make test ``` ``` == Tests result: SUCCESS == 466 tests OK. 11 tests skipped: test.test_asyncio.test_windows_events test.test_asyncio.test_windows_utils test_devpoll test_ioctl test_kqueue test_launcher test_msilib test_startfile test_winconsoleio test_winreg test_wmi 6 tests skipped (resource denied): test_ossaudiodev test_tix test_tkinter test_ttk test_winsound test_zipfile64 Total duration: 1 min 15 sec Total tests: run=41,575 skipped=1,482 Total test files: run=477/483 skipped=11 resource_denied=6 Result: SUCCESS ``` ## CentOS 7 Tested on [centos:7](https://hub.docker.com/_/centos) ~~The script should work with RHEL7 based systems.~~ _Update on Jun 26, 2023_: Thanks to the [decision from Red Hat](https://www.redhat.com/en/blog/furthering-evolution-centos-stream), I'm no longer sure if RHEL-derived system will work anymore since I have no intension to apply Red Hat developer subscription. Also, well done to someone that came up with that decision, making Canonical's Ubuntu Server more attractive for enterprise users than ever. * Note: `_tkinter` module is not available because Python 3.11 requires tk 8.6, while package `tk-devel` is using tk 8.5. ### Build RPM Checkout my repo about [building Python 3.10 for EL7](https://github.com/jacky9813/python3.10-el7). That being said, since that is focus on Python 3.10, it may require changes here and there, so use it carefully. ### Normal Install ```bash #!/bin/bash PYTHON_VERSION=3.11.5 yum install -y epel-release yum install -y ncurses-devel bzip2-devel gdbm-devel libnsl2-devel xz-devel \ libuuid-devel readline-devel sqlite-devel libffi-devel \ openssl11-devel gcc make automake wget perl-core pkgconfig wget https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz tar -xvf Python-${PYTHON_VERSION}.tgz pushd Python-${PYTHON_VERSION} # Hats off to Yebolin https://github.com/pyenv/pyenv/issues/2416#issuecomment-1207579730 # openssl11-devel is in epel export CFLAGS="$CFLAGS $(pkg-config --cflags openssl11)" export LDFLAGS="$LDFLAGS $(pkg-config --libs openssl11)" ./configure --enable-loadable-sqlite-extensions make -j$(nproc) make altinstall popd ``` Testing (Before `make altinstall`) ```bash #!/bin/bash yum install -y gcc-c++ pushd /usr/share/i18n/charmaps for charmap_file in $(ls *.gz); do gzip -d $charmap_file done popd pushd Lib/test TESTED_LOCALES=$(../../python -c 'import test__locale; print(" ".join(test__locale.candidate_locales))') popd # Building Locales for testing for locale_id in $TESTED_LOCALES; do locale=$(echo $locale_id | sed 's/\..*//') charmap=$(echo $locale_id | grep '\.' | sed 's/^.*\.//' | sed 's/@.*//') variant=$(echo $locale_id | grep '@' | sed 's/.*@//') charmap=${charmap:-UTF-8} if echo $charmap | grep -E '^euc[A-Z]+$' &>/dev/null ; then charmap=$(echo $charmap | sed -E 's/euc([A-Z]+)/EUC-\1/') fi if echo $charmap | grep -E 'ISO[0-9]+.*' &>/dev/null ; then charmap=$(echo $charmap | sed 's/^ISO/ISO-/') fi echo "Building locale $locale_id" if ! [ -z "$variant" ]; then localedef -f ${charmap} -i ${locale}@${variant} $locale_id else localedef -f ${charmap} -i $locale $locale_id fi done make test ``` ``` == Tests result: FAILURE then SUCCESS == 415 tests OK. 19 tests skipped: test_devpoll test_gdb test_idle test_ioctl test_kqueue test_launcher test_msilib test_ossaudiodev test_startfile test_tcl test_tix test_tk test_ttk_guionly test_ttk_textonly test_turtle test_winconsoleio test_winreg test_winsound test_zipfile64 1 re-run test: test_nntplib Total duration: 4 min 6 sec Tests result: FAILURE then SUCCESS ``` * `test_gdb` will be ignored as test script detects CentOS 7 official `gdb` package is compiled with Python 2. ### Distributable Tarball #### Build ```bash #!/bin/bash source /etc/os-release PYTHON_VERSION=3.11.5 OPENSSL_VERSION=1.1.1w OPENSSL_SHA256=cf3098950cb4d853ad95c0841f1f9c6d3dc102dccfcacd521d93925208b76ac8 yum install -y epel-release yum install -y ncurses-devel bzip2-devel gdbm-devel xz-devel zlib-devel \ libuuid-devel readline-devel sqlite-devel libffi-devel \ gcc make automake perl-core pkgconfig nasm curl # For unknown reason, build process wouldn't pick up openssl11-devel package # when prefix is set. # I have to build my own OpenSSL here. # Also, the certificate for www.openssl.org cannot be verified by # curl or wget for some reason. curl -Ok https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz if echo "${OPENSSL_SHA256} openssl-${OPENSSL_VERSION}.tar.gz" | sha256sum -c; then tar xvf openssl-${OPENSSL_VERSION}.tar.gz pushd openssl-${OPENSSL_VERSION} ./config --prefix=/opt/python/${PYTHON_VERSION}-el${VERSION_ID}.$(uname -m) --openssldir=/opt/python/${PYTHON_VERSION}-el${VERSION_ID}.$(uname -m) make -j$(nproc) make install_sw popd else echo "SHA256 check failed" exit 1 fi # Build Python curl -O https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz tar -xvf Python-${PYTHON_VERSION}.tgz pushd Python-${PYTHON_VERSION} ./configure \ --enable-loadable-sqlite-extensions \ --prefix=/opt/python/${PYTHON_VERSION}-el${VERSION_ID}.$(uname -m) \ --with-openssl=/opt/python/${PYTHON_VERSION}-el${VERSION_ID}.$(uname -m) \ --with-openssl-rpath=auto make -j$(nproc) make install popd ``` #### Install ```bash #!/bin/bash # Basic setup source /etc/os-release PYTHON_INSTALL_BASE=/opt/python PYTHON_VERSION=3.10 PYTHON_FULL_VERSION=${PYTHON_VERSION}.13 PYTHON_INSTALL_VERSION=${PYTHON_FULL_VERSION}-el${VERSION_ID}.$(uname -m) # Make this variable empty for not to create symbolic link to installed Python # MKLINK_TO_LOCAL_BIN= MKLINK_TO_LOCAL_BIN=1 if ! [ -d ${PYTHON_INSTALL_BASE}/${PYTHON_INSTALL_VERSION} ]; then # Installing dependencies yum install -y epel-release # It is not required to install openssl11-libs as OpenSSL 1.1.1 came bundled. yum install -y ncurses-libs bzip2-libs gdbm xz-libs libuuid readline sqlite libffi # Install Python install -d ${PYTHON_INSTALL_BASE} tar xvf python-${PYTHON_INSTALL_VERSION}.tar.gz -C ${PYTHON_INSTALL_BASE} # Configure PATH echo "export PATH=\"\$PATH:${PYTHON_INSTALL_BASE}/${PYTHON_INSTALL_VERSION}/bin\"" | tee /etc/profile.d/python3.10.sh . /etc/profile if [ -f ~/.bashrc ]; then . ~/.bashrc fi if ! [ -z "$MKLINK_TO_LOCAL_BIN" ]; then for program in 2to3-3.10 pip3.10 pydoc3.10 python3.10 python3.10-config; do if [ -L /usr/local/bin/${program} ] ; then rm -rv /usr/local/bin/${program} fi ln -sv ${PYTHON_INSTALL_BASE}/${PYTHON_INSTALL_VERSION}/bin/${program} /usr/local/bin/${program} done fi # Links for pkgconfig LIB=lib if uname -m | grep 64 > /dev/null && [ -d /usr/lib64/pkgconfig ] ; then LIB=lib64 fi ln -sv ${PYTHON_INSTALL_BASE}/${PYTHON_INSTALL_VERSION}/lib/pkgconfig/python-3.10.pc /usr/${LIB}/pkgconfig/python-3.10.pc # Links for includes if [ -L /usr/local/include/python${PYTHON_VERSION} ]; then rm -fv /usr/local/include/python${PYTHON_VERSION} fi ln -sv ${PYTHON_INSTALL_BASE}/${PYTHON_INSTALL_VERSION}/include/python${PYTHON_VERSION} /usr/local/include/python${PYTHON_VERSION} # Install man page for python if system has man and no python pages available. if which man &> /dev/null && ! man -k python &> /dev/null ; then ln -sv $(find ${PYTHON_INSTALL_BASE}/${PYTHON_INSTALL_VERSION}/share/man/man1 -type f) /usr/local/share/man/man1/python${PYTHON_VERSION} fi else echo ${PYTHON_INSTALL_VERSION} has already been installed. fi ``` ## Rocky Linux 8 IMPORTANT: EL8, or at least Rocky Linux 8, has `python3.11` package in App Stream repo. BEWARE THAT IT MAY GET VERY CONFUSING IF MULTIPLE PYTHON 3.11 BUILDS ARE INSTALLED. Tested in [rockylinux:8](https://hub.docker.com/_/rockylinux) ~~The script should work in RHEL8 based systems.~~ _Update on Jun 26, 2023_: Thanks to the [decision from Red Hat](https://www.redhat.com/en/blog/furthering-evolution-centos-stream), I'm no longer sure if RHEL-derived system will work anymore since I have no intension to apply Red Hat developer subscription. Also, well done to someone that came up with that decision, making Canonical's Ubuntu Server more attractive for enterprise users than ever. ```bash #!/bin/bash PYTHON_VERSION=3.11.5 # libnsl2-devel in PowerTools for Rocky Linux 8 dnf install -y 'dnf-command(config-manager)' dnf config-manager --set-enabled powertools dnf install -y ncurses-devel bzip2-devel gdbm-devel libnsl2-devel xz-devel \ openssl-devel tk-devel libuuid-devel readline-devel sqlite-devel \ libffi-devel gcc make automake wget wget https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz tar -xvf Python-${PYTHON_VERSION}.tgz cd Python-${PYTHON_VERSION} ./configure --enable-loadable-sqlite-extensions make -j$(nproc) make altinstall ``` Testing (Before `make altinstall`) ```bash dnf install -y gdb gcc-c++ glibc-locale-source findutils pushd /usr/share/i18n/charmaps for charmap_file in $(ls *.gz); do gzip -d $charmap_file done popd pushd Lib/test TESTED_LOCALES=$(../../python -c 'import test__locale; print(" ".join(test__locale.candidate_locales))') popd # Building Locales for testing for locale_id in $TESTED_LOCALES; do locale=$(echo $locale_id | sed 's/\..*//') charmap=$(echo $locale_id | grep '\.' | sed 's/^.*\.//' | sed 's/@.*//') variant=$(echo $locale_id | grep '@' | sed 's/.*@//') charmap=${charmap:-UTF-8} if echo $charmap | grep -E '^euc[A-Z]+$' &>/dev/null ; then charmap=$(echo $charmap | sed -E 's/euc([A-Z]+)/EUC-\1/') fi if echo $charmap | grep -E 'ISO[0-9]+.*' &>/dev/null ; then charmap=$(echo $charmap | sed 's/^ISO/ISO-/') fi echo "Building locale $locale_id" if ! [ -z "$variant" ]; then localedef -f ${charmap} -i ${locale}@${variant} $locale_id else localedef -f ${charmap} -i $locale $locale_id fi done dnf install -y glibc-langpack-* make test ``` ``` == Tests result: FAILURE then FAILURE == 419 tests OK. 1 test failed: test__locale 14 tests skipped: test_devpoll test_ioctl test_kqueue test_launcher test_msilib test_ossaudiodev test_startfile test_tix test_tk test_ttk_guionly test_winconsoleio test_winreg test_winsound test_zipfile64 2 re-run tests: test__locale test_tools Total duration: 3 min 2 sec Tests result: FAILURE then FAILURE ``` * I'm having problem getting `test__locale` working, with or without `glibc-langpack-*` installed. ## Rocky Linux 9 IMPORTANT: EL9, or at least Rocky Linux 9, has `python3.11` package in App Stream repo. BEWARE THAT IT MAY GET VERY CONFUSING IF MULTIPLE PYTHON 3.11 BUILDS ARE INSTALLED. Tested in [rockylinux:9](https://hub.docker.com/_/rockylinux) ~~The script should work in RHEL9 based systems.~~ _Update on Jun 26, 2023_: Thanks to the [decision from Red Hat](https://www.redhat.com/en/blog/furthering-evolution-centos-stream), I'm no longer sure if RHEL-derived system will work anymore since I have no intension to apply Red Hat developer subscription. Also, well done to someone that came up with that decision, making Canonical's Ubuntu Server more attractive for enterprise users than ever. ```bash #!/bin/bash PYTHON_VERSION=3.11.5 # gdbm-devel and libnsl2-devel in CRB for Rocky Linux 9 dnf install -y 'dnf-command(config-manager)' dnf config-manager --set-enabled crb dnf install -y ncurses-devel bzip2-devel gdbm-devel libnsl2-devel xz-devel \ openssl-devel tk-devel libuuid-devel readline-devel sqlite-devel \ libffi-devel gcc make automake wget wget https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz tar -xvf Python-${PYTHON_VERSION}.tgz cd Python-${PYTHON_VERSION} ./configure --enable-loadable-sqlite-extensions make -j$(nproc) make altinstall ``` Testing (before `make install`) ```bash dnf install -y gdb gcc-c++ findutils glibc-locale-source pushd /usr/share/i18n/charmaps for charmap_file in $(ls *.gz); do gzip -d $charmap_file done popd pushd Lib/test TESTED_LOCALES=$(../../python -c 'import test__locale; print(" ".join(test__locale.candidate_locales))') popd # Building Locales for testing for locale_id in $TESTED_LOCALES; do locale=$(echo $locale_id | sed 's/\..*//') charmap=$(echo $locale_id | grep '\.' | sed 's/^.*\.//' | sed 's/@.*//') variant=$(echo $locale_id | grep '@' | sed 's/.*@//') charmap=${charmap:-UTF-8} if echo $charmap | grep -E '^euc[A-Z]+$' &>/dev/null ; then charmap=$(echo $charmap | sed -E 's/euc([A-Z]+)/EUC-\1/') fi if echo $charmap | grep -E 'ISO[0-9]+.*' &>/dev/null ; then charmap=$(echo $charmap | sed 's/^ISO/ISO-/') fi echo "Building locale $locale_id" if ! [ -z "$variant" ]; then localedef -f ${charmap} -i ${locale}@${variant} $locale_id else localedef -f ${charmap} -i $locale $locale_id fi done dnf install -y glibc-langpack-* make test ``` ``` == Tests result: FAILURE then FAILURE == 419 tests OK. 1 test failed: test__locale 14 tests skipped: test_devpoll test_ioctl test_kqueue test_launcher test_msilib test_ossaudiodev test_startfile test_tix test_tk test_ttk_guionly test_winconsoleio test_winreg test_winsound test_zipfile64 1 re-run test: test__locale Total duration: 2 min 39 sec Tests result: FAILURE then FAILURE ``` * Just like Rocky Linux 8, having problem getting `test__locale` working. ## openSUSE Leap 15 IMPORTANT: SUSE 15 has `python311` package in Updates from SLES 15 repo. BEWARE THAT IT MAY GET VERY CONFUSING IF MULTIPLE PYTHON 3.11 BUILDS ARE INSTALLED. Tested in [opensuse/leap:15.5](https://hub.docker.com/r/opensuse/leap). ```bash PYTHON_VERSION=3.11.5 zypper install -y ncurses-devel libbz2-devel gdbm-devel libnsl-devel xz-devel \ libopenssl-3-devel tk-devel libuuid-devel readline-devel \ sqlite3-devel libffi-devel gcc make automake wget tar gzip wget https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz tar -xvf Python-${PYTHON_VERSION}.tgz cd Python-${PYTHON_VERSION} ./configure --enable-loadable-sqlite-extensions make -j$(nproc) make altinstall ``` Test ```bash #!/bin/bash # within compiled source directory zypper install -y gdb glibc-locale gcc-c++ make test ``` ``` == Tests result: FAILURE then SUCCESS == 420 tests OK. 14 tests skipped: test_devpoll test_ioctl test_kqueue test_launcher test_msilib test_ossaudiodev test_startfile test_tix test_tk test_ttk_guionly test_winconsoleio test_winreg test_winsound test_zipfile64 3 re-run tests: test___all__ test_nntplib test_tools Total duration: 5 min 22 sec Tests result: FAILURE then SUCCESS ``` ## openSUSE Leap 42 Tested in Docker image [opensuse/leap:42.3](https://hub.docker.com/r/opensuse/leap). openSUSE Leap 42 doesn't have OpenSSL 1.1.1 in its official repository, thus we'll build our own OpenSSL. ### Normal Install ```bash #!/bin/bash PYTHON_VERSION=3.11.5 zypper install -y libffi-devel-gcc5 ncurses-devel libbz2-devel gdbm-devel libnsl-devel \ xz-devel tk-devel libuuid-devel readline-devel sqlite3-devel gcc make \ automake tar gzip curl nasm curl -LOk https://www.openssl.org/source/openssl-1.1.1w.tar.gz tar xf openssl-1.1.1w.tar.gz pushd openssl-1.1.1w ./config make -j$(nproc) make install_sw popd curl -LO https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz tar xf Python-${PYTHON_VERSION}.tgz pushd Python-${PYTHON_VERSION} ./configure \ --enable-loadable-sqlite-extensions \ --with-openssl=/usr/local/ssl \ --with-openssl-rpath=auto make -j$(nproc) make install popd ``` Testing ```bash #!/bin/bash # within compiled source directory zypper install -y glibc-locale gcc-c++ make test ``` ``` == Tests result: FAILURE then SUCCESS == 419 tests OK. 15 tests skipped: test_devpoll test_gdb test_ioctl test_kqueue test_launcher test_msilib test_ossaudiodev test_startfile test_tix test_tk test_ttk_guionly test_winconsoleio test_winreg test_winsound test_zipfile64 1 re-run test: test_nntplib Total duration: 4 min 54 sec Tests result: FAILURE then SUCCESS ``` * `test_gdb` will be ignored as test script detects openSUSE official `gdb` package is compiled with Python 2. ### TAR ball ```bash #!/bin/bash PYTHON_VERSION=3.11.5 PYTHON_BUILD=opensuse42-$(uname -m) PYTHON_PREFIX=/opt/python/${PYTHON_VERSION}-${PYTHON_BUILD} zypper install -y libffi-devel-gcc5 ncurses-devel libbz2-devel gdbm-devel libnsl-devel \ xz-devel tk-devel libuuid-devel readline-devel sqlite3-devel gcc make \ automake tar gzip curl nasm curl -LOk https://www.openssl.org/source/openssl-1.1.1w.tar.gz tar xf openssl-1.1.1w.tar.gz pushd openssl-1.1.1w ./config --prefix=${PYTHON_PREFIX} --openssldir=${PYTHON_PREFIX} make -j$(nproc) make install_sw popd curl -LO https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz tar xf Python-${PYTHON_VERSION}.tgz pushd Python-${PYTHON_VERSION} ./configure \ --enable-loadable-sqlite-extensions \ --with-openssl=${PYTHON_PREFIX} \ --with-openssl-rpath=auto \ --prefix=${PYTHON_PREFIX} make -j$(nproc) make install popd CURRENT_DIR="$(pwd)" pushd ${PYTHON_PREFIX}/.. tar czf "${CURRENT_DIR}/python-${PYTHON_VERSION}-${PYTHON_BUILD}.tar.gz" ${PYTHON_VERSION}-${PYTHON_BUILD} popd ``` When unpacking `python-${PYTHON_VERSION}-${PYTHON_BUILD}.tar.gz`, user should always unpack it into `/opt/python` to prevent shared objects not being found. Also, the target system may require install `libffi4` package. For example: ```bash #!/bin/bash zypper install -y libffi4 mkdir -p /opt/python tar -xzvf python-3.11.5-opensuse42-x86_64.tar.gz -C /opt/python export PATH="$PATH:/opt/python/3.11.5-opensuse42-x86_64/bin" ``` ## Alpine Linux 3.18 IMPORTANT: Alpine has `python3` package that can be Python 3.11. BEWARE THAT IT MAY GET VERY CONFUSING IF MULTIPLE PYTHON 3.11 BUILDS ARE INSTALLED. Tested with [alpine:3.18](https://hub.docker.com/_/alpine). The package release cycle for Alpine Linux is really fast, consider using `apk add python3` to install officially supported Python. ``` bash apk add musl-dev ncurses-dev bzip2-dev gdbm-dev libnsl-dev xz-dev openssl3-dev tk-dev libuuid \ readline-dev sqlite-dev libffi-dev gcc make automake wget tar gzip wget https://www.python.org/ftp/python/3.11.0/Python-3.11.0.tgz tar -xvf Python-3.11.0.tgz cd Python-3.11.0 ./configure --enable-loadable-sqlite-extensions make -j$(nproc) make altinstall ``` Testing (before `make altinstall`) ```sh #!/bin/sh apk add gdb g++ musl-locales musl-locales-lang make test ``` ``` == Tests result: FAILURE then FAILURE == 415 tests OK. 5 tests failed: test__locale test_c_locale_coercion test_locale test_os test_re 14 tests skipped: test_devpoll test_ioctl test_kqueue test_launcher test_msilib test_ossaudiodev test_startfile test_tix test_tk test_ttk_guionly test_winconsoleio test_winreg test_winsound test_zipfile64 6 re-run tests: test__locale test_c_locale_coercion test_locale test_os test_re test_tools Total duration: 3 min 26 sec Tests result: FAILURE then FAILURE ``` * Seems like CPython doesn't like i18n implementation in musl, all failures are related to locales and charmaps.