如何获取Distutils compile()函数使用的实际命令?
我有一个使用 Distutils 的 setup.py 脚本,它通过 new_compiler().compile() 来编译测试程序,以确保系统上某些功能(比如 MPI)可用。
我遇到的问题是,有一种情况调用 compile() 会出现编译错误,但手动编译同样的小测试程序却没有问题。错误出现在一个标准头文件(mpi.h)里。我的所有实际源文件也都包含这个头文件,但它们编译得很好!这个检查对我来说非常有用,但我需要弄清楚为什么它在不应该出错的情况下出错。
所以我想知道,如何才能获取 ccompiler.compile() 实际使用的命令?
3 个回答
另外,如果你想查看链接器的标志(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
这条信息虽然来得有点晚,但我发现了如何在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并添加这个功能,但那样工作量会非常大。
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)
在任何编译命令运行之前。这样一来,所有的编译命令(还有其他一些信息)就应该会打印到标准输出上。