注意: 虽然 JavaScript 对于本网站不是必需的,但您与内容的互动将受到限制。请开启 JavaScript 以获得完整的体验。

“扩展构建”会议纪要

“扩展构建被认为是痛苦的”:会议纪要

作者:Greg Ward

在 IPC7 举行的“扩展构建被认为是痛苦的”会议非常富有成效,与会者就所需内容、适用于各种用户类别的方法以及可以从其他相关系统(最接近的是 Red Hat 的 RPM 和 Perl 的 MakeMaker)借鉴的想法达成了良好共识。

已做出的决定

大家似乎都同意我提出的操作模式:用户下载一个包,解包,然后运行一个暂定名为 setup.py 的 Python 脚本(这个名字与 Python 发行版和许多大型模块发行版中的模块描述文件有所重叠,这是故意的,可以被视为一个 bug 或一个功能)。关于 setup.py 的参数以及运行时的行为,请参见下文。

此外,没有人不同意我的观点,即该系统在“扩展”(即用 C 编写的模块)或普通的“模块”(用 Python 编写)上应该以相同的用户界面工作。人们似乎也接受了将所有东西——C 模块、Python 模块,最终还有文档——“构建”到一个临时“构建库”目录的想法,暂定名为 blib/。(这是 Perl 的 MakeMaker 少数几个在会议中幸存下来的实现细节之一。)blib/ 目录至少有两个目的:它使安装几乎微不足道,并且它为运行测试脚本提供了一个看起来真实的伪安装树。blib/ 树的实际结构尚未确定(尽管我对它应该是什么样子有明确的想法!)

我们稍微讨论了术语。这将是一个棘手的问题,必须是我们将在 SIG 上解决的首批问题之一。首先,moduleextension module(简称 extension)和 package 将保留其传统的 Python 含义:一个旨在被其他模块或脚本导入的单个 .py 文件、一个用 C 编写的模块以及一个包含 __init__.py 文件的目录下分组的模块集合。

我建议使用 distribution(或在需要绝对清晰时使用 module distribution)来替代已经被占用的 package。一个模块发行版包含一个或多个模块(包括扩展模块)、它们的文档、测试套件以及一个 setup.py 文件。(文档和测试套件是可选的,但强烈推荐,特别是如果有人能想出一种标准的方法来文档化 Python 模块的话。但一个发行版必须至少有一个模块和一个 setup.py)。一个单独的发行版将向世界呈现多种面貌:开发者的源代码树、源代码发行版、各种二进制发行版、最终安装的产品等。(据我所知,源代码和二进制发行版是 Trove 将称之为 resources 的东西。)

我们没有时间决定的一个事情是这个傻东西的名字。对于一个长而正式的名字,我投票给 Module Distribution Utilities,简称 distutils。短名称将用于将所有各种模块分组到一个包中:例如,我们计划有 distutils.builddistutils.installdistutils.gen_make 等等。

setup.py 的作用

setup.py 的计划接口和任务在整个会议期间迅速演变,主要由 Eric Raymond 确定的劳动分工和 Greg Stein 清晰记录的,以及由???绘制并由我扩展的工作流程图(见下文)驱动。

显然,setup.py 必须包含 1) 有关包的元数据,以及 2) 为当前机器配置发行版所需的任何自定义代码。目前尚不清楚这些应该如何表达。第一个想法是借鉴 MakeMaker 的想法,调用一个带有一堆命名参数的函数,然后该函数在幕后完成所有工作。使用此方案的 setup.py 示例如下:

    #!/usr/bin/env python

    import distutils

    distutils.setup (name = 'mymod',
                     version_from = 'mymod.py',
                     pyfiles = ['mymod.py', 'othermod.py'],
                     cfiles = ['myext.c'])
    
显然,自定义配置代码将在调用 distutils.setup 之前或之后用 Python 编写;目前尚不清楚如何使此代码与 distutils.setup 背后的一切进行交互。

主要的竞争想法是以更面向对象的方式做事,通过定义一个子类并覆盖各种属性和方法。这是一个关于该概念的即兴说明:

    #!/usr/bin/env python

    from distutils import Setup

    class MySetup (Setup):
        name = 'mydist
        version_from = 'mymod.py'
        pyfiles = ['mymod.py', 'othermod.py']
        cfiles = ['myext.c']
    
在这种情况下,覆盖所有 distutils 类的特定行为会更清楚一些:只需根据需要子类化并覆盖即可。显然,所有类都必须有良好的文档!

模块分发的工作流程

下图说明了开发、打包、构建和安装模块分发所涉及的工作流程(但不包括测试或文档,这些最终也应是计划的一部分)。

Diagram of module distribution workflow

请注意图中出现的这三种人

开发者
基础源代码树的创建者/维护者
打包者
a) 某人(可能是开发者,戴着他的“打包者”帽子),他将基础源代码树转换为源代码分发;或 b) 某人(可能是开发者,也可能是一个拥有运行 X 机器的朋友,也可能是一个可以访问运行 X 机器的归档机器人),他构建源代码分发以创建架构 X 的“构建分发”
用户
那个可怜的家伙,他想在他的机器上安装分发;不一定是了解(或想了解)任何 Python 知识的人
(这些是 Eric Raymond 和 Greg Stein 确定的劳动分工。)还要注意,开发者、打包者和用户都面带微笑。此功能已计划,但尚未实施。

开发者工具

显然,工作流程从顶部的开发者源代码树开始。当开发者努力工作时,他可能需要一个了解如何构建 Python 模块和扩展模块(尤其是后者)的 Makefile。他无需自己编写,而是可以要求 setup.py 为他生成一个(大概使用 distutils.gen_make 模块)

    ./setup.py gen_make
    
然后开发者可以像他可能习惯做的那样运行 make, make test 等等(假设他是一个老式的 Unix 程序员!)。如果他不喜欢 Makefiles,或者因为这是一个微不足道的小项目而不需要一个,他可以直接要求 setup.py 构建、测试等。
    ./setup.py build
    ./setup.py test
    
(我们的想法是 setup.py 将支持“命令”——buildtest 等——这些命令与 Makefile 目标相对应。这样,没有人需要依赖 Makefile,但可以为开发者的方便和效率生成一个(尤其是在处理包含大量待编译扩展模块的大型发行版时)。)

打包者工具

当开发者对他的模块的当前状态满意并到了发布的时候,他戴上他的“打包者”帽子并创建了一个源代码发布

    make dist
    # or, equivalently
    ./setup.py dist
    
这会将发行版中的所有文件(如 MANIFEST 文件中列出的)打包成某种存档文件——可能在 Unix 下是 .tar.gz,在 Windows 下是 .zip 等。存档文件的名称将源自模块发行版的名称和版本:例如 mydist-1.2.3.tar.gz

如果他愿意,开发者可以就此止步,并将他的源代码发布上传到存档。或者,他可以为他可以访问的所有架构创建构建发行版。(请注意,我明确避免使用更熟悉的术语二进制发行版。这是因为模块发行版可能只包含 .py 文件及其相关文档。但即使在这些情况下,也有理由需要一个可以立即安装的可下载资源。主要原因是保持一致性:如果新手用户只需要处理一种文件来分发 Python 模块(例如,Red Hat Linux 用户可以下载并安装一批 RPM;无论这些 RPM 包含 .py 文件还是 .so 文件或两者都有,这都无关紧要),那就很好了。其次,可能有一些非二进制文件是从源代码发布中的文件生成的,例如从 SGML 源生成的 man 页面。Unix 平台的构建发行版可能包含已准备好安装的 man 页面,因此不需要进行文档处理。)

重要的是要强调打包者作为一个独立于开发者的概念。这对于支持多个平台的构建发行版是必要的,因为没有多少开发者能够访问多种 Unix 变体、Windows 和 Mac——他们可能需要一些帮助来为一个或多个平台制作构建发行版。这种帮助可能来自一个朋友(在走廊对面或世界各地)他确实可以访问特定平台;它可能来自一个自愿为某些平台保持某些发行版最新的人;或者它可能采取自动化此过程的存档机器人的形式。在遍历这个列表时,安全问题变得越来越重要。

我设想了几种可能的接口来创建构建发行版;此外,自开发者日以来,“哑”与“智能”构建发行版的想法一直在我的脑海中形成。(因此,它可能不属于这里,因为这是开发者日会议的摘要。所以起诉我吧。)首先,考虑创建一个传统的 Unix 构建发行版:一个要解包到 /usr/local 下的 .tar.gz 文件(或者,在 Python 库上下文中,更可能是 /usr/local/lib/python1.x)。这可以通过以下方式实现:

    ./setup.py bdist
    
这将进行一次构建(将模拟安装树放入 ./blib/),并将构建树打包到一个以发行版名称、版本号和当前平台命名的存档文件,例如 mydist-1.2.3-sunos5.tar.gzmydist-1.2.3-win32.zip

然而,人们对 Red Hat 的 RPM 等“智能”安装程序很感兴趣(我得到的印象是 Windows 世界有几个相互竞争的可能性——来自黑暗面的人必须向我补充这一点)。我目前的想法是,对于每个这些安装程序,都应该有一个单独的命令(或 Makefile 目标),因此你可以在 Red Hat Linux 机器上运行

    make rpm
    
在 Red Hat Linux 机器上,以及
    setup.py xxx
    
在 Windows 机器上(其中 xxx 是 Windows 某种智能安装程序的缩写名称)。然而,支持老式的“哑”构建发行版模型很重要——不是每个人都会拥有那个花哨的新安装程序(或者他们可能拥有不同的智能安装程序)。

用户工具

最后,也许最重要的是,我们必须考虑希望在他的计算机上安装 Python 模块发行版的幸运用户。用户形形色色,但我们主要关注两个区别:

  • 构建发行版用户:任何在流行平台上可获得(或必需:许多 Mac 和 Windows 用户没有编译器)构建发行版的用户
  • 源代码发行版用户:在不太流行的平台上,很可能可以获得编译器(以及其他可能必要的工具)的用户
显然,对于只想安装一些模块(可能是纯 Python,也可能是扩展——这无关紧要!)以使其他功能正常工作的初级用户来说,事情应该完全无痛且简单。像 RPM 这样的智能安装程序会有所帮助,但从“哑”构建发行版或源代码发行版开始也应该几乎同样容易。我们还必须记住,许多必须使用源代码发行版的人不一定是程序员,他们只是想让这个傻东西安装并工作——所以使用源代码发行版应该像使用构建发行版一样容易(尽管它会需要更多的机器时间和更多的命令)。即使是经验丰富的黑客,他们可以深入源代码并进行修改,或者修补 Makefile,或者提供所需库的位置,但他们很少愿意做这些事情。

下一步去哪里

首先,我认为这个话题足够大,值得一个新的 SIG,我暂定称之为 distutils-sig。该 {将要|已} 发布的章程已提交给 meta-sig,所以如果你认为整个概念是无望的,并且想在我开始之前就将我扼杀,或者如果你认为这个名字很糟糕,就去那里吧。

SIG 成立后,我想花一些时间讨论元问题:有人强烈反对这个想法吗?“distutils”这个名字足够好吗?第一阶段应该有哪些功能,以及完整发布需要哪些功能?然后我们就可以深入研究具体的设计和实现问题。