如何获取Distutils compile()函数使用的实际命令?

4 投票
3 回答
2070 浏览
提问于 2025-04-17 05:09

我有一个使用 Distutils 的 setup.py 脚本,它通过 new_compiler().compile() 来编译测试程序,以确保系统上某些功能(比如 MPI)可用。

我遇到的问题是,有一种情况调用 compile() 会出现编译错误,但手动编译同样的小测试程序却没有问题。错误出现在一个标准头文件(mpi.h)里。我的所有实际源文件也都包含这个头文件,但它们编译得很好!这个检查对我来说非常有用,但我需要弄清楚为什么它在不应该出错的情况下出错。

所以我想知道,如何才能获取 ccompiler.compile() 实际使用的命令?

3 个回答

1

另外,如果你想查看链接器的标志(linker flags),可以这样做:

import distutils.sysconfig
import distutils.ccompiler
compiler = distutils.ccompiler.new_compiler()
distutils.sysconfig.customize_compiler(compiler)
print compiler.linker_so   # This gives linker information
3

这条信息虽然来得有点晚,但我发现了如何在Python中做到这一点(不需要查看日志),如果有人感兴趣的话:

import distutils.sysconfig
import distutils.ccompiler
compiler = distutils.ccompiler.new_compiler()
distutils.sysconfig.customize_compiler(compiler)
print compiler.compiler_so   # This attribute is what you want

“compiler_so”这个属性是一个列表,里面包含了distutils在编译某些东西时会用到的所有参数。当它真正开始编译时,会在这个列表中加上文件名和-c(用于生成目标文件)。

补充说明:我只在macOS和Linux上测试过,似乎在Windows上不太适用。

再补充一下,这个列表并不是完整的命令,而只是distutils在处理任何Extension()实例之前的参数。剩下的参数是特定于扩展的,取决于你在创建Extension类时提供的参数,比如sources、include_dirs和define_macros。

如果你想要distutils运行的完整原始命令,我知道的唯一方法(不需要解析日志)是,在所有处理完成后、在调用spawn函数之前,抓取命令字符串。这里有一个非常“黑科技”的方法可以做到:

# Put this at the very top of all your imports 
import distutils.spawn
old_spawn = distutils.spawn.spawn
def my_spawn(*args, **kwargs):
    print " ".join(args[0]) # <-- this is your command right here 
    old_spawn(*args, **kwargs)
distutils.spawn.spawn = my_spawn

更优雅的做法是分叉distutils并添加这个功能,但那样工作量会非常大。

3

Adam (Wagner) 的评论是个不错的起点:你需要仔细查看 Distutils 的源代码。里面有很多抽象的层次,所以你得在几个不同的文件中追踪执行过程,但大致的意思是这样的:

  • distutils.ccompiler 包含一个抽象类 CCompiler,它负责调用实际的编译器。这个类有一些方法,用于编译器需要执行的各种任务,比如 preprocess(预处理)、compile(编译)和 link(链接)。不过,CCompiler 本身并没有实现这些方法,除了 compile,但即使在这里,它也把实际的编译器调用委托给了 _compile 方法。所以你需要查看你平台上使用的 CCompiler 的子类,并看看它是怎么实现 _compile 的。
  • 有几个不同的 CCompiler 子类,每个子类都在自己的包里实现,但最主要的两个是 distutils.unixccompiler.UnixCCompiler(在类 UNIX 系统上调用本地编译器)和 distutils.msvccompiler.MSVCCompiler(调用 Visual Studio)。这两个都使用 distutils.spawn.spawn 函数来实际运行外部进程。
  • distutils.spawn.spawn 的源代码中,你会注意到每个命令在运行之前都会以 INFO 级别进行记录。但这里有个问题,这并不是使用 Python 内置的日志系统,而是使用了 Distutils 自定义的日志记录器,这个记录器在 distutils.log 中实现。
  • 如果你现在查看 distutils.log 的源代码,你会看到有一个函数 set_verbosity,它用来设置日志记录的级别。不幸的是,这个函数并没有和 Distutils 中的其他调试基础设施(比如 DISTUTILS_DEBUG 环境变量)关联,所以你需要在你的设置脚本中手动调用

    distutils.log.set_verbosity(1)
    

    在任何编译命令运行之前。这样一来,所有的编译命令(还有其他一些信息)就应该会打印到标准输出上。

撰写回答