././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1576672145.164062 extension-helpers-0.1/0000755000077000000240000000000000000000000014745 5ustar00tomstaff00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1576672145.1426864 extension-helpers-0.1/.circleci/0000755000077000000240000000000000000000000016600 5ustar00tomstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576529417.0 extension-helpers-0.1/.circleci/config.yml0000644000077000000240000000156100000000000020573 0ustar00tomstaff00000000000000version: 2 jobs: html-docs: docker: - image: circleci/python:3.6 steps: - checkout - run: name: Install Python dependencies command: | python3 -m venv venv . venv/bin/activate pip install .[docs] - run: name: Build Documentation command: | . venv/bin/activate cd docs make html make linkcheck - store_artifacts: path: docs/_build/html - run: name: "Built documentation is available at:" command: DOCS_URL="${CIRCLE_BUILD_URL}/artifacts/${CIRCLE_NODE_INDEX}/${CIRCLE_WORKING_DIRECTORY/#\~/$HOME}/docs/_build/html/index.html"; echo $DOCS_URL workflows: version: 2 tests_and_docs: jobs: - html-docs notify: webhooks: - url: https://giles.cadair.com/circleci ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576529417.0 extension-helpers-0.1/.coveragerc0000644000077000000240000000125700000000000017073 0ustar00tomstaff00000000000000[run] source = extension_helpers omit = extension_helpers/*/setup_package.py extension_helpers/tests/* extension_helpers/conftest.py [report] omit = extension_helpers/*/setup_package.py extension_helpers/tests/* extension_helpers/conftest.py 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} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576529417.0 extension-helpers-0.1/.gitignore0000644000077000000240000000116300000000000016736 0ustar00tomstaff00000000000000# 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576529417.0 extension-helpers-0.1/.readthedocs.yml0000644000077000000240000000021700000000000020033 0ustar00tomstaff00000000000000build: image: latest python: version: 3.6 pip_install: true extra_requirements: ['docs'] # Don't build any extra formats formats: [] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672082.0 extension-helpers-0.1/CHANGES.rst0000644000077000000240000000016400000000000016550 0ustar00tomstaff000000000000000.1 (2019-12-18) ---------------- Initial release of extension-helpers, which was forked from astropy-helpers 4.0. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576529417.0 extension-helpers-0.1/CONTRIBUTING.md0000644000077000000240000000217700000000000017205 0ustar00tomstaff00000000000000Contributing 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/master/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). The key subtlety lies in understanding the relationship between ``astropy`` and ``extension-helpers``. This package contains the build, installation, and documentation tools used by astropy. It also includes support for the ``setup.py test`` command, though Astropy is still required for this to function (it does not currently include the full Astropy test runner). So issues or improvements to that functionality should be addressed in this package. Any other aspect of the [astropy core package](http://github.com/astropy/astropy) (or any other package that uses ``extension-helpers``) should be addressed in the github repository for that package. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672090.0 extension-helpers-0.1/LICENSE.rst0000644000077000000240000000272300000000000016565 0ustar00tomstaff00000000000000Copyright (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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672040.0 extension-helpers-0.1/MANIFEST.in0000644000077000000240000000022000000000000016475 0ustar00tomstaff00000000000000include README.rst include CHANGES.rst include LICENSE.rst recursive-include licenses * include ah_bootstrap.py exclude *.pyc *.o prune build ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1576672145.1644526 extension-helpers-0.1/PKG-INFO0000644000077000000240000000342500000000000016046 0ustar00tomstaff00000000000000Metadata-Version: 2.1 Name: extension-helpers Version: 0.1 Summary: Utilities for building and installing packages in the Astropy ecosystem Home-page: https://github.com/astropy/astropy-helpers Author: The Astropy Developers Author-email: astropy.team@gmail.com License: BSD 3-Clause License Description: extension-helpers ================= .. image:: https://dev.azure.com/astropy-project/extension-helpers/_apis/build/status/astropy.extension-helpers?branchName=master :target: https://dev.azure.com/astropy-project/extension-helpers/_build/latest?definitionId=4&branchName=master .. image:: https://codecov.io/gh/astropy/extension-helpers/branch/master/graph/badge.svg :target: https://codecov.io/gh/astropy/extension-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. For more information, see the documentation at http://extension-helpers.readthedocs.io Platform: UNKNOWN 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.6 Provides-Extra: test Provides-Extra: docs ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672040.0 extension-helpers-0.1/README.rst0000644000077000000240000000137000000000000016435 0ustar00tomstaff00000000000000extension-helpers ================= .. image:: https://dev.azure.com/astropy-project/extension-helpers/_apis/build/status/astropy.extension-helpers?branchName=master :target: https://dev.azure.com/astropy-project/extension-helpers/_build/latest?definitionId=4&branchName=master .. image:: https://codecov.io/gh/astropy/extension-helpers/branch/master/graph/badge.svg :target: https://codecov.io/gh/astropy/extension-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. For more information, see the documentation at http://extension-helpers.readthedocs.io ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672040.0 extension-helpers-0.1/azure-pipelines.yml0000644000077000000240000000147600000000000020614 0ustar00tomstaff00000000000000resources: repositories: - repository: OpenAstronomy type: github endpoint: astropy name: OpenAstronomy/azure-pipelines-templates ref: master jobs: - template: run-tox-env.yml@OpenAstronomy parameters: posargs: --openmp-expected=True coverage: codecov envs: # Code style - linux: style # Docs - linux: build_docs # Standard tests - linux: py36-test - linux: py37-test - linux: py38-test - linux: py38-test-dev - macos: py36-test posargs: --openmp-expected=False - macos: py38-test-dev posargs: --openmp-expected=False - windows: py36-test - windows: py38-test-dev # Test with more compilers, for the OpenMP helpers - macos: py38-test-osxgcc-conda - macos: py38-test-osxclang-conda - linux: py38-test-linuxgcc-conda ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672040.0 extension-helpers-0.1/conftest.py0000644000077000000240000000021000000000000017135 0ustar00tomstaff00000000000000def pytest_addoption(parser): parser.addoption("--openmp-expected", action="store", default=None, help="help") ././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1576672145.14892 extension-helpers-0.1/docs/0000755000077000000240000000000000000000000015675 5ustar00tomstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576529417.0 extension-helpers-0.1/docs/Makefile0000644000077000000240000000111000000000000017326 0ustar00tomstaff00000000000000# 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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672040.0 extension-helpers-0.1/docs/api.rst0000644000077000000240000000013300000000000017175 0ustar00tomstaff00000000000000API Documentation ================= .. automodapi:: extension_helpers :no-main-docstr: ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672040.0 extension-helpers-0.1/docs/conf.py0000644000077000000240000000305300000000000017175 0ustar00tomstaff00000000000000# -*- coding: utf-8 -*- import sys from 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 = {'https://docs.python.org/3/': 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 = None # 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576529417.0 extension-helpers-0.1/docs/index.rst0000644000077000000240000000115300000000000017536 0ustar00tomstaff00000000000000Extension 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576529417.0 extension-helpers-0.1/docs/make.bat0000644000077000000240000000142300000000000017302 0ustar00tomstaff00000000000000@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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672040.0 extension-helpers-0.1/docs/openmp.rst0000644000077000000240000000114700000000000017730 0ustar00tomstaff00000000000000OpenMP 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] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672040.0 extension-helpers-0.1/docs/using.rst0000644000077000000240000000316300000000000017557 0ustar00tomstaff00000000000000Using 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"] If you have Cython extensions, you will need to make sure ``cython`` is included in the above list too. 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:`distutils.core.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. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1576672145.1551697 extension-helpers-0.1/extension_helpers/0000755000077000000240000000000000000000000020503 5ustar00tomstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672040.0 extension-helpers-0.1/extension_helpers/__init__.py0000644000077000000240000000037700000000000022623 0ustar00tomstaff00000000000000from ._distutils_helpers import get_compiler from ._openmp_helpers import add_openmp_flags_if_available from ._setup_helpers import get_extensions, pkg_config from ._utils import import_file, write_if_different from .version import version as __version__ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672040.0 extension-helpers-0.1/extension_helpers/_distutils_helpers.py0000644000077000000240000000606600000000000024772 0ustar00tomstaff00000000000000""" This module contains various utilities for introspecting the distutils module and the setup process. Some of these utilities require the `extension_helpers.setup_helpers.register_commands` function to be called first, as it will affect introspection of setuptools command-line arguments. Other utilities in this module do not have that restriction. """ import os import sys from distutils import ccompiler from distutils.dist import Distribution from distutils.errors import DistutilsError from ._utils import silence __all__ = ['get_compiler'] def get_dummy_distribution(): """ Returns a distutils Distribution object used to instrument the setup environment before calling the actual setup() function. """ # Pre-parse the Distutils command-line options and config files to if # the option is set. dist = Distribution({'script_name': os.path.basename(sys.argv[0]), 'script_args': sys.argv[1:]}) with silence(): try: dist.parse_config_files() dist.parse_command_line() except (DistutilsError, AttributeError, SystemExit): # Let distutils handle DistutilsErrors itself AttributeErrors can # get raise for ./setup.py --help SystemExit can be raised if a # display option was used, for example pass return dist def get_main_package_directory(distribution): """ Given a Distribution object, return the main package directory. """ return min(distribution.packages, key=len).replace('.', os.sep) def get_distutils_option(option, commands): """ Returns the value of the given distutils option. Parameters ---------- option : str The name of the option commands : list of str The list of commands on which this option is available Returns ------- val : str or None the value of the given distutils option. If the option is not set, returns None. """ dist = get_dummy_distribution() for cmd in commands: cmd_opts = dist.command_options.get(cmd) if cmd_opts is not None and option in cmd_opts: return cmd_opts[option][1] else: return None def get_distutils_build_option(option): """ Returns the value of the given distutils build option. Parameters ---------- option : str The name of the option Returns ------- val : str or None The value of the given distutils build option. If the option is not set, returns None. """ return get_distutils_option(option, ['build', 'build_ext', 'build_clib']) 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. """ compiler = get_distutils_build_option('compiler') if compiler is None: return ccompiler.get_default_compiler() return compiler ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672040.0 extension-helpers-0.1/extension_helpers/_openmp_helpers.py0000644000077000000240000002214500000000000024240 0ustar00tomstaff00000000000000# 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. import os import sys import glob import time import datetime import tempfile import subprocess from distutils import log from distutils.errors import LinkError, CompileError from distutils.ccompiler import new_compiler from distutils.sysconfig import get_config_var, customize_compiler from ._distutils_helpers import get_compiler __all__ = ['add_openmp_flags_if_available'] try: # Check if this has already been instantiated, only set the default once. _ASTROPY_DISABLE_SETUP_WITH_OPENMP_ except NameError: import builtins # It hasn't, so do so. builtins._ASTROPY_DISABLE_SETUP_WITH_OPENMP_ = False CCODE = """ #include #include int main(void) { #pragma omp parallel printf("nthreads=%d\\n", omp_get_num_threads()); return 0; } """ 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 ``distutils.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 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) compile_flags.append('-fopenmp') link_flags.append('-fopenmp') 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 compiliation 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') tmp_dir = tempfile.mkdtemp() 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.warn("Unexpected number of lines from output of test OpenMP " "program (output was {0})".format(output)) is_openmp_supported = False else: log.warn("Unexpected output from test OpenMP " "program (output was {0})".format(output)) is_openmp_supported = False except (CompileError, LinkError, subprocess.CalledProcessError): 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.set_threshold(log.FATAL) ret = check_openmp_support() log.set_threshold(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 _ASTROPY_DISABLE_SETUP_WITH_OPENMP_: 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.warn("Cannot compile Cython/C/C++ extension with OpenMP, reverting " "to non-parallel code") return using_openmp _IS_OPENMP_ENABLED_SRC = """ # Autogenerated by {packagetitle}'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. """ if packagename.lower() == 'astropy': packagetitle = 'Astropy' else: packagetitle = packagename epoch = int(os.environ.get('SOURCE_DATE_EPOCH', time.time())) timestamp = datetime.datetime.utcfromtimestamp(epoch) if disable_openmp is not None: import builtins builtins._ASTROPY_DISABLE_SETUP_WITH_OPENMP_ = disable_openmp if _ASTROPY_DISABLE_SETUP_WITH_OPENMP_: log.info("OpenMP support has been explicitly disabled.") openmp_support = False if _ASTROPY_DISABLE_SETUP_WITH_OPENMP_ else is_openmp_supported() src = _IS_OPENMP_ENABLED_SRC.format(packagetitle=packagetitle, 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) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672040.0 extension-helpers-0.1/extension_helpers/_setup_helpers.py0000644000077000000240000002314700000000000024105 0ustar00tomstaff00000000000000# 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 os import sys import shutil import subprocess from distutils import log from collections import defaultdict from distutils.core import Extension from setuptools import find_packages from setuptools.config import read_configuration from ._distutils_helpers import get_compiler from ._utils import import_file, walk_skip_hidden __all__ = ['get_extensions', 'pkg_config'] 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:`distutils.core.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.copy(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) 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 `~distutils.core.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 `~distutils.core.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 distutils 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 ibrary names to use if the pkg-config lookup fails, a a list of strings. Returns ------- config : dict A dictionary containing keyword arguments to :class:`~distutils.core.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 = "{0} --libs --cflags {1}".format(executable, ' '.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 = [ ("{0} failed. This may cause the build to fail below." .format(executable)), " command: {0}".format(e.cmd), " returncode: {0}".format(e.returncode), " output: {0}".format(e.output) ] log.warn('\n'.join(lines)) result['libraries'].extend(default_libraries) else: if pipe.returncode != 0: lines = [ "pkg-config could not lookup up package(s) {0}.".format( ", ".join(packages)), "This may cause the build to fail below." ] log.warn('\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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672040.0 extension-helpers-0.1/extension_helpers/_utils.py0000644000077000000240000001264600000000000022365 0ustar00tomstaff00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst import os import sys import glob import contextlib from importlib import machinery as import_machinery __all__ = ['write_if_different', 'import_file'] # Note: The following Warning subclasses are simply copies of the Warnings in # Astropy of the same names. class AstropyWarning(Warning): """ The base warning class from which all Astropy warnings should inherit. Any warning inheriting from this class is handled by the Astropy logger. """ class AstropyDeprecationWarning(AstropyWarning): """ A warning class to indicate a deprecated feature. """ class AstropyPendingDeprecationWarning(PendingDeprecationWarning, AstropyWarning): """ A warning class to indicate a soon-to-be deprecated feature. """ class _DummyFile(object): """A noop writeable object.""" errors = '' def write(self, s): pass def flush(self): pass @contextlib.contextmanager def silence(): """A context manager that silences sys.stdout and sys.stderr.""" old_stdout = sys.stdout old_stderr = sys.stderr sys.stdout = _DummyFile() sys.stderr = _DummyFile() exception_occurred = False try: yield except: # noqa exception_occurred = True # Go ahead and clean up so that exception handling can work normally sys.stdout = old_stdout sys.stderr = old_stderr raise if not exception_occurred: sys.stdout = old_stdout sys.stderr = old_stderr 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 The file name to be written to. data : bytes The data to be written to ``filename``. """ assert isinstance(data, bytes) if os.path.exists(filename): with open(filename, 'rb') as fd: original_data = fd.read() else: original_data = None if original_data != data: with open(filename, 'wb') as fd: fd.write(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 'astropy' 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. mode = 'r' if name is None: basename = os.path.splitext(filename)[0] name = '_'.join(os.path.relpath(basename).split(os.sep)[1:]) if not os.path.exists(filename): raise ImportError('Could not import file {0}'.format(filename)) loader = import_machinery.SourceFileLoader(name, filename) mod = loader.load_module() return mod ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576529417.0 extension-helpers-0.1/extension_helpers/conftest.py0000644000077000000240000000362400000000000022707 0ustar00tomstaff00000000000000# 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 os import glob try: from coverage import CoverageData except ImportError: HAS_COVERAGE = False else: HAS_COVERAGE = True if HAS_COVERAGE: SUBPROCESS_COVERAGE = [] def pytest_configure(config): if HAS_COVERAGE: SUBPROCESS_COVERAGE.clear() def pytest_unconfigure(config): if HAS_COVERAGE: # We create an empty coverage data object combined_cdata = CoverageData() # 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)) combined_cdata.add_lines(lines) combined_cdata.write_file('.coverage.subprocess') ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1576672145.1601388 extension-helpers-0.1/extension_helpers/src/0000755000077000000240000000000000000000000021272 5ustar00tomstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576529417.0 extension-helpers-0.1/extension_helpers/src/compiler.c0000644000077000000240000000524000000000000023251 0ustar00tomstaff00000000000000#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; } ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1576672145.1625779 extension-helpers-0.1/extension_helpers/tests/0000755000077000000240000000000000000000000021645 5ustar00tomstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672040.0 extension-helpers-0.1/extension_helpers/tests/__init__.py0000644000077000000240000001115300000000000023757 0ustar00tomstaff00000000000000import os import sys import subprocess as sp import pytest try: from coverage import CoverageData except ImportError: HAS_COVERAGE = False else: HAS_COVERAGE = True from ..conftest import SUBPROCESS_COVERAGE 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( "The command `{0}` with args {1!r} exited with code {2}.\n" "Stdout:\n\n{3}\n\nStderr:\n\n{4}".format( cmd, list(args), return_code, streams[0], 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() 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) @pytest.fixture(scope='function', autouse=True) def reset_distutils_log(): """ This is a setup/teardown fixture that ensures the log-level of the distutils log is always set to a default of WARN, since different settings could affect tests that check the contents of stdout. """ from distutils import log log.set_threshold(log.WARN) 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(tmpdir, version='0.1'): source = tmpdir.mkdir('testpkg') with source.as_cwd(): source.mkdir('_extension_helpers_test_') init = source.join('_extension_helpers_test_', '__init__.py') init.write('__version__ = {0!r}'.format(version)) 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(tmpdir, 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(tmpdir, 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] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672040.0 extension-helpers-0.1/extension_helpers/tests/test_openmp_helpers.py0000644000077000000240000000302500000000000026276 0ustar00tomstaff00000000000000import os import sys import types from copy import deepcopy from importlib import machinery from distutils.core import Extension import pytest from .._openmp_helpers import add_openmp_flags_if_available, generate_openmp_enabled_py @pytest.fixture def openmp_expected(request): openmp_expected = request.config.getoption("--openmp-expected") if openmp_expected is not None: return openmp_expected.lower() == 'true' 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672040.0 extension-helpers-0.1/extension_helpers/tests/test_setup_helpers.py0000644000077000000240000001257500000000000026152 0ustar00tomstaff00000000000000import os import sys import importlib from textwrap import dedent import pytest from .._setup_helpers import get_extensions from . import reset_distutils_log # noqa from . import cleanup_import, run_setup 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) def _extension_test_package(tmpdir, request, extension_type='c', include_numpy=False): """Creates a simple test package with an extension module.""" test_pkg = tmpdir.mkdir('test_pkg') test_pkg.mkdir('apyhtest_eva').ensure('__init__.py') # 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.join('apyhtest_eva', 'unit01.c').write(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.join('apyhtest_eva', 'unit02.pyx').write(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 = [ "Extension('apyhtest_eva.{0}', [join('apyhtest_eva', '{1}')], include_dirs={2})".format( os.path.splitext(extension)[0], extension, include_dirs) for extension in extensions] test_pkg.join('apyhtest_eva', 'setup_package.py').write(dedent("""\ from setuptools import Extension from os.path import join def get_extensions(): return [{0}] """.format(', '.join(extensions_list)))) test_pkg.join('setup.py').write(dedent("""\ 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='apyhtest_eva', version='0.1', packages=find_packages(), ext_modules=get_extensions() ) """.format(extension_helpers_path=extension_helpers_PATH))) if '' in sys.path: sys.path.remove('') sys.path.insert(0, '') def finalize(): cleanup_import('apyhtest_eva') request.addfinalizer(finalize) return test_pkg @pytest.fixture def extension_test_package(tmpdir, request): return _extension_test_package(tmpdir, request, extension_type='both') @pytest.fixture def c_extension_test_package(tmpdir, request): # Check whether numpy is installed in the test environment has_numpy = bool(importlib.util.find_spec('numpy')) return _extension_test_package(tmpdir, request, extension_type='c', include_numpy=has_numpy) @pytest.fixture def pyx_extension_test_package(tmpdir, request): return _extension_test_package(tmpdir, request, extension_type='pyx') def test_cython_autoextensions(tmpdir): """ Regression test for https://github.com/astropy/extension-helpers/pull/19 Ensures that Cython extensions in sub-packages are discovered and built only once. """ # Make a simple test package test_pkg = tmpdir.mkdir('test_pkg') test_pkg.mkdir('yoda').mkdir('luke') test_pkg.ensure('yoda', '__init__.py') test_pkg.ensure('yoda', 'luke', '__init__.py') test_pkg.join('yoda', 'luke', 'dagobah.pyx').write( """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.mkdir('install_temp') with test_pkg.as_cwd(): # 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', '--install-lib={0}'.format(install_temp), '--record={0}'.format(install_temp.join('record.txt'))]) with install_temp.as_cwd(): import apyhtest_eva # Make sure we imported the apyhtest_eva package from the correct place dirname = os.path.abspath(os.path.dirname(apyhtest_eva.__file__)) assert dirname == str(install_temp.join('apyhtest_eva')) import apyhtest_eva.compiler_version assert apyhtest_eva.compiler_version != 'unknown' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672144.0 extension-helpers-0.1/extension_helpers/version.py0000644000077000000240000000016200000000000022541 0ustar00tomstaff00000000000000# coding: utf-8 # file generated by setuptools_scm # don't change, don't track in version control version = '0.1' ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1576672145.159395 extension-helpers-0.1/extension_helpers.egg-info/0000755000077000000240000000000000000000000022175 5ustar00tomstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672144.0 extension-helpers-0.1/extension_helpers.egg-info/PKG-INFO0000644000077000000240000000342500000000000023276 0ustar00tomstaff00000000000000Metadata-Version: 2.1 Name: extension-helpers Version: 0.1 Summary: Utilities for building and installing packages in the Astropy ecosystem Home-page: https://github.com/astropy/astropy-helpers Author: The Astropy Developers Author-email: astropy.team@gmail.com License: BSD 3-Clause License Description: extension-helpers ================= .. image:: https://dev.azure.com/astropy-project/extension-helpers/_apis/build/status/astropy.extension-helpers?branchName=master :target: https://dev.azure.com/astropy-project/extension-helpers/_build/latest?definitionId=4&branchName=master .. image:: https://codecov.io/gh/astropy/extension-helpers/branch/master/graph/badge.svg :target: https://codecov.io/gh/astropy/extension-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. For more information, see the documentation at http://extension-helpers.readthedocs.io Platform: UNKNOWN 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.6 Provides-Extra: test Provides-Extra: docs ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672145.0 extension-helpers-0.1/extension_helpers.egg-info/SOURCES.txt0000644000077000000240000000170700000000000024066 0ustar00tomstaff00000000000000.coveragerc .gitignore .readthedocs.yml CHANGES.rst CONTRIBUTING.md LICENSE.rst MANIFEST.in README.rst azure-pipelines.yml conftest.py pyproject.toml setup.cfg setup.py tox.ini .circleci/config.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/_distutils_helpers.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/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/test_openmp_helpers.py extension_helpers/tests/test_setup_helpers.py licenses/LICENSE_ASTROSCRAPPY.rst././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672144.0 extension-helpers-0.1/extension_helpers.egg-info/dependency_links.txt0000644000077000000240000000000100000000000026243 0ustar00tomstaff00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672144.0 extension-helpers-0.1/extension_helpers.egg-info/not-zip-safe0000644000077000000240000000000100000000000024423 0ustar00tomstaff00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672144.0 extension-helpers-0.1/extension_helpers.egg-info/requires.txt0000644000077000000240000000010400000000000024570 0ustar00tomstaff00000000000000 [docs] sphinx-astropy [test] pytest-astropy pytest-cov coverage<5 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672144.0 extension-helpers-0.1/extension_helpers.egg-info/top_level.txt0000644000077000000240000000002200000000000024721 0ustar00tomstaff00000000000000extension_helpers ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1576672145.1634433 extension-helpers-0.1/licenses/0000755000077000000240000000000000000000000016552 5ustar00tomstaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576529417.0 extension-helpers-0.1/licenses/LICENSE_ASTROSCRAPPY.rst0000644000077000000240000000315400000000000022403 0ustar00tomstaff00000000000000# 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. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672040.0 extension-helpers-0.1/pyproject.toml0000644000077000000240000000020400000000000017655 0ustar00tomstaff00000000000000[build-system] requires = ["setuptools", "setuptools_scm", "wheel"] build-backend = 'setuptools.build_meta' ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1576672145.1662316 extension-helpers-0.1/setup.cfg0000644000077000000240000000263600000000000016575 0ustar00tomstaff00000000000000[metadata] name = extension-helpers provides = extension_helpers author = The Astropy Developers author_email = astropy.team@gmail.com license = BSD 3-Clause License license_file = LICENSE.rst url = https://github.com/astropy/astropy-helpers description = Utilities for building and installing packages in the Astropy ecosystem long_description = file: 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 [options] zip_safe = False python_requires = >=3.6 packages = find: [options.package_data] extension_helpers = src/compiler.c [options.extras_require] test = pytest-astropy pytest-cov coverage<5 docs = sphinx-astropy [tool:pytest] norecursedirs = .tox extension_helpers/tests/package_template python_functions = test_ [isort] line_length = 100 sections = FUTURE,STDLIB,THIRDPARTY,NUMPY,FIRSTPARTY,LOCALFOLDER default_section = THIRDPARTY known_first_party = extension_helpers known_numpy = numpy multi_line_output = 0 balanced_wrapping = True include_trailing_comma = false length_sort_stdlib = true [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672040.0 extension-helpers-0.1/setup.py0000755000077000000240000000102700000000000016462 0ustar00tomstaff00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst # NOTE: most of the configuration, including the version number, # is defined in setup.cfg import os import sys from distutils.version import LooseVersion import setuptools from setuptools import setup if LooseVersion(setuptools.__version__) < '30.3': sys.stderr.write("ERROR: setuptools 30.3 or later is required by extension-helpers\n") sys.exit(1) setup(use_scm_version={'write_to': os.path.join('extension_helpers', 'version.py')}) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1576672040.0 extension-helpers-0.1/tox.ini0000644000077000000240000000267700000000000016274 0ustar00tomstaff00000000000000[tox] envlist = py{36,37,38}-test{,-conda} build_docs pycodestyle requires = setuptools >= 30.3.0 pip >= 19.3.1 isolated_build = true [testenv] setenv = osxgcc: CC=gcc osxclang: CC=clang-4.0 linuxgcc: CC=x86_64-conda_cos6-linux-gnu-gcc changedir = test: .tmp/{envname} build_docs: docs whitelist_externals = dev: bash description = test: run tests with pytest build_docs: invoke sphinx-build to build the HTML docs all: run tests with all optional dependencies dev: run tests with numpy and astropy dev versions conda_deps = osxgcc: gcc osxclang: clang_osx-64 osxclang: llvm-openmp linuxgcc: gcc_linux-64 conda_channels = osxgcc: conda-forge extras = test: test build_docs: docs all: all commands = dev: bash -ec "rm -rf setuptools_repo; git clone https://github.com/pypa/setuptools.git setuptools_repo && cd setuptools_repo && python bootstrap.py" dev: pip install setuptools_repo/ --no-build-isolation pip freeze test: python -c 'import setuptools; print(setuptools.__version__)' test: pytest --pyargs extension_helpers {toxinidir}/docs --cov extension_helpers {posargs} build_docs: sphinx-build -W -b html . _build/html [testenv:style] skip_install = true description = invoke pycodestyle and isort on package code deps = pycodestyle isort commands = pycodestyle extension_helpers --max-line-length=100 isort -c -rc extension_helpers