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

“扩展构建”会议总结

“扩展构建被认为很痛苦”:会议总结

作者:Greg Ward

在 IPC7 上举行的“扩展构建被认为很痛苦”会议非常富有成效,与会者对于需要什么、什么对各类用户有效,以及从其他相关系统(最接近的是 Red Hat 的 RPM 和 Perl 的 MakeMaker)借鉴哪些想法达成了很好的共识。

已做出的决定

似乎每个人都同意我提议的操作模式:用户下载一个软件包,解压它,并运行一个暂定名为 setup.py 的 Python 脚本(这个名称与 Python 发行版和许多大型模块发行版中的模块描述文件有些重叠,这是故意的,并且可以被解释为错误或功能)。请参见下文关于 setup.py 的参数以及运行时会发生什么。

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

我们稍微谈了一下术语。这将是一个棘手的问题,并且将成为我们在 SIG 上首先要解决的问题之一。首先,模块扩展模块(简称 扩展)和 将保留其传统的 Pythonic 含义:一个旨在被其他模块或脚本导入的单个 .py 文件、一个用 C 编写的模块以及一个包含 __init__.py 文件的目录下的模块集合。

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

我们没有时间决定的一件事是这个傻东西的名字。对于一个长而正式的名字,我投票支持 模块发行版实用程序,简称 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
    
然后,开发人员可以像他可能习惯做的那样运行 makemake test 等(假设他是一个老式的 Unix 书呆子!)。如果他不喜欢 Makefiles,或者因为这是一个很小的项目而不需要,他可以直接让 setup.py 构建、测试等。
    ./setup.py build
    ./setup.py test
    
(想法是 setup.py 将支持与 Makefile 目标相对应的“命令”——buildtest 等。这样,没有人必须依赖 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 构建发行版:一个 .tar.gz 文件,解压后放在 /usr/local 下(或者,在 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
    
并且在 Windows 机器上运行:
    setup.py xxx
    
(其中 xxx 是 Windows 智能安装程序的缩写名称)。支持老式的“哑”构建发行版模型也很重要 —— 不是每个人都会有那个花哨的新安装程序(或者他们可能使用不同的智能安装程序)。

用户实用程序

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

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

下一步去哪里

首先,我认为这个话题足够大,值得成立一个新的 sig,我暂时称之为 distutils-sig。该 sig 的拟议章程{将会|已经}发布到 meta-sig,所以如果你认为整个概念是毫无希望的,并且想在它开始之前就用火焰射杀我(或者如果你认为这个名字很烂),请去那里看看。

一旦 sig 建立起来,我想花一些时间讨论元问题:是否有人强烈反对整个想法?“distutils”是否是一个足够好的名字?第一版中应该具备哪些功能,而完整发布需要哪些功能?然后,我们可以深入研究具体的设计和实现问题。