PEP 660 – 基於 pyproject.toml 之建置的可編輯安裝(基於 Wheel)
- 作者:
- Daniel Holth <dholth at gmail.com>, Stéphane Bidoul <stephane.bidoul at gmail.com>
- 贊助人:
- Paul Moore <p.f.moore at gmail.com>
- 討論於:
- Discourse 討論串
- 狀態:
- 最終 (Final)
- 類型:
- 標準軌跡 (Standards Track)
- 主題:
- 套件封裝 (Packaging)
- 建立日期:
- 2021年3月30日
- 公告歷史:
- 決議:
- Discourse 討論串
摘要
本文件描述了一種 PEP 517 風格的方法,用於以「可編輯(editable)」模式安裝套件。
動機
Python 開發者希望能夠開發套件而無需將其安裝(即複製)到 site-packages 中,例如,直接在原始碼儲存庫的檢出(checkout)目錄中工作。
雖然可以透過將相關原始碼目錄加入 PYTHONPATH 來達成,但 setuptools 提供了 setup.py develop 機制來簡化此流程,並同時安裝依賴項和進入點(如控制台腳本)。pip 透過其 pip install --editable 選項公開了此機制。
以這種方式安裝專案,使得所匯入的 Python 程式碼保留在原始碼目錄中,這被稱為*可編輯*安裝模式。
既然 PEP 517 提供了建立 setuptools 替代方案的機制,並將安裝前端與建置後端解耦,我們就需要一種新的機制來以可編輯模式安裝套件。
原理
PEP 517 暫緩了「可編輯安裝」,這意味著非 setup.py 的發佈版本缺乏此功能。要為這些發佈版本保留 editable 安裝的唯一方法是提供相容的 setup.py develop 實作。透過定義一個可編輯 Hook,其他建置前端就能與 setup.py 達到同等功能。
術語與目標
可編輯安裝模式意味著被安裝專案的原始碼在本地目錄中是可用的。
一旦專案以可編輯模式安裝,使用者期望對本地原始碼樹中 *python* 程式碼的修改能夠立即生效,而無需執行新的安裝步驟。
某些類型的變更,例如新增或修改進入點,或新增依賴項,需要執行新的安裝步驟才會生效。這些變更通常在建置後端設定檔(如 pyproject.toml)中進行,因此符合一般使用者對於 *python* 原始碼應從原始碼樹中匯入的預期。
對非 python 原始碼(如 C 擴充模組)的修改,顯然需要編譯和/或安裝步驟才能生效。要執行的確切步驟仍將特定於所使用的建置後端。
當專案以可編輯模式安裝時,使用者期望安裝行為與一般安裝完全相同。特別是程式碼必須可被其他程式碼匯入,且中繼資料必須對標準機制(如 importlib.metadata)可用。
根據建置後端實作本規範的方式,可能會出現一些細微差異,例如原始碼樹中存在但不會成為一般安裝一部分的額外檔案。鼓勵建置後端記錄此類潛在差異。
機制
本 PEP 為 PEP 517 後端介面新增了三個可選 Hook。這些 Hook 用於建置一個 Wheel,在安裝後,允許該發佈版本從其原始碼資料夾中匯入。
build_editable
def build_editable(wheel_directory, config_settings=None, metadata_directory=None):
...
必須建置一個 .whl 檔案,並將其放置在指定的 wheel_directory 中。它必須作為 Unicode 字串傳回它所建立的 .whl 檔案的基本名稱(而非完整路徑)。
可以作為副作用對發佈版本進行原地(in-place)建置,以便任何擴充模組或其他已建置的產物準備好被使用。
.whl 檔案必須符合 Wheel 二進位檔案格式規範 (PEP 427)。特別是它必須包含一個相容的 .dist-info 目錄。中繼資料必須與 build_wheel 或 prepare_metadata_for_build_wheel 所產生的中繼資料相同,唯一的例外是 Requires-Dist,其可能略有不同(如下所述)。
建置後端產生的 Wheel 必須具有與 build_wheel Hook 產生的 Wheel 相同的依賴項(Requires-Dist 中繼資料),但例外情況是它們可以新增其可編輯機制在執行時期運作所需的依賴項(如 editables)。
「可編輯」Wheel 的檔名也需要符合 PEP 427。它不需要使用與 build_wheel 相同的標籤,但必須標記為與系統相容。
如果建置前端先前已呼叫 prepare_metadata_for_build_editable,並且依賴此呼叫所產生的 Wheel 來擁有與先前呼叫相符的中繼資料,那麼它應該提供所建立 .dist-info 目錄的路徑作為 metadata_directory 參數。如果提供了此參數,則 build_editable 必須產生具有相同中繼資料的 Wheel。由建置前端傳入的目錄必須與 prepare_metadata_for_build_editable 所建立的目錄完全相同,包括其建立的任何無法識別的檔案。
「可編輯」Wheel 使用 Wheel 格式並非為了發佈,而是作為建置系統與前端之間的臨時通訊手段。這避免了讓建置後端直接安裝任何東西。此 Wheel 不得暴露給終端使用者,也不得被快取或散佈。
get_requires_for_build_editable
def get_requires_for_build_editable(config_settings=None):
...
此 Hook 必須回傳一個額外的字串列表,其中包含超過 pyproject.toml 檔案中指定內容之外的 PEP 508 依賴規範,以便在呼叫 build_editable Hook 時進行安裝。
如果未定義,預設實作相當於 return []。
prepare_metadata_for_build_editable
def prepare_metadata_for_build_editable(metadata_directory, config_settings=None):
...
必須在指定的 metadata_directory 內建立一個包含 Wheel 中繼資料的 .dist-info 目錄(即建立類似 {metadata_directory}/{package}-{version}.dist-info/ 的目錄)。此目錄必須是 Wheel 規範中定義的有效 .dist-info 目錄,但它不需要包含 RECORD 或簽章。該 Hook 也可以在此目錄中建立其他檔案,且建置前端必須保留(但忽略)這些檔案;此處的用意在於,當該中繼資料取決於建置時的決策時,建置後端可能需要以方便的格式記錄這些決策,以供實際的 Wheel 建置步驟重複使用。
這必須以 Unicode 字串形式回傳所建立 .dist-info 目錄的基底名稱(而非完整路徑)。
如果建置前端需要此資訊且該方法未定義,它應該呼叫 build_editable 並直接查看產生的中繼資料。
Wheel 內應包含什麼
建置後端必須在產生的 Wheel 中填入檔案,以便在安裝時實現可編輯安裝。建置後端可以使用不同的技術來達成可編輯安裝的目標。本節提供範例,並不具規範性。
- 建置後端可以選擇在
.whl檔案的根目錄放置一個.pth檔案,其中包含原始碼樹的根目錄。這種方法簡單但不夠精確,儘管它可能被認為已經足夠好(特別是在使用src佈局時),且與目前setup.py develop的做法類似。 - editables 程式庫展示了如何建置提供高品質可編輯安裝的代理模組(proxy modules)。它接受一個要包含和隱藏的模組列表。匯入時,這些代理模組會用原始碼樹中的程式碼替換自身。基於路徑的方法使路徑下的所有腳本皆可匯入,通常包括專案自身的
setup.py和其他不會成為一般安裝一部分的腳本。代理策略可以比基於路徑的方法實現更高程度的保真度。 - 符號連結(Symbolic links)是實現可編輯安裝的另一種有用機制。由於在撰寫本文時,
wheel規範不支援符號連結,因此無法直接用於在目標環境中建立符號連結。然而,後端可以在原始碼樹的某些build目錄中建立符號連結結構,並透過「可編輯」Wheel 中的.pth檔案將該目錄加入 python 路徑。如果以這種方式連結的某些檔案取決於 python 實作、版本、ABI 或平台,則必須注意根據相容性標籤在不同目錄中產生連結結構,以便同一個專案樹可以在多個環境中以可編輯模式安裝。
前端需求
前端必須以安裝一般 Wheel 的相同方式安裝「可編輯」Wheel。這也意味著解除安裝可編輯套件不需要任何特殊處理。
前端必須在已安裝發佈版本的 .dist-info 目錄中建立一個 direct_url.json 檔案,以符合 PEP 610。其 url 值必須是一個指向專案目錄(即包含 pyproject.toml 的目錄)的 file:// URL,且 dir_info 值必須為 {'editable': true}。
前端必須在包含 pyproject.toml 檔案中指定的啟動引導(bootstrap)需求的環境中執行 get_requires_for_build_editable Hook。
前端必須在包含 pyproject.toml 中的啟動引導需求,以及由 get_requires_for_build_editable Hook 指定的需求的環境中,執行 prepare_metadata_for_build_editable 和 build_editable Hook。
前端不得將從 build_editable 獲得的 Wheel 暴露給終端使用者。該 Wheel 安裝後必須被丟棄,不得快取或散佈。
限制
關於 Wheel 的 .data 目錄,本 PEP 專注於使 purelib 和 platlib 類別(安裝至 site-packages)變為「可編輯」。它沒有為其他類別(如 headers、data 和 scripts)提供特殊規定。鼓勵套件作者使用 console_scripts,將他們的 scripts 製作成圍繞程式庫功能的微小包裝器,或在開發期間從原始碼檢出中管理這些內容。
原型
在撰寫本 PEP 時,各種前端和後端已有幾種原型實作。我們在下方提供連結以說明可能的方法。
前端
- pip (PR (pull request))
建置後端
遭否決的想法
editable 本地版本識別符
關於建置後端附加或修改本地版本識別符以包含 editable 字串的想法已被拒絕,因為它無法滿足包含本地版本識別符的 == 版本規範。換句話說,版本 1.0+local.editable 無法滿足 pkg==1.0+local 的規範。
虛擬 Wheel
PEP 662 提出了另一種方法,其中建置後端傳回從原始檔和目錄到安裝佈局的映射。然後由安裝程式前端決定如何以其認為適合使用者的任何方式實現可編輯安裝。
就能力而言,這兩項提議都提供了核心的「可編輯」功能。
關鍵區別在於 PEP 662 將如何實現可編輯安裝的決定權留給前端,而本 PEP 規定該選擇必須由後端做出。這兩種方法原則上都可以為特定專案提供多種可編輯安裝方法,並讓開發者在安裝時進行選擇。
在撰寫本 PEP 時,顯然社群對於可編輯安裝有著廣泛的理論和實踐預期。現實情況是,唯一擁有廣泛經驗的方法是透過 .pth 進行路徑插入(即 setup.py develop 的做法)。
我們相信 PEP 660 以最可靠的方式解決了當今這些「未知的未知」,即讓專案作者選擇後端或實作最適合其需求的可編輯機制,並測試其運作是否正確。由於前端在 *如何* 安裝「可編輯」Wheel 上沒有自由裁量權,若發生問題,只有一個地方需要調查:建置後端。
使用 PEP 662,問題需要在前端、後端,甚至是規範中進行調查。此外,不同的前端以不同方式實作該規範,很有可能會產生與專案作者預期行為不同的安裝結果,從而造成混淆,甚至導致專案只能在特定的前端或 IDE 下運作的糟糕情況。
已解壓縮的 Wheel
曾有過一個原型,它在臨時目錄中建立了一個已解壓縮的 Wheel,供前端複製到目標環境。這種方法沒有繼續使用,因為後端很容易建立 Wheel 封存檔,且使用 Wheel 作為通訊機制更符合 PEP 517 的哲學,因此對前端而言事情更簡單。
版權
本文件已進入公有領域或遵循 CC0-1.0-Universal 授權,以較寬鬆者為準。
來源:https://github.com/python/peps/blob/main/peps/pep-0660.rst