extension-helpers

1.4.0 · active · verified Sat Apr 11

extension-helpers provides a suite of utilities designed to simplify the process of building and installing Python packages that include compiled extensions (e.g., C, C++, Fortran). It helps manage compiler flags, OpenMP support, and the Python Limited API (PEP 384) for ABI compatibility. The current version is 1.4.0, and it follows a stable release cadence with minor versions released every few months, often coinciding with Astropy-related ecosystem updates.

Warnings

Install

Imports

Quickstart

This quickstart demonstrates how to use `add_extension_helpers_build_options` to modify a `setuptools.Extension` object. It shows how the library injects compiler flags (e.g., for OpenMP if available) and how setting the `EXTENSION_HELPERS_PY_LIMITED_API` environment variable influences the compiler arguments for PEP 384 support. This code prepares the `Extension` object, but does not perform the actual compilation, which `setuptools` would handle in a `setup.py` or `pyproject.toml` based build.

import os
import shutil
from setuptools import Extension # setuptools must be installed
from extension_helpers import add_extension_helpers_build_options

# Ensure 'src' directory exists for the dummy C file
os.makedirs("src", exist_ok=True)

# For demonstration, create a dummy C file
dummy_c_file_path = os.path.join("src", "_dummy_ext.c")
dummy_c_file_content = """
#include <Python.h>

static PyObject *
dummy_func(PyObject *self, PyObject *args)
{
    Py_RETURN_NONE;
}

static PyMethodDef DummyMethods[] = {
    {"dummy_func", dummy_func, METH_NOARGS, "A dummy function."},
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef dummymodule = {
    PyModuleDef_HEAD_INIT,
    "_dummy_ext",   /* name of module */
    NULL, /* module documentation, may be NULL */
    -1,       /* size of per-interpreter state of the module,
                 or -1 if the module keeps state in global variables. */
    DummyMethods
};

PyMODINIT_FUNC
PyInit__dummy_ext(void)
{
    return PyModule_Create(&dummymodule);
}
"""
with open(dummy_c_file_path, "w") as f:
    f.write(dummy_c_file_content)

# Define a single extension
ext = Extension(
    'my_package._dummy_ext',
    sources=[dummy_c_file_path],
    include_dirs=[],
    extra_compile_args=[],
    extra_link_args=[]
)

print(f"Initial Extension: {ext.name}")
print(f"  Sources: {ext.sources}")
print(f"  Initial extra_compile_args: {ext.extra_compile_args}")

# Apply extension-helpers build options (e.g., for OpenMP, limited API)
# This modifies the 'ext' object in-place
add_extension_helpers_build_options(ext)

print(f"\nExtension after add_extension_helpers_build_options:")
print(f"  Updated extra_compile_args: {ext.extra_compile_args}")
print(f"  Updated extra_link_args: {ext.extra_link_args}")

# Demonstrate the Limited API env var effect
os.environ['EXTENSION_HELPERS_PY_LIMITED_API'] = 'true'
print("\n--- Rerunning with EXTENSION_HELPERS_PY_LIMITED_API=true ---")

ext_limited = Extension(
    'my_package._dummy_ext_limited',
    sources=[dummy_c_file_path],
    include_dirs=[],
    extra_compile_args=[],
    extra_link_args=[]
)
add_extension_helpers_build_options(ext_limited)
print(f"Extension (Limited API enabled): {ext_limited.name}")
print(f"  Updated extra_compile_args (Limited API): {ext_limited.extra_compile_args}")
print(f"  Updated extra_link_args (Limited API): {ext_limited.extra_link_args}")
del os.environ['EXTENSION_HELPERS_PY_LIMITED_API']

# Clean up the dummy C file and directory
if os.path.exists(dummy_c_file_path):
    os.remove(dummy_c_file_path)
if os.path.exists("src") and not os.listdir("src"):
    os.rmdir("src")
elif os.path.exists("src"):
    print("\nNote: 'src' directory not removed as it contains other files.")

view raw JSON →