觀前提醒
如果你的“老板”此刻正在催你加班加點(diǎn)地去安裝Python3.6,那么你可以直接跳到第5部分,在那里你將直接獲得你所需要的、可操作的一切。但如果你目前的狀態(tài)還OK,飯后還有時(shí)間來把游戲,那我強(qiáng)烈建議你一定要從頭到尾,仔細(xì)閱讀一遍這篇文章。因?yàn)橥ㄟ^這篇文章你將學(xué)到:
- Python版本的選擇;
- 什么是License;
- 替換編譯器的一些方法;
- 日志排查及代碼修復(fù);
- Python3.6在Mac OS中的編譯及安裝;
- 如何把編譯過程寫入HomeBrew Formula中;
- 源碼編譯的邏輯;
前言:關(guān)于Python3.6的執(zhí)念
?關(guān)于我為什么要在公元2022年,python3的版本已經(jīng)更新到3.10,并且可以通過一行簡單的brew install命令,完成了python3.10的自動(dòng)安裝的前提下,仍然堅(jiān)持要去自己寫一個(gè)關(guān)于python3.6的formula這件事,或許你可以在我上一篇的文章中找到一點(diǎn)端倪。但其實(shí),更重要的原因是,我在無意間找到了這篇Homebrew formula的制作教程:https://docs.brew.sh/Formula-Cookbook。雖然我的確是一個(gè)菜鳥,但我想,經(jīng)過這個(gè)過程,至少可以讓我的“含菜量”變得稍微少一點(diǎn)。所以,現(xiàn)在就讓我們一起把這個(gè)艱難的過程記錄下來吧~
brew install python3 #python3.10安裝命令
Part1:創(chuàng)建一個(gè)Ruby公式
?根據(jù)Homebrew官方文檔中的說法,我們可以通過brew create指定安裝源碼的tar包的URL,從而完成一個(gè)安裝公式的初步創(chuàng)建。那么具體到我們當(dāng)前的問題,可以先找到一個(gè)大致的解決方向:即如何確定URL?
?這里阿柒提供一種“偷懶”的辦法,別忘了,我們的電腦上可是已經(jīng)安裝過python3.10了呀!那么,通過3.10的brew安裝腳本“學(xué)習(xí)”一下URL的地址是不是也是可以的呢??~通過查閱資料可知:Homebrew統(tǒng)一存放公式腳本的路徑是:/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/,那么在上述路徑中,我們找到python3.10.rb文件,使用文本編輯器打開后,可以找到python3.10的源碼下載地址,即:
class PythonAT310 < Formula
desc "Interpreted, interactive, object-oriented programming language"
homepage "https://www.python.org/"
url "https://www.python.org/ftp/python/3.10.6/Python-3.10.6.tgz"
sha256 "xxxxxxxxxxxxxxxxxxxxx"
license "Python-2.0"
revision 1
?好的,接下來讓我們用瀏覽器打開這個(gè)網(wǎng)址,看看我們要下載的python3.6到底在不在里面。?好消息是,我們要下載的python3.6的確在里面;壞消息是,python3.6下又有很多版本,那么我們應(yīng)該選擇下載哪一個(gè)版本呢?
1.1 Python3.6的版本選擇
?對于具體選擇哪一個(gè)“小標(biāo)”的python3.6版本,大家其實(shí)完全可以根據(jù)自己的需要自行確定。這里阿柒僅將在版本確定的過程中,查到的一些版本及對應(yīng)的一些信息作一個(gè)簡單的整理。另外,特別要說明的一點(diǎn)是:根據(jù)Python官網(wǎng)發(fā)布的聲明:python3.6已經(jīng)不在支持期內(nèi)(End of support: 2021-12-23),這意味著Python官方將不會再根據(jù)任何使用過程中發(fā)現(xiàn)的問題對python3.6進(jìn)行bug修改。因此,對于當(dāng)前身處2022年的我們來說,我們要做好:使用python3.6的過程中,可能遇到各種“意想不到”的問題的心理準(zhǔn)備。具體地我們將在Part2的安裝過程中逐一展現(xiàn)。
版本號 | 備注 |
---|---|
3.6.15 | the final security bugfix release,同時(shí)也是目前win和mac系統(tǒng)中安裝最多的版本。 |
3.6.8 | the final bugfix release |
3.6.5 | 存在明顯缺陷:bpo-24658,并在3.6.8中進(jìn)行了修復(fù)。 |
1.2 創(chuàng)建python3.6的安裝腳本
?現(xiàn)在,我們終于可以通過指定的URL創(chuàng)建我們的formula腳本了!于是就在我興致勃勃的在命令行中輸入:
brew create https://www.python.org/ftp/python/3.6.15/Python-3.6.15.tgz
Error: /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/python@3.10.rb already exists
時(shí),不出意外地終端報(bào)錯(cuò)了??于是,我就嘗試按照doc文檔中的提示,將我的create語句修改為:
brew create https://www.python.org/ftp/python/3.6.15/Python-3.6.15.tgz --set-name python@3.6
非常幸運(yùn)的,這次成功了(雖然我也不是很清楚為什么,可能大概就是創(chuàng)建的formula腳本與已有的python@3.10.rb文件名之類的沖突了吧)!
1.3 補(bǔ)充腳本中的必填信息
?接下來就是補(bǔ)充formula腳本中的一些必填信息。參照python3.10.rb文件中的寫法,我們可以非常輕松的確定,desc、homepage的寫法:
desc "latest version of PythonAT36, release 2021-09-03"
homepage "https://www.python.org/"
?稍微有點(diǎn)兒難度的是License,說實(shí)話,因?yàn)榄h(huán)境的原因,我一直不是很能理解老外們?yōu)樯杜獋€(gè)開源軟件還要搞license。于是借著這個(gè)機(jī)會,對python的license做了個(gè)簡單的研究,結(jié)論總結(jié)如下:
1.首先,代碼作為一種創(chuàng)造性的作品是要受版權(quán)保護(hù)的。License就可以理解為用戶獲得使用軟件的一種權(quán)利,是各位開源作者們對代碼可以如何使用的一種說明。只要在規(guī)定的范圍內(nèi),用戶都可以對軟件進(jìn)行拷貝、修改以及再發(fā)布;
2.作為開源世界的根基,開源協(xié)議的意義更多地是體現(xiàn)在其之于經(jīng)濟(jì)、社會等方面。而對于技術(shù)學(xué)習(xí)階段的“小白”來說,我們首先要樹立起“版權(quán)”的意識——?jiǎng)e人創(chuàng)做的代碼,就像別人的東西一樣,一定要獲得別人的同意后再去使用,否則便是小偷。然后,在充分汲取各路大神們的智慧精華的過程中,積累沉淀,等有一天我們的技術(shù)達(dá)到一定的水平時(shí),即使不能反哺開源世界,也請千萬記得:不要去破壞開源協(xié)議中的要求。
3.關(guān)于Python3的License:了解了License的基本概念后,讓我們現(xiàn)在把目光聚焦到Python3上。對于Python3.6.15來說,它遵守的License名稱為PSF License Agreement,其內(nèi)容包括:
- This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using Python 3.6.15 software in source or binary form and its associated documentation.
- Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python 3.6.15 alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright ? 2001-2021 Python Software Foundation; All Rights Reserved" are retained in Python 3.6.15 alone or in any derivative version prepared by Licensee.
- In the event Licensee prepares a derivative work that is based on or incorporates Python 3.6.15 or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python 3.6.15.
- PSF is making Python 3.6.15 available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 3.6.15 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
- PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.6.15 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.6.15, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
- This License Agreement will automatically terminate upon a material breach of its terms and conditions.
- Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party.
- By copying, installing or otherwise using Python 3.6.15, Licensee agrees to be bound by the terms and conditions of this License Agreement.
?綜上,阿柒將formula腳本中的License信息補(bǔ)充為:
license "PSF-2.0"
?其中,PSF-2.0是PSF License的SPDX identifier。SPDX可以簡單理解為一種針對通信軟件(包括源代碼)重要信息內(nèi)容的標(biāo)準(zhǔn)化格式,用戶可以單憑SPDX標(biāo)識符,在SPDX許可表中檢索出對應(yīng)許可的全名、內(nèi)容及URL等信息。
Part2:開始brew install吧~
?就在阿柒以為一切準(zhǔn)備就緒,按照文檔中的說法檢查構(gòu)建系統(tǒng)的時(shí)候,brew竟然崩了!萬惡之源:
localhost:~ username$ brew install --interactive python@3.6
Running `brew update --auto-update`...
/usr/local/Homebrew/Library/Homebrew/brew.sh: line 173: /usr/local/bin/brew: No such file or directory
/usr/local/Homebrew/Library/Homebrew/brew.sh: line 298: /usr/local/bin/brew: No such file or directory
/usr/local/Homebrew/Library/Homebrew/brew.sh: line 298: exec: /usr/local/bin/brew: cannot execute: No such file or directory
?brew崩潰到什么程度呢?就是一切和brew相關(guān)的命令都失效的程度??!就好像我的電腦里完全沒有裝過Homebrew一樣。沒辦法,我只好重新按照Lesson1中Homebrew的安裝步驟,又重新安裝了一遍,就一整個(gè)大無語!
localhost:~ username$ brew edit python@3.6
-bash: /usr/local/bin/brew: No such file or directory
localhost:~ username$ brew ls
-bash: /usr/local/bin/brew: No such file or directory
localhost:~ username$ brew doctor
-bash: /usr/local/bin/brew: No such file or directory
localhost:~ username$ brew clean
-bash: /usr/local/bin/brew: No such file or directory
?重新安裝了Homebrew后,阿柒又不信邪地重新嘗試了一次同樣的命令,這次得到的結(jié)果如下:
Error: python@3.6: no bottle available!
You can try to install from source with:
brew install --build-from-source python@3.6
Please note building from source is unsupported. You will encounter build
failures with some formulae. If you experience any issues please create pull
requests instead of asking for help on Homebrew's GitHub, Twitter or any other
official channels.
?這么看來,貌似還是阿柒Homebrew的問題吧。不過無論如何,這一關(guān)算是過掉啦,撒花??。這也告訴我們,在配置環(huán)境的過程中,失敗是免不了的,所以大家一定不要輕言放棄,大不了就重頭再來唄~當(dāng)然,阿柒也會在自己的教程中,將自己踩坑的過程盡可能地展示給大家,希望能為大家的踩坑之路提供一點(diǎn)點(diǎn)參照~
2.1 踩坑一:The latest Apple CLang's releases being incompatible with CPython's Configure
?既然上述命令行不通,那么我們可以將其修改為:
brew install --build-bottle -vd python@3.6
?不要激動(dòng),菜鳥成長的經(jīng)歷告訴我們:世界上的事對小白來說,沒有一帆風(fēng)順的。果不其然,在一路checking之后,報(bào)錯(cuò)如下:
configure: error: internal configure error for the platform triplet, please file a bug report
/usr/local/Homebrew/Library/Homebrew/shims/shared/git --version
/usr/local/Homebrew/Library/Homebrew/shims/shared/curl --version
/usr/local/Homebrew/Library/Homebrew/ignorable.rb:29:in `block in raise'
BuildError: Failed executing: ./configure --disable-debug --disable-dependency-tracking --prefix=/usr/local/Cellar/python@3.6/3.6.15 --libdir=/usr/local/Cellar/python@3.6/3.6.15/lib --disable-silent-rules
?根據(jù)阿柒踩坑的經(jīng)驗(yàn),一般遇到這種報(bào)錯(cuò),先不用急著去搜索答案。因?yàn)楹苡锌赡墚?dāng)你鋪天蓋地地去搜索了一番,也沒有找到合適的回答,然后氣急敗壞地重新在命令行里運(yùn)行了一遍和剛才一模一樣的命令后,原來的報(bào)錯(cuò)就通過了!(別問我是怎么知道的,問就是用頭發(fā)換的??)。所以,深呼吸,不要著急,讓我們重新再去運(yùn)行一次,3,2,1......
?還是一樣的報(bào)錯(cuò),好的,現(xiàn)在讓我們把主場交給搜索引擎????
?經(jīng)過一番搜索風(fēng)暴后,我們終于找到了產(chǎn)生問題的原因,這里阿柒直接引用native-api的解釋原文,供大家參考:
Duplicate of #2143.
It's due to the latest Apple CLang's releases being incompatible with CPython's Configure.
Fixed in 3.7.13, 3.8.13, 3.9.11 and 3.10.3 .
?簡單地說,出現(xiàn)上述問題的原因在于:對于MacOS Monterey 12.x來說,系統(tǒng)自帶的編譯器Apple Clang與Python源代碼中的配置項(xiàng)相沖突。當(dāng)然維護(hù)Python項(xiàng)目的大神們,早也通過各種渠道收到了這個(gè)問題的反饋,并已經(jīng)完成了對尚在支持期內(nèi)的3.7、3.8、3.9和3.10等版本的配置代碼修改。這也啟示我們:在安裝Python時(shí),為了避免意想不到的問題,最佳的思路自然是選擇最新或尚在支持期內(nèi)的版本。而對于已經(jīng)結(jié)束支持的版本來說,如果你不是和阿柒一樣想要挑戰(zhàn)一下自己的話,那就真心祝你藝高人膽大,遇山開路,逢兇化吉~
2.1.1 解決方案
?既然找到了報(bào)錯(cuò)的原因,是出在系統(tǒng)自帶的編譯器Clang與源碼編譯配置上,那我們自然而然的可以想到:是不是換個(gè)編譯器就能解決了呢?根據(jù)阿柒搜索到的結(jié)果來看,似乎這也確實(shí)是可行的。于是現(xiàn)在我們的問題就變成了:如何將Homebrew的編譯器替換成gcc?
?首先,阿柒嘗試了將系統(tǒng)的gcc --version修改為gcc。具體解決方案上,阿柒選擇了在~/.bash_profile中創(chuàng)建alias的方法,比起調(diào)整環(huán)境變量$PATH加載路徑的先后順序(/usr/local/bin與/usr/bin)可能引起的系統(tǒng)多處環(huán)境變量的變化,創(chuàng)建別名的方法毫無疑問影響更小。關(guān)于環(huán)境變量的作用與設(shè)置,大家可以參照Lesson1中4.3節(jié)。本小節(jié)中創(chuàng)建別名的具體寫法如下:
alias gcc='gcc-10'
alias cc='gcc-10'
alias g++='g++-10'
alias c++='c++-10'
?完成后,當(dāng)我們在命令行中輸入gcc --version時(shí),會得到如下結(jié)果:
gcc-10 (Homebrew GCC 10.4.0) 10.4.0
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
?你以為問題到此就結(jié)束了嗎?怎么可能?當(dāng)我們再次嘗試通過brew install --build-bottle -vd python@3.6進(jìn)行安裝時(shí),終端依然顯示同樣的報(bào)錯(cuò),而查看Homebrew編譯環(huán)境時(shí),我們可以清新地看到Homebrew使用的編譯器依然是系統(tǒng)自帶的Clang。
==> ENV
HOMEBREW_CC: clang
HOMEBREW_CXX: clang++
MAKEFLAGS: -j4
CMAKE_PREFIX_PATH: /usr/local
CMAKE_INCLUDE_PATH: /Library/Developer/CommandLineTools/SDKs/MacOSX12.sdk/System/Library/Frameworks/OpenGL.framework/Versions/Current/Headers
CMAKE_LIBRARY_PATH: /Library/Developer/CommandLineTools/SDKs/MacOSX12.sdk/System/Library/Frameworks/OpenGL.framework/Versions/Current/Libraries
PKG_CONFIG_PATH: /usr/local/opt/gmp/lib/pkgconfig:/usr/local/opt/isl/lib/pkgconfig:/usr/local/opt/mpfr/lib/pkgconfig:/usr/local/opt/lz4/lib/pkgconfig:/usr/local/opt/xz/lib/pkgconfig:/usr/local/opt/zstd/lib/pkgconfig
PKG_CONFIG_LIBDIR: /usr/lib/pkgconfig:/usr/local/Homebrew/Library/Homebrew/os/mac/pkgconfig/12
HOMEBREW_GIT: git
HOMEBREW_SDKROOT: /Library/Developer/CommandLineTools/SDKs/MacOSX12.sdk
ACLOCAL_PATH: /usr/local/share/aclocal
PATH: /usr/local/Homebrew/Library/Homebrew/shims/mac/super:/usr/local/opt/lz4/bin:/usr/local/opt/xz/bin:/usr/local/opt/zstd/bin:/usr/local/opt/gcc/bin:/usr/bin:/bin:/usr/sbin:/sbin
?既然此路不通,那就讓我們換個(gè)方向:如何指定Homebrew在編譯安裝過程中的gcc編譯器。萬幸的是,我們僅需要在安裝公式formula中def install模塊中指定此軟件編譯時(shí)的gcc路徑,即可完成通過Homebrew進(jìn)行編譯時(shí)的編譯器設(shè)置,具體代碼可參照:
def install
# Force compilation with gcc-12
ENV['CC'] = '/usr/local/bin/gcc-12'
ENV['LD'] = '/usr/local/bin/gcc-12'
ENV['CXX'] = '/usr/local/bin/g++-12'
# Compiler complains about link compatibility with FORTRAN otherwise
ENV.delete('CFLAGS')
ENV.delete('CXXFLAGS')
?至此,當(dāng)我們再去運(yùn)行brew install --build-bottle -vd python@3.6命令時(shí),我們不出意外地會看到軟件編譯成功了~(請注意,僅僅是編譯成功了,距離我們最終的目標(biāo):成功安裝軟件并完成python@3.6.rb腳本的提交還有很長一段距離??)。但是不管怎么樣,我們又成功地搞定了一個(gè)問題,這里阿柒再為大家提供一種未經(jīng)驗(yàn)證的Homebrew安裝軟件時(shí),C編譯器的指定方式,歡迎大家圍觀驗(yàn)證,更歡迎大家將你的發(fā)現(xiàn)以評論留言的方式告訴我。即通過在brew install命令前添加下列語句,指定編譯器版本:
HOMEBREW_CC=gcc-12 HOMEBREW_CXX=g++-12 brew install --build-bottle -vd python@3.6
2.1.2 問題溯源
?除了上述解決方案外,阿柒覺得本次踩坑最大的收獲是:學(xué)到了如何根據(jù)日志信息,來排查定位報(bào)錯(cuò)的原因。首先我們根據(jù)命令行的輸出,可以確定此過程日志的存放位置:
Logs:
/Users/username/Library/Logs/Homebrew/python@3.6/00.options.out
/Users/username/Library/Logs/Homebrew/python@3.6/01.configure.cc
/Users/username/Library/Logs/Homebrew/python@3.6/config.log
/Users/username/Library/Logs/Homebrew/python@3.6/01.configure
Do not report this issue to Homebrew/brew or Homebrew/core!
/usr/local/Homebrew/Library/Homebrew/shims/shared/curl --disable --cookie /dev/null --globoff --show-error --user-agent Homebrew/3.5.9-135-g6096bc6\ \(Macintosh\;\ Intel\ Mac\ OS\ X\ 12.0.1\)\ curl/7.77.0 --header Accept-Language:\ en --retry 3 --location https://api.github.com/search/issues\?q=python\%403.6\+repo\%3AHomebrew\%2Fhomebrew-core\+state\%3Aopen\+in\%3Atitle\&per_page=100 --header Accept:\ application/vnd.github\+json --write-out '
'\%\{http_code\} --dump-header /private/tmp/github_api_headers20220823-58026-t5syle
?好的,現(xiàn)在讓我們打開日志,看看這個(gè)報(bào)錯(cuò):configure: error: internal configure error for the platform triplet具體是個(gè)什么樣的問題。
1.問題定位
日志查看技巧
2.對癥下藥
2.2 踩坑二:Error: Empty installation
?阿柒想起小時(shí)侯看《西游記》的時(shí)候,有一句歌詞特別應(yīng)景:剛擒住了幾個(gè)妖,又降住了幾個(gè)魔,魑魅魍魎怎么它就這么多!就在我們終于編譯成功的同時(shí),我們的終端又來報(bào)錯(cuò)了:Error: Empty installation,詳細(xì)信息如下:
/usr/local/Homebrew/Library/Homebrew/formula_installer.rb:941:in `build'
/usr/local/Homebrew/Library/Homebrew/formula_installer.rb:444:in `install'
/usr/local/Homebrew/Library/Homebrew/upgrade.rb:212:in `install_formula'
/usr/local/Homebrew/Library/Homebrew/install.rb:329:in `install_formula'
/usr/local/Homebrew/Library/Homebrew/install.rb:319:in `block in install_formulae'
/usr/local/Homebrew/Library/Homebrew/install.rb:318:in `each'
/usr/local/Homebrew/Library/Homebrew/install.rb:318:in `install_formulae'
/usr/local/Homebrew/Library/Homebrew/cmd/install.rb:226:in `install'
/usr/local/Homebrew/Library/Homebrew/brew.rb:93:in `<main>'
?根據(jù)報(bào)錯(cuò)信息,我們大體可以判斷出出現(xiàn)上述錯(cuò)誤的原因,即:安裝formula中的模塊沒有成功執(zhí)行build環(huán)節(jié)。于是,如何為我們腳本添加合理的操作代碼,順利完成代碼build,就成了我們要在這個(gè)小節(jié)去完成的任務(wù)。
2.2.1 如何在Formula腳本中添加build步驟
?“工欲善其事,必先利其器”,既然我們要在我們的腳本中添加build步驟,那么我們首先就要對C代碼編譯安裝時(shí)的流程進(jìn)行一定的了解。對于不熟悉C編譯的同學(xué),阿柒已經(jīng)在本篇文章的第6.2節(jié)為大家整理出了Python源碼的編譯原理,大家可以自行學(xué)習(xí)。對于已經(jīng)有了一定基礎(chǔ)的同學(xué),我們現(xiàn)在知道:從源碼到可執(zhí)行程序,大約需要配置(./configure)——構(gòu)建(make)——安裝(install)3個(gè)步驟,那么具體到Homebrew中,這3個(gè)步驟又是如何被設(shè)計(jì)和執(zhí)行的呢?
?從前面的步驟中,我們不難看出:前面的一系列checking已經(jīng)基本實(shí)現(xiàn)了源碼編譯的第一步:configure,那么接下來讓我們檢查一下距離生成Makefile文件還差什么條件——構(gòu)建和安裝的執(zhí)行,這里參照python3.10.rb中的寫法,在我們的formula中添加以下語句:
system "make"
ENV.deparallelize do
# Tell Python not to install into /Applications (default for framework builds)
system "make", "install", "PYTHONAPPSDIR=#{prefix}"
system "make", "frameworkinstallextras", "PYTHONAPPSDIR=#{pkgshare}" if OS.mac?
end
if OS.mac?
# Any .app get a " 3" attached, so it does not conflict with python 2.x.
prefix.glob("*.app") { |app| mv app, app.to_s.sub(/\.app$/, " 3.6.app") }
pc_dir = frameworks/"Python.framework/Versions"/version.major_minor/"lib/pkgconfig"
# Symlink the pkgconfig files into HOMEBREW_PREFIX so they're accessible.
(lib/"pkgconfig").install_symlink pc_dir.children
# Prevent third-party packages from building against fragile Cellar paths
bad_cellar_path_files = [
lib_cellar/"_sysconfigdata__darwin_darwin.py",
lib_cellar/"config-#{version.major_minor}-darwin/Makefile",
pc_dir/"python-#{version.major_minor}.pc",
pc_dir/"python-#{version.major_minor}-embed.pc",
]
inreplace bad_cellar_path_files, prefix, opt_prefix
# Help third-party packages find the Python framework
inreplace lib_cellar/"config-#{version.major_minor}-darwin/Makefile",
/^LINKFORSHARED=(.*)PYTHONFRAMEWORKDIR(.*)/,
"LINKFORSHARED=\\1PYTHONFRAMEWORKINSTALLDIR\\2"
# Fix for https://github.com/Homebrew/homebrew-core/issues/21212
inreplace lib_cellar/"_sysconfigdata__darwin_darwin.py",
%r{('LINKFORSHARED': .*?)'(Python.framework/Versions/3.\d+/Python)'}m,
"\\1'#{opt_prefix}/Frameworks/\\2'"
else
# Prevent third-party packages from building against fragile Cellar paths
inreplace Dir[lib_cellar/"**/_sysconfigdata_*linux_x86_64-*.py",
lib_cellar/"config*/Makefile",
bin/"python#{version.major_minor}-config",
lib/"pkgconfig/python-3*.pc"],
prefix, opt_prefix
inreplace bin/"python#{version.major_minor}-config",
'prefix_real=$(installed_prefix "$0")',
"prefix_real=#{opt_prefix}"
end
接下來再讓終端執(zhí)行一下,看看結(jié)果:
zipimport.ZipImportError: can't decompress data; zlib not available
可以看到:此時(shí)源碼已經(jīng)在編譯過程中了,那接下來,我們就可以在formula中指定一個(gè)zlib,看看又將發(fā)生什么?
uses_from_macos "zlib"
終端繼續(xù)報(bào)錯(cuò)如下:
zipimport.ZipImportError: can't decompress data; zlib not available
這次讓我們直接raise error,查看一下輸出日志:
In file included from /private/tmp/pythonA3.6-20221228-17963-1icadcq/Python-3.6.15/Modules/_tkinter.c:50:
/usr/local/include/tk.h:96:13: fatal error: X11/Xlib.h: No such file or directory
96 | # include <X11/Xlib.h>
| ^~~~~~~~~~~~
compilation terminated.
根據(jù)搜索結(jié)果可知:之所以不斷地產(chǎn)生上述報(bào)錯(cuò),是因?yàn)镸ac系統(tǒng)不再直接包括X11,用戶在使用過程中如需用到X11的庫,可以通過安裝XQuartz.pkg獲取。
About X11 for Mac: X11 is no longer included with Mac, but X11 server and client libraries are available from the XQuartz project. For more information:
https://support.apple.com/en-us/HT201341
XQuartz.pkg的下載地址為:https://www.xquartz.org/
當(dāng)然,下載安裝好了XQuartz還不夠,因?yàn)槲覀兊腦11/Xlib.h文件是以XQuartz軟件包的形式安裝的,為了讓其可以被其他的軟件使用時(shí),系統(tǒng)直接調(diào)用,我們還需為其設(shè)置一個(gè)軟鏈接:
ln -s /opt/X11/include/X11 /usr/local/include/X11
接下來,讓我們繼續(xù)執(zhí)行formula install,看看我們的系統(tǒng)還缺什么吧:
zipimport.ZipImportError: can't decompress data; zlib not available
終端依然報(bào)錯(cuò):zlib not available,不過好消息是:這次終于bulid finished successfully!其實(shí)當(dāng)終端第一次出現(xiàn)此問題時(shí),我們就應(yīng)該去檢查系統(tǒng)是否已經(jīng)安裝了zlib。通過檢查/usr/local/opt路徑下的zlib文件夾,我們可以看到系統(tǒng)并沒有安裝zlib, 因此首先我們需要通過Homebrew安裝zlib,并且因?yàn)閦lib是keg-only的,所以它并不會被直接鏈接到/usr/local。為了讓編譯器在編譯的時(shí)候能成功找到zlib,我們還需要為其設(shè)置環(huán)境變量:
brew install zlib
export LDFLAGS="-L/usr/local/opt/zlib/lib"
export CPPFLAGS="-I/usr/local/opt/zlib/include"
結(jié)果還是不行?。≈链?,我們直接從brew install開始,似乎陷入了僵局,那么我們就去換個(gè)思路,從源碼包的Readme入手,讓我們看看距離成功install python3.6.15還差什么條件吧~
Part3:從Python源碼中尋找答案~
?現(xiàn)在,讓我們把目光聚焦到Python本身:我們真的知道應(yīng)該如何在MacOS中編譯安裝Python了嗎?反正看完了python@3.10.rb文件的490行代碼的我,是對自己產(chǎn)生了深深的懷疑的~pip、wheel等package之于python安裝的意義?多版本Python在系統(tǒng)中的管理?這些問題隨著那490行的“天書”,著實(shí)讓人摸不著頭腦。那么,這一切的頭緒在哪里呢?或許,我們可以在Python3.6.15的源碼包中可以找到答案~
3.1 在MacOS下的編譯嘗試
?如果把HomeBrew想象成一個(gè)自動(dòng)化軟件安裝的工具,那么拋開這個(gè)“工具”,手動(dòng)安裝Python3.6.15的過程應(yīng)該是:1.配置環(huán)境(configure);2.源碼編譯(make);3.安裝軟件(make install)。在MacOS系統(tǒng)中,配置環(huán)境又可以分為:安裝必要的系統(tǒng)組件——安裝必要的軟件——設(shè)置環(huán)境變量——執(zhí)行編譯。具體命令如下:
#1.為系統(tǒng)安裝必要的Git、Make以及C編譯器
xcode-select --install
#2.安裝python編譯時(shí)必須的dependencies
brew install openssl xz zlib
#3.設(shè)置必要的環(huán)境變量,并執(zhí)行./configure
CPPFLAGS="-I$(brew --prefix zlib)/include" \
LDFLAGS="-L$(brew --prefix zlib)/lib" \
CC=/usr/local/bin/gcc-12 \
LD=/usr/local/bin/gcc-12 \
CXX=/usr/local/bin/g++-12 \
./configure --with-openssl=$(brew --prefix openssl) --with-pydebug
?待上述命令執(zhí)行完畢,我們可以看到在我們的Python源碼文件夾下,生成了一個(gè)Makefile文件,以供后續(xù)自動(dòng)編譯Python的源碼。?接下來,便可以對Python源碼進(jìn)行編譯,具體命令如下:
make -j2 -s
?其中-j2表示以多線程同時(shí)進(jìn)行編譯,當(dāng)然,如果你的電腦是4核甚至更高的配置,你還可以輸入更高的數(shù)字;-s表示禁止輸出Makefile文件的每一條命令,如果去掉,你將獲得一個(gè)“瘋狂”滾動(dòng)的命令行窗口。待該過程執(zhí)行成功,不出意外地,你將在源碼文件夾中看到一個(gè)名為python.exe的可執(zhí)行文件,通過在命令行中執(zhí)行:./python.exe,你將看到熟悉的python命令窗口:
3.1.1 二進(jìn)制文件的安裝
3.1.2 一些編譯失敗的packages
?如果仔細(xì)閱讀編譯過程中的命令行輸出,不難發(fā)現(xiàn),一些packages沒有被正確地構(gòu)建,例如:_dbm, _sqlite3, _uuid, nis, ossaudiodev, spwd, _tkinter等。
The necessary bits to build these optional modules were not found:
_bz2 _curses _curses_panel
_dbm _gdbm _sqlite3
_ssl nis ossaudiodev
readline spwd
?阿柒認(rèn)為:與其現(xiàn)在研究清楚這些包究竟有何用途,不如了解當(dāng)需要用到這些packages時(shí),可以如何添加?這里阿柒以兩個(gè)開發(fā)過程中本人使用過的包:_sqlite3和_gdbm為例,為大家展示類似package的添加方法。
import sqlite3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/File/Path/Python-3.6.15/Lib/sqlite3/__init__.py", line 23, in <module>
from sqlite3.dbapi2 import *
File "/File/Path/Python-3.6.15/Lib/sqlite3/dbapi2.py", line 27, in <module>
from _sqlite3 import *
ModuleNotFoundError: No module named '_sqlite3'
?這里沒有成功構(gòu)建_sqlite3的原因有很多,例如系統(tǒng)沒有安裝sqlite或者為編譯配置系統(tǒng)環(huán)境時(shí)未啟用相關(guān)的選項(xiàng)等等。但一言以蔽之,發(fā)生這種情況最多的還是像我們這種毫無經(jīng)驗(yàn)的python“小白”,懵懂的我們在試著編譯時(shí)甚至不知道需要添加哪些必要的packages,而只是對著網(wǎng)絡(luò)上一篇不知道何年何人寫的教程“照貓畫虎”,既不知其然更不知其所以然。而解決這個(gè)問題的方法也異常簡單:即在保證系統(tǒng)正確安裝了相關(guān)工具的前提下,反復(fù)修改配置(configure)環(huán)節(jié)的參數(shù),注意:這里每重新生成一次Makefile,都意味著要重新構(gòu)建(make)一遍源代碼,不過好的一面是:這個(gè)過程可以幫助我們快速熟悉起來源碼編譯的主要過程,越編越熟練。比如:添加_sqlite3的過程就可以寫做:
#1. 通過HomeBrew為系統(tǒng)安裝sqlite
brew search sqlite3
brew install sqlite
#2. 添加配置參數(shù),重新生成Makefile
CPPFLAGS="-I$(brew --prefix zlib)/include \
-I$(xcrun -show-sdk-path)/usr/include" \
LDFLAGS="-L$(brew --prefix zlib)/lib" \
CC=/usr/local/bin/gcc-12 \
LD=/usr/local/bin/gcc-12 \
CXX=/usr/local/bin/g++-12 \
./configure --with-openssl=$(brew --prefix openssl) --enable-loadable-sqlite-extensions --with-pydebug
?好消息是上述命令執(zhí)行完畢后,_dbm和nis工具包一并被構(gòu)建成功了(暫時(shí)忽略unrecognized options: --with-openssl的問題)!悲劇的是:我們需要的_sqlite3由于某些原因沒有被成功構(gòu)建,具體問題如下:
warning: implicit declaration of function 'sqlite3_enable_load_extension'; did you mean 'pysqlite_enable_load_extension'? [-Wimplicit-function-declaration]
warning: implicit declaration of function 'sqlite3_load_extension'; did you mean 'pysqlite_load_extension'? [-Wimplicit-function-declaration]
WARNING: renaming "_sqlite3" since importing it failed: dlopen(build/lib.macosx-12.6-x86_64-3.6-pydebug/_sqlite3.cpython-36dm-darwin.so, 0x0002): symbol not found in flat namespace (_sqlite3_enable_load_extension)
Following modules built successfully but were removed because they could not be imported:
_sqlite3
?產(chǎn)生上述問題的原因可以參考這篇提問的思路,簡單地說:由于系統(tǒng)中sqlite3的編譯時(shí)的配置參數(shù)問題,此處需要保證sqlite3中正確設(shè)置了ENABLE_LOAD_EXTENSION選項(xiàng),查看系統(tǒng)中sqlite3版本及參數(shù)的命令如下:
#1. 查看sqlite3版本
$ sqlite3 --version
3.37.0
#2.查看sqlite3編譯選項(xiàng)
$ sqlite3
SQLite version 3.37.0 2021-12-09 01:34:53
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> PRAGMA compile_options;
ATOMIC_INTRINSICS=1
BUG_COMPATIBLE_20160819
COMPILER=clang-13.1.6
DEFAULT_AUTOVACUUM
DEFAULT_CACHE_SIZE=2000
DEFAULT_CKPTFULLFSYNC
DEFAULT_FILE_FORMAT=4
DEFAULT_JOURNAL_SIZE_LIMIT=32768
DEFAULT_LOOKASIDE=1200,102
DEFAULT_MEMSTATUS=0
DEFAULT_MMAP_SIZE=0
DEFAULT_PAGE_SIZE=4096
DEFAULT_PCACHE_INITSZ=20
DEFAULT_RECURSIVE_TRIGGERS
DEFAULT_SECTOR_SIZE=4096
DEFAULT_SYNCHRONOUS=2
DEFAULT_WAL_AUTOCHECKPOINT=1000
DEFAULT_WAL_SYNCHRONOUS=1
DEFAULT_WORKER_THREADS=0
ENABLE_API_ARMOR
ENABLE_BYTECODE_VTAB
ENABLE_COLUMN_METADATA
ENABLE_DBPAGE_VTAB
ENABLE_DBSTAT_VTAB
ENABLE_EXPLAIN_COMMENTS
ENABLE_FTS3
ENABLE_FTS3_PARENTHESIS
ENABLE_FTS3_TOKENIZER
ENABLE_FTS4
ENABLE_FTS5
ENABLE_JSON1
ENABLE_LOCKING_STYLE=1
ENABLE_NORMALIZE
ENABLE_PREUPDATE_HOOK
ENABLE_RTREE
ENABLE_SESSION
ENABLE_SNAPSHOT
ENABLE_SQLLOG
ENABLE_STMT_SCANSTATUS
ENABLE_UNKNOWN_SQL_FUNCTION
ENABLE_UPDATE_DELETE_LIMIT
HAVE_ISNAN
MALLOC_SOFT_LIMIT=1024
MAX_ATTACHED=10
MAX_COLUMN=2000
MAX_COMPOUND_SELECT=500
MAX_DEFAULT_PAGE_SIZE=8192
MAX_EXPR_DEPTH=1000
MAX_FUNCTION_ARG=127
MAX_LENGTH=2147483645
MAX_LIKE_PATTERN_LENGTH=50000
MAX_MMAP_SIZE=1073741824
MAX_PAGE_COUNT=1073741823
MAX_PAGE_SIZE=65536
MAX_SQL_LENGTH=1000000000
MAX_TRIGGER_DEPTH=1000
MAX_VARIABLE_NUMBER=500000
MAX_VDBE_OP=250000000
MAX_WORKER_THREADS=8
MUTEX_UNFAIR
OMIT_AUTORESET
OMIT_LOAD_EXTENSION
STMTJRNL_SPILL=131072
SYSTEM_MALLOC
TEMP_STORE=1
THREADSAFE=2
USE_URI
?可以清楚地看到在compile_options中,LOAD_EXTENSION是OMIT(禁用)的!因此要想在_sqlite3構(gòu)建的過程中,正確調(diào)用系統(tǒng)中的sqlite3功能,則需要告訴操作系統(tǒng)編譯時(shí)啟用sqlite的LOAD_EXTENSION選項(xiàng),并在相應(yīng)環(huán)境變量中添加好sqlite的路徑。具體命令如下:
CPPFLAGS="-I$(brew --prefix zlib)/include \
-I$(xcrun -show-sdk-path)/usr/include \
-I/usr/local/opt/sqlite/include" \
LDFLAGS="-L$(brew --prefix zlib)/lib \
-L/usr/local/opt/sqlite/lib" \
CC=/usr/local/bin/gcc-12 \
LD=/usr/local/bin/gcc-12 \
CXX=/usr/local/bin/g++-12 \
./configure --with-openssl=$(brew --prefix openssl) --enable-loadable-sqlite-extensions --with-pydebug
?類似地,對于_gdbm,我們需要以下幾步:
#1. 安裝相應(yīng)軟件
brew install gdbm
#2.添加配置參數(shù),生成Makefile
CPPFLAGS="-I$(brew --prefix zlib)/include \
-I$(brew --prefix openssl)/include \
-I$(xcrun -show-sdk-path)/usr/include \
-I/usr/local/opt/sqlite/include \
-I$(brew --prefix readline)/include \
-I$(brew --prefix gdbm)/include" \
LDFLAGS="-L$(brew --prefix zlib)/lib \
-L$(brew --prefix openssl)/lib \
-L/usr/local/opt/sqlite/lib \
-L$(brew --prefix readline)/lib \
-L$(brew --prefix gdbm)/lib" \
CC=/usr/local/bin/gcc-12 \
LD=/usr/local/bin/gcc-12 \
CXX=/usr/local/bin/g++-12 \
./configure --with-openssl=$(brew --prefix openssl) --enable-loadable-sqlite-extensions --with-pydebug
3.1.3 unrecognized options: --with-openssl
?如果你一步不落地走到這里,相信你一定會在環(huán)境配置和編譯的過程中發(fā)現(xiàn)類似以下的信息:unrecognized options: --with-openssl以及openssl 0x00000000 is too old for _hashlib。在解釋清楚這個(gè)問題之前,我們需要先清楚:為了避免和系統(tǒng)自帶的openssl沖突,我們通過Homebrew安裝的openssl被命名為了openssl@3,但是這并不影響我們在編譯時(shí)的使用,可以看到通過輸入openssl,系統(tǒng)依然可以準(zhǔn)確地找到相應(yīng)的路徑。
#1.查看系統(tǒng)中openssl版本
$ openssl version -a
LibreSSL 2.8.3
built on: date not available
platform: information not available
options: bn(64,64) rc4(16x,int) des(idx,cisc,16,int) blowfish(idx)
compiler: information not available
OPENSSLDIR: "/private/etc/ssl"
#2.查看HomeBrew安裝的openssl版本
$ brew info openssl
openssl@3: stable 3.0.7 (bottled) [keg-only]
#3.輸出openssl@3路徑
$ brew --prefix openssl
/usr/local/opt/openssl@3
?然而問題是:openssl@3雖然是最新的版本,但卻不是大多數(shù)開源軟件默認(rèn)使用的版本。對于很多開源軟件(包括cpython),其使用的openssl版本為1.1。當(dāng)然了,實(shí)踐證明:即使是使用openssl@3,在出現(xiàn)警告的前提下,也是能夠正確導(dǎo)入ssl和hashlib的。但至少,指定openssl@1.1進(jìn)行編譯時(shí),不會出現(xiàn)openssl 0x00000000 is too old for _hashlib的警告??其中,openssl@1.1的路徑可以通過如下命令進(jìn)行指定:
$ brew --prefix openssl@1.1
/usr/local/opt/openssl@1.1
?最終,全部完成上述packages的正確編譯,我們的配置參數(shù)命令行需要寫做:
#1. 安裝必要的軟件
$ brew install bzip2 ncurses
#2.添加配置參數(shù),生成Makefile
CPPFLAGS="-I$(brew --prefix zlib)/include \
-I$(brew --prefix openssl@1.1)/include \
-I$(xcrun -show-sdk-path)/usr/include \
-I/usr/local/opt/sqlite/include \
-I$(brew --prefix readline)/include \
-I$(brew --prefix gdbm)/include \
-I$(brew --prefix bzip2)/include \
-I$(brew --prefix ncurses)/include" \
LDFLAGS="-L$(brew --prefix zlib)/lib \
-L$(brew --prefix openssl@1.1)/lib \
-L/usr/local/opt/sqlite/lib \
-L$(brew --prefix readline)/lib \
-L$(brew --prefix gdbm)/lib \
-L$(brew --prefix bzip2)/lib \
-L$(brew --prefix ncurses)/lib" \
CC=/usr/local/bin/gcc-12 \
LD=/usr/local/bin/gcc-12 \
CXX=/usr/local/bin/g++-12 \
./configure --with-openssl=$(brew --prefix openssl@1.1) --enable-loadable-sqlite-extensions --with-pydebug
?這里值得特別注意的是ossaudiodev和spwd這兩個(gè)package。與其他packages的安裝方式稍有不同的是:這兩個(gè)package在HomeBrew上并沒有直接的formula或者cask可以用。因此這里想要正確實(shí)現(xiàn)這兩個(gè)內(nèi)置module的編譯和安裝,阿柒的解決思路是:首先,了解python在構(gòu)建過程中各內(nèi)置module是如何被確定的;接下來,針對特定的ossaudiodev和spwd模塊,查看其構(gòu)建所需的具體條件;最后,敲定這兩個(gè)module的安裝方式。于是,根據(jù)查閱到的資料可知:在Python構(gòu)建的過程中,針對當(dāng)前操作系統(tǒng)的module配置是寫在setup.py的腳本中的。其中,ossaudiodev的配置條件為:host_platform.startwith(('linux', 'freebsd', 'gnufreebsd')),而通過閱讀host_platform變量的取值邏輯并對比我們的系統(tǒng)實(shí)際,可以得到:當(dāng)前的host_platform的取值為“darwin"。即:在MacOS下,python未能成功構(gòu)建ossaudiodev module是符合python設(shè)計(jì)原則的,我們無需為此感到困惑。再來,根據(jù)spwd的配置條件,我們可以非常清楚的看到:只有當(dāng)config_h_vars中存在“HAVE_GETSPNAM"或“HAVE_GETSPENT"且兩常量至少有一個(gè)保證其值為True時(shí),spwd module才能夠進(jìn)行配置。而查看我們當(dāng)前系統(tǒng)的config_h_vars可知:上述兩個(gè)常量的取值均為False。因此在構(gòu)建過程中,spwd module未被成功構(gòu)建也是可以理解的。至于我們應(yīng)該如何將“HAVE_GETSPNAM"和“HAVE_GETSPENT"兩個(gè)常量覆寫為True,以及spwd的正確構(gòu)建,阿柒這里將其放入到Part7部分,留待將來或者聰明的你來解答。
# spwd的配置條件
if (config_h_vars.get('HAVE_GETSPNAM', False) or
config_h_vars.get('HAVE_GETSPENT', False)):
exts.append( Extension('spwd', ['spwdmodule.c']) )
else:
missing.append('spwd')
# config_h_vars詳覽
{'AC_APPLE_UNIVERSAL_BUILD': 0, 'AIX_GENUINE_CPLUSPLUS': 0, 'ANDROID_API_LEVEL': 0, 'DOUBLE_IS_ARM_MIXED_ENDIAN_IEEE754': 0, 'DOUBLE_IS_BIG_ENDIAN_IEEE754': 0,
'DOUBLE_IS_LITTLE_ENDIAN_IEEE754': 1, 'ENABLE_IPV6': 1, 'FLOCK_NEEDS_LIBBSD': 0, 'GETPGRP_HAVE_ARG': 0, 'GETTIMEOFDAY_NO_TZ': 0, 'HAVE_ACCEPT4': 0,
'HAVE_ACOSH': 1, 'HAVE_ADDRINFO': 1, 'HAVE_ALARM': 1, 'HAVE_ALIGNED_REQUIRED': 0, 'HAVE_ALLOCA_H': 1, 'HAVE_ALTZONE': 0, 'HAVE_ASINH': 1,
'HAVE_ASM_TYPES_H': 0, 'HAVE_ATANH': 1, 'HAVE_BIND_TEXTDOMAIN_CODESET': 0, 'HAVE_BLUETOOTH_BLUETOOTH_H': 0,
'HAVE_BLUETOOTH_H': 0, 'HAVE_BROKEN_MBSTOWCS': 0, 'HAVE_BROKEN_NICE': 0, 'HAVE_BROKEN_PIPE_BUF': 0, 'HAVE_BROKEN_POLL': 0,
'HAVE_BROKEN_POSIX_SEMAPHORES': 0, 'HAVE_BROKEN_PTHREAD_SIGMASK': 0, 'HAVE_BROKEN_SEM_GETVALUE': 1,
'HAVE_BROKEN_UNSETENV': 0, 'HAVE_BUILTIN_ATOMIC': 1, 'HAVE_CHFLAGS': 1, 'HAVE_CHOWN': 1, 'HAVE_CHROOT': 0, 'HAVE_CLOCK': 1,
'HAVE_CLOCK_GETRES': 1, 'HAVE_CLOCK_GETTIME': 1, 'HAVE_CLOCK_SETTIME': 1, 'HAVE_COMPUTED_GOTOS': 1, 'HAVE_CONFSTR': 1, 'HAVE_CONIO_H': 0,
'HAVE_COPYSIGN': 1, 'HAVE_CRYPT_H': 0, 'HAVE_CTERMID': 1, 'HAVE_CTERMID_R': 1, 'HAVE_CURSES_FILTER': 1, 'HAVE_CURSES_H': 1,
'HAVE_CURSES_HAS_KEY': 1, 'HAVE_CURSES_IMMEDOK': 1, 'HAVE_CURSES_IS_PAD': 0, 'HAVE_CURSES_IS_TERM_RESIZED': 1, 'HAVE_CURSES_RESIZETERM': 1,
'HAVE_CURSES_RESIZE_TERM': 1, 'HAVE_CURSES_SYNCOK': 1, 'HAVE_CURSES_TYPEAHEAD': 1,
'HAVE_CURSES_USE_ENV': 1, 'HAVE_CURSES_WCHGAT': 1, 'HAVE_DECL_ISFINITE': 1, 'HAVE_DECL_ISINF': 1, 'HAVE_DECL_ISNAN': 1, 'HAVE_DECL_RTLD_DEEPBIND': 0,
'HAVE_DECL_RTLD_GLOBAL': 1, 'HAVE_DECL_RTLD_LAZY': 1, 'HAVE_DECL_RTLD_LOCAL': 1, 'HAVE_DECL_RTLD_NODELETE': 1,
'HAVE_DECL_RTLD_NOLOAD': 1, 'HAVE_DECL_RTLD_NOW': 1, 'HAVE_DECL_TZNAME': 0, 'HAVE_DEVICE_MACROS': 1, 'HAVE_DEV_PTC': 0,
'HAVE_DEV_PTMX': 1, 'HAVE_DIRECT_H': 0, 'HAVE_DIRENT_D_TYPE': 1, 'HAVE_DIRENT_H': 1, 'HAVE_DIRFD': 1, 'HAVE_DLFCN_H': 1, 'HAVE_DLOPEN': 1,
'HAVE_DUP2': 1, 'HAVE_DUP3': 0, 'HAVE_DYNAMIC_LOADING': 1, 'HAVE_ENDIAN_H': 0, 'HAVE_EPOLL': 0, 'HAVE_EPOLL_CREATE1': 0,
'HAVE_ERF': 1, 'HAVE_ERFC': 1, 'HAVE_ERRNO_H': 1, 'HAVE_EXECV': 1, 'HAVE_EXPM1': 1, 'HAVE_FACCESSAT': 1, 'HAVE_FCHDIR': 1, 'HAVE_FCHMOD': 1,
'HAVE_FCHMODAT': 1, 'HAVE_FCHOWN': 1, 'HAVE_FCHOWNAT': 1, 'HAVE_FCNTL_H': 1, 'HAVE_FDATASYNC': 0, 'HAVE_FDOPENDIR': 1,
'HAVE_FEXECVE': 0, 'HAVE_FINITE': 1, 'HAVE_FLOCK': 1, 'HAVE_FORK': 1, 'HAVE_FORKPTY': 1, 'HAVE_FPATHCONF': 1, 'HAVE_FSEEK64': 0, 'HAVE_FSEEKO': 1, 'HAVE_FSTATAT': 1,
'HAVE_FSTATVFS': 1, 'HAVE_FSYNC': 1, 'HAVE_FTELL64': 0, 'HAVE_FTELLO': 1, 'HAVE_FTIME': 1, 'HAVE_FTRUNCATE': 1, 'HAVE_FUTIMENS': 1, 'HAVE_FUTIMES': 1,
'HAVE_FUTIMESAT': 0, 'HAVE_GAI_STRERROR': 1, 'HAVE_GAMMA': 1, 'HAVE_GCC_ASM_FOR_MC68881': 0, 'HAVE_GCC_ASM_FOR_X64': 1,
'HAVE_GCC_ASM_FOR_X87': 1, 'HAVE_GCC_UINT128_T': 1, 'HAVE_GETADDRINFO': 1, 'HAVE_GETC_UNLOCKED': 1, 'HAVE_GETENTROPY': 1, 'HAVE_GETGROUPLIST': 1,
'HAVE_GETGROUPS': 1, 'HAVE_GETHOSTBYNAME': 1, 'HAVE_GETHOSTBYNAME_R': 0, 'HAVE_GETHOSTBYNAME_R_3_ARG': 0,
'HAVE_GETHOSTBYNAME_R_5_ARG': 0, 'HAVE_GETHOSTBYNAME_R_6_ARG': 0, 'HAVE_GETITIMER': 1, 'HAVE_GETLOADAVG': 1,
'HAVE_GETLOGIN': 1, 'HAVE_GETNAMEINFO': 1, 'HAVE_GETPAGESIZE': 0, 'HAVE_GETPEERNAME': 1, 'HAVE_GETPGID': 1, 'HAVE_GETPGRP': 1, 'HAVE_GETPID': 1,
'HAVE_GETPRIORITY': 1, 'HAVE_GETPWENT': 1, 'HAVE_GETRANDOM': 0, 'HAVE_GETRANDOM_SYSCALL': 0, 'HAVE_GETRESGID': 0, 'HAVE_GETRESUID': 0,
'HAVE_GETSID': 1, 'HAVE_GETSPENT': 0, 'HAVE_GETSPNAM': 0, 'HAVE_GETTIMEOFDAY': 1, 'HAVE_GETWD': 1, 'HAVE_GLIBC_MEMMOVE_BUG': 0,
'HAVE_GRP_H': 1, 'HAVE_HSTRERROR': 1, 'HAVE_HTOLE64': 0, 'HAVE_HYPOT': 1, 'HAVE_IEEEFP_H': 0, 'HAVE_IF_NAMEINDEX': 1, 'HAVE_INET_ATON': 1,
'HAVE_INET_PTON': 1, 'HAVE_INITGROUPS': 1, 'HAVE_INTTYPES_H': 1, 'HAVE_IO_H': 0, 'HAVE_IPA_PURE_CONST_BUG': 0, 'HAVE_KILL': 1,
'HAVE_KILLPG': 1, 'HAVE_KQUEUE': 1, 'HAVE_LANGINFO_H': 1, 'HAVE_LARGEFILE_SUPPORT': 0, 'HAVE_LCHFLAGS': 1, 'HAVE_LCHMOD': 1,
'HAVE_LCHOWN': 1, 'HAVE_LGAMMA': 1, 'HAVE_LIBDL': 1, 'HAVE_LIBDLD': 0, 'HAVE_LIBIEEE': 0, 'HAVE_LIBINTL_H': 0, 'HAVE_LIBREADLINE': 1, 'HAVE_LIBRESOLV': 0,
'HAVE_LIBSENDFILE': 0, 'HAVE_LIBUTIL_H': 0, 'HAVE_LINK': 1, 'HAVE_LINKAT': 1, 'HAVE_LINUX_CAN_BCM_H': 0, 'HAVE_LINUX_CAN_H': 0,
'HAVE_LINUX_CAN_RAW_FD_FRAMES': 0, 'HAVE_LINUX_CAN_RAW_H': 0, 'HAVE_LINUX_NETLINK_H': 0, 'HAVE_LINUX_RANDOM_H': 0, 'HAVE_LINUX_TIPC_H': 0,
'HAVE_LOCKF': 1, 'HAVE_LOG1P': 1, 'HAVE_LOG2': 1, 'HAVE_LONG_DOUBLE': 1, 'HAVE_LSTAT': 1, 'HAVE_LUTIMES': 1, 'HAVE_MAKEDEV': 1,
'HAVE_MBRTOWC': 1, 'HAVE_MEMMOVE': 1, 'HAVE_MEMORY_H': 1, 'HAVE_MEMRCHR': 0, 'HAVE_MKDIRAT': 1, 'HAVE_MKFIFO': 1, 'HAVE_MKFIFOAT': 0,
'HAVE_MKNOD': 1, 'HAVE_MKNODAT': 0, 'HAVE_MKTIME': 1, 'HAVE_MMAP': 1, 'HAVE_MREMAP': 0, 'HAVE_NCURSES_H': 1, 'HAVE_NDIR_H': 0,
'HAVE_NETPACKET_PACKET_H': 0, 'HAVE_NET_IF_H': 1, 'HAVE_NICE': 1, 'HAVE_OPENAT': 1, 'HAVE_OPENPTY': 1, 'HAVE_PATHCONF': 1, 'HAVE_PAUSE': 1, 'HAVE_PIPE2': 0,
'HAVE_PLOCK': 0, 'HAVE_POLL': 1, 'HAVE_POLL_H': 1, 'HAVE_POSIX_FADVISE': 0, 'HAVE_POSIX_FALLOCATE': 0, 'HAVE_PREAD': 1, 'HAVE_PRLIMIT': 0, 'HAVE_PROCESS_H': 0,
'HAVE_PROTOTYPES': 1, 'HAVE_PTHREAD_ATFORK': 1, 'HAVE_PTHREAD_DESTRUCTOR': 0, 'HAVE_PTHREAD_H': 1, 'HAVE_PTHREAD_INIT': 0, 'HAVE_PTHREAD_KILL': 1,
'HAVE_PTHREAD_SIGMASK': 1, 'HAVE_PTY_H': 0, 'HAVE_PUTENV': 1, 'HAVE_PWRITE': 1, 'HAVE_READLINK': 1, 'HAVE_READLINKAT': 1, 'HAVE_READV': 1,
'HAVE_REALPATH': 1, 'HAVE_RENAMEAT': 1, 'HAVE_RL_APPEND_HISTORY': 1, 'HAVE_RL_CALLBACK': 1, 'HAVE_RL_CATCH_SIGNAL': 1,
'HAVE_RL_COMPLETION_APPEND_CHARACTER': 1, 'HAVE_RL_COMPLETION_DISPLAY_MATCHES_HOOK': 1, 'HAVE_RL_COMPLETION_MATCHES': 1,
'HAVE_RL_COMPLETION_SUPPRESS_APPEND': 1, 'HAVE_RL_PRE_INPUT_HOOK': 1, 'HAVE_RL_RESIZE_TERMINAL': 1, 'HAVE_ROUND': 1,
'HAVE_SCHED_GET_PRIORITY_MAX': 1, 'HAVE_SCHED_H': 1, 'HAVE_SCHED_RR_GET_INTERVAL': 0, 'HAVE_SCHED_SETAFFINITY': 0, 'HAVE_SCHED_SETPARAM': 0,
'HAVE_SCHED_SETSCHEDULER': 0, 'HAVE_SELECT': 1, 'HAVE_SEM_GETVALUE': 1, 'HAVE_SEM_OPEN': 1, 'HAVE_SEM_TIMEDWAIT': 0, 'HAVE_SEM_UNLINK': 1,
'HAVE_SENDFILE': 1, 'HAVE_SETEGID': 1, 'HAVE_SETEUID': 1, 'HAVE_SETGID': 1, 'HAVE_SETGROUPS': 1, 'HAVE_SETHOSTNAME': 1, 'HAVE_SETITIMER': 1,
'HAVE_SETLOCALE': 1, 'HAVE_SETPGID': 1, 'HAVE_SETPGRP': 1, 'HAVE_SETPRIORITY': 1, 'HAVE_SETREGID': 1, 'HAVE_SETRESGID': 0,
'HAVE_SETRESUID': 0, 'HAVE_SETREUID': 1, 'HAVE_SETSID': 1, 'HAVE_SETUID': 1, 'HAVE_SETVBUF': 1, 'HAVE_SHADOW_H': 0, 'HAVE_SIGACTION': 1, 'HAVE_SIGALTSTACK': 1, 'HAVE_SIGINTERRUPT': 1,
'HAVE_SIGNAL_H': 1, 'HAVE_SIGPENDING': 1, 'HAVE_SIGRELSE': 1, 'HAVE_SIGTIMEDWAIT': 0, 'HAVE_SIGWAIT': 1, 'HAVE_SIGWAITINFO': 0,
'HAVE_SNPRINTF': 1, 'HAVE_SOCKADDR_ALG': 0, 'HAVE_SOCKADDR_SA_LEN': 1, 'HAVE_SOCKADDR_STORAGE': 1, 'HAVE_SOCKETPAIR': 1,
'HAVE_SPAWN_H': 1, 'HAVE_SSIZE_T': 1, 'HAVE_STATVFS': 1, 'HAVE_STAT_TV_NSEC': 0, 'HAVE_STAT_TV_NSEC2': 1, 'HAVE_STDARG_PROTOTYPES': 1, 'HAVE_STDINT_H': 1,
'HAVE_STDLIB_H': 1, 'HAVE_STD_ATOMIC': 1, 'HAVE_STRDUP': 1, 'HAVE_STRFTIME': 1, 'HAVE_STRINGS_H': 1, 'HAVE_STRING_H': 1,
'HAVE_STRLCPY': 1, 'HAVE_STROPTS_H': 0, 'HAVE_STRUCT_PASSWD_PW_GECOS': 1, 'HAVE_STRUCT_PASSWD_PW_PASSWD': 1,
'HAVE_STRUCT_STAT_ST_BIRTHTIME': 1, 'HAVE_STRUCT_STAT_ST_BLKSIZE': 1, 'HAVE_STRUCT_STAT_ST_BLOCKS': 1,
'HAVE_STRUCT_STAT_ST_FLAGS': 1, 'HAVE_STRUCT_STAT_ST_GEN': 1, 'HAVE_STRUCT_STAT_ST_RDEV': 1,
'HAVE_STRUCT_TM_TM_ZONE': 1, 'HAVE_SYMLINK': 1, 'HAVE_SYMLINKAT': 1, 'HAVE_SYNC': 1, 'HAVE_SYSCONF': 1, 'HAVE_SYSEXITS_H': 1, 'HAVE_SYS_AUDIOIO_H': 0,
'HAVE_SYS_BSDTTY_H': 0, 'HAVE_SYS_DEVPOLL_H': 0, 'HAVE_SYS_DIR_H': 0, 'HAVE_SYS_ENDIAN_H': 0, 'HAVE_SYS_EPOLL_H': 0, 'HAVE_SYS_EVENT_H': 1,
'HAVE_SYS_FILE_H': 1, 'HAVE_SYS_IOCTL_H': 1, 'HAVE_SYS_KERN_CONTROL_H': 1, 'HAVE_SYS_LOADAVG_H': 0, 'HAVE_SYS_LOCK_H': 1,
'HAVE_SYS_MKDEV_H': 0, 'HAVE_SYS_MODEM_H': 0, 'HAVE_SYS_NDIR_H': 0, 'HAVE_SYS_PARAM_H': 1, 'HAVE_SYS_POLL_H': 1, 'HAVE_SYS_RANDOM_H': 1,
'HAVE_SYS_RESOURCE_H': 1, 'HAVE_SYS_SELECT_H': 1, 'HAVE_SYS_SENDFILE_H': 0, 'HAVE_SYS_SOCKET_H': 1, 'HAVE_SYS_STATVFS_H': 1, 'HAVE_SYS_STAT_H': 1,
'HAVE_SYS_SYSCALL_H': 1, 'HAVE_SYS_SYSMACROS_H': 0, 'HAVE_SYS_SYS_DOMAIN_H': 1, 'HAVE_SYS_TERMIO_H': 0, 'HAVE_SYS_TIMES_H': 1, 'HAVE_SYS_TIME_H': 1,
'HAVE_SYS_TYPES_H': 1, 'HAVE_SYS_UIO_H': 1, 'HAVE_SYS_UN_H': 1, 'HAVE_SYS_UTSNAME_H': 1, 'HAVE_SYS_WAIT_H': 1, 'HAVE_SYS_XATTR_H': 1,
'HAVE_TCGETPGRP': 1, 'HAVE_TCSETPGRP': 1, 'HAVE_TEMPNAM': 1, 'HAVE_TERMIOS_H': 1, 'HAVE_TERM_H': 1, 'HAVE_TGAMMA': 1, 'HAVE_TIMEGM': 1,
'HAVE_TIMES': 1, 'HAVE_TMPFILE': 1, 'HAVE_TMPNAM': 1, 'HAVE_TMPNAM_R': 0, 'HAVE_TM_ZONE': 1, 'HAVE_TRUNCATE': 1, 'HAVE_TZNAME': 0,
'HAVE_UCS4_TCL': 0, 'HAVE_UNAME': 1, 'HAVE_UNISTD_H': 1, 'HAVE_UNLINKAT': 1, 'HAVE_UNSETENV': 1, 'HAVE_USABLE_WCHAR_T': 0,
'HAVE_UTIL_H': 1, 'HAVE_UTIMENSAT': 1, 'HAVE_UTIMES': 1, 'HAVE_UTIME_H': 1, 'HAVE_WAIT3': 1, 'HAVE_WAIT4': 1, 'HAVE_WAITID': 1, 'HAVE_WAITPID': 1, 'HAVE_WCHAR_H': 1,
'HAVE_WCSCOLL': 1, 'HAVE_WCSFTIME': 1, 'HAVE_WCSXFRM': 1, 'HAVE_WMEMCMP': 1, 'HAVE_WORKING_TZSET': 1, 'HAVE_WRITEV': 1,
'HAVE_ZLIB_COPY': 1, 'HAVE__GETPTY': 0, 'LOG1P_DROPS_ZERO_SIGN': 0, 'MAJOR_IN_MKDEV': 0, 'MAJOR_IN_SYSMACROS': 0,
'MVWDELCH_IS_EXPRESSION': 1, 'PACKAGE_BUGREPORT': 0, 'PACKAGE_NAME': 0, 'PACKAGE_STRING': 0, 'PACKAGE_TARNAME': 0, 'PACKAGE_URL': 0,
'PACKAGE_VERSION': 0, 'POSIX_SEMAPHORES_NOT_ENABLED': 0, 'PTHREAD_SYSTEM_SCHED_SUPPORTED': 1,
'PYLONG_BITS_IN_DIGIT': 0, 'PY_FORMAT_SIZE_T': '"z"', 'Py_DEBUG': 1, 'Py_ENABLE_SHARED': 0, 'Py_HASH_ALGORITHM': 0, 'RETSIGTYPE': 'void',
'SETPGRP_HAVE_ARG': 0, 'SIGNED_RIGHT_SHIFT_ZERO_FILLS': 0, 'SIZEOF_DOUBLE': 8, 'SIZEOF_FLOAT': 4, 'SIZEOF_FPOS_T': 8, 'SIZEOF_INT': 4,
'SIZEOF_LONG': 8, 'SIZEOF_LONG_DOUBLE': 16, 'SIZEOF_LONG_LONG': 8, 'SIZEOF_OFF_T': 8, 'SIZEOF_PID_T': 4, 'SIZEOF_PTHREAD_T': 8,
'SIZEOF_SHORT': 2, 'SIZEOF_SIZE_T': 8, 'SIZEOF_TIME_T': 8, 'SIZEOF_UINTPTR_T': 8, 'SIZEOF_VOID_P': 8, 'SIZEOF_WCHAR_T': 4, 'SIZEOF__BOOL': 1,
'STDC_HEADERS': 1, 'SYS_SELECT_WITH_SYS_TIME': 1, 'TANH_PRESERVES_ZERO_SIGN': 1, 'TIMEMODULE_LIB': 0, 'TIME_WITH_SYS_TIME': 1, 'TM_IN_SYS_TIME': 0,
'USE_COMPUTED_GOTOS': 0, 'USE_INLINE': 1, 'WANT_SIGFPE_HANDLER': 0, 'WINDOW_HAS_FLAGS': 1, 'WITH_DOC_STRINGS': 1, 'WITH_DTRACE': 0, 'WITH_DYLD': 1,
'WITH_LIBINTL': 0, 'WITH_NEXT_FRAMEWORK': 0, 'WITH_PYMALLOC': 1, 'WITH_THREAD': 1, 'WITH_VALGRIND': 0, 'X87_DOUBLE_ROUNDING': 0,
'STRICT_SYSV_CURSES': "/* Don't use ncurses extensions */"}
?我想這或許就是為什么當(dāng)年多篇文章提到的:使用HomeBrew安裝Python3.6有一些問題,因此更建議大家使用官方的安裝程序進(jìn)行安裝的原因吧。果然是沒有垃圾的蘋果本,只有垃圾的**??。當(dāng)然了通過這個(gè)過程,阿柒也意識到了編譯安裝的過程中,環(huán)境變量的重要性(關(guān)于此部分的詳細(xì)展開,可以在本文第6部分詳解環(huán)境變量中找到答案)。另外本著刨根問底的精神,阿柒還為大家整理出了上述packages的用途,以便諸位小小白們自行參照選擇。不過既然是Python源碼中自帶的工具包,阿柒還是覺得在條件允許的前提下盡量一勞永逸比較靠譜??,你覺得呢?
序號 | 名稱 | 用途 | 場景 |
---|---|---|---|
1 | _sqlite | SQLite是一個(gè)C庫,它提供了一個(gè)輕量級的基于磁盤的數(shù)據(jù)庫,不需要單獨(dú)的服務(wù)器進(jìn)程,并且允許使用SQL查詢語言的非標(biāo)準(zhǔn)變體訪問數(shù)據(jù)庫。一些應(yīng)用程序可以使用SQLite進(jìn)行內(nèi)部數(shù)據(jù)存儲。也可以使用SQLite創(chuàng)建應(yīng)用程序原型,然后將代碼移植到更大的數(shù)據(jù)庫,如PostgreSQL或Oracle。 | 詳見 https://docs. python.org /3/library /sqlite3.html????? |
2 | _dbm | DBM數(shù)據(jù)庫變體的通用接口 | 詳見 https://docs. python.org /3.6/library /dbm.html#module-dbm????? |
3 | _bz2 | 該模塊提供了使用bzip2壓縮算法壓縮和解壓縮數(shù)據(jù)的綜合接口。 | 詳見 https://docs. python.org /3.6/library /bz2.html#module-bz2????? |
4 | _curses | curses庫為基于文本的終端,提供了獨(dú)立于終端的屏幕繪制和鍵盤處理設(shè)施;curses模塊提供了一個(gè)到curses庫的接口,這是便攜式高級終端處理的事實(shí)上的標(biāo)準(zhǔn)。 | 詳見 https://docs. python.org /3.6/library /curses.html#module-curses????? |
5 | _curses_panel | 面板是具有深度特性的窗口,因此它們可以堆疊在彼此之上,并且只顯示每個(gè)窗口的可見部分。可以添加面板,在堆棧中向上或向下移動(dòng),以及刪除面板。 | 詳見 https://docs. python.org /3.6/library /curses.panel.html #module-curses.panel????? |
6 | _gdbm | 這個(gè)模塊與dbm模塊非常相似,但是使用GNU庫gdbm來提供一些額外的功能。請注意dbm創(chuàng)建的文件格式。Gnu和dbm。NDBM不兼容。 | 詳見 https://docs. python.org /3.6/library /dbm.html#module-dbm.gnu????? |
7 | _ssl | 該模塊為客戶端和服務(wù)器端網(wǎng)絡(luò)套接字提供了對傳輸層安全(通常稱為“安全套接字層”)加密和對等身份驗(yàn)證功能的訪問。本模塊使用OpenSSL庫。 | 詳見 https://docs. python.org /3.6/library /ssl.html#module-ssl????? |
8 | nis | nis模塊為nis庫提供了一個(gè)簡單的包裝器,對于多個(gè)主機(jī)的集中管理非常有用(可從NIS數(shù)據(jù)庫中獲取值)。由于NIS僅存在于Unix系統(tǒng)上,因此此模塊僅適用于Unix。 | 詳見 https://docs. python.org /3.6/library /nis.html#module-nis????? |
9 | ossaudiodev | 這個(gè)模塊允許您訪問OSS(開放聲音系統(tǒng))音頻接口。OSS可用于廣泛的開源和商業(yè)unix,并且是Linux和FreeBSD最新版本的標(biāo)準(zhǔn)音頻接口。 | 詳見 https://docs. python.org /3.6/library /ossaudiodev.html????? |
10 | readline | readline模塊定義了許多函數(shù),以方便從Python解釋器完成和讀取/寫入歷史文件。 | 詳見 https://docs. python.org /3.6/library /readline.html#module-readline????? |
11 | spwd | 這個(gè)模塊提供了對Unix shadow password數(shù)據(jù)庫的訪問。它可以在各種Unix版本上使用。您必須有足夠的權(quán)限來訪問影子密碼數(shù)據(jù)庫(這通常意味著您必須是根用戶)。 | 詳見 https://docs. python.org /3.6/library /spwd.html????? |
12 | _hashlib | 這個(gè)模塊實(shí)現(xiàn)了許多不同的安全哈希和消息摘要算法的公共接口。 | 詳見 https://docs. python.org /3.6/library /hashlib.html#module-hashlib????? |
?至此一套完整、可執(zhí)行的Python3.6.15的手動(dòng)編譯流程便被完整的記錄了下來。恭喜??,你又掌握了一項(xiàng)新技能。
3.2 把編譯過程固定下來
?事實(shí)上,仔細(xì)對照python@3.10.rb中的一行行代碼,不就是Python源碼在各種操作系統(tǒng)下(MacOS、Linux)編譯時(shí)的命令嗎!那么,遵照HomeBrew Formula的書寫規(guī)則,我們將上述編譯命令寫入Ruby腳本中,即可得到:
?執(zhí)行上述腳本:
Part4:完成Formula腳本
Part5:總結(jié)與反思
?很開心能夠以這種邊學(xué)習(xí)邊記錄的形式,來完成個(gè)人的第一篇正式技術(shù)博客的創(chuàng)作。我在創(chuàng)作的過程中,一個(gè)最直觀的體驗(yàn)就是:在技術(shù)探索的過程中,可能會遇到很多零零散散的小問題,如雪泥鴻爪,失不可追。唯有將這個(gè)過程完整地記錄下來,再以整體的思路去回顧理解,方能形成一點(diǎn)思路。當(dāng)然,這也為自己無論是技術(shù)探索還是文章創(chuàng)作提出了更高的要求,要在兩種思路間自如的切換,并保持專注,著實(shí)不是一件容易的事??幸運(yùn)的是,經(jīng)過不斷的摸索嘗試,我終于找到了適合自己的工作節(jié)奏和模式,現(xiàn)在就讓我?guī)Т蠹乙黄鹂偨Y(jié)一波使用HomeBrew安裝Python3.6的最優(yōu)過程吧~
5.4 手工編譯的弊端以及HomeBrew的解決方案
?在手工編譯的過程中,偶爾可能出現(xiàn)因系統(tǒng)軟件安裝不全,所導(dǎo)致的部分packages編譯失敗的問題。例如:
ld: warning: directory not found for option '-L/usr/local/opt/bzip2/lib'
ld: warning: directory not found for option '-L/usr/local/opt/ncurses/lib'
?出現(xiàn)上述問題的原因是:此時(shí)系統(tǒng)沒有安裝bzip2和ncurses。當(dāng)我們使用brew install安裝上述軟件后,即可正確編譯上述packages了。但是,在通過HomeBrew Python@3.10的Formula執(zhí)行安裝的過程中,我們的系統(tǒng)即使沒有安裝對應(yīng)的軟件,也并不影響其對相關(guān)package的引用。例如,為了驗(yàn)證上述結(jié)論,我們可以通過brew remove命令,卸載預(yù)先安裝的bzip2和ncurses,然后重新進(jìn)行源碼編譯,并將結(jié)果與python@3.10進(jìn)行對比??梢钥吹剑涸趐ython@3.10中,bz2和curses均被正常引用;而在我們自己編譯的源碼中,則可以清晰的看到bz2雖然有被正常的引用,但在引用curses時(shí)卻不出意外地發(fā)生了報(bào)錯(cuò)。
>>> import readline
>>> import bz2
>>> import curses
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/File/Folder/Path/Python-3.6.15/Lib/curses/__init__.py", line 13, in <module>
from _curses import *
ImportError: dlopen(/File/Folder/Path/Python-3.6.15/build/lib.macosx-12.6-x86_64-3.6-pydebug/_curses.cpython-36dm-darwin.so, 0x0002): Library not loaded: '/usr/local/opt/ncurses/lib/libncursesw.6.dylib'
Referenced from: '/File/Folder/Path/Python-3.6.15/build/lib.macosx-12.6-x86_64-3.6-pydebug/_curses.cpython-36dm-darwin.so'
Reason: tried: '/usr/local/opt/ncurses/lib/libncursesw.6.dylib' (no such file), '/usr/local/lib/libncursesw.6.dylib' (no such file), '/usr/lib/libncursesw.6.dylib' (no such file)
?要厘清楚這個(gè)問題,我們還是得從
==> bzip2
bzip2 is keg-only, which means it was not symlinked into /usr/local,
because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.
If you need to have bzip2 first in your PATH, run:
echo 'export PATH="/usr/local/opt/bzip2/bin:$PATH"' >> /Users/luna/.bash_profile
For compilers to find bzip2 you may need to set:
export LDFLAGS="-L/usr/local/opt/bzip2/lib"
export CPPFLAGS="-I/usr/local/opt/bzip2/include"
==> ncurses
ncurses is keg-only, which means it was not symlinked into /usr/local,
because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.
If you need to have ncurses first in your PATH, run:
echo 'export PATH="/usr/local/opt/ncurses/bin:$PATH"' >> /Users/luna/.bash_profile
For compilers to find ncurses you may need to set:
export LDFLAGS="-L/usr/local/opt/ncurses/lib"
export CPPFLAGS="-I/usr/local/opt/ncurses/include"
Part6:積土成山
6.1 MacOS中的Python版本管理
6.1.1 python@3.10: command not found
?就在我們反復(fù)嘗試著對Python3.6的源碼進(jìn)行編譯時(shí),為了驗(yàn)證:當(dāng)系統(tǒng)未安裝相關(guān)軟件時(shí),會導(dǎo)致對應(yīng)package缺失的結(jié)論,我發(fā)現(xiàn)之前我們安裝的python@3.10只能通過特定的命令python3被調(diào)用??具體過程就是:
#1.運(yùn)行python@3.10
$ python@3.10
-bash: python@3.10: command not found
$ python
-bash: python: command not found
$ python3
Python 3.10.9 (main, Dec 15 2022, 18:18:30) [Clang 14.0.0 (clang-1400.0.29.202)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>
#2.確認(rèn)是否已安裝python@3.10
$ brew list
==> Formulae
autoconf gdbm libtool pkg-config sqlite
automake gettext lz4 pyenv tbb
bzip2 git m4 pygments wxmac
ca-certificates gmp mpdecimal python@3.10 wxwidgets
cmake ilmbase mpfr python@3.11 xz
docutils isl ncurses rabbitmq zlib
eigen jpeg openexr readline zstd
erlang libmpc openssl@1.1 redis
gcc libpng openssl@3 rename
gcc@10 libtiff pcre2 sphinx-doc
?那么對于系統(tǒng)中不同版本的python,我們又該如何設(shè)置和管理,以保證能最便捷的調(diào)用到所需要的指定python版本呢?
6.2 Python源碼編譯原理
?不知道有沒有和阿柒一樣的小伙伴,雖然看過很多有關(guān)源碼編譯安裝的文章,但每次遇到自己編譯安裝某個(gè)軟件報(bào)錯(cuò)時(shí),還是依舊手忙腳亂,不知該從何下手。因此,阿柒打算在這一部分對源碼的編譯原理來個(gè)詳細(xì)的研究,爭取徹底告別源碼編譯的煩惱。
6.2.1 為什么要用源碼安裝?
?請大家回憶一下自己是什么時(shí)候第一次接觸到源碼安裝的呢?在阿柒的印象里,那是阿柒大四上半學(xué)期準(zhǔn)備畢業(yè)設(shè)計(jì)的時(shí)候,需要在Ubuntu系統(tǒng)上安裝一個(gè)Android虛擬機(jī)。由于整個(gè)過程90%都處于自己摸索的狀態(tài),實(shí)在過于痛苦和折磨,以至于多年后只要遇到自行源碼安裝的工作,內(nèi)心仍萬分忐忑。事過經(jīng)年,阿柒依然費(fèi)解:為什么在如今圖形界面安裝工具猖獗的今天,大部分軟件仍然保留了源碼安裝這種費(fèi)時(shí)費(fèi)力、對初學(xué)者極不友好的安裝方式?不過幸運(yùn)的是,這次阿柒找到了答案。
?對于一切Unix、類Unix操作系統(tǒng)來說,其上運(yùn)行的應(yīng)用程序一般是用C語言來編寫的,而為了便于傳播,大多數(shù)開源軟件都選擇遵守了GPL協(xié)議。因此掌握了源碼安裝這項(xiàng)技能,從理論上,我們便擁有了在我們的操作系統(tǒng)上肆意安裝使用一切開源軟件的可能。所以,源碼安裝的真正意義恰恰在于:降低了軟件使用者的安裝門檻,讓開源做到所見即所用。所以,讓我們告別源碼安裝的PTSD,盡情享受源碼安裝的樂趣吧~
6.2.2 源碼安裝的過程
?知道了上面的原因后,現(xiàn)在讓我們把目光聚焦到源碼上來,看看一個(gè)個(gè)互相關(guān)聯(lián)的源碼文件是如何變成能夠被計(jì)算機(jī)執(zhí)行的軟件的吧~?為了方便展示,阿柒將這個(gè)過程以流程圖的形式展現(xiàn)了出來,詳見圖一。對于一個(gè)準(zhǔn)備好發(fā)布的軟件源碼來說,開發(fā)者還需要完成最后一步:即為軟件的使用者提供一個(gè)可以安裝的路徑。而對于絕大多數(shù)的開源軟件來說,這個(gè)路徑被簡化成了:配置(configure)——構(gòu)建(make)——安裝(make install)三個(gè)步驟。首先在配置環(huán)節(jié),configure腳本需要準(zhǔn)備系統(tǒng)中的構(gòu)建環(huán)境,確保接下來構(gòu)建和安裝環(huán)節(jié)中的依賴能夠被正確執(zhí)行。具體地:configure腳本會根據(jù)安裝包中自帶的Makefile.in文件,生成一個(gè)適配于當(dāng)前電腦環(huán)境的定制化的Makefile文件;接下來在構(gòu)建環(huán)節(jié),系統(tǒng)會根據(jù)Makefile文件對軟件包中的源代碼進(jìn)行編譯,轉(zhuǎn)換為二進(jìn)制文件;再到了安裝環(huán)節(jié),系統(tǒng)會將編譯好的二進(jìn)制文件等存放到指定的安裝目錄,以完成軟件的安裝(安裝目錄會在配置環(huán)節(jié),通過--prefix=/install/path進(jìn)行指定,并體現(xiàn)在生成的Makefile中)。值得注意的是:configure腳本和Makefile.in文件都是軟件安裝包中自帶的,而Makefile則是軟件安裝過程中生成的中間文件,僅對當(dāng)前系統(tǒng)環(huán)境有效。
dnl ***********************************************
dnl * Please run autoreconf to test your changes! *
dnl ***********************************************
# Set VERSION so we only need to edit in one place (i.e., here)
m4_define(PYTHON_VERSION, 3.6)
AC_PREREQ(2.65)
AC_INIT(python, PYTHON_VERSION, https://bugs.python.org/)
AC_SUBST(BASECPPFLAGS)
if test "$srcdir" != . -a "$srcdir" != "$(pwd)"; then
# If we're building out-of-tree, we need to make sure the following
# resources get picked up before their $srcdir counterparts.
# Objects/ -> typeslots.inc
# Include/ -> Python-ast.h, graminit.h
# Python/ -> importlib.h
# (A side effect of this is that these resources will automatically be
# regenerated when building out-of-tree, regardless of whether or not
# the $srcdir counterpart is up-to-date. This is an acceptable trade
# off.)
BASECPPFLAGS="-IObjects -IInclude -IPython"
else
BASECPPFLAGS=""
fi
?同樣的,Makefile.in文件也是通過automake工具自動(dòng)生成的。而它的依據(jù)我們一般寫在一個(gè)名為Makefile.am的腳本中。而對于軟件的使用者來說,如何編寫一個(gè)configure.ac或者M(jìn)akefile.am腳本并不是他們所關(guān)心的部分,因此軟件的發(fā)布者在進(jìn)行軟件發(fā)布時(shí),只需要確定里面包含了configure腳本和Makefile.in文件,保證使用者能夠正常安裝即可,而無需發(fā)布configure.ac和Makefile.am。另外多提一點(diǎn):我們也可以通過autotools來構(gòu)建一個(gè)可以發(fā)布的軟件包,具體操作為:
./configure #生成Makefile文件
make dist #構(gòu)建可發(fā)布的軟件包
make distcheck #測試軟件包能否在各種操作系統(tǒng)上正常安裝
?好了,至此關(guān)于軟件編譯安裝的原理我們就全都理清了,最后再讓我們一起回顧一下軟件發(fā)布和安裝使用到的命令吧~
發(fā)布 | 安裝 |
---|---|
1.aclocal #準(zhǔn)備m4腳本環(huán)境 autoconf #將configure.ac轉(zhuǎn)換為configure腳本 2.automake --add-missing #生成Makefile.in文件 3. ./configure #生成Makefile文件,用于構(gòu)建可發(fā)布的軟件包 4.make distcheck #構(gòu)建軟件包并測試 |
1. ./configure #生成Makefile文件,用于安裝軟件 2.make #編譯源代碼 3.make install #安裝軟件 |
6.3 Homebrew是如何指定C編譯器的
?根據(jù)已經(jīng)查到的資料可知:Homebrew是無法根據(jù)命令行的選項(xiàng)來調(diào)整gcc版本的。那這是為什么呢?Homebrew到底是如何來指定C編譯器的呢?
?要理解清楚這個(gè)問題,阿柒決定嘗試先從Homebrew的架構(gòu)入手,現(xiàn)在讓我們一起來探究一下:怎樣才能實(shí)現(xiàn)系統(tǒng)的軟件包管理?
6.4 環(huán)境變量的引號問題
?一直追更的讀者可能會注意到:我在最開始寫配置命令行時(shí),指定C編譯器的寫法是這樣的:
CPPFLAGS="-I$(brew --prefix zlib)/include" \
LDFLAGS="-L$(brew --prefix zlib)/lib" \
CC="/usr/local/bin/gcc-12" \
LD="/usr/local/bin/gcc-12" \
CXX="/usr/local/bin/g++-12" \
./configure --with-openssl=$(brew --prefix openssl) --with-pydebug
?如果你原封不動(dòng)的復(fù)制到你的命令行中,你將會得到一個(gè)這樣“不死不活”的回應(yīng):
?
Part7:只道尋常
一、brew install --interactive到底干了什么!
二、開源軟件的發(fā)布
三、make與cmake
?相信很多小伙伴在軟件編譯安裝的過程中,也曾遇到過cmake這種編譯形式。那么cmake到底是什么呢?在什么情況下,我們要用cmake,又在什么情況下,我們該使用make呢?
?首先,要明確的一點(diǎn)是:cmake與make一樣,都是編譯源代碼的工具。但不同的是,cmake通過CMakeLists.txt文件,可以控制源代碼在不同平臺、不同編譯器下的Makefile,從而達(dá)到靈活控制編譯過程的目的。其編譯過程可表示為:
四、函數(shù)庫與速度提升
五、spwd模塊的用途與構(gòu)建
Part8:溫故知新
?這篇文章是阿柒寫過的技術(shù)類的文章中最認(rèn)真、也最詳盡的一篇。這是一個(gè)蠻有趣的過程(雖然中途不止一次想要放棄,也曾無數(shù)次誤入歧途??),至少它完整的包含了我從“I have an idea“到一步一步深入探索、求解并解決的全過程。這不僅是一次技術(shù)方面收獲與提升的過程,也是一次自己同自己對話、反思和進(jìn)步的過程,更是一次主動(dòng)記錄與分享的過程。正是通過這次嘗試,我發(fā)現(xiàn)了許多技術(shù)方面過去從未思考或者說是無暇思考過的盲區(qū),也想通了很多關(guān)于堅(jiān)持與熱愛、意義與價(jià)值等哲學(xué)命題,更發(fā)現(xiàn)了同自己對話、同只是留下了一個(gè)點(diǎn)擊量的你們對話的樂趣。
?很抱歉,我沒能將文章寫成一篇純技術(shù)類的文章,因?yàn)樵谖铱磥?,理解一?xiàng)技術(shù)實(shí)現(xiàn)的原理與思路,記錄學(xué)習(xí)過程中成功與失敗的經(jīng)驗(yàn),遠(yuǎn)比能夠按部就班的操作來得高級。另外,我也希望我的文章能夠啟發(fā)到一些人:掌握一項(xiàng)技術(shù),遠(yuǎn)不止作為一件謀生的工具那么簡單。你可能會在獨(dú)自探索的過程中收獲認(rèn)知的提升,發(fā)現(xiàn)一片更加廣闊的天地;你也可能會在堅(jiān)持還是放棄的糾結(jié)中發(fā)現(xiàn)自己的潛能,原來自己比想象中的聰明和堅(jiān)強(qiáng);當(dāng)然,你更有可能通過你的親身經(jīng)歷啟發(fā)并激勵(lì)到一些和你一樣對技術(shù)抱有熱忱、但實(shí)操一塌糊涂的小白們??,告訴他們:菜就去練,你的大腦比你想象的更加聰明!
?以上,就是我寫這篇文章甚至是這個(gè)系列的全部初衷了。未來我也將不斷的更新并且開新坑,歡迎喜歡這種風(fēng)格的朋友們持續(xù)關(guān)注我。此外,鑒于我自己這種邊寫邊更,不定時(shí)抽風(fēng)的奇葩風(fēng)格??我也會在每篇文章的溫故知新板塊設(shè)置更新日志,以便大家第一時(shí)間了解文章的更新動(dòng)態(tài)。最后感謝大家這半年來的信任和喜愛,best wishes 兔?? you~