././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1729087650.9301364 extension_helpers-1.2.0/0000755000175100001660000000000014703744243014663 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1729087650.9231365 extension_helpers-1.2.0/.github/0000755000175100001660000000000014703744243016223 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/.github/dependabot.yml0000644000175100001660000000030314703744231021044 0ustar00runnerdockerversion: 2 updates: - package-ecosystem: "github-actions" directory: ".github/workflows" schedule: interval: "weekly" groups: actions: patterns: - "*" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/.github/release.yml0000644000175100001660000000056314703744231020367 0ustar00runnerdockerchangelog: exclude: authors: - pre-commit-ci categories: - title: New Features labels: - enhancement - title: Bug Fixes labels: - bug - title: Infrastructure labels: - infrastructure - title: Documentation labels: - Documentation - title: Other Changes labels: - "*" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1729087650.9241364 extension_helpers-1.2.0/.github/workflows/0000755000175100001660000000000014703744243020260 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/.github/workflows/main.yml0000644000175100001660000000271014703744231021724 0ustar00runnerdockername: CI on: push: pull_request: workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: tests: uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@924441154cf3053034c6513d5e06c69d262fb9a6 # v1.13.0 with: posargs: --openmp-expected=True coverage: codecov envs: | # Code style - linux: style # Standard tests - linux: py38-test-oldestdeps - linux: py39-test - linux: py310-test - linux: py311-test - linux: py312-test - linux: py312-test-devdeps - macos: py312-test-devdeps posargs: --openmp-expected=False - windows: py38-test runs-on: windows-2019 - windows: py312-test-devdeps runs-on: windows-2019 # Test with more compilers, for the OpenMP helpers - macos: py39-test-osxclang-conda toxdeps: 'tox>=4' coverage: '' - linux: py39-test-linuxgcc-conda toxdeps: 'tox>=4' coverage: '' # Test downstream packages - linux: py312-downstream publish: needs: tests uses: OpenAstronomy/github-actions-workflows/.github/workflows/publish_pure_python.yml@924441154cf3053034c6513d5e06c69d262fb9a6 # v1.13.0 with: test_extras: test test_command: pytest --pyargs extension_helpers secrets: pypi_token: ${{ secrets.PYPI_TOKEN }} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/.github/workflows/update-changelog.yml0000644000175100001660000000176614703744231024221 0ustar00runnerdocker# This workflow takes the GitHub release notes an updates the changelog on the # main branch with the body of the release notes, thereby keeping a log in # the git repo of the changes. name: "Update Changelog" on: release: types: [released] jobs: update: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: ref: main - name: Update Changelog uses: stefanzweifel/changelog-updater-action@61ce794778aa787ea8d204d9fe2928543cb2fe40 # v1.11.0 with: release-notes: ${{ github.event.release.body }} latest-version: ${{ github.event.release.name }} path-to-changelog: CHANGES.md - name: Commit updated CHANGELOG uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 # v5.0.1 with: branch: main commit_message: Update CHANGELOG file_pattern: CHANGES.md ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/.gitignore0000644000175100001660000000124514703744231016652 0ustar00runnerdocker# Compiled files *.py[cod] *.a *.o *.so *.pyd __pycache__ # Ignore .c files by default to avoid including generated code. If you want to # add a non-generated .c extension, use `git add -f filename.c`. *.c # Other generated files MANIFEST extension_helpers/version.py extension_helpers/cython_version.py # Sphinx _build _generated api # Packages/installer info *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg distribute-*.tar.gz # Other .cache .tox .*.swp *~ .project .pydevproject .settings .coverage .coverage.subprocess cover htmlcov .pytest_cache # Mac OSX .DS_Store # PyCharm .idea # Hypothesis .hypothesis # vscode settings.json ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/.pre-commit-config.yaml0000644000175100001660000000171014703744231021140 0ustar00runnerdockerrepos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - id: check-added-large-files - id: check-case-conflict - id: check-yaml - id: debug-statements - id: end-of-file-fixer exclude: ".*(data.*|extern.*|licenses.*|.*.fits)$" - id: trailing-whitespace exclude: ".*(data.*|extern.*|licenses.*|.*.fits)$" - repo: https://github.com/psf/black-pre-commit-mirror rev: 24.10.0 hooks: - id: black - repo: https://github.com/scientific-python/cookie rev: 2024.08.19 hooks: - id: sp-repo-review - repo: https://github.com/codespell-project/codespell rev: v2.3.0 hooks: - id: codespell args: ["--write-changes"] additional_dependencies: - tomli - repo: https://github.com/astral-sh/ruff-pre-commit rev: "v0.6.9" hooks: - id: ruff args: ["--fix", "--show-fixes"] ci: autofix_prs: false ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/.readthedocs.yaml0000644000175100001660000000047414703744231020114 0ustar00runnerdockerversion: 2 build: os: ubuntu-20.04 tools: python: "3.9" apt_packages: - graphviz sphinx: builder: html configuration: docs/conf.py fail_on_warning: true python: install: - method: pip extra_requirements: - docs path: . # Don't build any extra formats formats: [] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/CHANGES.md0000644000175100001660000000771014703744231016257 0ustar00runnerdocker## v1.1.1 - 2023-12-07 ### What's Changed #### Bug Fixes * get_extensions: use shutil.copyfile to avoid PermissionError by @doronbehar in https://github.com/astropy/extension-helpers/pull/59 * Fix bug that caused extension-helpers to not work correctly if pyproject was the only configuration file present by @astrofrog in https://github.com/astropy/extension-helpers/pull/66 #### Other Changes * Replace all instances of distutils in docs with setuptools by @lpsinger in https://github.com/astropy/extension-helpers/pull/65 * Fix typos by @lpsinger in https://github.com/astropy/extension-helpers/pull/64 * MNT: handle deprecation warnings seen in tests by @neutrinoceros in https://github.com/astropy/extension-helpers/pull/67 * Add note about pinning extension-helpers by @astrofrog in https://github.com/astropy/extension-helpers/pull/72 * DEP: drop dependency on tomli on Python 3.11 and newer by @neutrinoceros in https://github.com/astropy/extension-helpers/pull/73 * TST: treat warnings as errors by @neutrinoceros in https://github.com/astropy/extension-helpers/pull/74 * MNT: find and replace log.warn -> log.warning (the warn method is deprecated) by @neutrinoceros in https://github.com/astropy/extension-helpers/pull/75 * Infrastructure updates by @astrofrog in https://github.com/astropy/extension-helpers/pull/68 * Bump actions/checkout from 2 to 4 by @dependabot in https://github.com/astropy/extension-helpers/pull/77 * Bump stefanzweifel/git-auto-commit-action from 4 to 5 by @dependabot in https://github.com/astropy/extension-helpers/pull/76 * Add back support for absolute source paths but deprecate it by @astrofrog in https://github.com/astropy/extension-helpers/pull/70 ### New Contributors * @doronbehar made their first contribution in https://github.com/astropy/extension-helpers/pull/59 * @neutrinoceros made their first contribution in https://github.com/astropy/extension-helpers/pull/67 * @dependabot made their first contribution in https://github.com/astropy/extension-helpers/pull/77 **Full Changelog**: https://github.com/astropy/extension-helpers/compare/v1.1.0...v1.1.1 ## v1.1.0 - 2023-07-24 ### What's Changed #### New Features - Support enabling via `pyproject.toml` by @WilliamJamieson in https://github.com/astropy/extension-helpers/pull/48 #### Bug Fixes - OpenMP functions should detect the Intel oneAPI compiler by @lpsinger in https://github.com/astropy/extension-helpers/pull/44 #### Infrastructure - Skip hypothesis tests in downstream testing by @astrofrog in https://github.com/astropy/extension-helpers/pull/39 - Set language for docs by @lpsinger in https://github.com/astropy/extension-helpers/pull/45 - Update python requirements by @WilliamJamieson in https://github.com/astropy/extension-helpers/pull/50 - Add pre-commit configuration by @astrofrog in https://github.com/astropy/extension-helpers/pull/53 - Set testpaths to avoid picking up other tests by @astrofrog in https://github.com/astropy/extension-helpers/pull/54 - Added configuration required to update changelog when doing release through GitHub UI by @astrofrog in https://github.com/astropy/extension-helpers/pull/56 ### New Contributors - @WilliamJamieson made their first contribution in https://github.com/astropy/extension-helpers/pull/50 - @pre-commit-ci made their first contribution in https://github.com/astropy/extension-helpers/pull/55 **Full Changelog**: https://github.com/astropy/extension-helpers/compare/v1.0.0...v1.1.0 ## 1.0.0 - 2022-03-16 - Added support for coverage>=5 for the extension-helpers test suite. [#24] - Removed any direct usage of distutils. [#34] - Remove support for the undocumented --compiler argument to setup.py. [#36] - Added support for enabling extension-helpers from setup.cfg. [#33] ## 0.1 - 2019-12-18 - Initial release of extension-helpers, which was forked from astropy-helpers 4.0. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/CONTRIBUTING.md0000644000175100001660000000075514703744231017120 0ustar00runnerdockerContributing to extension-helpers =============================== The guidelines for contributing to ``extension-helpers`` are generally the same as the [contributing guidelines for the astropy core package](http://github.com/astropy/astropy/blob/main/CONTRIBUTING.md). Basically, report relevant issues in the ``extension-helpers`` issue tracker, and we welcome pull requests that broadly follow the [Astropy coding guidelines](http://docs.astropy.org/en/latest/development/codeguide.html). ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/LICENSE.rst0000644000175100001660000000272314703744231016500 0ustar00runnerdockerCopyright (c) 2019, Astropy Developers All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Astropy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/MANIFEST.in0000644000175100001660000000022014703744231016410 0ustar00runnerdockerinclude README.rst include CHANGES.rst include LICENSE.rst recursive-include licenses * include ah_bootstrap.py exclude *.pyc *.o prune build ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1729087650.9301364 extension_helpers-1.2.0/PKG-INFO0000644000175100001660000000421414703744243015761 0ustar00runnerdockerMetadata-Version: 2.1 Name: extension-helpers Version: 1.2.0 Summary: Utilities for building and installing packages with compiled extensions Author-email: The Astropy Developers License: BSD 3-Clause License Project-URL: Homepage, https://github.com/astropy/extension-helpers Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Framework :: Setuptools Plugin Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Build Tools Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: System :: Archiving :: Packaging Provides: extension_helpers Requires-Python: >=3.8 Description-Content-Type: text/x-rst License-File: LICENSE.rst Requires-Dist: setuptools>=40.2 Requires-Dist: tomli>=1.0.0; python_version < "3.11" Provides-Extra: test Requires-Dist: wheel; extra == "test" Requires-Dist: pytest; extra == "test" Requires-Dist: pytest-cov; extra == "test" Requires-Dist: cython; extra == "test" Provides-Extra: docs Requires-Dist: sphinx; extra == "docs" Requires-Dist: sphinx-automodapi; extra == "docs" extension-helpers ================= .. image:: https://github.com/astropy/extension-helpers/actions/workflows/main.yml/badge.svg :target: https://github.com/astropy/extension-helpers/actions/workflows/main.yml .. image:: https://codecov.io/gh/astropy/extension-helpers/branch/main/graph/badge.svg :target: https://codecov.io/gh/astropy/extension-helpers .. image:: https://readthedocs.org/projects/extension-helpers/badge/?version=latest :target: https://extension-helpers.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status The **extension-helpers** package includes convenience helpers to assist with building Python packages with compiled C/Cython extensions. It is developed by the Astropy project but is intended to be general and usable by any Python package. For more information, see the documentation at http://extension-helpers.readthedocs.io ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/README.rst0000644000175100001660000000156014703744231016351 0ustar00runnerdockerextension-helpers ================= .. image:: https://github.com/astropy/extension-helpers/actions/workflows/main.yml/badge.svg :target: https://github.com/astropy/extension-helpers/actions/workflows/main.yml .. image:: https://codecov.io/gh/astropy/extension-helpers/branch/main/graph/badge.svg :target: https://codecov.io/gh/astropy/extension-helpers .. image:: https://readthedocs.org/projects/extension-helpers/badge/?version=latest :target: https://extension-helpers.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status The **extension-helpers** package includes convenience helpers to assist with building Python packages with compiled C/Cython extensions. It is developed by the Astropy project but is intended to be general and usable by any Python package. For more information, see the documentation at http://extension-helpers.readthedocs.io ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/conftest.py0000644000175100001660000000016314703744231017057 0ustar00runnerdockerdef pytest_addoption(parser): parser.addoption("--openmp-expected", action="store", default=None, help="help") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1729087650.9251363 extension_helpers-1.2.0/docs/0000755000175100001660000000000014703744243015613 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/docs/Makefile0000644000175100001660000000111014703744231017241 0ustar00runnerdocker# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = -W SPHINXBUILD = sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/docs/api.rst0000644000175100001660000000013314703744231017110 0ustar00runnerdockerAPI Documentation ================= .. automodapi:: extension_helpers :no-main-docstr: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/docs/conf.py0000644000175100001660000000313714703744231017113 0ustar00runnerdockerfrom pkg_resources import get_distribution project = "extension-helpers" copyright = "2019, The Astropy Developers" author = "The Astropy Developers" # We need to get the version number from the package version = release = get_distribution("extension-helpers").version extensions = [ "sphinx.ext.autodoc", "sphinx.ext.doctest", "sphinx.ext.intersphinx", "sphinx.ext.napoleon", "sphinx_automodapi.automodapi", ] intersphinx_mapping = { "python": ("https://docs.python.org/3/", None), "setuptools": ("https://setuptools.pypa.io/en/latest/", None), } # The suffix(es) of source filenames. source_suffix = ".rst" # The master toctree document. master_doc = "index" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = "alabaster" html_theme_options = { "description": "A build time package to simplify C/Cython extensions.", "code_font_family": "'Fira Code', monospace", "github_user": "astropy", "github_repo": "extension-helpers", "sidebar_width": "300px", } # Enable nitpicky mode to pick reference issues default_role = "obj" nitpicky = True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/docs/index.rst0000644000175100001660000000115314703744231017451 0ustar00runnerdockerExtension Helpers ================= The **extension-helpers** package includes convenience helpers to assist with building Python packages with compiled C/Cython extensions. It is developed by the Astropy project but is intended to be general and usable by any Python package. This is not a traditional package in the sense that it is not intended to be installed directly by users or developers. Instead, it is meant to be accessed when the ``setup.py`` command is run and should be defined as a build-time dependency in ``pyproject.toml`` files. .. toctree:: :maxdepth: 1 using.rst openmp.rst api.rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/docs/make.bat0000644000175100001660000000142314703744231017215 0ustar00runnerdocker@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end popd ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/docs/openmp.rst0000644000175100001660000000114714703744231017643 0ustar00runnerdockerOpenMP helpers ============== We provide a helper function :func:`~extension_helpers.add_openmp_flags_if_available` that can be used to automatically add OpenMP flags for C/Cython extensions, based on whether OpenMP is available and produces executable code. To use this, edit the ``setup_package.py`` file where you define a C extension, import the helper function:: from extension_helpers import add_openmp_flags_if_available then once you have defined the extension and before returning it, use it as:: extension = Extension(...) add_openmp_flags_if_available(extension) return [extension] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/docs/using.rst0000644000175100001660000000536714703744231017502 0ustar00runnerdockerUsing extension-helpers ======================= To use extension-helpers in your package, you will need to make sure your package uses a ``pyproject.toml`` file as described in `PEP 518 `_. You can then add extension-helpers to the build-time dependencies in your ``pyproject.toml`` file:: [build-system] requires = ["setuptools", "wheel", "extension-helpers==1.*"] If you have Cython extensions, you will need to make sure ``cython`` is included in the above list too. .. note:: It is highly recommended to pin the version of extension-helpers to a major version, such as ``1.*``, since extension-helpers uses `semantic versioning `_ and there will therefore likely be breaking changes when the major version is bumped. If you do not specify any pinning, then old versions of your package that are already on PyPI may no longer be installable on source without disabling the build isolation and installing build dependencies manually. The main functionality in extension-helpers is the :func:`~extension_helpers.get_extensions` function which can be used to collect package extensions. Defining functions is then done in two ways: * For simple Cython extensions, :func:`~extension_helpers.get_extensions` will automatically generate extension modules with no further work. * For other extensions, you can create ``setup_package.py`` files anywhere in your package, and these files can then include a ``get_extensions`` function that returns a list of :class:`setuptools.Extension` objects. In the second case, the idea is that for large packages, extensions can be defined in the relevant sub-packages rather than having to all be listed in the main ``setup.py`` file. To use this, you should modify your ``setup.py`` file to use :func:`~extension_helpers.get_extensions` as follows:: from extension_helpers import get_extensions ... setup(..., ext_modules=get_extensions()) Note that if you use this, extension-helpers will also we create a ``packagename.compiler_version`` submodule that contain information about the compilers used. It is also possible to enable extension-helpers in ``setup.cfg`` instead of ``setup.py`` by adding the following configuration to the ``setup.cfg`` file:: [extension-helpers] use_extension_helpers = true Moreover, one can also enable extension-helpers in ``pyproject.toml`` by adding the following configuration to the ``pyproject.toml`` file:: [tool.extension-helpers] use_extension_helpers = true .. note:: For backwards compatibility, the setting of ``use_extension_helpers`` in ``setup.cfg`` will override any setting of it in ``pyproject.toml``. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1729087650.9261365 extension_helpers-1.2.0/extension_helpers/0000755000175100001660000000000014703744243020421 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/extension_helpers/__init__.py0000644000175100001660000000321214703744231022525 0ustar00runnerdockerimport sys from configparser import ConfigParser from ._openmp_helpers import add_openmp_flags_if_available # noqa: F401 from ._setup_helpers import get_compiler, get_extensions, pkg_config # noqa: F401 from ._utils import import_file, write_if_different # noqa: F401 from .version import version as __version__ # noqa: F401 def _finalize_distribution_hook(distribution): """ Entry point for setuptools which allows extension-helpers to be enabled from setup.cfg without the need for setup.py. """ import os from pathlib import Path if sys.version_info >= (3, 11): import tomllib else: import tomli as tomllib found_config = False config_files = distribution.find_config_files() if len(config_files) > 0: cfg = ConfigParser() cfg.read(config_files[0]) if cfg.has_option("extension-helpers", "use_extension_helpers"): found_config = True if cfg.get("extension-helpers", "use_extension_helpers").lower() == "true": distribution.ext_modules = get_extensions() pyproject = Path(distribution.src_root or os.curdir, "pyproject.toml") if pyproject.exists() and not found_config: with pyproject.open("rb") as f: pyproject_cfg = tomllib.load(f) if ( "tool" in pyproject_cfg and "extension-helpers" in pyproject_cfg["tool"] and "use_extension_helpers" in pyproject_cfg["tool"]["extension-helpers"] and pyproject_cfg["tool"]["extension-helpers"]["use_extension_helpers"] ): distribution.ext_modules = get_extensions() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/extension_helpers/_openmp_helpers.py0000644000175100001660000002430514703744231024153 0ustar00runnerdocker# This module defines functions that can be used to check whether OpenMP is # available and if so what flags to use. To use this, import the # add_openmp_flags_if_available function in a setup_package.py file where you # are defining your extensions: # # from extension_helpers.openmp_helpers import add_openmp_flags_if_available # # then call it with a single extension as the only argument: # # add_openmp_flags_if_available(extension) # # this will add the OpenMP flags if available. __doctest_skip__ = ["_get_flag_value_from_var"] import datetime import glob import logging import os import subprocess import sys import tempfile import time from setuptools.command.build_ext import ( customize_compiler, get_config_var, new_compiler, ) from ._setup_helpers import get_compiler __all__ = ["add_openmp_flags_if_available"] try: # Check if this has already been instantiated, only set the default once. _EXTENSION_HELPERS_DISABLE_OPENMP_SETUP_ # noqa: B018 except NameError: import builtins # It hasn't, so do so. builtins._EXTENSION_HELPERS_DISABLE_OPENMP_SETUP_ = False log = logging.getLogger(__name__) CCODE = """ #include #include int main(void) { #pragma omp parallel printf("nthreads=%d\\n", omp_get_num_threads()); return 0; } """ CCODE_ICX = """ #ifndef __INTEL_LLVM_COMPILER #error This is not the Intel oneAPI compiler #endif """ def _get_flag_value_from_var(flag, var, delim=" "): """ Extract flags from an environment variable. Parameters ---------- flag : str The flag to extract, for example '-I' or '-L' var : str The environment variable to extract the flag from, e.g. CFLAGS or LDFLAGS. delim : str, optional The delimiter separating flags inside the environment variable Examples -------- Let's assume the LDFLAGS is set to '-L/usr/local/include -customflag'. This function will then return the following: >>> _get_flag_value_from_var('-L', 'LDFLAGS') '/usr/local/include' Notes ----- Environment variables are first checked in ``os.environ[var]``, then in ``sysconfig.get_config_var(var)``. This function is not supported on Windows. """ if sys.platform.startswith("win"): return None # Simple input validation if not var or not flag: return None flag_length = len(flag) if not flag_length: return None # Look for var in os.eviron then in get_config_var if var in os.environ: flags = os.environ[var] else: try: flags = get_config_var(var) except KeyError: return None # Extract flag from {var:value} if flags: for item in flags.split(delim): if item.startswith(flag): return item[flag_length:] def _check_if_compiler_is_icx(): """ Check whether the compiler is the Intel oneAPI compiler. Returns ------- result : bool `True` if the test passed, `False` otherwise. """ ccompiler = new_compiler() customize_compiler(ccompiler) with tempfile.TemporaryDirectory() as tmp_dir: start_dir = os.path.abspath(".") try: os.chdir(tmp_dir) # Write test program with open("test_icx.c", "w") as f: f.write(CCODE_ICX) os.mkdir("objects") # Compile program ccompiler.compile(["test_icx.c"], output_dir="objects") except Exception: is_icx = False else: is_icx = True finally: os.chdir(start_dir) return is_icx def get_openmp_flags(): """ Utility for returning compiler and linker flags possibly needed for OpenMP support. Returns ------- result : `{'compiler_flags':, 'linker_flags':}` Notes ----- The flags returned are not tested for validity, use `check_openmp_support(openmp_flags=get_openmp_flags())` to do so. """ compile_flags = [] link_flags = [] if get_compiler() == "msvc": compile_flags.append("-openmp") else: include_path = _get_flag_value_from_var("-I", "CFLAGS") if include_path: compile_flags.append("-I" + include_path) lib_path = _get_flag_value_from_var("-L", "LDFLAGS") if lib_path: link_flags.append("-L" + lib_path) link_flags.append("-Wl,-rpath," + lib_path) if _check_if_compiler_is_icx(): openmp_flags = "-qopenmp" else: openmp_flags = "-fopenmp" compile_flags.append(openmp_flags) link_flags.append(openmp_flags) return {"compiler_flags": compile_flags, "linker_flags": link_flags} def check_openmp_support(openmp_flags=None): """ Check whether OpenMP test code can be compiled and run. Parameters ---------- openmp_flags : dict, optional This should be a dictionary with keys ``compiler_flags`` and ``linker_flags`` giving the compilation and linking flags respectively. These are passed as `extra_postargs` to `compile()` and `link_executable()` respectively. If this is not set, the flags will be automatically determined using environment variables. Returns ------- result : bool `True` if the test passed, `False` otherwise. """ ccompiler = new_compiler() customize_compiler(ccompiler) if not openmp_flags: # customize_compiler() extracts info from os.environ. If certain keys # exist it uses these plus those from sysconfig.get_config_vars(). # If the key is missing in os.environ it is not extracted from # sysconfig.get_config_var(). E.g. 'LDFLAGS' get left out, preventing # clang from finding libomp.dylib because -L is not passed to # linker. Call get_openmp_flags() to get flags missed by # customize_compiler(). openmp_flags = get_openmp_flags() compile_flags = openmp_flags.get("compiler_flags") link_flags = openmp_flags.get("linker_flags") with tempfile.TemporaryDirectory() as tmp_dir: start_dir = os.path.abspath(".") try: os.chdir(tmp_dir) # Write test program with open("test_openmp.c", "w") as f: f.write(CCODE) os.mkdir("objects") # Compile, test program ccompiler.compile(["test_openmp.c"], output_dir="objects", extra_postargs=compile_flags) # Link test program objects = glob.glob(os.path.join("objects", "*" + ccompiler.obj_extension)) ccompiler.link_executable(objects, "test_openmp", extra_postargs=link_flags) # Run test program output = subprocess.check_output("./test_openmp") output = output.decode(sys.stdout.encoding or "utf-8").splitlines() if "nthreads=" in output[0]: nthreads = int(output[0].strip().split("=")[1]) if len(output) == nthreads: is_openmp_supported = True else: log.warning( "Unexpected number of lines from output of test OpenMP " "program (output was {})".format(output) ) is_openmp_supported = False else: log.warning("Unexpected output from test OpenMP program (output was %s)", output) is_openmp_supported = False except Exception: is_openmp_supported = False finally: os.chdir(start_dir) return is_openmp_supported def is_openmp_supported(): """ Determine whether the build compiler has OpenMP support. """ log_threshold = log.level log.setLevel("CRITICAL") ret = check_openmp_support() log.setLevel(log_threshold) return ret def add_openmp_flags_if_available(extension): """ Add OpenMP compilation flags, if supported (if not a warning will be printed to the console and no flags will be added.) Returns `True` if the flags were added, `False` otherwise. """ if _EXTENSION_HELPERS_DISABLE_OPENMP_SETUP_: # noqa: F821 log.info("OpenMP support has been explicitly disabled.") return False openmp_flags = get_openmp_flags() using_openmp = check_openmp_support(openmp_flags=openmp_flags) if using_openmp: compile_flags = openmp_flags.get("compiler_flags") link_flags = openmp_flags.get("linker_flags") log.info("Compiling Cython/C/C++ extension with OpenMP support") extension.extra_compile_args.extend(compile_flags) extension.extra_link_args.extend(link_flags) else: log.warning( "Cannot compile Cython/C/C++ extension with OpenMP, reverting to non-parallel code" ) return using_openmp _IS_OPENMP_ENABLED_SRC = """ # Autogenerated by {packagename}'s setup.py on {timestamp!s} def is_openmp_enabled(): \"\"\" Determine whether this package was built with OpenMP support. \"\"\" return {return_bool} """[ 1: ] def generate_openmp_enabled_py(packagename, srcdir=".", disable_openmp=None): """ Generate ``package.openmp_enabled.is_openmp_enabled``, which can then be used to determine, post build, whether the package was built with or without OpenMP support. """ epoch = int(os.environ.get("SOURCE_DATE_EPOCH", time.time())) if sys.version_info >= (3, 11): timestamp = datetime.datetime.fromtimestamp(epoch, datetime.UTC) else: timestamp = datetime.datetime.utcfromtimestamp(epoch) if disable_openmp is not None: import builtins builtins._EXTENSION_HELPERS_DISABLE_OPENMP_SETUP_ = disable_openmp if _EXTENSION_HELPERS_DISABLE_OPENMP_SETUP_: # noqa: F821 log.info("OpenMP support has been explicitly disabled.") openmp_support = False else: openmp_support = is_openmp_supported() src = _IS_OPENMP_ENABLED_SRC.format( packagename=packagename, timestamp=timestamp, return_bool=openmp_support ) package_srcdir = os.path.join(srcdir, *packagename.split(".")) is_openmp_enabled_py = os.path.join(package_srcdir, "openmp_enabled.py") with open(is_openmp_enabled_py, "w") as f: f.write(src) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/extension_helpers/_setup_helpers.py0000644000175100001660000002610214703744231024012 0ustar00runnerdocker# Licensed under a 3-clause BSD style license - see LICENSE.rst """ This module contains a number of utilities for use during setup/build/packaging that are useful to astropy as a whole. """ import logging import os import shutil import subprocess import sys from collections import defaultdict from setuptools import Extension, find_packages from setuptools.command.build_ext import new_compiler from ._utils import import_file, walk_skip_hidden __all__ = ["get_compiler", "get_extensions", "pkg_config"] log = logging.getLogger(__name__) def get_compiler(): """ Determines the compiler that will be used to build extension modules. Returns ------- compiler : str The compiler option specified for the build, build_ext, or build_clib command; or the default compiler for the platform if none was specified. """ return new_compiler().compiler_type def get_extensions(srcdir="."): """ Collect all extensions from Cython files and ``setup_package.py`` files. If numpy is importable, the numpy include path will be added to all Cython extensions which are automatically generated. This function obtains that information by iterating through all packages in ``srcdir`` and locating a ``setup_package.py`` module. This module can contain the ``get_extensions()`` function which returns a list of :class:`setuptools.Extension` objects. """ ext_modules = [] packages = [] package_dir = {} # Use the find_packages tool to locate all packages and modules packages = find_packages(srcdir) # Update package_dir if the package lies in a subdirectory if srcdir != ".": package_dir[""] = srcdir for setuppkg in iter_setup_packages(srcdir, packages): # get_extensions must include any Cython extensions by their .pyx # filename. if hasattr(setuppkg, "get_extensions"): ext_modules.extend(setuppkg.get_extensions()) # Locate any .pyx files not already specified, and add their extensions in. # The default include dirs include numpy to facilitate numerical work. includes = [] try: import numpy includes = [numpy.get_include()] except ImportError: pass ext_modules.extend(get_cython_extensions(srcdir, packages, ext_modules, includes)) # Now remove extensions that have the special name 'skip_cython', as they # exist Only to indicate that the cython extensions shouldn't be built for i, ext in reversed(list(enumerate(ext_modules))): if ext.name == "skip_cython": del ext_modules[i] # On Microsoft compilers, we need to pass the '/MANIFEST' # commandline argument. This was the default on MSVC 9.0, but is # now required on MSVC 10.0, but it doesn't seem to hurt to add # it unconditionally. if get_compiler() == "msvc": for ext in ext_modules: ext.extra_link_args.append("/MANIFEST") if len(ext_modules) > 0: main_package_dir = min(packages, key=len) src_path = os.path.join(os.path.dirname(__file__), "src") shutil.copyfile( os.path.join(src_path, "compiler.c"), os.path.join(srcdir, main_package_dir, "_compiler.c"), ) ext = Extension( main_package_dir + ".compiler_version", [os.path.join(main_package_dir, "_compiler.c")] ) ext_modules.append(ext) # Since https://github.com/astropy/extension-helpers/pull/67, # extensions that used absolute paths in source names stopped working. # Absolute paths in source paths are undesirable but we need to # preserve backward-compatibility until we bump the major release, # so we check for the case of absolute paths and emit a deprecation # warning for now. for extension in ext_modules: sources = [] fixed = [] for source in extension.sources: if os.path.isabs(source): try: source = os.path.relpath(source) except ValueError: # In some cases it's impossible to use a relative path, for # instance if the source files are on a different drive. In # this case there's not much we can do so we just proceed. pass fixed.append(source) sources.append(source) if fixed: log.warning( "Extension {} contains source files " "({}) that are specified using an absolute " "path, which will not be supported in future.".format( extension.name, ", ".join(fixed) ) ) extension.sources = sources return ext_modules def iter_setup_packages(srcdir, packages): """A generator that finds and imports all of the ``setup_package.py`` modules in the source packages. Returns ------- modgen : generator A generator that yields (modname, mod), where `mod` is the module and `modname` is the module name for the ``setup_package.py`` modules. """ for packagename in packages: package_parts = packagename.split(".") package_path = os.path.join(srcdir, *package_parts) setup_package = os.path.join(package_path, "setup_package.py") if os.path.isfile(setup_package): module = import_file(setup_package, name=packagename + ".setup_package") yield module def iter_pyx_files(package_dir, package_name): """ A generator that yields Cython source files (ending in '.pyx') in the source packages. Returns ------- pyxgen : generator A generator that yields (extmod, fullfn) where `extmod` is the full name of the module that the .pyx file would live in based on the source directory structure, and `fullfn` is the path to the .pyx file. """ for dirpath, _dirnames, filenames in walk_skip_hidden(package_dir): for fn in filenames: if fn.endswith(".pyx"): fullfn = os.path.join(dirpath, fn) # Package must match file name extmod = ".".join([package_name, fn[:-4]]) yield (extmod, fullfn) break # Don't recurse into subdirectories def get_cython_extensions(srcdir, packages, prevextensions=tuple(), extincludedirs=None): """ Looks for Cython files and generates Extensions if needed. Parameters ---------- srcdir : str Path to the root of the source directory to search. prevextensions : list The extensions that are already defined, as a list of of `~setuptools.Extension` objects. Any .pyx files already here will be ignored. extincludedirs : list or None Directories to include as the `include_dirs` argument to the generated `~setuptools.Extension` objects, as a list of strings. Returns ------- exts : list The new extensions that are needed to compile all .pyx files (does not include any already in `prevextensions`). """ # Vanilla setuptools and old versions of distribute include Cython files # as .c files in the sources, not .pyx, so we cannot simply look for # existing .pyx sources in the previous sources, but we should also check # for .c files with the same remaining filename. So we look for .pyx and # .c files, and we strip the extension. prevsourcepaths = [] ext_modules = [] for ext in prevextensions: for s in ext.sources: if s.endswith((".pyx", ".c", ".cpp")): sourcepath = os.path.realpath(os.path.splitext(s)[0]) prevsourcepaths.append(sourcepath) for package_name in packages: package_parts = package_name.split(".") package_path = os.path.join(srcdir, *package_parts) for extmod, pyxfn in iter_pyx_files(package_path, package_name): sourcepath = os.path.realpath(os.path.splitext(pyxfn)[0]) if sourcepath not in prevsourcepaths: ext_modules.append(Extension(extmod, [pyxfn], include_dirs=extincludedirs)) return ext_modules def pkg_config(packages, default_libraries, executable="pkg-config"): """ Uses pkg-config to update a set of setuptools Extension arguments to include the flags necessary to link against the given packages. If the pkg-config lookup fails, default_libraries is applied to libraries. Parameters ---------- packages : list The pkg-config packages to look up, as a list of strings. default_libraries : list The library names to use if the pkg-config lookup fails, a list of strings. Returns ------- config : dict A dictionary containing keyword arguments to :class:`~setuptools.Extension`. These entries include: - ``include_dirs``: A list of include directories - ``library_dirs``: A list of library directories - ``libraries``: A list of libraries - ``define_macros``: A list of macro defines - ``undef_macros``: A list of macros to undefine - ``extra_compile_args``: A list of extra arguments to pass to the compiler """ flag_map = { "-I": "include_dirs", "-L": "library_dirs", "-l": "libraries", "-D": "define_macros", "-U": "undef_macros", } command = f"{executable} --libs --cflags {' '.join(packages)}" result = defaultdict(list) try: pipe = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) output = pipe.communicate()[0].strip() except subprocess.CalledProcessError as e: lines = [ (f"{executable} failed. This may cause the build to fail below."), f" command: {e.cmd}", f" returncode: {e.returncode}", f" output: {e.output}", ] log.warning("\n".join(lines)) result["libraries"].extend(default_libraries) else: if pipe.returncode != 0: lines = [ f"pkg-config could not lookup up package(s) {', '.join(packages)}.", "This may cause the build to fail below.", ] log.warning("\n".join(lines)) result["libraries"].extend(default_libraries) else: for token in output.split(): # It's not clear what encoding the output of # pkg-config will come to us in. It will probably be # some combination of pure ASCII (for the compiler # flags) and the filesystem encoding (for any argument # that includes directories or filenames), but this is # just conjecture, as the pkg-config documentation # doesn't seem to address it. arg = token[:2].decode("ascii") value = token[2:].decode(sys.getfilesystemencoding()) if arg in flag_map: if arg == "-D": value = tuple(value.split("=", 1)) result[flag_map[arg]].append(value) else: result["extra_compile_args"].append(value) return result ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/extension_helpers/_utils.py0000644000175100001660000001017514703744231022273 0ustar00runnerdocker# Licensed under a 3-clause BSD style license - see LICENSE.rst import os import sys from importlib import machinery as import_machinery from importlib.util import module_from_spec, spec_from_file_location from pathlib import Path __all__ = ["write_if_different", "import_file"] if sys.platform == "win32": import ctypes def _has_hidden_attribute(filepath): """ Returns True if the given filepath has the hidden attribute on MS-Windows. Based on a post here: http://stackoverflow.com/questions/284115/cross-platform-hidden-file-detection """ if isinstance(filepath, bytes): filepath = filepath.decode(sys.getfilesystemencoding()) try: attrs = ctypes.windll.kernel32.GetFileAttributesW(filepath) assert attrs != -1 result = bool(attrs & 2) except (AttributeError, AssertionError): result = False return result else: def _has_hidden_attribute(filepath): return False def is_path_hidden(filepath): """ Determines if a given file or directory is hidden. Parameters ---------- filepath : str The path to a file or directory Returns ------- hidden : bool Returns `True` if the file is hidden """ name = os.path.basename(os.path.abspath(filepath)) if isinstance(name, bytes): is_dotted = name.startswith(b".") else: is_dotted = name.startswith(".") return is_dotted or _has_hidden_attribute(filepath) def walk_skip_hidden(top, onerror=None, followlinks=False): """ A wrapper for `os.walk` that skips hidden files and directories. This function does not have the parameter `topdown` from `os.walk`: the directories must always be recursed top-down when using this function. See also -------- os.walk : For a description of the parameters """ for root, dirs, files in os.walk(top, topdown=True, onerror=onerror, followlinks=followlinks): # These lists must be updated in-place so os.walk will skip # hidden directories dirs[:] = [d for d in dirs if not is_path_hidden(d)] files[:] = [f for f in files if not is_path_hidden(f)] yield root, dirs, files def write_if_different(filename, data): """ Write ``data`` to ``filename``, if the content of the file is different. This can be useful if e.g. generating ``.c`` or ``.h`` files, to make sure that Python does not re-build unchanged files. Parameters ---------- filename : str or `pathlib.Path` The file name to be written to. data : bytes The data to be written to ``filename``. """ filepath = Path(filename) assert isinstance(data, bytes) if filepath.exists(): original_data = filepath.read_bytes() else: original_data = None if original_data != data: filepath.write_bytes(data) def import_file(filename, name=None): """ Imports a module from a single file without importing the package that the file is in. This is useful for cases where a file needs to be imported from ``setup_package.py`` files without importing the parent package. The returned module will have the optional ``name`` if given, or else a name generated from the filename. """ # Specifying a traditional dot-separated fully qualified name here # results in a number of "Parent module '...' not found while # handling absolute import" warnings. Using the same name, the # namespaces of the modules get merged together. So, this # generates an underscore-separated name which is more likely to # be unique, and it doesn't really matter because the name isn't # used directly here anyway. filepath = Path(filename) if name is None: name = "_".join(filepath.resolve().with_suffix("").parts[1:]) if not filepath.exists(): raise ImportError(f"Could not import file {filepath}") loader = import_machinery.SourceFileLoader(name, str(filepath)) spec = spec_from_file_location(name, str(filepath)) mod = module_from_spec(spec) loader.exec_module(mod) return mod ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/extension_helpers/conftest.py0000644000175100001660000000451714703744231022624 0ustar00runnerdocker# This file contains settings for pytest that are specific to extension-helpers. # Since we run many of the tests in sub-processes, we need to collect coverage # data inside each subprocess and then combine it into a single .coverage file. # To do this we set up a list which run_setup appends coverage objects to. # This is not intended to be used by packages other than extension-helpers. import glob import os try: from coverage import CoverageData from coverage import __version__ as coverage_version except ImportError: HAS_COVERAGE = False CoverageData = None else: # Set to the major version number HAS_COVERAGE = int(coverage_version.split(".")[0]) SUBPROCESS_COVERAGE = [] def pytest_configure(config): if HAS_COVERAGE: SUBPROCESS_COVERAGE.clear() def pytest_unconfigure(config): if HAS_COVERAGE: # Add all files from extension_helpers to make sure we compute the total # coverage, not just the coverage of the files that have non-zero # coverage. lines = {} for filename in glob.glob(os.path.join("extension_helpers", "**", "*.py"), recursive=True): lines[os.path.abspath(filename)] = [] for cdata in SUBPROCESS_COVERAGE: # For each CoverageData object, we go through all the files and # change the filename from one which might be a temporary path # to the local filename. We then only keep files that actually # exist. for filename in cdata.measured_files(): try: pos = filename.rindex("extension_helpers") except ValueError: continue short_filename = filename[pos:] if os.path.exists(short_filename): lines[os.path.abspath(short_filename)].extend(cdata.lines(filename)) if HAS_COVERAGE >= 5: # Support coverage<5 and >=5; see # https://github.com/astropy/extension-helpers/issues/24 # We create an empty coverage data object combined_cdata = CoverageData(suffix="subprocess") combined_cdata.add_lines(lines) combined_cdata.write() else: combined_cdata = CoverageData() combined_cdata.add_lines(lines) combined_cdata.write_file(".coverage.subprocess") ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1729087650.9271364 extension_helpers-1.2.0/extension_helpers/src/0000755000175100001660000000000014703744243021210 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/extension_helpers/src/compiler.c0000644000175100001660000000524014703744231023164 0ustar00runnerdocker#include /*************************************************************************** * Macros for determining the compiler version. * * These are borrowed from boost, and majorly abridged to include only * the compilers we care about. ***************************************************************************/ #define STRINGIZE(X) DO_STRINGIZE(X) #define DO_STRINGIZE(X) #X #if defined __clang__ /* Clang C++ emulates GCC, so it has to appear early. */ # define COMPILER "Clang version " __clang_version__ #elif defined(__INTEL_COMPILER) || defined(__ICL) || defined(__ICC) || defined(__ECC) /* Intel */ # if defined(__INTEL_COMPILER) # define INTEL_VERSION __INTEL_COMPILER # elif defined(__ICL) # define INTEL_VERSION __ICL # elif defined(__ICC) # define INTEL_VERSION __ICC # elif defined(__ECC) # define INTEL_VERSION __ECC # endif # define COMPILER "Intel C compiler version " STRINGIZE(INTEL_VERSION) #elif defined(__GNUC__) /* gcc */ # define COMPILER "GCC version " __VERSION__ #elif defined(__SUNPRO_CC) /* Sun Workshop Compiler */ # define COMPILER "Sun compiler version " STRINGIZE(__SUNPRO_CC) #elif defined(_MSC_VER) /* Microsoft Visual C/C++ Must be last since other compilers define _MSC_VER for compatibility as well */ # if _MSC_VER < 1200 # define COMPILER_VERSION 5.0 # elif _MSC_VER < 1300 # define COMPILER_VERSION 6.0 # elif _MSC_VER == 1300 # define COMPILER_VERSION 7.0 # elif _MSC_VER == 1310 # define COMPILER_VERSION 7.1 # elif _MSC_VER == 1400 # define COMPILER_VERSION 8.0 # elif _MSC_VER == 1500 # define COMPILER_VERSION 9.0 # elif _MSC_VER == 1600 # define COMPILER_VERSION 10.0 # else # define COMPILER_VERSION _MSC_VER # endif # define COMPILER "Microsoft Visual C++ version " STRINGIZE(COMPILER_VERSION) #else /* Fallback */ # define COMPILER "Unknown compiler" #endif /*************************************************************************** * Module-level ***************************************************************************/ struct module_state { /* The Sun compiler can't handle empty structs */ #if defined(__SUNPRO_C) || defined(_MSC_VER) int _dummy; #endif }; static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "compiler_version", NULL, sizeof(struct module_state), NULL, NULL, NULL, NULL, NULL }; #define INITERROR return NULL PyMODINIT_FUNC PyInit_compiler_version(void) { PyObject* m; m = PyModule_Create(&moduledef); if (m == NULL) INITERROR; PyModule_AddStringConstant(m, "compiler", COMPILER); return m; } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1729087650.9281363 extension_helpers-1.2.0/extension_helpers/tests/0000755000175100001660000000000014703744243021563 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/extension_helpers/tests/__init__.py0000644000175100001660000001047614703744231023701 0ustar00runnerdockerimport os import subprocess as sp import sys import pytest from ..conftest import HAS_COVERAGE, SUBPROCESS_COVERAGE, CoverageData PACKAGE_DIR = os.path.dirname(__file__) def run_cmd(cmd, args, path=None, raise_error=True): """ Runs a shell command with the given argument list. Changes directory to ``path`` if given, otherwise runs the command in the current directory. Returns a 3-tuple of (stdout, stderr, exit code) If ``raise_error=True`` raise an exception on non-zero exit codes. """ if path is not None: # Transparently support py.path objects path = str(path) p = sp.Popen([cmd] + list(args), stdout=sp.PIPE, stderr=sp.PIPE, cwd=path) streams = tuple(s.decode("latin1").strip() for s in p.communicate()) return_code = p.returncode if raise_error and return_code != 0: raise RuntimeError( f"The command `{cmd}` with args {list(args)!r} exited with code {return_code}.\n" f"Stdout:\n\n{streams[0]}\n\nStderr:\n\n{streams[1]}" ) return streams + (return_code,) def run_setup(setup_script, args): # This used to call setuptools.sandbox's run_setup, but due to issues with # this and Cython (which caused segmentation faults), we now use subprocess. setup_script = os.path.abspath(setup_script) path = os.path.dirname(setup_script) setup_script = os.path.basename(setup_script) if HAS_COVERAGE: # In this case, we run the command using the coverage command and we # then collect the coverage data into a SUBPROCESS_COVERAGE list which # is set up at the start of the testing process and is then combined # into a single .coverage file at the end of the testing process. p = sp.Popen( ["coverage", "run", setup_script] + list(args), cwd=path, stdout=sp.PIPE, stderr=sp.PIPE ) stdout, stderr = p.communicate() cdata = CoverageData() if HAS_COVERAGE >= 5: # Support coverage<5 and >=5; see # https://github.com/astropy/extension-helpers/issues/24 cdata.read() else: cdata.read_file(os.path.join(path, ".coverage")) SUBPROCESS_COVERAGE.append(cdata) else: # Otherwise we just run the tests with Python p = sp.Popen( [sys.executable, setup_script] + list(args), cwd=path, stdout=sp.PIPE, stderr=sp.PIPE ) stdout, stderr = p.communicate() sys.stdout.write(stdout.decode("utf-8")) sys.stderr.write(stderr.decode("utf-8")) if p.returncode != 0: raise SystemExit(p.returncode) TEST_PACKAGE_SETUP_PY = """\ #!/usr/bin/env python from setuptools import setup NAME = 'extension-helpers-test' VERSION = {version!r} setup(name=NAME, version=VERSION, packages=['_extension_helpers_test_'], zip_safe=False) """ def create_testpackage(tmp_path, version="0.1"): source = tmp_path / "testpkg" os.mkdir(source) with source.as_cwd(): source.mkdir("_extension_helpers_test_") init = source.join("_extension_helpers_test_", "__init__.py") init.write(f"__version__ = {version!r}") setup_py = TEST_PACKAGE_SETUP_PY.format(version=version) source.join("setup.py").write(setup_py) # Make the new test package into a git repo run_cmd("git", ["init"]) run_cmd("git", ["add", "--all"]) run_cmd("git", ["commit", "-m", "test package"]) return source @pytest.fixture def testpackage(tmp_path, version="0.1"): """ This fixture creates a simplified package called _extension_helpers_test_ used primarily for testing ah_boostrap, but without using the extension_helpers package directly and getting it confused with the extension_helpers package already under test. """ return create_testpackage(tmp_path, version=version) def cleanup_import(package_name): """Remove all references to package_name from sys.modules""" for k in list(sys.modules): if not isinstance(k, str): # Some things will actually do this =_= continue elif k.startswith("extension_helpers.tests"): # Don't delete imported test modules or else the tests will break, # badly continue if k == package_name or k.startswith(package_name + "."): del sys.modules[k] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/extension_helpers/tests/py311_backports.py0000644000175100001660000000564314703744231025067 0ustar00runnerdocker""" PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 -------------------------------------------- 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation; All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python. 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License Agreement. """ import os from contextlib import AbstractContextManager # this class is vendored from Python 3.11.0 class chdir(AbstractContextManager): """Non thread-safe context manager to change the current working directory.""" def __init__(self, path): self.path = path self._old_cwd = [] def __enter__(self): self._old_cwd.append(os.getcwd()) os.chdir(self.path) def __exit__(self, *excinfo): os.chdir(self._old_cwd.pop()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/extension_helpers/tests/test_openmp_helpers.py0000644000175100001660000000305214703744231026211 0ustar00runnerdockerimport os import types from importlib import machinery import pytest from setuptools import Extension from .._openmp_helpers import add_openmp_flags_if_available, generate_openmp_enabled_py @pytest.fixture def openmp_expected(request): try: openmp_expected = request.config.getoption("--openmp-expected") if openmp_expected is not None: return openmp_expected.lower() == "true" except ValueError: return None def test_add_openmp_flags_if_available(openmp_expected): using_openmp = add_openmp_flags_if_available(Extension("test", [])) # Make sure that on Travis (Linux) and AppVeyor OpenMP does get used (for # MacOS X usually it will not work but this will depend on the compiler). # Having this is useful because we'll find out if OpenMP no longer works # for any reason on platforms on which it does work at the time of writing. if openmp_expected is not None: assert openmp_expected is using_openmp def test_generate_openmp_enabled_py(openmp_expected): # Test file generation generate_openmp_enabled_py("") assert os.path.isfile("openmp_enabled.py") # Load openmp_enabled file as a module to check the result loader = machinery.SourceFileLoader("openmp_enabled", "openmp_enabled.py") mod = types.ModuleType(loader.name) loader.exec_module(mod) is_openmp_enabled = mod.is_openmp_enabled() # Test is_openmp_enabled() assert isinstance(is_openmp_enabled, bool) if openmp_expected is not None: assert openmp_expected is is_openmp_enabled ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/extension_helpers/tests/test_setup_helpers.py0000644000175100001660000003115614703744231026061 0ustar00runnerdockerimport importlib import os import subprocess import sys import uuid from textwrap import dedent import pytest from .._setup_helpers import get_compiler, get_extensions from . import cleanup_import, run_setup if sys.version_info >= (3, 11): from contextlib import chdir else: from .py311_backports import chdir extension_helpers_PATH = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", "..") ) # noqa def teardown_module(module): # Remove file generated by test_generate_openmp_enabled_py but # somehow needed in test_cython_autoextensions tmpfile = "openmp_enabled.py" if os.path.exists(tmpfile): os.remove(tmpfile) POSSIBLE_COMPILERS = ["unix", "msvc", "bcpp", "cygwin", "mingw32"] def test_get_compiler(): assert get_compiler() in POSSIBLE_COMPILERS def _extension_test_package(tmp_path, request, extension_type="c", include_numpy=False): """Creates a simple test package with an extension module.""" test_pkg = tmp_path / "test_pkg" os.makedirs(test_pkg / "helpers_test_package") (test_pkg / "helpers_test_package" / "__init__.py").touch() # TODO: It might be later worth making this particular test package into a # reusable fixture for other build_ext tests if extension_type in ("c", "both"): # A minimal C extension for testing (test_pkg / "helpers_test_package" / "unit01.c").write_text( dedent( """\ #include static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "unit01", NULL, -1, NULL }; PyMODINIT_FUNC PyInit_unit01(void) { return PyModule_Create(&moduledef); } """ ) ) if extension_type in ("pyx", "both"): # A minimal Cython extension for testing (test_pkg / "helpers_test_package" / "unit02.pyx").write_text( dedent( """\ print("Hello cruel angel.") """ ) ) if extension_type == "c": extensions = ["unit01.c"] elif extension_type == "pyx": extensions = ["unit02.pyx"] elif extension_type == "both": extensions = ["unit01.c", "unit02.pyx"] include_dirs = ["numpy"] if include_numpy else [] extensions_list = [ f"Extension('helpers_test_package.{os.path.splitext(extension)[0]}', " f"[join('helpers_test_package', '{extension}')], " f"{include_dirs=})" for extension in extensions ] (test_pkg / "helpers_test_package" / "setup_package.py").write_text( dedent( """\ from setuptools import Extension from os.path import join def get_extensions(): return [{}] """.format( ", ".join(extensions_list) ) ) ) (test_pkg / "setup.py").write_text( dedent( f"""\ import sys from os.path import join from setuptools import setup, find_packages sys.path.insert(0, r'{extension_helpers_PATH}') from extension_helpers import get_extensions setup( name='helpers_test_package', version='0.1', packages=find_packages(), ext_modules=get_extensions() ) """ ) ) if "" in sys.path: sys.path.remove("") sys.path.insert(0, "") def finalize(): cleanup_import("helpers_test_package") request.addfinalizer(finalize) return test_pkg @pytest.fixture def extension_test_package(tmp_path, request): return _extension_test_package(tmp_path, request, extension_type="both") @pytest.fixture def c_extension_test_package(tmp_path, request): # Check whether numpy is installed in the test environment has_numpy = bool(importlib.util.find_spec("numpy")) return _extension_test_package(tmp_path, request, extension_type="c", include_numpy=has_numpy) @pytest.fixture def pyx_extension_test_package(tmp_path, request): return _extension_test_package(tmp_path, request, extension_type="pyx") def test_cython_autoextensions(tmp_path): """ Regression test for https://github.com/astropy/astropy-helpers/pull/19 Ensures that Cython extensions in sub-packages are discovered and built only once. """ # Make a simple test package test_pkg = tmp_path / "test_pkg" os.makedirs(test_pkg / "yoda" / "luke") (test_pkg / "yoda" / "__init__.py").touch() (test_pkg / "yoda" / "luke" / "__init__.py").touch() (test_pkg / "yoda" / "luke" / "dagobah.pyx").write_text("""def testfunc(): pass""") # Required, currently, for get_extensions to work ext_modules = get_extensions(str(test_pkg)) assert len(ext_modules) == 2 assert ext_modules[0].name == "yoda.luke.dagobah" def test_compiler_module(capsys, c_extension_test_package): """ Test ensuring that the compiler module is built and installed for packages that have extension modules. """ test_pkg = c_extension_test_package install_temp = test_pkg / "install_temp" os.mkdir(install_temp) with chdir(test_pkg): # This is one of the simplest ways to install just a package into a # test directory run_setup( "setup.py", [ "install", "--single-version-externally-managed", f"--install-lib={install_temp}", "--record={}".format(install_temp / "record.txt"), ], ) with chdir(install_temp): import helpers_test_package # Make sure we imported the helpers_test_package package from the correct place dirname = os.path.abspath(os.path.dirname(helpers_test_package.__file__)) assert dirname == str(install_temp / "helpers_test_package") import helpers_test_package.compiler_version assert helpers_test_package.compiler_version != "unknown" @pytest.mark.parametrize("use_extension_helpers", [None, False, True]) @pytest.mark.parametrize("pyproject_use_helpers", [None, False, True]) def test_no_setup_py(tmp_path, use_extension_helpers, pyproject_use_helpers): """ Test that makes sure that extension-helpers can be enabled without a setup.py file. """ package_name = "helpers_test_package_" + str(uuid.uuid4()).replace("-", "_") test_pkg = tmp_path / "test_pkg" os.makedirs(test_pkg / package_name) (test_pkg / package_name / "__init__.py").touch() simple_c = test_pkg / package_name / "simple.c" simple_c.write_text( dedent( """\ #include static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "simple", NULL, -1, NULL }; PyMODINIT_FUNC PyInit_simple(void) { return PyModule_Create(&moduledef); } """ ) ) (test_pkg / package_name / "setup_package.py").write_text( dedent( f"""\ from setuptools import Extension from os.path import join def get_extensions(): return [Extension('{package_name}.simple', [join('{package_name}', 'simple.c')])] """ ) ) if use_extension_helpers is None: (test_pkg / "setup.cfg").write_text( dedent( f"""\ [metadata] name = {package_name} version = 0.1 [options] packages = find: """ ) ) else: (test_pkg / "setup.cfg").write_text( dedent( f"""\ [metadata] name = {package_name} version = 0.1 [options] packages = find: [extension-helpers] use_extension_helpers = {str(use_extension_helpers).lower()} """ ) ) if pyproject_use_helpers is None: (test_pkg / "pyproject.toml").write_text( dedent( """\ [build-system] requires = ["setuptools>=43.0.0", "wheel"] build-backend = 'setuptools.build_meta' """ ) ) else: (test_pkg / "pyproject.toml").write_text( dedent( f"""\ [build-system] requires = ["setuptools>=43.0.0", "wheel"] build-backend = 'setuptools.build_meta' [tool.extension-helpers] use_extension_helpers = {str(pyproject_use_helpers).lower()} """ ) ) install_temp = test_pkg / "install_temp" os.mkdir(install_temp) with chdir(test_pkg): # NOTE: we disable build isolation as we need to pick up the current # developer version of extension-helpers subprocess.call( [ sys.executable, "-m", "pip", "install", ".", "--no-build-isolation", f"--target={install_temp}", ] ) if "" in sys.path: sys.path.remove("") sys.path.insert(0, "") with chdir(install_temp): importlib.import_module(package_name) if use_extension_helpers or (use_extension_helpers is None and pyproject_use_helpers): compiler_version_mod = importlib.import_module(package_name + ".compiler_version") assert compiler_version_mod.compiler != "unknown" else: try: importlib.import_module(package_name + ".compiler_version") except ImportError: pass else: raise AssertionError(package_name + ".compiler_version should not exist") @pytest.mark.parametrize("pyproject_use_helpers", [None, False, True]) def test_only_pyproject(tmp_path, pyproject_use_helpers): """ Test that makes sure that extension-helpers can be enabled without a setup.py and without a setup.cfg file. """ pytest.importorskip("setuptools", minversion="62.0") package_name = "helpers_test_package_" + str(uuid.uuid4()).replace("-", "_") test_pkg = tmp_path / "test_pkg" os.makedirs(test_pkg / package_name) (test_pkg / package_name / "__init__.py").touch() simple_pyx = test_pkg / package_name / "simple.pyx" simple_pyx.write_text( dedent( """\ def test(): pass """ ) ) if pyproject_use_helpers is None: extension_helpers_option = "" else: extension_helpers_option = dedent( f""" [tool.extension-helpers] use_extension_helpers = {str(pyproject_use_helpers).lower()} """ ) buildtime_requirements = ["setuptools>=43.0.0", "wheel", "Cython"] (test_pkg / "pyproject.toml").write_text( dedent( f"""\ [project] name = "{package_name}" version = "0.1" [tool.setuptools.packages] find = {{namespaces = false}} [build-system] requires = [{', '.join(f'"{_}"' for _ in buildtime_requirements)}] build-backend = 'setuptools.build_meta' """ ) + extension_helpers_option ) install_temp = test_pkg / "install_temp" os.mkdir(install_temp) with chdir(test_pkg): # NOTE: we disable build isolation as we need to pick up the current # developer version of extension-helpers # In order to do so, we need to ensure that build-time dependencies are # installed first cmd1 = [ sys.executable, "-m", "pip", "install", *buildtime_requirements, f"--target={install_temp}", ] subprocess.call(cmd1) cmd2 = [ sys.executable, "-m", "pip", "install", ".", "--no-build-isolation", f"--target={install_temp}", ] subprocess.call(cmd2) if "" in sys.path: sys.path.remove("") sys.path.insert(0, "") with chdir(install_temp): importlib.import_module(package_name) if pyproject_use_helpers: compiler_version_mod = importlib.import_module(package_name + ".compiler_version") assert compiler_version_mod.compiler != "unknown" else: try: importlib.import_module(package_name + ".compiler_version") except ImportError: pass else: raise AssertionError(package_name + ".compiler_version should not exist") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/extension_helpers/tests/test_utils.py0000644000175100001660000000170114703744231024330 0ustar00runnerdockerimport os import time import pytest from .._utils import import_file, write_if_different @pytest.mark.parametrize("path_type", ("str", "path")) def test_import_file(tmp_path, path_type): filepath = tmp_path / "spam.py" if path_type == "str": filepath = str(filepath) with open(filepath, "w") as f: f.write("magic = 12345") module = import_file(filepath) assert module.magic == 12345 @pytest.mark.parametrize("path_type", ("str", "path")) def test_write_if_different(tmp_path, path_type): filepath = tmp_path / "test.txt" if path_type == "str": filepath = str(filepath) write_if_different(filepath, b"abc") time1 = os.path.getmtime(filepath) time.sleep(0.01) write_if_different(filepath, b"abc") time2 = os.path.getmtime(filepath) assert time2 == time1 time.sleep(0.01) write_if_different(filepath, b"abcd") time3 = os.path.getmtime(filepath) assert time3 > time1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087650.0 extension_helpers-1.2.0/extension_helpers/version.py0000644000175100001660000000063314703744242022461 0ustar00runnerdocker# file generated by setuptools_scm # don't change, don't track in version control TYPE_CHECKING = False if TYPE_CHECKING: from typing import Tuple, Union VERSION_TUPLE = Tuple[Union[int, str], ...] else: VERSION_TUPLE = object version: str __version__: str __version_tuple__: VERSION_TUPLE version_tuple: VERSION_TUPLE __version__ = version = '1.2.0' __version_tuple__ = version_tuple = (1, 2, 0) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1729087650.9291365 extension_helpers-1.2.0/extension_helpers.egg-info/0000755000175100001660000000000014703744243022113 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087650.0 extension_helpers-1.2.0/extension_helpers.egg-info/PKG-INFO0000644000175100001660000000421414703744242023210 0ustar00runnerdockerMetadata-Version: 2.1 Name: extension-helpers Version: 1.2.0 Summary: Utilities for building and installing packages with compiled extensions Author-email: The Astropy Developers License: BSD 3-Clause License Project-URL: Homepage, https://github.com/astropy/extension-helpers Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Framework :: Setuptools Plugin Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Build Tools Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: System :: Archiving :: Packaging Provides: extension_helpers Requires-Python: >=3.8 Description-Content-Type: text/x-rst License-File: LICENSE.rst Requires-Dist: setuptools>=40.2 Requires-Dist: tomli>=1.0.0; python_version < "3.11" Provides-Extra: test Requires-Dist: wheel; extra == "test" Requires-Dist: pytest; extra == "test" Requires-Dist: pytest-cov; extra == "test" Requires-Dist: cython; extra == "test" Provides-Extra: docs Requires-Dist: sphinx; extra == "docs" Requires-Dist: sphinx-automodapi; extra == "docs" extension-helpers ================= .. image:: https://github.com/astropy/extension-helpers/actions/workflows/main.yml/badge.svg :target: https://github.com/astropy/extension-helpers/actions/workflows/main.yml .. image:: https://codecov.io/gh/astropy/extension-helpers/branch/main/graph/badge.svg :target: https://codecov.io/gh/astropy/extension-helpers .. image:: https://readthedocs.org/projects/extension-helpers/badge/?version=latest :target: https://extension-helpers.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status The **extension-helpers** package includes convenience helpers to assist with building Python packages with compiled C/Cython extensions. It is developed by the Astropy project but is intended to be general and usable by any Python package. For more information, see the documentation at http://extension-helpers.readthedocs.io ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087650.0 extension_helpers-1.2.0/extension_helpers.egg-info/SOURCES.txt0000644000175100001660000000213114703744242023773 0ustar00runnerdocker.gitignore .pre-commit-config.yaml .readthedocs.yaml CHANGES.md CONTRIBUTING.md LICENSE.rst MANIFEST.in README.rst conftest.py pyproject.toml tox.ini .github/dependabot.yml .github/release.yml .github/workflows/main.yml .github/workflows/update-changelog.yml docs/Makefile docs/api.rst docs/conf.py docs/index.rst docs/make.bat docs/openmp.rst docs/using.rst extension_helpers/__init__.py extension_helpers/_openmp_helpers.py extension_helpers/_setup_helpers.py extension_helpers/_utils.py extension_helpers/conftest.py extension_helpers/version.py extension_helpers.egg-info/PKG-INFO extension_helpers.egg-info/SOURCES.txt extension_helpers.egg-info/dependency_links.txt extension_helpers.egg-info/entry_points.txt extension_helpers.egg-info/not-zip-safe extension_helpers.egg-info/requires.txt extension_helpers.egg-info/top_level.txt extension_helpers/src/compiler.c extension_helpers/tests/__init__.py extension_helpers/tests/py311_backports.py extension_helpers/tests/test_openmp_helpers.py extension_helpers/tests/test_setup_helpers.py extension_helpers/tests/test_utils.py licenses/LICENSE_ASTROSCRAPPY.rst././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087650.0 extension_helpers-1.2.0/extension_helpers.egg-info/dependency_links.txt0000644000175100001660000000000114703744242026160 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087650.0 extension_helpers-1.2.0/extension_helpers.egg-info/entry_points.txt0000644000175100001660000000017414703744242025412 0ustar00runnerdocker[setuptools.finalize_distribution_options] extension_helpers_get_extensions = extension_helpers:_finalize_distribution_hook ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087650.0 extension_helpers-1.2.0/extension_helpers.egg-info/not-zip-safe0000644000175100001660000000000114703744242024340 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087650.0 extension_helpers-1.2.0/extension_helpers.egg-info/requires.txt0000644000175100001660000000020214703744242024504 0ustar00runnerdockersetuptools>=40.2 [:python_version < "3.11"] tomli>=1.0.0 [docs] sphinx sphinx-automodapi [test] wheel pytest pytest-cov cython ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087650.0 extension_helpers-1.2.0/extension_helpers.egg-info/top_level.txt0000644000175100001660000000002214703744242024636 0ustar00runnerdockerextension_helpers ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1729087650.9281363 extension_helpers-1.2.0/licenses/0000755000175100001660000000000014703744243016470 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/licenses/LICENSE_ASTROSCRAPPY.rst0000644000175100001660000000315414703744231022316 0ustar00runnerdocker# The OpenMP helpers include code heavily adapted from astroscrappy, released # under the following license: # # Copyright (c) 2015, Curtis McCully # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, this # list of conditions and the following disclaimer in the documentation and/or # other materials provided with the distribution. # * Neither the name of the Astropy Team nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/pyproject.toml0000644000175100001660000001007114703744231017573 0ustar00runnerdocker[project] name = "extension-helpers" authors = [{name = "The Astropy Developers", email = "astropy.team@gmail.com"}] license = {text = "BSD 3-Clause License"} description = "Utilities for building and installing packages with compiled extensions" readme = "README.rst" classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Framework :: Setuptools Plugin", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Topic :: Software Development :: Build Tools", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Archiving :: Packaging", ] requires-python = ">=3.8" dependencies = [ "setuptools>=40.2", "tomli>=1.0.0 ; python_version < '3.11'", ] dynamic = ["version"] [project.urls] Homepage = "https://github.com/astropy/extension-helpers" [project.entry-points."setuptools.finalize_distribution_options"] extension_helpers_get_extensions = "extension_helpers:_finalize_distribution_hook" [project.optional-dependencies] test = [ "wheel", "pytest", "pytest-cov", "cython", ] docs = [ "sphinx", "sphinx-automodapi", ] [tool.setuptools] zip-safe = false provides = ["extension_helpers"] license-files = ["LICENSE.rst"] include-package-data = false [tool.setuptools.packages] find = {namespaces = false} [tool.setuptools.package-data] extension_helpers = ["src/compiler.c"] [tool.pytest.ini_options] minversion = "6" addopts = ["-ra", "--strict-config", "--strict-markers"] log_cli_level = "INFO" xfail_strict = true testpaths = ['"extension_helpers"', '"docs"'] norecursedirs = ["build", "docs/_build"] markers = ["flaky"] filterwarnings = ["error"] [tool.coverage.run] omit = [ "extension_helpers/*/setup_package.py", "extension_helpers/tests/*", "extension_helpers/conftest.py", "*/extension_helpers/*/setup_package.py", "*/extension_helpers/tests/*", "*/extension_helpers/conftest.py", ] [tool.coverage.report] exclude_lines = [ # Have to re-enable the standard pragma "pragma: no cover", # Don't complain about packages we have installed "except ImportError", # Don't complain if tests don't hit assertions "raise AssertionError", "raise NotImplementedError", # Don't complain about script hooks 'def main\(.*\):', # Ignore branches that don't pertain to this version of Python "pragma: py{ignore_python_version}", # Don't complain about IPython completion helper "def _ipython_key_completions_", ] [build-system] requires = ["setuptools>=43.0.0", "setuptools_scm>=6.2"] build-backend = 'setuptools.build_meta' [tool.setuptools_scm] write_to = "extension_helpers/version.py" [tool.isort] profile = "black" multi_line_output = 3 extend_skip_glob = [ "docs/*", "setup.py"] line_length = 100 known_third_party = ["astropy"] known_first_party = ["reproject"] group_by_package = true indented_import_headings = false length_sort_sections = ["future", "stdlib"] [tool.black] line-length = 100 target-version = ['py38'] [tool.numpydoc_validation] checks = [ "all", # report on all checks, except the below "EX01", "SA01", "SS06", "ES01", "GL08", ] [tool.repo-review] ignore = [ "MY", # ignore MyPy setting checks "GH102", # auto-cancel of PRs "PC111", # ignore using `blacken-docs` in pre-commit "PC140", # ignore using `mypy` in pre-commit "PC180", # ignore using `prettier` in pre-commit "PC901", # ignore using custom update message (we have many of the default ones in our history already) "PC170", # ignore using pygrep "PY005", # ignore having a tests/ folder ] [tool.ruff] [tool.ruff.lint] extend-select = [ "B", # flake8-bugbear "I", # isort "UP", # pyupgrade ] [tool.ruff.lint.extend-per-file-ignores] "docs/conf.py" = ["F405"] # Sphinx injects variables into namespace "extension_helpers/_openmp_helpers.py" = ["UP032"] # Avoid using f-strings in logger [tool.codespell] ignore-words-list = """ ccompiler, """ ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1729087650.9301364 extension_helpers-1.2.0/setup.cfg0000644000175100001660000000004614703744243016504 0ustar00runnerdocker[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1729087641.0 extension_helpers-1.2.0/tox.ini0000644000175100001660000000345514703744231016202 0ustar00runnerdocker[tox] envlist = py{38,39,310,311,312}-test{,-osxclang,-linuxgcc}{,-conda}{,-devdeps} py{38,39,310,311,312}-downstream style # conda jobs need this because it is pulling in tox 3, not 4 isolated_build = true [testenv] passenv = CONDA_BUILD_SYSROOT,CI setenv = osxclang: CC=clang-10 linuxgcc: CC=gcc_linux-64 changedir = test: .tmp/{envname} whitelist_externals = devdeps: bash description = test: run tests with pytest devdeps: run tests with developer versions of setuptools oldestdeps: run tests with oldest supported version of setuptools deps = oldestdeps: setuptools==42.0 devdeps: git+https://github.com/pypa/setuptools.git conda_deps = osxclang: clang_osx-64==10 osxclang: llvm-openmp linuxgcc: gcc_linux-64 conda_channels = linuxgcc: conda-forge extras = test: test commands = pip freeze test: python -c 'import setuptools; print(setuptools.__version__)' test: pytest --pyargs extension_helpers {toxinidir}/docs --cov extension_helpers --cov-config={toxinidir}/pyproject.toml {posargs} [testenv:py{38,39,310,311,312}-downstream] changedir = .tmp/downstream commands = pip install setuptools setuptools_scm wheel cython numpy pip install --no-build-isolation "astropy[test] @ git+https://github.com/astropy/astropy.git" pytest --pyargs astropy -m "not hypothesis" -Wdefault pip install --no-build-isolation "sunpy[all,tests] @ git+https://github.com/sunpy/sunpy.git" pip freeze pytest --pyargs sunpy -k "not test_saveframe and not test_hpc_observer_version and not test_hcc_observer_version and not test_simple_write_compressed_difftypeinst" -Wdefault [testenv:style] skip_install = true deps = pre-commit commands = pre-commit install-hooks pre-commit run --color always --all-files --show-diff-on-failure