最近學到了 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 實作的工具、但不想要污染到專案本身的測試環境。
經過探索後我決定使用這套組合拳:
- 跑個 setup-python 把工具需要的 Python 版本裝起來
- 用 venv 把工具裝進獨立的虛擬環境裡
- 把該工具的進入點丟進 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 去跑測試等情境下,專案所需的環境、工作路徑等都不會被污染,這樣就不用擔心副作用了!