在較新的 clang (Ventura 13.2, XCode 14.2) 下以 pyenv 安裝較舊 Python (eg. 3.8.2, 3.6.9) 的解決方式
注意! 此方法無法適用 Monterey (macos 12) 或以下 ,目前僅能解決 Ventura (macos 13)
錯誤訊息
如果直接安裝,大致會遇到以下 stdout 錯誤訊息
BUILD FAILED (OS X 12.6.3 using python-build 20180424) Inspect or clean up the working tree at /var/folders/ct/qm_ngjnj4cs9dk8z8lfq60jw0000gn/T/python-build.20230202142252.7919 Results logged to /var/folders/ct/qm_ngjnj4cs9dk8z8lfq60jw0000gn/T/python-build.20230202142252.7919.log Last 10 log lines: checking for --with-cxx-main=... no checking for clang++... no configure: By default, distutils will build C++ extension modules with "clang++". If this is not intended, then set CXX on the configure command line. checking for the platform triplet based on compiler characteristics... darwin configure: error: internal configure error for the platform triplet, please file a bug report make: *** No targets specified and no makefile found. Stop.
這時候直接看這個訊息會有點誤解,以為原因是缺少 clang,但其實不是,要去看安裝過程的 config.log
以上面為例,你可以看到 log 路徑,你就去 log 所在資料夾 /var/folders/ct/qm_ngjnj4cs9dk8z8lfq60jw0000gn/T/,應該會有個跟 log 同名的 folder,進去後看到 Python-3.8.2 資料夾,再進去就會看到 config.log
目前主要問題大致是新版 clang 行為有變,所以當 pyenv 準備安裝時,去呼叫新的 clang 要來裝 3.8.2 時,會出問題。
次要問題則是另外有個 funtion sendfile 的問題,錯誤訊息可能是
BUILD FAILED (OS X 13.2 using python-build 20180424) Inspect or clean up the working tree at /var/folders/ct/qm_ngjnj4cs9dk8z8lfq60jw0000gn/T/python-build.20230202165519.23224 Results logged to /var/folders/ct/qm_ngjnj4cs9dk8z8lfq60jw0000gn/T/python-build.20230202165519.23224.log Last 10 log lines: clang -Wno-unused-result -Wsign-compare -Wunreachable-code -DNDEBUG -g -fwrapv -O3 -Wall -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include -std=c99 -Wextra -Wno-unused-result -Wno-unused-parameter -Wno-missing-field-initializers -Wstrict-prototypes -Werror=implicit-function-declaration -I./Include/internal -I. -I./Include -I/usr/local/opt/readline/include -I/usr/local/opt/readline/include -I/Users/hank/.pyenv/versions/3.8.2/include -I/usr/local/opt/zlib/include -I/usr/local/opt/bzip2/include -I/usr/local/opt/readline/include -I/usr/local/opt/readline/include -I/Users/hank/.pyenv/versions/3.8.2/include -I/usr/local/opt/zlib/include -I/usr/local/opt/bzip2/include -DPy_BUILD_CORE_BUILTIN -c ./Modules/errnomodule.c -o Modules/errnomodule.o ./Modules/posixmodule.c:9197:15: error: implicit declaration of function 'sendfile' is invalid in C99 [-Werror,-Wimplicit-function-declaration] ret = sendfile(in, out, offset, &sbytes, &sf, flags);
但這個問題的時間點比較早,所以如果是大概兩年或之前在安裝 pyenv 時,就有可能會在安裝時,用到一個 8ea6353 的 patch,就是由於這個問題。
因此,這次除了要處理 clang 問題,這個問題一樣要在安裝時處理。
系統資訊
以下是我的系統與環境版本,提供參考
1. 筆電
MacBook Pro 13-inch, 2017, Two Thunderbolt 3 ports
2. OS
macos Ventura 13.2(22D49)
3. XCode
Xcode 14.2
Build version 14C18
4. clang
Apple clang version 14.0.0 (clang-1400.0.29.202)
Target: x86_64-apple-darwin22.3.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
前置步驟
這裡並非一定要 follow 的步驟,而是讓你知道我在安裝 pyenv 之前做了什麼,以便於你在遇到跟我不同的錯誤訊息時,有機會做一些原因上的推測,看你的環境跟我的有什麼不同。
- 以 Mac 復原工具重新安裝 macos Ventura,得到一個乾淨的新環境
- App Store 安裝 Xcode 14.2
- 從 apple developer 下載 Command Line Tools for Xcode 14.2.dmg 自行安裝 (網路上通常提供的 xcode-select –install 方法,我無法成功,安裝跑一段時間後會彈出一個彈窗顯示『無法安裝軟體。』因此我只好自己下載)
- 安裝 homebrew
- 用 homebrew 安裝 zsh 與 Oh My Zsh (本步驟與主題無關,只是告訴你我還有裝這個,以作判斷參考,雖然理論上不應該影響到本次主題)
安裝步驟
1. 安裝 pyenv
(1) brew 安裝
brew install pyenv brew install --HEAD pyenv-virtualenv
(2) 編輯啟動 shell
打開啟動 shell (我是 .zshrc,這裡請依自己 shell 去調整,例如 .bashrc)
vim ~/.zshrc
PATH="$HOME/.pyenv/bin:$PATH" eval "$(pyenv init -)" eval "$(pyenv virtualenv-init -)"
2. 安裝套件
brew install zlib bzip2 xz
討論
沒裝這些套件,可能會遇到一些 ERROR 或 WARNING,例如沒裝 xz 可能遇上 WARNING
ModuleNotFoundError: No module named '_lzma' WARNING: The Python lzma extension was not compiled. Missing the lzma lib?
3. 製作 patch
需要兩份 patch,因為 pyenv 似乎無法同時指定兩份 patch ,故這裡自行手動合併他們成為一份 patch
1. 8ea6353
- 不修正這個,會遇到錯誤訊息 error: implicit declaration of function ‘sendfile’ is invalid in C99
2. 9c47667
- 這個來自 pyenv issue 2143 討論, native-api 説:『有興趣在新 clang 裝較早 python 的人可以試試 9c47667 這個 patch』
- 注意! 我曾在 Monterey 嘗試,得到錯誤訊息顯示這個方式可能僅適用 macos13 (Ventura),訊息如下
BUILD FAILED (OS X 12.6.3 using python-build 20180424) Inspect or clean up the working tree at /var/folders/ct/qm_ngjnj4cs9dk8z8lfq60jw0000gn/T/python-build.20230202155536.91623 Results logged to /var/folders/ct/qm_ngjnj4cs9dk8z8lfq60jw0000gn/T/python-build.20230202155536.91623.log Last 10 log lines: /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sys/stat.h:395:9: note: 'mknodat' has been marked as being introduced in macOS 13.0 here, but the deployment target is macOS 12.6.0 int mknodat(int, const char *, mode_t, dev_t) __API_AVAILABLE(macos(13.0), ios(16.0), tvos(16.0), watchos(9.0)); ^ ./Modules/posixmodule.c:9796:22: note: enclose 'mknodat' in a __builtin_available check to silence this warning result = mknodat(dir_fd, path->narrow, mode, device); ^~~~~~~ 2 warnings and 1 error generated. make: *** [Modules/posixmodule.o] Error 1 make: *** Waiting for unfinished jobs.... 1 warning generated.
討論
因為暫時沒找到合併兩份 patch 的工具,因此自行研究格式,首先看到製作 patch 的方式是用 git diff 指令,然後自己觀察 patch 格式,進而推測應該最有用的是那些 diff– 的段落。
因此最後我是自己手動合併,請下載我合併的 patch file 做參考,並附上我的筆記:
- 最前面是作者資訊,我猜應該不會用到,所以我的合併就是把兩個作者依照時間先後放置。
- 之後會接三個減號 (—)
- 再來緊接的是檔案變動與行數變動資訊,我自己人工計算合併後的結果:(1) 兩次 diff 分別都動 3 個檔案,但總計只動了 4 個(configure, configure.ac 兩次都有改) (2) 行數也同理,自己計算總變動數即可。 (其實這個應該不重要,應該格式對即可)
- 接下來每個檔案的變動,都會以 diff –git a/filename b/filename 這樣的形式開頭,一直到下一個 diff –git a/filename b/filename,或者到檔案節尾。 所以這邊我就是簡單的把他們都複製貼上留下來,因為這裡還要合併起來太困難,因此我就不去合併他們。 事實證明他不會在乎 a/filename b/filename 是否多次出現同一檔案的修改,他只會按照 a/filename b/filename 內容 一一的去打上 patch,這個看安裝時 stdout 就知道了。
4. 執行安裝
- 使用以下指令安裝,這邊因為我把 combined.patch 放到家目錄,所以 patch 那邊檔案路徑是 ~/combined.patch
- 這邊 assign 了很多路徑到環境變數,如果沒有 assign ,則有可能 build 過程中會找不到 zlib, bzip2 的相關檔案
CPPFLAGS="-I$(brew --prefix zlib)/include -I$(brew --prefix bzip2)/include" \ CFLAGS="-I$(brew --prefix openssl)/include -I$(brew --prefix bzip2)/include -I$(brew --prefix readline)/include -I$(xcrun --show-sdk-path)/usr/include -I$(brew --prefix xz)/include" \ LDFLAGS="-L$(brew --prefix openssl)/lib -L$(brew --prefix readline)/lib -L$(brew --prefix zlib)/lib -L$(brew --prefix bzip2)/lib -L$(brew --prefix xz)/lib" \ pyenv install 3.8.2 --patch < ~/combined.patch
跑出訊息,安裝成功
留意訊息,可以看到 patch的動作就是依序套用每一個 diff –git 的變動。
python-build: use openssl from homebrew python-build: use readline from homebrew Downloading Python-3.8.2.tar.xz... -> https://www.python.org/ftp/python/3.8.2/Python-3.8.2.tar.xz Installing Python-3.8.2... patching file 'Misc/NEWS.d/next/macOS/2020-06-24-13-51-57.bpo-41100.mcHdc5.rst' patching file configure patching file configure.ac patching file 'Misc/NEWS.d/next/Build/2021-10-11-16-27-38.bpo-45405.iSfdW5.rst' patching file configure patching file configure.ac python-build: use readline from homebrew python-build: use zlib from xcode sdk Installed Python-3.8.2 to /Users/hank/.pyenv/versions/3.8.2
附註: 以相同方法在 Ventura 安裝 3.6.9 成功
綜合討論
- 最重要因素就是新的 Xcode 內的 clang 行為有變,所以會出錯,只看 stdout 會誤解問題所在,要仔細看 build 過程具體的 error 原因。
- 很多討論提到可用 gcc-11 試著裝裝看,我也嘗試過,沒有成功過,解了一個問題又會遇到下一個問題,大概重複三次以後,剛好看到 issue 2143 的討論 ,其中 Vash63 原本有提到他可以用 gcc11 安裝 ,但之後他又提到,雖然安裝成功但後續 pip 安裝時仍有問題,那這樣其實還是沒解決問題,之後也有人附議此觀點。 後面 native-api 提到 可以用 9c47667 patch 在新的 clang 下安裝,因此就有了這篇解決方式的出現。
- 除了 clang 帶來的變動要套上 patch,先前的 patch 也要,所以要設法讓兩份 patch 都能用。
Leave a Reply