Build a snap with any version of Python I want

I want to share my experience with this exact problem that you’re facing: in short, there’s there’s no ‘built-in’ easy way that I came across that easily allows you to use whatever python version you want. What we settled on for our snaps was to actually build the python interpreter that we use ourselves (as a part) during the snap build process and this way we have full control of what version of python we ship.

This is what we’ve settled on:

parts:
  python38:
    source: https://www.python.org/ftp/python/3.8.3/Python-3.8.3.tar.xz
    source-type: tar
    source-checksum: md5/3000cf50aaa413052aef82fd2122ca78
    plugin: autotools
    configflags:
      - --prefix=/usr
      # Enabling this will make the build times go up significantly because it
      # turns on link time optimizations and profile guided optimizations.
      #
      # For PGO, python is compiled twice. Once to collect profiling data (by
      # from running all UTs) and once to create an optimized build based on
      # that data.
      #
      # Unfortunately, one of the unit tests, test_socket, hangs for some
      # reason and it seems to be a known issue. For now just disable
      # optimizations as a workaround.
      #
      # - --enable-optimizations
    build-packages:
      # not needed: tk-dev
      - libbz2-dev
      - libexpat1-dev
      - libffi-dev
      - libgdbm-dev
      - liblzma-dev
      - libncurses5-dev
      - libreadline-dev
      - libsqlite3-dev
      - libssl-dev
      - libzip-dev
      - uuid-dev
    stage-packages:
      # not needed: tk8.6
      - libbz2-1.0
      - libexpat1
      - libffi6
      - libgdbm3
      - liblzma5
      - libncurses5
      - libreadline6
      - libsqlite3-0
      - libssl1.0.0
      - libzip4
      - uuid-runtime
    override-stage: |
      # We want the latest pip to be able to install pyproject.toml based projects
      PYTHONUSERBASE="$SNAPCRAFT_PART_INSTALL/usr" python3.8 -m pip install --user --upgrade pip wheel

      # Apply the same shebang rewrite as done by snapcraft
      find $SNAPCRAFT_PART_INSTALL/usr/bin/ -maxdepth 1 -mindepth 1 -type f -executable -exec \
        sed -i                                                                                \
          "s|^#!${SNAPCRAFT_PART_INSTALL}/usr/bin/python3.8$|#!/usr/bin/env python3|" {} \;

      snapcraftctl stage
    filesets:
      exclusion:
        - -etc
        - -lib/systemd
        - -usr/bin/2to3
        - -usr/bin/2to3-3.8
        - -usr/bin/deb-systemd-helper
        - -usr/bin/deb-systemd-invoke
        - -usr/bin/easy_install-3.8
        - -usr/bin/idle3
        - -usr/bin/idle3.8
        - -usr/bin/pip3
        - -usr/bin/pip3.8
        - -usr/bin/pydoc3
        - -usr/bin/pydoc3.8
        - -usr/bin/python3.8-config
        - -usr/bin/python3-config
        - -usr/bin/uuidgen
        - -usr/include
        - -usr/lib/*.a
        - -usr/lib/pkgconfig
        - -usr/lib/python3.8/test
        - -usr/sbin
        - -usr/share
        - -var
    prime:
      - "$exclusion"

If you’re okay with building python like this then you can use the recipe above and just change the source link to a different python version and update the shebang rewrite to point to the right python executable. Any part that is going to use python should have the following in its recipe:

after:
  - python38

Which guarantees that it will be built after the python interpret has been built. The major drawbacks is that this will significantly increase your build times (albeit, this can be amortized if you cache builds).

To be honest, this whole process is kind of a mess and required me to dig around in the snapcraft implementation to figure it out. And if you need to do builds of your python application for other CPU architectures then the best thing you can do to not waste your time is to get dedicated build hardware (I haven’t kept up with snap updates so I’m not sure if python mulit-arch build experience improved since the last time I checked things out last year).

2 Likes