将SCons用作distutils的构建引擎
我有一个Python包,里面有一些C语言代码,这些代码需要用来构建一个扩展(构建过程有点复杂)。我选择使用SCons作为我的构建系统,因为它非常好用且灵活。
我想找到一种方法,使用SCons来编译我的Python扩展,并且能够和distutils一起分发。我希望用户只需输入setup.py install,就能用SCons编译扩展,而不是使用默认的distutils构建工具。
我想到一个办法,就是重新定义distutils中的build_ext命令,但我找不到详细的文档来说明该怎么做。
有没有什么建议?
4 个回答
我用scons来生成一个setup.py文件。所以我创建了一个叫做setup.py.in的模板,然后用scons把这个模板扩展成setup.py。
这里有一些链接到我的项目,展示了这个过程:
这个过程会计算出一个包含键值对的字典,用来替换setup.py.in模板中的内容,从而生成setup.py。
所以最终用户需要做两件事:
scons setup.py
python setup.py install
注意:我的sconstruct部分有点乱,因为我写这个的时候已经有一段时间了,但它应该能展示这个概念。
如果你学会了如何编写合适的scons工具,那么这个过程可以简化成一个单一的目标,比如:
scons --pymod
这里有一个scons工具,可以用SWIG生成Python的包装器:
import SCons.Action
from SCons.Script import EnsureSConsVersion
SCons.Script.EnsureSConsVersion(0,96,92)
SwigGenAction = SCons.Action.Action('$SWIGGENCOM', '$SWIGGENCOMSTR')
def emitter(target, source, env):
"""
Add dependency from target to source
"""
env.Depends(target, source)
return target, source
def generate(env):
"""
Add builders and construction variables for the SwigGen builder.
"""
if 'SWIGCOM' not in env:
raise SystemError("SCons build environment could not detect tool: swig")
bld = env.Builder(
action = SwigGenAction,
emitter = emitter,
target_factory = env.fs.File)
env['BUILDERS']['SwigGen'] = bld
env['SWIGGENCOM'] = env['SWIGCOM']
def exists(env):
return env.Detect('swig')
现在从你的SConstruct文件中,你可以生成包装器代码:
foobar_cc = env.SwigGen("foobar_wrap.cc", "foobar.i")
如果你修改了foobar.i,它会重新生成foobar_wrap.cc。一旦你有了这个,你可以编写其他工具来实际执行python setup.py install,这样当提供--pymod时,它会构建Python模块。
查看这个页面:http://www.scons.org/wiki/PythonExtensions
我正在使用稍微修改过的版本来为Python构建pyrex-c扩展。
enscons包似乎是为了满足提问者的需求而设计的。你可以在这里找到一个使用它来构建带有C扩展的包的例子。
你可以有一个基本的包结构,比如:
pkgroot/
pyproject.toml
setup.py
SConstruct
README.md
pkgname/
__init__.py
pkgname.py
cfile.c
在这个结构中,pyproject.toml
文件可能看起来像这样:
[build-system]
requires = ["enscons"]
[tool.enscons]
name = "pkgname"
description = "My nice packahe"
version = "0.0.1"
author = "Me"
author_email = "me@me.com"
keywords = ["spam"]
url = "https://github.com/me/pkgname"
src_root = ""
packages = ["pkgname"]
其中,[tool.enscons]
部分包含了许多你在setuptools/distutils的setup
函数中熟悉的内容。从这里复制过来,setup.py
函数可能包含如下内容:
#!/usr/bin/env python
# Call enscons to emulate setup.py, installing if necessary.
import sys, subprocess, os.path
sys.path[0:0] = ['setup-requires']
try:
import enscons.setup
except ImportError:
requires = ["enscons"]
subprocess.check_call([sys.executable, "-m", "pip", "install",
"-t", "setup-requires"] + requires)
del sys.path_importer_cache['setup-requires'] # needed if setup-requires was absent
import enscons.setup
enscons.setup.setup()
最后,SConstruct
文件可能看起来像这样:
# Build pkgname
import sys, os
import pytoml as toml
import enscons, enscons.cpyext
metadata = dict(toml.load(open('pyproject.toml')))['tool']['enscons']
# most specific binary, non-manylinux1 tag should be at the top of this list
import wheel.pep425tags
full_tag = next(tag for tag in wheel.pep425tags.get_supported() if not 'manylinux' in tag)
env = Environment(tools=['default', 'packaging', enscons.generate, enscons.cpyext.generate],
PACKAGE_METADATA=metadata,
WHEEL_TAG=full_tag)
ext_filename = os.path.join('pkgname', 'libcfile')
extension = env.SharedLibrary(target=ext_filename,
source=['pkgname/cfile.c'])
py_source = Glob('pkgname/*.py')
platlib = env.Whl('platlib', py_source + extension, root='')
whl = env.WhlFile(source=platlib)
# Add automatic source files, plus any other needed files.
sdist_source=list(set(FindSourceFiles() +
['PKG-INFO', 'setup.py'] +
Glob('pkgname/*', exclude=['pkgname/*.os'])))
sdist = env.SDist(source=sdist_source)
env.Alias('sdist', sdist)
install = env.Command("#DUMMY", whl,
' '.join([sys.executable, '-m', 'pip', 'install', '--no-deps', '$SOURCE']))
env.Alias('install', install)
env.AlwaysBuild(install)
env.Default(whl, sdist)
完成这些后,你应该能够运行
sudo python setup.py install
来编译C扩展,构建一个wheel文件,并安装Python包,或者
python setup.py sdist
来构建一个源代码分发包。
我认为你基本上可以在SConstruct
文件中做任何你能用SCons做到的事情。