I have a python package with some C code needed to build an extension (with some non-trivial building needs). I have used SCons as my build system because it's really good and flexible.
I'm looking for a way to compile my python extensions with SCons ready to be distributed with distutils. I want that the user simply types setup.py install and get the extension compiled with SCons instead of the default distutils build engine.
An idea that comes to mind is to redefine build_ext command in distutils, but I can't find extensive documentation for it.
Any suggestion?
The enscons package seems to be designed to do what the question asked. An example of using it to build a package with C extensions is here.
You could have a basic package structure like:
pkgroot/
pyproject.toml
setup.py
SConstruct
README.md
pkgname/
__init__.py
pkgname.py
cfile.c
In this the pyproject.toml
file could look something like:
[build-system]
requires = ["enscons"]
[tool.enscons]
name = "pkgname"
description = "My nice packahe"
version = "0.0.1"
author = "Me"
author_email = "[email protected]"
keywords = ["spam"]
url = "https://github.com/me/pkgname"
src_root = ""
packages = ["pkgname"]
where the [tool.enscons]
section contains many things familiar to the setuptools/distutils setup
functions. Copying from here, the setup.py
function could contain something like:
#!/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()
Finally, the SConstruct
file could look something like:
# 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)
After this you should be able to run
sudo python setup.py install
to compile the C extension and build a wheel, and install the python package, or
python setup.py sdist
to build a source distribution.
I think you can basically do anything you could with SCons in the SConstruct
file though.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With