Использование SCons в качестве движка сборки для distutils - PullRequest
7 голосов
/ 13 апреля 2010

У меня есть пакет на Python с кодом C, необходимым для создания расширения (с некоторыми нетривиальными требованиями к сборке). Я использовал SCons в качестве своей системы сборки, потому что она действительно хорошая и гибкая.

Я ищу способ компилировать мои расширения Python с помощью SCons, готовые к распространению с помощью distutils. Я хочу, чтобы пользователь просто набрал setup.py install и получил расширение, скомпилированное с помощью SCons, вместо стандартного движка сборки distutils.

Идея, которая приходит в голову, состоит в том, чтобы переопределить команду build_ext в distutils, но я не могу найти для нее обширную документацию.

Есть предложения?

Ответы [ 4 ]

2 голосов
/ 19 июля 2017

Пакет 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 и собрать колесо, и установить пакет python, или

python setup.py sdist

для создания исходного дистрибутива.

Я думаю, что вы можете делать все, что можете, используя SCons в файле SConstruct.

1 голос
/ 13 апреля 2010

см. Страницу: http://www.scons.org/wiki/PythonExtensions

Я использую слегка модифицированную версию для сборки расширений pyrex-c для python.

0 голосов
/ 19 декабря 2013

Решение состоит в том, чтобы предоставить пользовательский cmdclass, полученный из distutils.cmd.Commmand, который собирает модуль так, как вы хотите:

import distutils.cmd

class build_py_cmd(distutils.cmd.Command):
    def initialize_options(self):
        pass

    def finalize_options(self):
        pass
    def run(self):
        print("Calling SCons to build the module")
        pbs.scons()


setup(name = 'riak3k',
      packages = ['riak3k'],
      package_dir = {'': 'build/release'},
      cmdclass = {'build_py': build_py_cmd},
0 голосов
/ 22 августа 2013

Я использую scons для создания файла setup.py. Поэтому я создал шаблон с именем setup.py.in и использовал scons, чтобы развернуть шаблон для генерации setup.py.

Вот несколько ссылок на мой проект, который делает это:

setup.py.in template

SConstruct

Это вычисляет словарь пар ключ-значение для замены в шаблоне setup.py.in для создания setup.py.

Таким образом, конечный пользователь делает две вещи:

scons setup.py
python setup.py install

Предупреждение: мой материал sconstruct немного грязный, как я писал это недавно, но он должен продемонстрировать концепцию.

Если вы научитесь писать правильные инструменты scons, то это может быть преобразовано в одну цель, например:

scons --pymod

Вот инструмент scons, который генерирует оболочку Python с SWIG:

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 для вас, поэтому, если указан параметр --pymod, он создаст модуль python.

...