在 GitHub Actions 中藏一個 Python 直譯器

最近學到了 actions/setup-python 的奇妙玩法——它可以幫我安裝 Python 直譯器、但是不把直譯器加到 PATH 裡面。

為什麼需要這個功能?

我要解決的問題是:我需要切割不同工具所使用的 Python 版本

我想要使用某個以 Python 實作的工具、但它使用的 Python 版本跟專案本身要跑測試的版本不同。在個人電腦上這類的需求可以透過 pipx / pyenv 等工具花式搭配來處理,但是在 CI 中會希望能有一個比較簡單高效的方式來建立環境,我不太想等 CI 轉圈圈等到海枯石爛。

怎麼使用

在 setup-python 的進階使用說明裡面藏了一段: Using update-environment flag

對應的使用是 setup-python 有接受 update-environment 這個輸入,當設為否時它就不會去更新各種環境變數,故用這個方法安裝的 Python 直譯器不會在 $PATH 裡面看到:

steps:
- uses: actions/setup-python@v5
id: cp313
with:
python-version: "3.13"
update-environment: false

而需要使用到這個直譯器的步驟則直接使用 python-path 這個輸出參數來做對應的動作:

- run: ${{ steps.cp313.outputs.python-path }} my_script.py

跟 venv 搭配起來用

回到我的需求:我想要使用某個以 Python 實作的工具、但不想要污染到專案本身的測試環境。

經過探索後我決定使用這套組合拳:

  1. 跑個 setup-python 把工具需要的 Python 版本裝起來
  2. venv 把工具裝進獨立的虛擬環境裡
  3. 把該工具的進入點丟進 PATH

其中安裝的部分是因為我使用的 runner 裡面沒有 pipx(GitHub Actions 的 ubuntu runner 的話有內建),不然直接跑個 pipx install --python 會是比較簡單的方法。

以下備份一下目前探索出來的方法:

steps:
# set up the Python environment and virtual environment
- uses: actions/setup-python@v5
id: setup-tool-python
with:
python-version: "3.13"
update-environment: false
# create a virtual environment
- shell: bash
id: setup-venv
run: |+
${{ steps.setup-tool-python.outputs.python-path }} -m venv '${{ runner.temp }}/my-tool/venv'
echo 'venv-path=${{ runner.temp }}/my-tool/venv' >> "$GITHUB_OUTPUT"
echo 'pip-path=${{ runner.temp }}/my-tool/venv/bin/pip' >> "$GITHUB_OUTPUT"
# install the tool
- shell: bash
run: |+
${{ steps.setup-venv.outputs.pip-path }} install my-tool
# bring the entry point into the PATH
- shell: bash
run: |+
mkdir '${{ runner.temp }}/my-tool/bin'
ln -s '${{ steps.setup-venv.outputs.venv-path }}/bin/my-tool' '${{ runner.temp }}/my-tool/bin/my-tool'
echo '${{ runner.temp }}/my-tool/bin' >> "$GITHUB_PATH"

透過這樣的方法,在後續步驟中就可以直接使用 my-tool 這個 CLI 進入點,並且可以完全無視其背後的環境與相依。在專案本身也需要使用 Python 去跑測試等情境下,專案所需的環境、工作路徑等都不會被污染,這樣就不用擔心副作用了!