Lesson2:使用Homebrew安装Python3.6的艰难探索

观前提醒

如果你的“老板”此刻正在催你加班加点地去安装Python3.6,那么你可以直接跳到第5部分,在那里你将直接获得你所需要的、可操作的一切。但如果你目前的状态还OK,饭后还有时间来把游戏,那我强烈建议你一定要从头到尾,仔细阅读一遍这篇文章。因为通过这篇文章你将学到:

  • Python版本的选择;
  • 什么是License;
  • 替换编译器的一些方法;
  • 日志排查及代码修复;
  • Python3.6在Mac OS中的编译及安装;
  • 如何把编译过程写入HomeBrew Formula中;
  • 源码编译的逻辑;

前言:关于Python3.6的执念

 关于我为什么要在公元2022年,python3的版本已经更新到3.10,并且可以通过一行简单的brew install命令,完成了python3.10的自动安装的前提下,仍然坚持要去自己写一个关于python3.6的formula这件事,或许你可以在我上一篇的文章中找到一点端倪。但其实,更重要的原因是,我在无意间找到了这篇Homebrew formula的制作教程:https://docs.brew.sh/Formula-Cookbook。虽然我的确是一个菜鸟,但我想,经过这个过程,至少可以让我的“含菜量”变得稍微少一点。所以,现在就让我们一起把这个艰难的过程记录下来吧~

brew install python3    #python3.10安装命令

Part1:创建一个Ruby公式

 根据Homebrew官方文档中的说法,我们可以通过brew create指定安装源码的tar包的URL,从而完成一个安装公式的初步创建。那么具体到我们当前的问题,可以先找到一个大致的解决方向:即如何确定URL?
 这里阿柒提供一种“偷懒”的办法,别忘了,我们的电脑上可是已经安装过python3.10了呀!那么,通过3.10的brew安装脚本“学习”一下URL的地址是不是也是可以的呢🤗~通过查阅资料可知:Homebrew统一存放公式脚本的路径是:/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

 好的,接下来让我们用浏览器打开这个网址,看看我们要下载的python3.6到底在不在里面。
Index of /ftp/python截图

 好消息是,我们要下载的python3.6的确在里面;坏消息是,python3.6下又有很多版本,那么我们应该选择下载哪一个版本呢?

1.1 Python3.6的版本选择

 对于具体选择哪一个“小标”的python3.6版本,大家其实完全可以根据自己的需要自行确定。这里阿柒仅将在版本确定的过程中,查到的一些版本及对应的一些信息作一个简单的整理。另外,特别要说明的一点是:根据Python官网发布的声明:python3.6已经不在支持期内(End of support: 2021-12-23),这意味着Python官方将不会再根据任何使用过程中发现的问题对python3.6进行bug修改。因此,对于当前身处2022年的我们来说,我们要做好:使用python3.6的过程中,可能遇到各种“意想不到”的问题的心理准备。具体地我们将在Part2的安装过程中逐一展现。

版本号 备注
3.6.15 the final security bugfix release,同时也是目前win和mac系统中安装最多的版本。
3.6.8 the final bugfix release
3.6.5 存在明显缺陷:bpo-24658,并在3.6.8中进行了修复。

1.2 创建python3.6的安装脚本

 现在,我们终于可以通过指定的URL创建我们的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

时,不出意外地终端报错了😂于是,我就尝试按照doc文档中的提示,将我的create语句修改为:

brew create https://www.python.org/ftp/python/3.6.15/Python-3.6.15.tgz --set-name python@3.6

非常幸运的,这次成功了(虽然我也不是很清楚为什么,可能大概就是创建的formula脚本与已有的python@3.10.rb文件名之类的冲突了吧)!

1.3 补充脚本中的必填信息

 接下来就是补充formula脚本中的一些必填信息。参照python3.10.rb文件中的写法,我们可以非常轻松的确定,deschomepage的写法:

desc "latest version of PythonAT36, release 2021-09-03"
homepage "https://www.python.org/"

 稍微有点儿难度的是License,说实话,因为环境的原因,我一直不是很能理解老外们为啥弄个开源软件还要搞license。于是借着这个机会,对python的license做了个简单的研究,结论总结如下:
1.首先,代码作为一种创造性的作品是要受版权保护的。License就可以理解为用户获得使用软件的一种权利,是各位开源作者们对代码可以如何使用的一种说明。只要在规定的范围内,用户都可以对软件进行拷贝、修改以及再发布;
2.作为开源世界的根基,开源协议的意义更多地是体现在其之于经济、社会等方面。而对于技术学习阶段的“小白”来说,我们首先要树立起“版权”的意识——别人创做的代码,就像别人的东西一样,一定要获得别人的同意后再去使用,否则便是小偷。然后,在充分汲取各路大神们的智慧精华的过程中,积累沉淀,等有一天我们的技术达到一定的水平时,即使不能反哺开源世界,也请千万记得:不要去破坏开源协议中的要求。
3.关于Python3的License:了解了License的基本概念后,让我们现在把目光聚焦到Python3上。对于Python3.6.15来说,它遵守的License名称为PSF License Agreement,其内容包括:

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. This License Agreement will automatically terminate upon a material breach of its terms and conditions.
  7. 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.
  8. 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信息补充为:

license "PSF-2.0"

 其中,PSF-2.0是PSF License的SPDX identifierSPDX可以简单理解为一种针对通信软件(包括源代码)重要信息内容的标准化格式,用户可以单凭SPDX标识符,在SPDX许可表中检索出对应许可的全名、内容及URL等信息。

Part2:开始brew install吧~

 就在阿柒以为一切准备就绪,按照文档中的说法检查构建系统的时候,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相关的命令都失效的程度!!就好像我的电脑里完全没有装过Homebrew一样。没办法,我只好重新按照Lesson1中Homebrew的安装步骤,又重新安装了一遍,就一整个大无语!

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后,阿柒又不信邪地重新尝试了一次同样的命令,这次得到的结果如下:

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的问题吧。不过无论如何,这一关算是过掉啦,撒花🎉。这也告诉我们,在配置环境的过程中,失败是免不了的,所以大家一定不要轻言放弃,大不了就重头再来呗~当然,阿柒也会在自己的教程中,将自己踩坑的过程尽可能地展示给大家,希望能为大家的踩坑之路提供一点点参照~

2.1 踩坑一:The latest Apple CLang's releases being incompatible with CPython's Configure

 既然上述命令行不通,那么我们可以将其修改为:

brew install --build-bottle -vd python@3.6

 不要激动,菜鸟成长的经历告诉我们:世界上的事对小白来说,没有一帆风顺的。果不其然,在一路checking之后,报错如下:

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

 根据阿柒踩坑的经验,一般遇到这种报错,先不用急着去搜索答案。因为很有可能当你铺天盖地地去搜索了一番,也没有找到合适的回答,然后气急败坏地重新在命令行里运行了一遍和刚才一模一样的命令后,原来的报错就通过了!(别问我是怎么知道的,问就是用头发换的🧐)。所以,深呼吸,不要着急,让我们重新再去运行一次,3,2,1......
 还是一样的报错,好的,现在让我们把主场交给搜索引擎👩💻
 经过一番搜索风暴后,我们终于找到了产生问题的原因,这里阿柒直接引用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 .

 简单地说,出现上述问题的原因在于:对于MacOS Monterey 12.x来说,系统自带的编译器Apple Clang与Python源代码中的配置项相冲突。当然维护Python项目的大神们,早也通过各种渠道收到了这个问题的反馈,并已经完成了对尚在支持期内的3.7、3.8、3.9和3.10等版本的配置代码修改。这也启示我们:在安装Python时,为了避免意想不到的问题,最佳的思路自然是选择最新或尚在支持期内的版本。而对于已经结束支持的版本来说,如果你不是和阿柒一样想要挑战一下自己的话,那就真心祝你艺高人胆大,遇山开路,逢凶化吉~

2.1.1 解决方案

 既然找到了报错的原因,是出在系统自带的编译器Clang与源码编译配置上,那我们自然而然的可以想到:是不是换个编译器就能解决了呢?根据阿柒搜索到的结果来看,似乎这也确实是可行的。于是现在我们的问题就变成了:如何将Homebrew的编译器替换成gcc?
 首先,阿柒尝试了将系统的gcc --version修改为gcc。具体解决方案上,阿柒选择了在~/.bash_profile中创建alias的方法,比起调整环境变量$PATH加载路径的先后顺序(/usr/local/bin与/usr/bin)可能引起的系统多处环境变量的变化,创建别名的方法毫无疑问影响更小。关于环境变量的作用与设置,大家可以参照Lesson1中4.3节。本小节中创建别名的具体写法如下:

alias gcc='gcc-10'
alias cc='gcc-10'
alias g++='g++-10'
alias c++='c++-10'

 完成后,当我们在命令行中输入gcc --version时,会得到如下结果:

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.

 你以为问题到此就结束了吗?怎么可能?当我们再次尝试通过brew install --build-bottle -vd python@3.6进行安装时,终端依然显示同样的报错,而查看Homebrew编译环境时,我们可以清新地看到Homebrew使用的编译器依然是系统自带的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

 既然此路不通,那就让我们换个方向:如何指定Homebrew在编译安装过程中的gcc编译器。万幸的是,我们仅需要在安装公式formula中def install模块中指定此软件编译时的gcc路径,即可完成通过Homebrew进行编译时的编译器设置,具体代码可参照:

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')

 至此,当我们再去运行brew install --build-bottle -vd python@3.6命令时,我们不出意外地会看到软件编译成功了~(请注意,仅仅是编译成功了,距离我们最终的目标:成功安装软件并完成python@3.6.rb脚本的提交还有很长一段距离😂)。但是不管怎么样,我们又成功地搞定了一个问题,这里阿柒再为大家提供一种未经验证的Homebrew安装软件时,C编译器的指定方式,欢迎大家围观验证,更欢迎大家将你的发现以评论留言的方式告诉我。即通过在brew install命令前添加下列语句,指定编译器版本:

HOMEBREW_CC=gcc-12 HOMEBREW_CXX=g++-12 brew install --build-bottle -vd python@3.6

2.1.2 问题溯源

 除了上述解决方案外,阿柒觉得本次踩坑最大的收获是:学到了如何根据日志信息,来排查定位报错的原因。首先我们根据命令行的输出,可以确定此过程日志的存放位置:

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

 好的,现在让我们打开日志,看看这个报错:configure: error: internal configure error for the platform triplet具体是个什么样的问题。

1.问题定位

日志查看技巧

2.对症下药

2.2 踩坑二:Error: Empty installation

 阿柒想起小时侯看《西游记》的时候,有一句歌词特别应景:刚擒住了几个妖,又降住了几个魔,魑魅魍魉怎么它就这么多!就在我们终于编译成功的同时,我们的终端又来报错了:Error: Empty installation,详细信息如下:

/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>'

 根据报错信息,我们大体可以判断出出现上述错误的原因,即:安装formula中的模块没有成功执行build环节。于是,如何为我们脚本添加合理的操作代码,顺利完成代码build,就成了我们要在这个小节去完成的任务

2.2.1 如何在Formula脚本中添加build步骤

 “工欲善其事,必先利其器”,既然我们要在我们的脚本中添加build步骤,那么我们首先就要对C代码编译安装时的流程进行一定的了解。对于不熟悉C编译的同学,阿柒已经在本篇文章的第6.2节为大家整理出了Python源码的编译原理,大家可以自行学习。对于已经有了一定基础的同学,我们现在知道:从源码到可执行程序,大约需要配置(./configure)——构建(make)——安装(install)3个步骤,那么具体到Homebrew中,这3个步骤又是如何被设计和执行的呢?
 从前面的步骤中,我们不难看出:前面的一系列checking已经基本实现了源码编译的第一步:configure,那么接下来让我们检查一下距离生成Makefile文件还差什么条件——构建和安装的执行,这里参照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

接下来再让终端执行一下,看看结果:

zipimport.ZipImportError: can't decompress data; zlib not available

可以看到:此时源码已经在编译过程中了,那接下来,我们就可以在formula中指定一个zlib,看看又将发生什么?

uses_from_macos "zlib"

终端继续报错如下:

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.

根据搜索结果可知:之所以不断地产生上述报错,是因为Mac系统不再直接包括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/
当然,下载安装好了XQuartz还不够,因为我们的X11/Xlib.h文件是以XQuartz软件包的形式安装的,为了让其可以被其他的软件使用时,系统直接调用,我们还需为其设置一个软链接:

 ln -s /opt/X11/include/X11 /usr/local/include/X11

接下来,让我们继续执行formula install,看看我们的系统还缺什么吧:

zipimport.ZipImportError: can't decompress data; zlib not available

终端依然报错:zlib not available,不过好消息是:这次终于bulid finished successfully!其实当终端第一次出现此问题时,我们就应该去检查系统是否已经安装了zlib。通过检查/usr/local/opt路径下的zlib文件夹,我们可以看到系统并没有安装zlib, 因此首先我们需要通过Homebrew安装zlib,并且因为zlib是keg-only的,所以它并不会被直接链接到/usr/local。为了让编译器在编译的时候能成功找到zlib,我们还需要为其设置环境变量:

brew install zlib
export LDFLAGS="-L/usr/local/opt/zlib/lib"
export CPPFLAGS="-I/usr/local/opt/zlib/include"

结果还是不行!!至此,我们直接从brew install开始,似乎陷入了僵局,那么我们就去换个思路,从源码包的Readme入手,让我们看看距离成功install python3.6.15还差什么条件吧~

Part3:从Python源码中寻找答案~

 现在,让我们把目光聚焦到Python本身:我们真的知道应该如何在MacOS中编译安装Python了吗?反正看完了python@3.10.rb文件的490行代码的我,是对自己产生了深深的怀疑的~pip、wheel等package之于python安装的意义?多版本Python在系统中的管理?这些问题随着那490行的“天书”,着实让人摸不着头脑。那么,这一切的头绪在哪里呢?或许,我们可以在Python3.6.15的源码包中可以找到答案~

3.1 在MacOS下的编译尝试

 如果把HomeBrew想象成一个自动化软件安装的工具,那么抛开这个“工具”,手动安装Python3.6.15的过程应该是:1.配置环境(configure);2.源码编译(make);3.安装软件(make install)。在MacOS系统中,配置环境又可以分为:安装必要的系统组件——安装必要的软件——设置环境变量——执行编译。具体命令如下:

#1.为系统安装必要的Git、Make以及C编译器
xcode-select --install  

#2.安装python编译时必须的dependencies
brew install openssl xz zlib 

#3.设置必要的环境变量,并执行./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

 待上述命令执行完毕,我们可以看到在我们的Python源码文件夹下,生成了一个Makefile文件,以供后续自动编译Python的源码。
生成Makefile

 接下来,便可以对Python源码进行编译,具体命令如下:

make -j2 -s

 其中-j2表示以多线程同时进行编译,当然,如果你的电脑是4核甚至更高的配置,你还可以输入更高的数字;-s表示禁止输出Makefile文件的每一条命令,如果去掉,你将获得一个“疯狂”滚动的命令行窗口。待该过程执行成功,不出意外地,你将在源码文件夹中看到一个名为python.exe的可执行文件,通过在命令行中执行:./python.exe,你将看到熟悉的python命令窗口:

Python编译执行界面
生成python
 至此,我们已经基本完成了在MacOS下手动实现Python源码编译的过程🎉但不可否认的是,距离真正将python安装到系统目录中,我们还需解决两个至关重要的问题。

3.1.1 二进制文件的安装

3.1.2 一些编译失败的packages

 如果仔细阅读编译过程中的命令行输出,不难发现,一些packages没有被正确地构建,例如:_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   

 阿柒认为:与其现在研究清楚这些包究竟有何用途,不如了解当需要用到这些packages时,可以如何添加?这里阿柒以两个开发过程中本人使用过的包:_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'

 这里没有成功构建_sqlite3的原因有很多,例如系统没有安装sqlite或者为编译配置系统环境时未启用相关的选项等等。但一言以蔽之,发生这种情况最多的还是像我们这种毫无经验的python“小白”,懵懂的我们在试着编译时甚至不知道需要添加哪些必要的packages,而只是对着网络上一篇不知道何年何人写的教程“照猫画虎”,既不知其然更不知其所以然。而解决这个问题的方法也异常简单:即在保证系统正确安装了相关工具的前提下,反复修改配置(configure)环节的参数,注意:这里每重新生成一次Makefile,都意味着要重新构建(make)一遍源代码,不过好的一面是:这个过程可以帮助我们快速熟悉起来源码编译的主要过程,越编越熟练。比如:添加_sqlite3的过程就可以写做:

#1. 通过HomeBrew为系统安装sqlite
brew search sqlite3
brew install sqlite

#2. 添加配置参数,重新生成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 

 好消息是上述命令执行完毕后,_dbm和nis工具包一并被构建成功了(暂时忽略unrecognized options: --with-openssl的问题)!悲剧的是:我们需要的_sqlite3由于某些原因没有被成功构建,具体问题如下:

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

 产生上述问题的原因可以参考这篇提问的思路,简单地说:由于系统中sqlite3的编译时的配置参数问题,此处需要保证sqlite3中正确设置了ENABLE_LOAD_EXTENSION选项,查看系统中sqlite3版本及参数的命令如下:

#1. 查看sqlite3版本
$ sqlite3 --version
 3.37.0

#2.查看sqlite3编译选项
$ 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构建的过程中,正确调用系统中的sqlite3功能,则需要告诉操作系统编译时启用sqlite的LOAD_EXTENSION选项,并在相应环境变量中添加好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. 安装相应软件
brew install gdbm

#2.添加配置参数,生成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

 如果你一步不落地走到这里,相信你一定会在环境配置和编译的过程中发现类似以下的信息:unrecognized options: --with-openssl以及openssl 0x00000000 is too old for _hashlib。在解释清楚这个问题之前,我们需要先清楚:为了避免和系统自带的openssl冲突,我们通过Homebrew安装的openssl被命名为了openssl@3,但是这并不影响我们在编译时的使用,可以看到通过输入openssl,系统依然可以准确地找到相应的路径。

#1.查看系统中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虽然是最新的版本,但却不是大多数开源软件默认使用的版本。对于很多开源软件(包括cpython),其使用的openssl版本为1.1。当然了,实践证明:即使是使用openssl@3,在出现警告的前提下,也是能够正确导入sslhashlib的。但至少,指定openssl@1.1进行编译时,不会出现openssl 0x00000000 is too old for _hashlib的警告⚠️其中,openssl@1.1的路径可以通过如下命令进行指定:

$ brew --prefix openssl@1.1
/usr/local/opt/openssl@1.1

 最终,全部完成上述packages的正确编译,我们的配置参数命令行需要写做:

#1. 安装必要的软件
$ brew install bzip2 ncurses

#2.添加配置参数,生成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 

 这里值得特别注意的是ossaudiodevspwd这两个package。与其他packages的安装方式稍有不同的是:这两个package在HomeBrew上并没有直接的formula或者cask可以用。因此这里想要正确实现这两个内置module的编译和安装,阿柒的解决思路是:首先,了解python在构建过程中各内置module是如何被确定的;接下来,针对特定的ossaudiodevspwd模块,查看其构建所需的具体条件;最后,敲定这两个module的安装方式。于是,根据查阅到的资料可知:在Python构建的过程中,针对当前操作系统的module配置是写在setup.py的脚本中的。其中,ossaudiodev的配置条件为:host_platform.startwith(('linux', 'freebsd', 'gnufreebsd')),而通过阅读host_platform变量的取值逻辑并对比我们的系统实际,可以得到:当前的host_platform的取值为“darwin"。即:在MacOS下,python未能成功构建ossaudiodev module是符合python设计原则的,我们无需为此感到困惑。再来,根据spwd的配置条件,我们可以非常清楚的看到:只有当config_h_vars中存在“HAVE_GETSPNAM"或“HAVE_GETSPENT"且两常量至少有一个保证其值为True时,spwd module才能够进行配置。而查看我们当前系统的config_h_vars可知:上述两个常量的取值均为False。因此在构建过程中,spwd module未被成功构建也是可以理解的。至于我们应该如何将“HAVE_GETSPNAM"和“HAVE_GETSPENT"两个常量覆写为True,以及spwd的正确构建,阿柒这里将其放入到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 */"}

 我想这或许就是为什么当年多篇文章提到的:使用HomeBrew安装Python3.6有一些问题,因此更建议大家使用官方的安装程序进行安装的原因吧。果然是没有垃圾的苹果本,只有垃圾的**👀。当然了通过这个过程,阿柒也意识到了编译安装的过程中,环境变量的重要性(关于此部分的详细展开,可以在本文第6部分详解环境变量中找到答案)。另外本着刨根问底的精神,阿柒还为大家整理出了上述packages的用途,以便诸位小小白们自行参照选择。不过既然是Python源码中自带的工具包,阿柒还是觉得在条件允许的前提下尽量一劳永逸比较靠谱🤣,你觉得呢?

序号 名称 用途 场景
1 _sqlite SQLite是一个C库,它提供了一个轻量级的基于磁盘的数据库,不需要单独的服务器进程,并且允许使用SQL查询语言的非标准变体访问数据库。一些应用程序可以使用SQLite进行内部数据存储。也可以使用SQLite创建应用程序原型,然后将代码移植到更大的数据库,如PostgreSQL或Oracle。 详见
https://docs.
python.org
/3/library
/sqlite3.html
     
2 _dbm DBM数据库变体的通用接口 详见
https://docs.
python.org
/3.6/library
/dbm.html#module-dbm
     
3 _bz2 该模块提供了使用bzip2压缩算法压缩和解压缩数据的综合接口。 详见
https://docs.
python.org
/3.6/library
/bz2.html#module-bz2
     
4 _curses curses库为基于文本的终端,提供了独立于终端的屏幕绘制和键盘处理设施;curses模块提供了一个到curses库的接口,这是便携式高级终端处理的事实上的标准。 详见
https://docs.
python.org
/3.6/library
/curses.html#module-curses
     
5 _curses_panel 面板是具有深度特性的窗口,因此它们可以堆叠在彼此之上,并且只显示每个窗口的可见部分。可以添加面板,在堆栈中向上或向下移动,以及删除面板。 详见
https://docs.
python.org
/3.6/library
/curses.panel.html
#module-curses.panel
     
6 _gdbm 这个模块与dbm模块非常相似,但是使用GNU库gdbm来提供一些额外的功能。请注意dbm创建的文件格式。Gnu和dbm。NDBM不兼容。 详见
https://docs.
python.org
/3.6/library
/dbm.html#module-dbm.gnu
     
7 _ssl 该模块为客户端和服务器端网络套接字提供了对传输层安全(通常称为“安全套接字层”)加密和对等身份验证功能的访问。本模块使用OpenSSL库。 详见
https://docs.
python.org
/3.6/library
/ssl.html#module-ssl
     
8 nis nis模块为nis库提供了一个简单的包装器,对于多个主机的集中管理非常有用(可从NIS数据库中获取值)。由于NIS仅存在于Unix系统上,因此此模块仅适用于Unix。 详见
https://docs.
python.org
/3.6/library
/nis.html#module-nis
     
9 ossaudiodev 这个模块允许您访问OSS(开放声音系统)音频接口。OSS可用于广泛的开源和商业unix,并且是Linux和FreeBSD最新版本的标准音频接口。 详见
https://docs.
python.org
/3.6/library
/ossaudiodev.html
     
10 readline readline模块定义了许多函数,以方便从Python解释器完成和读取/写入历史文件。 详见
https://docs.
python.org
/3.6/library
/readline.html#module-readline
     
11 spwd 这个模块提供了对Unix shadow password数据库的访问。它可以在各种Unix版本上使用。您必须有足够的权限来访问影子密码数据库(这通常意味着您必须是根用户)。 详见
https://docs.
python.org
/3.6/library
/spwd.html
     
12 _hashlib 这个模块实现了许多不同的安全哈希和消息摘要算法的公共接口。 详见
https://docs.
python.org
/3.6/library
/hashlib.html#module-hashlib
     

 至此一套完整、可执行的Python3.6.15的手动编译流程便被完整的记录了下来。恭喜🎉,你又掌握了一项新技能。

3.2 把编译过程固定下来

 事实上,仔细对照python@3.10.rb中的一行行代码,不就是Python源码在各种操作系统下(MacOS、Linux)编译时的命令吗!那么,遵照HomeBrew Formula的书写规则,我们将上述编译命令写入Ruby脚本中,即可得到:

 执行上述脚本:

Part4:完成Formula脚本

Part5:总结与反思

 很开心能够以这种边学习边记录的形式,来完成个人的第一篇正式技术博客的创作。我在创作的过程中,一个最直观的体验就是:在技术探索的过程中,可能会遇到很多零零散散的小问题,如雪泥鸿爪,失不可追。唯有将这个过程完整地记录下来,再以整体的思路去回顾理解,方能形成一点思路。当然,这也为自己无论是技术探索还是文章创作提出了更高的要求,要在两种思路间自如的切换,并保持专注,着实不是一件容易的事😂幸运的是,经过不断的摸索尝试,我终于找到了适合自己的工作节奏和模式,现在就让我带大家一起总结一波使用HomeBrew安装Python3.6的最优过程吧~

5.4 手工编译的弊端以及HomeBrew的解决方案

 在手工编译的过程中,偶尔可能出现因系统软件安装不全,所导致的部分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'

 出现上述问题的原因是:此时系统没有安装bzip2和ncurses。当我们使用brew install安装上述软件后,即可正确编译上述packages了。但是,在通过HomeBrew Python@3.10的Formula执行安装的过程中,我们的系统即使没有安装对应的软件,也并不影响其对相关package的引用。例如,为了验证上述结论,我们可以通过brew remove命令,卸载预先安装的bzip2和ncurses,然后重新进行源码编译,并将结果与python@3.10进行对比。可以看到:在python@3.10中,bz2curses均被正常引用;而在我们自己编译的源码中,则可以清晰的看到bz2虽然有被正常的引用,但在引用curses时却不出意外地发生了报错。

>>> 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)

 要厘清楚这个问题,我们还是得从

==> 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

 就在我们反复尝试着对Python3.6的源码进行编译时,为了验证:当系统未安装相关软件时,会导致对应package缺失的结论,我发现之前我们安装的python@3.10只能通过特定的命令python3被调用😱具体过程就是:

#1.运行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.确认是否已安装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

 那么对于系统中不同版本的python,我们又该如何设置和管理,以保证能最便捷的调用到所需要的指定python版本呢?

6.2 Python源码编译原理

 不知道有没有和阿柒一样的小伙伴,虽然看过很多有关源码编译安装的文章,但每次遇到自己编译安装某个软件报错时,还是依旧手忙脚乱,不知该从何下手。因此,阿柒打算在这一部分对源码的编译原理来个详细的研究,争取彻底告别源码编译的烦恼。

6.2.1 为什么要用源码安装?

 请大家回忆一下自己是什么时候第一次接触到源码安装的呢?在阿柒的印象里,那是阿柒大四上半学期准备毕业设计的时候,需要在Ubuntu系统上安装一个Android虚拟机。由于整个过程90%都处于自己摸索的状态,实在过于痛苦和折磨,以至于多年后只要遇到自行源码安装的工作,内心仍万分忐忑。事过经年,阿柒依然费解:为什么在如今图形界面安装工具猖獗的今天,大部分软件仍然保留了源码安装这种费时费力、对初学者极不友好的安装方式?不过幸运的是,这次阿柒找到了答案。
 对于一切Unix、类Unix操作系统来说,其上运行的应用程序一般是用C语言来编写的,而为了便于传播,大多数开源软件都选择遵守了GPL协议。因此掌握了源码安装这项技能,从理论上,我们便拥有了在我们的操作系统上肆意安装使用一切开源软件的可能。所以,源码安装的真正意义恰恰在于:降低了软件使用者的安装门槛,让开源做到所见即所用。所以,让我们告别源码安装的PTSD,尽情享受源码安装的乐趣吧~

6.2.2 源码安装的过程

 知道了上面的原因后,现在让我们把目光聚焦到源码上来,看看一个个互相关联的源码文件是如何变成能够被计算机执行的软件的吧~
图1:源码编译安装

 为了方便展示,阿柒将这个过程以流程图的形式展现了出来,详见图一。对于一个准备好发布的软件源码来说,开发者还需要完成最后一步:即为软件的使用者提供一个可以安装的路径。而对于绝大多数的开源软件来说,这个路径被简化成了:配置(configure)——构建(make)——安装(make install)三个步骤。首先在配置环节,configure脚本需要准备系统中的构建环境,确保接下来构建和安装环节中的依赖能够被正确执行。具体地:configure脚本会根据安装包中自带的Makefile.in文件,生成一个适配于当前电脑环境的定制化的Makefile文件;接下来在构建环节,系统会根据Makefile文件对软件包中的源代码进行编译,转换为二进制文件;再到了安装环节,系统会将编译好的二进制文件等存放到指定的安装目录,以完成软件的安装(安装目录会在配置环节,通过--prefix=/install/path进行指定,并体现在生成的Makefile中)。值得注意的是:configure脚本和Makefile.in文件都是软件安装包中自带的,而Makefile则是软件安装过程中生成的中间文件,仅对当前系统环境有效。

Python3.6.15中的安装文件
 另一方面,对于软件的发布者来说,复杂的configure脚本和Makefile.in文件显然不可能是通过人工一行一行敲出来的。事实上,通过源码编译安装的软件通常都是通过一个叫做autotools的工具集来打包的,其中就包括了众所周知的autoconf、automake。以configure脚本的创建为例,发布者通过创建一个configure.ac的文件来描述软件配置过程中所需要做的事情。文件使用m4sh格式编写,代表m4宏命令和shell脚本的组合。autoconf软件会根据configure.ac中的代码,自动生成configure脚本。以Python3.6.15的软件包中configure.ac为例,我们可以看到发布者通过AC_INIT命令对autoconf进行了初始化,并对软件的基本信息(软件名称、版本号和维护者信息)进行了设置。至于其他命令的具体含义和用法,与本文要讨论的内容暂时无关,故阿柒将其放入只道寻常部分,欢迎感兴趣的小伙伴自行研究。

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工具自动生成的。而它的依据我们一般写在一个名为Makefile.am的脚本中。而对于软件的使用者来说,如何编写一个configure.ac或者Makefile.am脚本并不是他们所关心的部分,因此软件的发布者在进行软件发布时,只需要确定里面包含了configure脚本和Makefile.in文件,保证使用者能够正常安装即可,而无需发布configure.ac和Makefile.am。另外多提一点:我们也可以通过autotools来构建一个可以发布的软件包,具体操作为:

./configure #生成Makefile文件
make dist #构建可发布的软件包
make distcheck #测试软件包能否在各种操作系统上正常安装

 好了,至此关于软件编译安装的原理我们就全都理清了,最后再让我们一起回顾一下软件发布和安装使用到的命令吧~

发布 安装
1.aclocal #准备m4脚本环境
autoconf #将configure.ac转换为configure脚本
2.automake --add-missing #生成Makefile.in文件
3. ./configure #生成Makefile文件,用于构建可发布的软件包
4.make distcheck #构建软件包并测试
1. ./configure #生成Makefile文件,用于安装软件
2.make #编译源代码
3.make install #安装软件

6.3 Homebrew是如何指定C编译器的

 根据已经查到的资料可知:Homebrew是无法根据命令行的选项来调整gcc版本的。那这是为什么呢?Homebrew到底是如何来指定C编译器的呢?
 要理解清楚这个问题,阿柒决定尝试先从Homebrew的架构入手,现在让我们一起来探究一下:怎样才能实现系统的软件包管理?

6.4 环境变量的引号问题

 一直追更的读者可能会注意到:我在最开始写配置命令行时,指定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

 如果你原封不动的复制到你的命令行中,你将会得到一个这样“不死不活”的回应:

引号与环境变量
 于是,你认定阿柒和网上其他那些不负责任的文章一样,全无可信度!进而关闭浏览页面,发誓再也不看这个骗子的任何东西👀但很多时候,事情就是这样子的:当我们越是接近真相,越有可能因为一个小小的引号,而差之千里。而关注的重点,从来不是那个微不足道的“双引号”,而是其背后所代表着的我们与真实存在的差距。好了,现在让我们打起精神,好好看看这个问题吧。

Part7:只道寻常

一、brew install --interactive到底干了什么!

二、开源软件的发布

三、make与cmake

 相信很多小伙伴在软件编译安装的过程中,也曾遇到过cmake这种编译形式。那么cmake到底是什么呢?在什么情况下,我们要用cmake,又在什么情况下,我们该使用make呢?
 首先,要明确的一点是:cmake与make一样,都是编译源代码的工具。但不同的是,cmake通过CMakeLists.txt文件,可以控制源代码在不同平台、不同编译器下的Makefile,从而达到灵活控制编译过程的目的。其编译过程可表示为:

图2-cmake编译简图
当然,cmake的功能远不止如此,感兴趣的小伙伴可以自行探索一波。未完待续

四、函数库与速度提升

五、spwd模块的用途与构建

Part8:温故知新

 这篇文章是阿柒写过的技术类的文章中最认真、也最详尽的一篇。这是一个蛮有趣的过程(虽然中途不止一次想要放弃,也曾无数次误入歧途😂),至少它完整的包含了我从“I have an idea“到一步一步深入探索、求解并解决的全过程。这不仅是一次技术方面收获与提升的过程,也是一次自己同自己对话、反思和进步的过程,更是一次主动记录与分享的过程。正是通过这次尝试,我发现了许多技术方面过去从未思考或者说是无暇思考过的盲区,也想通了很多关于坚持与热爱、意义与价值等哲学命题,更发现了同自己对话、同只是留下了一个点击量的你们对话的乐趣。
 很抱歉,我没能将文章写成一篇纯技术类的文章,因为在我看来,理解一项技术实现的原理与思路,记录学习过程中成功与失败的经验,远比能够按部就班的操作来得高级。另外,我也希望我的文章能够启发到一些人:掌握一项技术,远不止作为一件谋生的工具那么简单。你可能会在独自探索的过程中收获认知的提升,发现一片更加广阔的天地;你也可能会在坚持还是放弃的纠结中发现自己的潜能,原来自己比想象中的聪明和坚强;当然,你更有可能通过你的亲身经历启发并激励到一些和你一样对技术抱有热忱、但实操一塌糊涂的小白们😂,告诉他们:菜就去练,你的大脑比你想象的更加聪明!
 以上,就是我写这篇文章甚至是这个系列的全部初衷了。未来我也将不断的更新并且开新坑,欢迎喜欢这种风格的朋友们持续关注我。此外,鉴于我自己这种边写边更,不定时抽风的奇葩风格😂我也会在每篇文章的温故知新板块设置更新日志,以便大家第一时间了解文章的更新动态。最后感谢大家这半年来的信任和喜爱,best wishes 兔🐰 you~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容