Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using SCons as a build engine for distutils

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?

like image 623
pygabriel Avatar asked Apr 13 '10 07:04

pygabriel


1 Answers

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.

like image 166
Matt Pitkin Avatar answered Sep 21 '22 19:09

Matt Pitkin