供應商策略

  • 供應的函式庫必須只在成功供應它們時修改。

  • 供應的函式庫必須為 PyPI 上可取得函式庫的已釋出複本。

  • 供應的函式庫必須可根據允許它們整合進 pip (該程式已發布於 MIT 授權條款)的授權條款取得。

  • 供應的函式庫必須附有授權文件。

  • 在 pip 中供應函式庫的版本必須反映於 pip/_vendor/vendor.txt

  • 供應的函式庫必須沒有以下任何建置步驟就能運作,例如 2to3 或 C 程式編譯,這實際上限制為純 Python 的單一來源 2.x/3.x。

  • 對函式庫所做的任何修改必須記載於 pip/_vendor/README.rst 中,且其相對應的修補程式必須包含於 tools/vendoring/patches 中。

  • 供應的函式庫應在 pip/_vendor/__init__.py 中有相對應的 vendored() 輸入。

依據

過去,pip 並沒有任何相依性,除了 setuptools,它選擇自己實作任何所需的功能性,以避免相依性。然而,從 pip 1.5 開始,我們開始以 PyPI 中的可重複使用函式庫來替換在 pip 內建置的程式碼。這樣就帶來重用函式庫而非重複發明輪子的典型優點,例如更高品質且更具實戰測試的程式碼、特定錯誤(尤其是與安全性相關的錯誤)的集中修正,以及同樣的工作量能得到更好的/更多功能。

然而,以傳統方式(透過 install_requires)在 pip 中相依於其他函式庫,則會產生一些問題。這些問題是

脆弱性

當 pip 相依於其他函式庫才能運作時,如果由於某種原因而未安裝該函式庫,或是安裝的版本不相容,則 pip 會停止運作。對於所有 Python 應用程式來說,這當然都是如此,但對於除了 pip 以外的每個應用程式,修復方式都是重新執行 pip。顯然,當 pip 無法執行時,就無法使用 pip 修復 pip,因此只能手動解決相依性問題並自行安裝。

導致無法卸載其他函式庫

pip目前的相依套件之一是requests函式庫,pip需要相當新的版本才能執行。如果pip以傳統方式相依於requests,那麼我們必須維護與曾經存在的(以及將來會存在的)所有requests版本相容,或是允許pip讓某些requests版本無法移除。(第二個問題,雖然對於任何Python應用程式在技術上都屬實,但因為pip的普遍性而被放大了;pip是預設安裝在Python、pyvenvvirtualenv中。)

安全性

這乍看之下可能令人費解,因為加入套件供應商的傾向是讓供應商難以針對安全更新更新相依套件,而這對於pip來說也是如此。然而,考量到避免相依套件的其他理由,替代方式是pip自行重新發明輪子。這正是pip過去的做法。它迫使pip重新實作自己的HTTPS驗證例程,作為Python標準函式庫缺乏SSL驗證的因應措施,導致requestsurllib3中的驗證例程出現類似的錯誤,只不過它們必須分別發現並修復。雖然我們正在加入供應商,重新使用函式庫透過依賴相依套件的優良運作讓pip更安全,而且透過簡單地納入較新的相依套件版本,讓安全修復變得更快速、更輕鬆。

開機

目前安裝pip最受歡迎的方法仰賴pip的自我封裝性質來安裝pip本身。這些工具的工作方式是:綑綁一份pip的副本,將其加入sys.path,然後執行該pip副本。此舉是為了執行「迷你安裝程式」(減少重複);pip已經知道如何安裝Python套件,而且遠比任何「迷你安裝程式」所能做到的更經得起考驗。

許多下游的再發行商有針對這類綑綁的政策,而且選擇修補他們所發行的軟體,將其解綁並使其依賴他們已經封裝的軟體的全球版本(可能已經套用自己的修補程式)。我們(pip團隊)比較希望不要以這種方式解綁pip,因為上述理由,我們比較希望讓pip維持現狀。

從長遠來看,如果有人找出了上述問題的可移植解決方案(除了我們目前使用的捆綁方法之外),而且不會新添任何不可理喻的附加問題,那麼我們將樂於考慮,並有望轉換至該方法。此解決方案必須正確運作於我們預期 pip 將被用於的所有情況中,且不得要求某些外部機制(例如 OS 套件)。

修改內容

  • setuptools 已完全被移除,僅保留 pkg_resources

  • pkg_resources 已被修改,以從 pip._vendor 中匯入其相依項目,並使用 platformdirs(而不是 appdirs)的外掛版本。

  • packaging 已被修改,以從 pip._vendor 中匯入其相依項目。

  • CacheControl 已被修改,以從 pip._vendor 中匯入其相依項目。

  • requests 已被修改,以從 pip._vendor 中匯入其他相依項目,且(所有平台)載入 simplejson 以及(Windows)pyopenssl

  • platformdirs 已被修改,以從 pip._vendor.platformdirs 中匯入其子模組。

自動外掛

外掛會經由 vendoring 工具自動使用 pip/_vendor/vendor.txt 中的內容,以及 tools/vendoring/patches 中的不同修補程式。透過 vendoring sync . -v 來啟動它(需要 vendoring>=0.2.2)。工具設定透過 pyproject.toml 來完成。

管理本機修補程式

vendoring 工具會自動套用我們的本機修補程式,不過在更新時,修補程式有時會無法順利套用。這種情況下,更新會失敗。為了解決此問題,請採取下列步驟

  1. 還原外掛分支中的任何不完整變更,以確保您擁有乾淨的起點。

  2. 再次執行函式庫重新引用的問題:nox -s vendoring -- --upgrade <函式庫名稱>

  3. 這將再次失敗,但原始程式碼會儲存在您的工作目錄中。檢閱針對程式碼的現有修補程式,然後修改修補程式以反映程式碼的新版本。如果您 git add 引用所做的變更,您可以修改程式碼以反映修補程式檔,然後使用 git diff 產生新的修補程式。

  4. 現在,復原所有變更除了修補程式檔變更。讓修改後的修補程式檔保持未分段但儲存在工作樹中。

  5. 重新執行引用。這次,它應該選取已變更的修補程式檔並乾脆地套用它。修補程式檔變更會與引用一起提交,因此新的提交應該準備好測試並作為公關測試和發布。

解除綑綁

如原理中所述,我們 pip 團隊希望 pip 不要解除綑綁(除了 pip/_vendor/requests/cacert.pem 之外)並且 pip 保持完整。但是,如果您堅持這麼做,我們有一個半受支援的方法(我們沒有在 CI 中測試過),並且需要您額外付出一些工作來解決上述問題。

  1. 刪除 pip/_vendor/ 中的所有項目除了 pip/_vendor/__init__.pypip/_vendor/vendor.txt

  2. 使用您對這些函式庫修補的拷貝,為每個 pip 依賴項(及任何它們的依賴項)產生輪檔。這些必須放置在 pip 可以存取的檔案系統的某處(pip/_vendor 是預設假設)。

  3. 修改 pip/_vendor/__init__.py 以使 DEBUNDLED 變數為 True

  4. 安裝時,pip 自己的 dist-info 目錄中的 INSTALLER 檔應設定為 pip 之外的其他名稱,以便 pip 可以偵測到它不是使用它自己安裝的。

  5. (任選)如果輪組放置在 pip/_vendor/ 以外的位置,修改 pip/_vendor/__init__.py,讓 WHEEL_DIR 變數指向輪組放置的位置。

  6. (任選)更新 pip_self_version_check 邏輯,使用適當的邏輯來判斷 pip 的最新可用版本,並用正確的升級訊息提示使用者。

請注意,部分取消捆綁不支持。你需要為所有相依關係準備輪組,才能成功取消捆綁。