🐍

ディレクトリ内に閉じた任意バージョンの Python 環境構築 (python-build-standalone)

2024/07/01に公開

はじめに

追記 2024-09-20: ササッと実行したい人はこちら

本記事では、python-build-standalone のバイナリを使い、お手軽にディレクトリ内に閉じた Python 環境構築する方法を紹介します。

ユースケースとしては以下のような場面を想定しています。

  • 「サーバー上で動かすちょっとしたスクリプトを実行したいけど、システムの Python のバージョンが古い」
  • 「Python をインストールしたいけど他の環境やディレクトリには影響を与えたくない」
  • 「Docker を導入するほどではない」

※ python-build-standalone バイナリの Runtime Requirements はこちら

結論

以下のスクリプトを実行して Python バイナリをディレクトリ以下に配置します。

./install-python.sh

#!/usr/bin/env bash
set -eu -o pipefail

# インストールしたいバイナリを選択 (対応する release date も適宜変更)
# https://github.com/indygreg/python-build-standalone/releases
# 例
# https://github.com/indygreg/python-build-standalone/releases/download/20240415/cpython-3.12.3+20240415-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst
readonly python_version="3.12.3"
readonly python_build_standalone_release_name="20240415"
readonly python_build_standalone_filename="cpython-"${python_version}"+"${python_build_standalone_release_name}"-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst"

script_dir=$(
  cd -- "$(dirname "$0")" &>/dev/null
  pwd -P
)
readonly script_dir

readonly local_dir="$script_dir"/.local
readonly tmp_dir="$local_dir/.cache"

readonly python_build_standalone_dir="${local_dir}"/py/"${python_version:?}"
readonly python_installed_dir="${python_build_standalone_dir:?}"/install

if [ ! -d "${python_installed_dir:?}" ]; then
  readonly python_archive="${tmp_dir}/python-${python_version}.tar.zst"
  if [ ! -f "${python_archive}" ]; then
    echo "Downloading Python..."
    mkdir -p "$(dirname "$python_archive")"
    # standalone-python からダウンロード
    curl -L -C - -o "${python_archive}" https://github.com/indygreg/python-build-standalone/releases/download/"${python_build_standalone_release_name}"/"${python_build_standalone_filename}"
    curl -L -C - -o "${python_archive}".sha256 https://github.com/indygreg/python-build-standalone/releases/download/"${python_build_standalone_release_name}"/"${python_build_standalone_filename}".sha256
    echo "Successfully downloaded."

    # check hash
    diff -s <(cut -d' ' -f1 "${python_archive}".sha256) <(shasum -a 256 "${python_archive}" | cut -d' ' -f1)
    echo "Hash is OK."
  fi

  echo "Extracting Python..."
  mkdir -p "${python_build_standalone_dir}"
  # Need zstd pacakge (apt install zstd)
  zstd -d -c "${python_archive}" \
    | tar --strip-components=1 -axf - -C "${python_build_standalone_dir}"
fi

echo "Python is installed at ${python_build_standalone_dir}/install/bin"

ダウンロードした Python を実行する際は ${python_build_standalone_dir}/install/bin を PATH に追加してあげれば OK です。

以下のような wrapper のスクリプトを用意しておくと便利です。

./python.sh

#!/usr/bin/env bash
set -eu -o pipefail

readonly python_version="3.12.3"

script_dir=$(
  cd -- "$(dirname "$0")" &>/dev/null
  pwd -P
)
readonly script_dir
readonly local_dir="$script_dir"/.local
readonly python_build_standalone_dir="${local_dir}"/py/"${python_version:?}"

# ディレクトリに閉じた Python 環境への PATH を追加
export PATH="${python_build_standalone_dir}/install/bin:${PATH}"

# pip install のインストール先指定
export PYTHONUSERBASE="${local_dir}"
# pip install でインストールした CLI ツールへの PATH 追加
export PATH="${local_dir}/bin:${PATH}"

python "$@"

PYTHONUSERBASE については後述します。

想定ディレクトリ構成

some_directory/
|-- .cache/           ... python-build-standalone からダウンロードしたファイルを保存
|-- .local/           ... `~/.local` (XDG_DATA_HOME) と似た役割を持たせる
|   `-- py/3.X,Y/     ... Python のインストール先 (展開先)
|      |-- ...
|      `-- bin/       ... PATH に追加
|
|-- install-python.sh ... Python のインストールスクリプト
`-- python.sh         ... Python のラッパー (シェルスクリプト)

PYTHONUSERBASE に付いて

Python は pip install でパッケージをインストールすることができますが、特に設定をしない場合は ~/.local が使われてしまいます。
Python がディレクトリ内に閉じていても、パッケージがディレクトリの外にインストールされてしまうのでは本末転倒です。

CLI ツールの例として Pipx をインストールする例を以下に示したいと思います。

まず、環境変数を設定せずに以下のように pip install した場合、 ~/.local にインストールされてしまいます。

./python.sh -m pip install --user pipx

これは pip install --user がデフォルトでパッケージを ~/.local 以下にインストールするためです。

インストール先を特定のディレクトリに向けるには環境変数 PYTHONUSERBASE を設定する必要があります。

export PYTHONUSERBASE="$(realpath .)/.local"
./python.sh -m pip install --user pipx

このようにすることで pipx は ./.local にインストールされ、 ./.local/bin/pipx で pipx を実行できるようになります。

注意: アプリ個別のユーザーディレクトリ設定

先の例では pipx をインストールしてみましたが、 pipx 自体の設定にも注意する必要があります。
環境変数を設定しない場合とユーザーディレクトリ以下を使ってしまいます。

pipx において注意が必要なものは以下の 3 つです。

  • PIPX_HOME ... 仮想環境を配置する場所
  • PIPX_BIN_DIR ... 実行ファイルへの symlink を配置する場所
  • PIPX_MAN_DIR ... マニュアルページへの symlink を配置する場所

https://pipx.pypa.io/latest/docs/

Virtual Environment location is ~/.local/share/pipx/venvs.
Symlinks to apps are placed in ~/.local/bin.
Symlinks to manual pages are placed in ~/.local/share/man.

optional environment variables:
  PIPX_HOME              Overrides default pipx location. Virtual Environments
                        will be installed to $PIPX_HOME/venvs.
  PIPX_BIN_DIR           Overrides location of app installations. Apps are
                        symlinked or copied here.
  PIPX_MAN_DIR           Overrides location of manual pages installations.
                        Manual pages are symlinked or copied here.

設定例は次のセクションで示します。

実行例

今までの情報を元に一連の流れを示します。

  • 独立した Python をインストール
  • インストールした Python の pip を利用して pipx をインストール
  • pipx を使って pyjokes (CLI ツール) をインストール
  • pyjokes を実行
# .local/py 以下に Python をインストール
./install-python.sh
# pip をアップグレード (./.local/bin/pip)
./python.sh -m pip install --user -U pip
# .local 以下に pipx をインストール
./python.sh -m pip install --user pipx

# pipx 用の環境変数設定
local_dir="$(realpath .)/.local"
export PIPX_HOME="${local_dir}/share/pipx/venvs"
export PIPX_BIN_DIR="${local_dir}/bin"
export PIPX_MAN_DIR="${local_dir}/share/man"
# pipx を使って pyjokes をインストール
./python.sh -m pipx install pyjokes

# pyjoke コマンド実行
./.local/bin/pyjoke

その他の方法

ディレクトリ内に閉じていることをキにしなければ、今回紹介した方法以外にも任意の Python バージョンを利用する方法はいくつかあります。

  • Python ビルド
    • 公式の Python ビルド
    • pyenv の利用
  • 裏で python-build-standalone を利用しているツール等
    • rye ... パッケージマネジメント以外に CLI ツールのインストールにも対応
    • hatch ... パッケージマネジメントを目的とした際に任意の Python バージョンを利用可能
GitHubで編集を提案

Discussion