Merge pull request #1091 from mayeut/abi3

feature: add support for ABI3 wheels
This commit is contained in:
Joe Rickerby
2022-04-29 14:31:27 +01:00
committed by GitHub
8 changed files with 345 additions and 201 deletions
+79 -63
View File
@@ -12,6 +12,7 @@ from .typing import OrderedDict, PathOrStr, assert_never
from .util import ( from .util import (
BuildSelector, BuildSelector,
NonPlatformWheelError, NonPlatformWheelError,
find_compatible_abi3_wheel,
get_build_verbosity_extra_flags, get_build_verbosity_extra_flags,
prepare_command, prepare_command,
read_python_configs, read_python_configs,
@@ -132,6 +133,8 @@ def build_on_docker(
) )
docker.call(["sh", "-c", before_all_prepared], env=env) docker.call(["sh", "-c", before_all_prepared], env=env)
built_wheels: List[PurePath] = []
for config in platform_configs: for config in platform_configs:
log.build_start(config.identifier) log.build_start(config.identifier)
build_options = options.build_options(config.identifier) build_options = options.build_options(config.identifier)
@@ -174,74 +177,83 @@ def build_on_docker(
) )
sys.exit(1) sys.exit(1)
if build_options.before_build: abi3_wheel = find_compatible_abi3_wheel(built_wheels, config.identifier)
log.step("Running before_build...") if abi3_wheel:
before_build_prepared = prepare_command( log.step_end()
build_options.before_build, print(
project=container_project_path, f"\nFound previously built wheel {abi3_wheel.name}, that's compatible with {config.identifier}. Skipping build step..."
package=container_package_dir,
)
docker.call(["sh", "-c", before_build_prepared], env=env)
log.step("Building wheel...")
temp_dir = PurePath("/tmp/cibuildwheel")
built_wheel_dir = temp_dir / "built_wheel"
docker.call(["rm", "-rf", built_wheel_dir])
docker.call(["mkdir", "-p", built_wheel_dir])
verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity)
if build_options.build_frontend == "pip":
docker.call(
[
"python",
"-m",
"pip",
"wheel",
container_package_dir,
f"--wheel-dir={built_wheel_dir}",
"--no-deps",
*verbosity_flags,
],
env=env,
)
elif build_options.build_frontend == "build":
config_setting = " ".join(verbosity_flags)
docker.call(
[
"python",
"-m",
"build",
container_package_dir,
"--wheel",
f"--outdir={built_wheel_dir}",
f"--config-setting={config_setting}",
],
env=env,
) )
repaired_wheels = [abi3_wheel]
else: else:
assert_never(build_options.build_frontend)
built_wheel = docker.glob(built_wheel_dir, "*.whl")[0] if build_options.before_build:
log.step("Running before_build...")
before_build_prepared = prepare_command(
build_options.before_build,
project=container_project_path,
package=container_package_dir,
)
docker.call(["sh", "-c", before_build_prepared], env=env)
repaired_wheel_dir = temp_dir / "repaired_wheel" log.step("Building wheel...")
docker.call(["rm", "-rf", repaired_wheel_dir])
docker.call(["mkdir", "-p", repaired_wheel_dir])
if built_wheel.name.endswith("none-any.whl"): temp_dir = PurePath("/tmp/cibuildwheel")
raise NonPlatformWheelError() built_wheel_dir = temp_dir / "built_wheel"
docker.call(["rm", "-rf", built_wheel_dir])
docker.call(["mkdir", "-p", built_wheel_dir])
if build_options.repair_command: verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity)
log.step("Repairing wheel...")
repair_command_prepared = prepare_command(
build_options.repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir
)
docker.call(["sh", "-c", repair_command_prepared], env=env)
else:
docker.call(["mv", built_wheel, repaired_wheel_dir])
repaired_wheels = docker.glob(repaired_wheel_dir, "*.whl") if build_options.build_frontend == "pip":
docker.call(
[
"python",
"-m",
"pip",
"wheel",
container_package_dir,
f"--wheel-dir={built_wheel_dir}",
"--no-deps",
*verbosity_flags,
],
env=env,
)
elif build_options.build_frontend == "build":
config_setting = " ".join(verbosity_flags)
docker.call(
[
"python",
"-m",
"build",
container_package_dir,
"--wheel",
f"--outdir={built_wheel_dir}",
f"--config-setting={config_setting}",
],
env=env,
)
else:
assert_never(build_options.build_frontend)
built_wheel = docker.glob(built_wheel_dir, "*.whl")[0]
repaired_wheel_dir = temp_dir / "repaired_wheel"
docker.call(["rm", "-rf", repaired_wheel_dir])
docker.call(["mkdir", "-p", repaired_wheel_dir])
if built_wheel.name.endswith("none-any.whl"):
raise NonPlatformWheelError()
if build_options.repair_command:
log.step("Repairing wheel...")
repair_command_prepared = prepare_command(
build_options.repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir
)
docker.call(["sh", "-c", repair_command_prepared], env=env)
else:
docker.call(["mv", built_wheel, repaired_wheel_dir])
repaired_wheels = docker.glob(repaired_wheel_dir, "*.whl")
if build_options.test_command and build_options.test_selector(config.identifier): if build_options.test_command and build_options.test_selector(config.identifier):
log.step("Testing wheel...") log.step("Testing wheel...")
@@ -292,8 +304,12 @@ def build_on_docker(
docker.call(["rm", "-rf", venv_dir]) docker.call(["rm", "-rf", venv_dir])
# move repaired wheels to output # move repaired wheels to output
docker.call(["mkdir", "-p", container_output_dir]) if abi3_wheel is None:
docker.call(["mv", *repaired_wheels, container_output_dir]) docker.call(["mkdir", "-p", container_output_dir])
docker.call(["mv", *repaired_wheels, container_output_dir])
built_wheels.extend(
container_output_dir / repaired_wheel.name for repaired_wheel in repaired_wheels
)
log.build_end() log.build_end()
+85 -70
View File
@@ -23,6 +23,7 @@ from .util import (
call, call,
detect_ci_provider, detect_ci_provider,
download, download,
find_compatible_abi3_wheel,
get_build_verbosity_extra_flags, get_build_verbosity_extra_flags,
get_pip_version, get_pip_version,
install_certifi_script, install_certifi_script,
@@ -291,6 +292,8 @@ def build(options: Options, tmp_path: Path) -> None:
) )
shell(before_all_prepared, env=env) shell(before_all_prepared, env=env)
built_wheels: List[Path] = []
for config in python_configurations: for config in python_configurations:
build_options = options.build_options(config.identifier) build_options = options.build_options(config.identifier)
log.build_start(config.identifier) log.build_start(config.identifier)
@@ -318,84 +321,94 @@ def build(options: Options, tmp_path: Path) -> None:
build_options.build_frontend, build_options.build_frontend,
) )
if build_options.before_build: abi3_wheel = find_compatible_abi3_wheel(built_wheels, config.identifier)
log.step("Running before_build...") if abi3_wheel:
before_build_prepared = prepare_command( log.step_end()
build_options.before_build, project=".", package=build_options.package_dir print(
f"\nFound previously built wheel {abi3_wheel.name}, that's compatible with {config.identifier}. Skipping build step..."
) )
shell(before_build_prepared, env=env) repaired_wheel = abi3_wheel
else:
log.step("Building wheel...") if build_options.before_build:
built_wheel_dir.mkdir() log.step("Running before_build...")
before_build_prepared = prepare_command(
verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity) build_options.before_build, project=".", package=build_options.package_dir
if build_options.build_frontend == "pip":
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
# see https://github.com/pypa/cibuildwheel/pull/369
call(
"python",
"-m",
"pip",
"wheel",
build_options.package_dir.resolve(),
f"--wheel-dir={built_wheel_dir}",
"--no-deps",
*verbosity_flags,
env=env,
)
elif build_options.build_frontend == "build":
config_setting = " ".join(verbosity_flags)
build_env = env.copy()
if build_options.dependency_constraints:
constraint_path = build_options.dependency_constraints.get_for_python_version(
config.version
) )
build_env["PIP_CONSTRAINT"] = constraint_path.as_uri() shell(before_build_prepared, env=env)
build_env["VIRTUALENV_PIP"] = get_pip_version(env)
call(
"python",
"-m",
"build",
build_options.package_dir,
"--wheel",
f"--outdir={built_wheel_dir}",
f"--config-setting={config_setting}",
env=build_env,
)
else:
assert_never(build_options.build_frontend)
built_wheel = next(built_wheel_dir.glob("*.whl")) log.step("Building wheel...")
built_wheel_dir.mkdir()
repaired_wheel_dir.mkdir() verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity)
if built_wheel.name.endswith("none-any.whl"): if build_options.build_frontend == "pip":
raise NonPlatformWheelError() # Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
# see https://github.com/pypa/cibuildwheel/pull/369
if build_options.repair_command: call(
log.step("Repairing wheel...") "python",
"-m",
if config_is_universal2: "pip",
delocate_archs = "x86_64,arm64" "wheel",
elif config_is_arm64: build_options.package_dir.resolve(),
delocate_archs = "arm64" f"--wheel-dir={built_wheel_dir}",
"--no-deps",
*verbosity_flags,
env=env,
)
elif build_options.build_frontend == "build":
config_setting = " ".join(verbosity_flags)
build_env = env.copy()
if build_options.dependency_constraints:
constraint_path = (
build_options.dependency_constraints.get_for_python_version(
config.version
)
)
build_env["PIP_CONSTRAINT"] = constraint_path.as_uri()
build_env["VIRTUALENV_PIP"] = get_pip_version(env)
call(
"python",
"-m",
"build",
build_options.package_dir,
"--wheel",
f"--outdir={built_wheel_dir}",
f"--config-setting={config_setting}",
env=build_env,
)
else: else:
delocate_archs = "x86_64" assert_never(build_options.build_frontend)
repair_command_prepared = prepare_command( built_wheel = next(built_wheel_dir.glob("*.whl"))
build_options.repair_command,
wheel=built_wheel,
dest_dir=repaired_wheel_dir,
delocate_archs=delocate_archs,
)
shell(repair_command_prepared, env=env)
else:
shutil.move(str(built_wheel), repaired_wheel_dir)
repaired_wheel = next(repaired_wheel_dir.glob("*.whl")) repaired_wheel_dir.mkdir()
log.step_end() if built_wheel.name.endswith("none-any.whl"):
raise NonPlatformWheelError()
if build_options.repair_command:
log.step("Repairing wheel...")
if config_is_universal2:
delocate_archs = "x86_64,arm64"
elif config_is_arm64:
delocate_archs = "arm64"
else:
delocate_archs = "x86_64"
repair_command_prepared = prepare_command(
build_options.repair_command,
wheel=built_wheel,
dest_dir=repaired_wheel_dir,
delocate_archs=delocate_archs,
)
shell(repair_command_prepared, env=env)
else:
shutil.move(str(built_wheel), repaired_wheel_dir)
repaired_wheel = next(repaired_wheel_dir.glob("*.whl"))
log.step_end()
if build_options.test_command and build_options.test_selector(config.identifier): if build_options.test_command and build_options.test_selector(config.identifier):
machine_arch = platform.machine() machine_arch = platform.machine()
@@ -521,7 +534,9 @@ def build(options: Options, tmp_path: Path) -> None:
) )
# we're all done here; move it to output (overwrite existing) # we're all done here; move it to output (overwrite existing)
shutil.move(str(repaired_wheel), build_options.output_dir) if abi3_wheel is None:
shutil.move(str(repaired_wheel), build_options.output_dir)
built_wheels.append(build_options.output_dir / repaired_wheel.name)
# clean up # clean up
shutil.rmtree(identifier_tmp_dir) shutil.rmtree(identifier_tmp_dir)
+40 -1
View File
@@ -13,7 +13,7 @@ import time
import urllib.request import urllib.request
from enum import Enum from enum import Enum
from functools import lru_cache from functools import lru_cache
from pathlib import Path from pathlib import Path, PurePath
from time import sleep from time import sleep
from typing import ( from typing import (
Any, Any,
@@ -26,6 +26,7 @@ from typing import (
Optional, Optional,
Sequence, Sequence,
TextIO, TextIO,
TypeVar,
Union, Union,
cast, cast,
overload, overload,
@@ -42,6 +43,7 @@ else:
from filelock import FileLock from filelock import FileLock
from packaging.requirements import InvalidRequirement, Requirement from packaging.requirements import InvalidRequirement, Requirement
from packaging.specifiers import SpecifierSet from packaging.specifiers import SpecifierSet
from packaging.utils import parse_wheel_filename
from packaging.version import Version from packaging.version import Version
from platformdirs import user_cache_path from platformdirs import user_cache_path
@@ -52,6 +54,7 @@ __all__ = [
"MANYLINUX_ARCHS", "MANYLINUX_ARCHS",
"call", "call",
"shell", "shell",
"find_compatible_abi3_wheel",
"format_safe", "format_safe",
"prepare_command", "prepare_command",
"get_build_verbosity_extra_flags", "get_build_verbosity_extra_flags",
@@ -568,6 +571,42 @@ def virtualenv(
return env return env
T = TypeVar("T", bound=PurePath)
def find_compatible_abi3_wheel(wheels: Sequence[T], identifier: str) -> Optional[T]:
"""
Finds an ABI3 wheel in `wheels` compatible with the Python interpreter
specified by `identifier`.
"""
interpreter, platform = identifier.split("-")
if not interpreter.startswith("cp3"):
return None
for wheel in wheels:
_, _, _, tags = parse_wheel_filename(wheel.name)
for tag in tags:
if tag.abi != "abi3":
continue
if not tag.interpreter.startswith("cp3"):
continue
if int(tag.interpreter[3:]) > int(interpreter[3:]):
continue
if platform.startswith(("manylinux", "musllinux", "macosx")):
# Linux, macOS
os_, arch = platform.split("_", 1)
if not tag.platform.startswith(os_):
continue
if not tag.platform.endswith("_" + arch):
continue
else:
# Windows
if not tag.platform == platform:
continue
return wheel
return None
if sys.version_info >= (3, 8): if sys.version_info >= (3, 8):
from functools import cached_property from functools import cached_property
else: else:
+81 -66
View File
@@ -22,6 +22,7 @@ from .util import (
NonPlatformWheelError, NonPlatformWheelError,
call, call,
download, download,
find_compatible_abi3_wheel,
get_build_verbosity_extra_flags, get_build_verbosity_extra_flags,
get_pip_version, get_pip_version,
prepare_command, prepare_command,
@@ -249,6 +250,8 @@ def build(options: Options, tmp_path: Path) -> None:
) )
shell(before_all_prepared, env=env) shell(before_all_prepared, env=env)
built_wheels: List[Path] = []
for config in python_configurations: for config in python_configurations:
build_options = options.build_options(config.identifier) build_options = options.build_options(config.identifier)
log.build_start(config.identifier) log.build_start(config.identifier)
@@ -274,83 +277,93 @@ def build(options: Options, tmp_path: Path) -> None:
build_options.build_frontend, build_options.build_frontend,
) )
# run the before_build command abi3_wheel = find_compatible_abi3_wheel(built_wheels, config.identifier)
if build_options.before_build: if abi3_wheel:
log.step("Running before_build...") log.step_end()
before_build_prepared = prepare_command( print(
build_options.before_build, project=".", package=options.globals.package_dir f"\nFound previously built wheel {abi3_wheel.name}, that's compatible with {config.identifier}. Skipping build step..."
) )
shell(before_build_prepared, env=env) repaired_wheel = abi3_wheel
else:
log.step("Building wheel...") # run the before_build command
built_wheel_dir.mkdir() if build_options.before_build:
log.step("Running before_build...")
verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity) before_build_prepared = prepare_command(
build_options.before_build, project=".", package=options.globals.package_dir
if build_options.build_frontend == "pip":
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
# see https://github.com/pypa/cibuildwheel/pull/369
call(
"python",
"-m",
"pip",
"wheel",
options.globals.package_dir.resolve(),
f"--wheel-dir={built_wheel_dir}",
"--no-deps",
*get_build_verbosity_extra_flags(build_options.build_verbosity),
env=env,
)
elif build_options.build_frontend == "build":
config_setting = " ".join(verbosity_flags)
build_env = env.copy()
if build_options.dependency_constraints:
constraints_path = build_options.dependency_constraints.get_for_python_version(
config.version
) )
# Bug in pip <= 21.1.3 - we can't have a space in the shell(before_build_prepared, env=env)
# constraints file, and pip doesn't support drive letters
# in uhi. After probably pip 21.2, we can use uri. For
# now, use a temporary file.
if " " in str(constraints_path):
assert " " not in str(identifier_tmp_dir)
tmp_file = identifier_tmp_dir / "constraints.txt"
tmp_file.write_bytes(constraints_path.read_bytes())
constraints_path = tmp_file
build_env["PIP_CONSTRAINT"] = str(constraints_path) log.step("Building wheel...")
build_env["VIRTUALENV_PIP"] = get_pip_version(env) built_wheel_dir.mkdir()
verbosity_flags = get_build_verbosity_extra_flags(build_options.build_verbosity)
if build_options.build_frontend == "pip":
# Path.resolve() is needed. Without it pip wheel may try to fetch package from pypi.org
# see https://github.com/pypa/cibuildwheel/pull/369
call( call(
"python", "python",
"-m", "-m",
"build", "pip",
build_options.package_dir, "wheel",
"--wheel", options.globals.package_dir.resolve(),
f"--outdir={built_wheel_dir}", f"--wheel-dir={built_wheel_dir}",
f"--config-setting={config_setting}", "--no-deps",
env=build_env, *get_build_verbosity_extra_flags(build_options.build_verbosity),
env=env,
) )
else: elif build_options.build_frontend == "build":
assert_never(build_options.build_frontend) config_setting = " ".join(verbosity_flags)
build_env = env.copy()
if build_options.dependency_constraints:
constraints_path = (
build_options.dependency_constraints.get_for_python_version(
config.version
)
)
# Bug in pip <= 21.1.3 - we can't have a space in the
# constraints file, and pip doesn't support drive letters
# in uhi. After probably pip 21.2, we can use uri. For
# now, use a temporary file.
if " " in str(constraints_path):
assert " " not in str(identifier_tmp_dir)
tmp_file = identifier_tmp_dir / "constraints.txt"
tmp_file.write_bytes(constraints_path.read_bytes())
constraints_path = tmp_file
built_wheel = next(built_wheel_dir.glob("*.whl")) build_env["PIP_CONSTRAINT"] = str(constraints_path)
build_env["VIRTUALENV_PIP"] = get_pip_version(env)
call(
"python",
"-m",
"build",
build_options.package_dir,
"--wheel",
f"--outdir={built_wheel_dir}",
f"--config-setting={config_setting}",
env=build_env,
)
else:
assert_never(build_options.build_frontend)
# repair the wheel built_wheel = next(built_wheel_dir.glob("*.whl"))
repaired_wheel_dir.mkdir()
if built_wheel.name.endswith("none-any.whl"): # repair the wheel
raise NonPlatformWheelError() repaired_wheel_dir.mkdir()
if build_options.repair_command: if built_wheel.name.endswith("none-any.whl"):
log.step("Repairing wheel...") raise NonPlatformWheelError()
repair_command_prepared = prepare_command(
build_options.repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir
)
shell(repair_command_prepared, env=env)
else:
shutil.move(str(built_wheel), repaired_wheel_dir)
repaired_wheel = next(repaired_wheel_dir.glob("*.whl")) if build_options.repair_command:
log.step("Repairing wheel...")
repair_command_prepared = prepare_command(
build_options.repair_command, wheel=built_wheel, dest_dir=repaired_wheel_dir
)
shell(repair_command_prepared, env=env)
else:
shutil.move(str(built_wheel), repaired_wheel_dir)
repaired_wheel = next(repaired_wheel_dir.glob("*.whl"))
if build_options.test_command and options.globals.test_selector(config.identifier): if build_options.test_command and options.globals.test_selector(config.identifier):
log.step("Testing wheel...") log.step("Testing wheel...")
@@ -405,7 +418,9 @@ def build(options: Options, tmp_path: Path) -> None:
shell(test_command_prepared, cwd="c:\\", env=virtualenv_env) shell(test_command_prepared, cwd="c:\\", env=virtualenv_env)
# we're all done here; move it to output (remove if already exists) # we're all done here; move it to output (remove if already exists)
shutil.move(str(repaired_wheel), build_options.output_dir) if abi3_wheel is None:
shutil.move(str(repaired_wheel), build_options.output_dir)
built_wheels.append(build_options.output_dir / repaired_wheel.name)
# clean up # clean up
# (we ignore errors because occasionally Windows fails to unlink a file and we # (we ignore errors because occasionally Windows fails to unlink a file and we
+6
View File
@@ -103,6 +103,12 @@ Linux), and the other architectures are emulated automatically.
{% include "../examples/github-with-qemu.yml" %} {% include "../examples/github-with-qemu.yml" %}
``` ```
### Building CPython ABI3 wheels (Limited API) {: #abi3}
The CPython Limited API is a subset of the Python C Extension API that's declared to be forward-compatible, meaning you can compile wheels for one version of Python, and they'll be compatible with future versions. Wheels that use the Limited API are known as ABI3 wheels.
To create a package that builds ABI3 wheels, you'll need to configure your build backend to compile libraries correctly create wheels with the right tags. [Check this repo](https://github.com/joerick/python-abi3-package-sample) for an example of how to do this with setuptools.
### Building packages with optional C extensions ### Building packages with optional C extensions
`cibuildwheel` defines the environment variable `CIBUILDWHEEL` to the value `1` allowing projects for which the C extension is optional to make it mandatory when building wheels. `cibuildwheel` defines the environment variable `CIBUILDWHEEL` to the value `1` allowing projects for which the C extension is optional to make it mandatory when building wheels.
+1 -1
View File
@@ -35,7 +35,7 @@ install_requires =
bracex bracex
certifi certifi
filelock filelock
packaging packaging>=20.9
platformdirs platformdirs
dataclasses;python_version < '3.7' dataclasses;python_version < '3.7'
tomli;python_version < '3.11' tomli;python_version < '3.11'
+50
View File
@@ -0,0 +1,50 @@
import textwrap
from . import test_projects, utils
limited_api_project = test_projects.new_c_project(
setup_py_add=textwrap.dedent(
r"""
cmdclass = {}
extension_kwargs = {}
if sys.version_info[:2] >= (3, 8):
from wheel.bdist_wheel import bdist_wheel as _bdist_wheel
class bdist_wheel_abi3(_bdist_wheel):
def finalize_options(self):
_bdist_wheel.finalize_options(self)
self.root_is_pure = False
def get_tag(self):
python, abi, plat = _bdist_wheel.get_tag(self)
return python, "abi3", plat
cmdclass["bdist_wheel"] = bdist_wheel_abi3
extension_kwargs["define_macros"] = [("Py_LIMITED_API", "0x03080000")]
extension_kwargs["py_limited_api"] = True
"""
),
setup_py_extension_args_add="**extension_kwargs",
setup_py_setup_args_add="cmdclass=cmdclass",
)
def test(tmp_path):
project_dir = tmp_path / "project"
limited_api_project.generate(project_dir)
# build the wheels
actual_wheels = utils.cibuildwheel_run(
project_dir,
add_env={
"CIBW_SKIP": "pp* ", # PyPy does not have a Py_LIMITED_API equivalent
},
)
# check that the expected wheels are produced
expected_wheels = [
w.replace("cp38-cp38", "cp38-abi3")
for w in utils.expected_wheels("spam", "0.1.0")
if "-pp" not in w and "-cp39" not in w and "-cp310" not in w
]
assert set(actual_wheels) == set(expected_wheels)
+3
View File
@@ -54,6 +54,7 @@ setup(
'spam', 'spam',
sources=['spam.c'], sources=['spam.c'],
libraries=libraries, libraries=libraries,
{{ setup_py_extension_args_add | indent(8) }}
)], )],
{{ setup_py_setup_args_add | indent(4) }} {{ setup_py_setup_args_add | indent(4) }}
) )
@@ -73,6 +74,7 @@ def new_c_project(
spam_c_top_level_add="", spam_c_top_level_add="",
spam_c_function_add="", spam_c_function_add="",
setup_py_add="", setup_py_add="",
setup_py_extension_args_add="",
setup_py_setup_args_add="", setup_py_setup_args_add="",
setup_cfg_add="", setup_cfg_add="",
): ):
@@ -91,6 +93,7 @@ def new_c_project(
"spam_c_top_level_add": spam_c_top_level_add, "spam_c_top_level_add": spam_c_top_level_add,
"spam_c_function_add": spam_c_function_add, "spam_c_function_add": spam_c_function_add,
"setup_py_add": setup_py_add, "setup_py_add": setup_py_add,
"setup_py_extension_args_add": setup_py_extension_args_add,
"setup_py_setup_args_add": setup_py_setup_args_add, "setup_py_setup_args_add": setup_py_setup_args_add,
"setup_cfg_add": setup_cfg_add, "setup_cfg_add": setup_cfg_add,
} }