APLpy
Advanced tools
| version: 2 | ||
| jobs: | ||
| image-tests-mpl212: | ||
| docker: | ||
| - image: astropy/image-tests-py36-mpl212:1.10 | ||
| steps: | ||
| - checkout | ||
| - run: | ||
| name: Install wheel | ||
| command: pip3 install wheel | ||
| - run: | ||
| name: Install dependencies | ||
| command: pip3 install .[test] codecov | ||
| - run: | ||
| name: Run tests | ||
| command: MPLBACKEND=Agg pytest --remote-data=any --mpl | ||
| - run: | ||
| name: Report coverage | ||
| command: codecov | ||
| image-tests-mpl222: | ||
| docker: | ||
| - image: astropy/image-tests-py36-mpl222:1.10 | ||
| steps: | ||
| - checkout | ||
| - run: | ||
| name: Install wheel | ||
| command: pip3 install wheel | ||
| - run: | ||
| name: Install dependencies | ||
| command: pip3 install .[test] codecov | ||
| - run: | ||
| name: Run tests | ||
| command: MPLBACKEND=Agg pytest --remote-data=any --mpl | ||
| - run: | ||
| name: Report coverage | ||
| command: codecov | ||
| image-tests-mpl302: | ||
| docker: | ||
| - image: astropy/image-tests-py37-mpl302:1.10 | ||
| steps: | ||
| - checkout | ||
| - run: | ||
| name: Install wheel | ||
| command: pip3 install wheel | ||
| - run: | ||
| name: Install dependencies | ||
| command: pip3 install .[test] codecov | ||
| - run: | ||
| name: Run tests | ||
| command: MPLBACKEND=Agg pytest --remote-data=any --mpl | ||
| - run: | ||
| name: Report coverage | ||
| command: codecov | ||
| image-tests-mpl311: | ||
| docker: | ||
| - image: astropy/image-tests-py37-mpl311:1.10 | ||
| steps: | ||
| - checkout | ||
| - run: | ||
| name: Install wheel | ||
| command: pip3 install wheel | ||
| - run: | ||
| name: Install dependencies | ||
| command: pip3 install .[test] codecov | ||
| - run: | ||
| name: Run tests | ||
| command: MPLBACKEND=Agg pytest --remote-data=any --mpl | ||
| - run: | ||
| name: Report coverage | ||
| command: codecov | ||
| image-tests-mpldev: | ||
| docker: | ||
| - image: astropy/image-tests-py37-base:1.4 | ||
| steps: | ||
| - checkout | ||
| - run: | ||
| name: Install wheel | ||
| command: pip3 install wheel | ||
| - run: | ||
| name: Install Matplotlib | ||
| command: pip3 install git+https://github.com/matplotlib/matplotlib.git | ||
| - run: | ||
| name: Install dependencies | ||
| command: pip3 install .[test] codecov | ||
| - run: | ||
| name: Run tests | ||
| command: MPLBACKEND=Agg pytest --remote-data=any --mpl | ||
| - run: | ||
| name: Report coverage | ||
| command: codecov | ||
| workflows: | ||
| version: 2 | ||
| build_and_test: | ||
| jobs: | ||
| - image-tests-mpl212 | ||
| - image-tests-mpl222 | ||
| - image-tests-mpl302 | ||
| - image-tests-mpl311 | ||
| - image-tests-mpldev |
+49
| # Compiled files | ||
| *.py[co] | ||
| *.a | ||
| *.o | ||
| *.so | ||
| __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 | ||
| */version.py | ||
| */cython_version.py | ||
| htmlcov | ||
| .coverage | ||
| # Sphinx | ||
| _build | ||
| # Packages/installer info | ||
| *.egg | ||
| *.egg-info | ||
| dist | ||
| build | ||
| eggs | ||
| parts | ||
| bin | ||
| var | ||
| sdist | ||
| develop-eggs | ||
| .installed.cfg | ||
| distribute-*.tar.gz | ||
| # Other | ||
| .*.swp | ||
| *~ | ||
| .project | ||
| .pydevproject | ||
| .settings | ||
| # Mac OSX | ||
| .DS_Store | ||
| .tox | ||
| docs/api | ||
| .tmp |
| version: 2 | ||
| build: | ||
| image: latest | ||
| python: | ||
| version: 3.7 | ||
| install: | ||
| - method: pip | ||
| path: . | ||
| extra_requirements: | ||
| - docs | ||
| - all | ||
| formats: [] |
Sorry, the diff of this file is not supported yet
| Metadata-Version: 2.1 | ||
| Name: aplpy | ||
| Version: 2.1.0 | ||
| Summary: The Astronomical Plotting Library in Python | ||
| Home-page: http://aplpy.github.io | ||
| Author: Thomas Robitaille and Eli Bressert | ||
| Author-email: thomas.robitaille@gmail.com | ||
| License: MIT | ||
| Platform: UNKNOWN | ||
| Requires-Python: >=3.6 | ||
| Description-Content-Type: text/x-rst | ||
| Provides-Extra: test | ||
| Provides-Extra: docs | ||
| License-File: LICENSE.md | ||
| |Azure| |CircleCI| |codecov| |Documentation Status| | ||
| About | ||
| ----- | ||
| APLpy (the Astronomical Plotting Library in Python) is a Python module | ||
| aimed at producing publication-quality plots of astronomical imaging | ||
| data in FITS format. | ||
| PyPI: https://pypi.python.org/pypi/APLpy | ||
| Website: http://aplpy.github.io | ||
| Documentation: http://aplpy.readthedocs.io | ||
| APLpy is released under an MIT open-source license. | ||
| Installing | ||
| ---------- | ||
| You can install APLpy and all its dependencies with:: | ||
| pip install aplpy | ||
| .. |Azure| image:: https://dev.azure.com/thomasrobitaille/aplpy/_apis/build/status/aplpy.aplpy?repoName=aplpy%2Faplpy&branchName=refs%2Fpull%2F441%2Fmerge | ||
| :target: https://dev.azure.com/thomasrobitaille/aplpy/_build/latest?definitionId=16&repoName=aplpy%2Faplpy&branchName=refs%2Fpull%2F441%2Fmerge | ||
| .. |CircleCI| image:: https://circleci.com/gh/aplpy/aplpy/tree/main.svg?style=svg | ||
| :target: https://circleci.com/gh/aplpy/aplpy/tree/main | ||
| .. |codecov| image:: https://codecov.io/gh/aplpy/aplpy/branch/main/graph/badge.svg | ||
| :target: https://codecov.io/gh/aplpy/aplpy | ||
| .. |Documentation Status| image:: https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat | ||
| :target: https://aplpy.readthedocs.io/en/latest/ | ||
| numpy | ||
| astropy>=3.1 | ||
| matplotlib>=2.0 | ||
| reproject>=0.4 | ||
| pyregion>=2.0 | ||
| pillow>=4.3 | ||
| pyavm>=0.9.4 | ||
| scikit-image>=0.14 | ||
| shapely>=1.7 | ||
| [docs] | ||
| sphinx-astropy | ||
| [test] | ||
| pytest-astropy | ||
| pytest-mpl |
| .gitignore | ||
| .readthedocs.yml | ||
| CHANGES.rst | ||
| CITATION | ||
| LICENSE.md | ||
| MANIFEST.in | ||
| README.rst | ||
| azure-pipelines.yml | ||
| conftest.py | ||
| pyproject.toml | ||
| setup.cfg | ||
| setup.py | ||
| tox.ini | ||
| .circleci/config.yml | ||
| aplpy/__init__.py | ||
| aplpy/_astropy_init.py | ||
| aplpy/axis_labels.py | ||
| aplpy/colorbar.py | ||
| aplpy/compat.py | ||
| aplpy/conftest.py | ||
| aplpy/convolve_util.py | ||
| aplpy/core.py | ||
| aplpy/decorators.py | ||
| aplpy/frame.py | ||
| aplpy/grid.py | ||
| aplpy/header.py | ||
| aplpy/layers.py | ||
| aplpy/overlays.py | ||
| aplpy/regions.py | ||
| aplpy/rgb.py | ||
| aplpy/slicer.py | ||
| aplpy/tick_labels.py | ||
| aplpy/ticks.py | ||
| aplpy/version.py | ||
| aplpy.egg-info/PKG-INFO | ||
| aplpy.egg-info/SOURCES.txt | ||
| aplpy.egg-info/dependency_links.txt | ||
| aplpy.egg-info/not-zip-safe | ||
| aplpy.egg-info/requires.txt | ||
| aplpy.egg-info/top_level.txt | ||
| aplpy/tests/__init__.py | ||
| aplpy/tests/coveragerc | ||
| aplpy/tests/helpers.py | ||
| aplpy/tests/setup_package.py | ||
| aplpy/tests/test_axis_labels.py | ||
| aplpy/tests/test_beam.py | ||
| aplpy/tests/test_colorbar.py | ||
| aplpy/tests/test_contour.py | ||
| aplpy/tests/test_convolve.py | ||
| aplpy/tests/test_downsample.py | ||
| aplpy/tests/test_frame.py | ||
| aplpy/tests/test_grid.py | ||
| aplpy/tests/test_images.py | ||
| aplpy/tests/test_init_cube.py | ||
| aplpy/tests/test_init_image.py | ||
| aplpy/tests/test_layers.py | ||
| aplpy/tests/test_misc.py | ||
| aplpy/tests/test_pixworldmarkers.py | ||
| aplpy/tests/test_rgb.py | ||
| aplpy/tests/test_save.py | ||
| aplpy/tests/test_scalebar.py | ||
| aplpy/tests/test_subplot.py | ||
| aplpy/tests/test_tick_labels.py | ||
| aplpy/tests/test_ticks.py | ||
| aplpy/tests/test_vectors.py | ||
| aplpy/tests/data/__init__.py | ||
| aplpy/tests/data/shapes.reg | ||
| aplpy/tests/data/2d_fits/1904-66_AIR.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_AIT.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_ARC.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_AZP.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_BON.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_CAR.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_CEA.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_COD.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_COE.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_COO.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_COP.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_CSC.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_CYP.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_HPX.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_MER.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_MOL.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_NCP.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_PAR.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_PCO.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_QSC.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_SFL.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_SIN.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_STG.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_SZP.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_TAN.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_TSC.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_ZEA.hdr | ||
| aplpy/tests/data/2d_fits/1904-66_ZPN.hdr | ||
| aplpy/tests/data/2d_fits/2MASS_k.hdr | ||
| aplpy/tests/data/2d_fits/2MASS_k_rot.hdr | ||
| aplpy/tests/data/3d_fits/cube.hdr | ||
| docs/Makefile | ||
| docs/aplpy_logo.png | ||
| docs/conf.py | ||
| docs/index.rst | ||
| docs/index.txt | ||
| docs/make.bat | ||
| docs/rgb.rst | ||
| docs/_templates/autosummary/base.rst | ||
| docs/_templates/autosummary/class.rst | ||
| docs/_templates/autosummary/module.rst | ||
| docs/fitsfigure/arbitrary_coordinate_systems.rst | ||
| docs/fitsfigure/buttons.png | ||
| docs/fitsfigure/howto_avm.rst | ||
| docs/fitsfigure/howto_noninteractive.rst | ||
| docs/fitsfigure/howto_subplot.rst | ||
| docs/fitsfigure/myfirstplot.png | ||
| docs/fitsfigure/quick_reference.rst | ||
| docs/fitsfigure/quickstart.rst | ||
| docs/fitsfigure/slicing.rst |
| aplpy |
| resources: | ||
| repositories: | ||
| - repository: OpenAstronomy | ||
| type: github | ||
| endpoint: aplpy | ||
| name: OpenAstronomy/azure-pipelines-templates | ||
| ref: master | ||
| jobs: | ||
| - template: run-tox-env.yml@OpenAstronomy | ||
| parameters: | ||
| coverage: codecov | ||
| envs: | ||
| - linux: codestyle | ||
| - macos: py37-test-cov | ||
| - macos: py38-test-cov | ||
| - macos: py39-test-cov | ||
| - macos: py310-test-cov | ||
| - linux: py36-test-oldestdeps-cov | ||
| - linux: py37-test-cov | ||
| - linux: py38-test-cov | ||
| - linux: py39-test-cov | ||
| - linux: py310-test-devdeps-cov | ||
| - windows: py36-test-oldestdeps-cov | ||
| - windows: py37-test-cov | ||
| - windows: py38-test-cov | ||
| - windows: py39-test-cov | ||
| - windows: py310-test-cov | ||
| - macos: build_docs | ||
| - linux: build_docs | ||
| - macos: build_docs |
+16
| include README.rst | ||
| include CHANGES.rst | ||
| include setup.cfg | ||
| include LICENSE.rst | ||
| include pyproject.toml | ||
| recursive-include aplpy *.pyx *.c *.pxd | ||
| recursive-include docs * | ||
| recursive-include licenses * | ||
| recursive-include scripts * | ||
| prune build | ||
| prune docs/_build | ||
| prune docs/api | ||
| global-exclude *.pyc *.o |
| [build-system] | ||
| requires = ["setuptools", | ||
| "setuptools_scm", | ||
| "wheel"] | ||
| build-backend = 'setuptools.build_meta' |
+99
| [tox] | ||
| envlist = | ||
| py{36,37,38}-test{,-alldeps,-devdeps}{,-cov} | ||
| py{36,37,38}-test-numpy{116,117,118} | ||
| py{36,37,38}-test-astropy{30,40,lts} | ||
| build_docs | ||
| linkcheck | ||
| codestyle | ||
| requires = | ||
| setuptools >= 30.3.0 | ||
| pip >= 19.3.1 | ||
| isolated_build = true | ||
| [testenv] | ||
| # Pass through the following environment variables which may be needed for the CI | ||
| passenv = HOME WINDIR LC_ALL LC_CTYPE CC CI TRAVIS | ||
| # Run the tests in a temporary directory to make sure that we don't import | ||
| # this package from the source tree | ||
| changedir = .tmp/{envname} | ||
| # tox environments are constructed with so-called 'factors' (or terms) | ||
| # separated by hyphens, e.g. test-devdeps-cov. Lines below starting with factor: | ||
| # will only take effect if that factor is included in the environment name. To | ||
| # see a list of example environments that can be run, along with a description, | ||
| # run: | ||
| # | ||
| # tox -l -v | ||
| # | ||
| description = | ||
| run tests | ||
| alldeps: with all optional dependencies | ||
| devdeps: with the latest developer version of key dependencies | ||
| oldestdeps: with the oldest supported version of key dependencies | ||
| cov: and test coverage | ||
| numpy116: with numpy 1.16.* | ||
| numpy117: with numpy 1.17.* | ||
| numpy118: with numpy 1.18.* | ||
| astropy30: with astropy 3.0.* | ||
| astropy40: with astropy 4.0.* | ||
| astropylts: with the latest astropy LTS | ||
| # The following provides some specific pinnings for key packages | ||
| deps = | ||
| numpy116: numpy==1.16.* | ||
| numpy117: numpy==1.17.* | ||
| numpy118: numpy==1.18.* | ||
| astropy30: astropy==3.0.* | ||
| astropy40: astropy==4.0.* | ||
| astropylts: astropy==4.0.* | ||
| devdeps: git+https://github.com/numpy/numpy.git#egg=numpy | ||
| devdeps: git+https://github.com/astropy/astropy.git#egg=astropy | ||
| oldestdeps: astropy==3.1.* | ||
| oldestdeps: matplotlib==2.0.* | ||
| oldestdeps: reproject==0.4.* | ||
| oldestdeps: pyregion==2.0.* | ||
| oldestdeps: pillow==4.3.* | ||
| oldestdeps: pyavm==0.9.4 | ||
| oldestdeps: scikit-image==0.14.* | ||
| oldestdeps: shapely==1.7.* | ||
| oldestdeps: pytest==4.* | ||
| # The following indicates which extras_require from setup.cfg will be installed | ||
| extras = | ||
| test | ||
| alldeps: all | ||
| commands = | ||
| pip freeze | ||
| !cov: pytest --pyargs aplpy {toxinidir}/docs {posargs} | ||
| cov: pytest --pyargs aplpy {toxinidir}/docs --cov aplpy --cov-config={toxinidir}/setup.cfg {posargs} | ||
| [testenv:build_docs] | ||
| changedir = docs | ||
| description = invoke sphinx-build to build the HTML docs | ||
| extras = docs | ||
| commands = | ||
| pip freeze | ||
| sphinx-build -W -b html . _build/html | ||
| [testenv:linkcheck] | ||
| changedir = docs | ||
| description = check the links in the HTML docs | ||
| extras = docs | ||
| commands = | ||
| pip freeze | ||
| sphinx-build -W -b linkcheck . _build/html | ||
| [testenv:codestyle] | ||
| skip_install = true | ||
| changedir = . | ||
| description = check code style, e.g. with flake8 | ||
| deps = flake8 | ||
| commands = flake8 aplpy --count --ignore=E501,W503,W504 |
| # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| __all__ = ['__version__', '__githash__', 'test'] | ||
| __all__ = ['__version__'] | ||
@@ -9,7 +9,3 @@ # this indicates whether or not we are in the package's setup.py | ||
| except NameError: | ||
| from sys import version_info | ||
| if version_info[0] >= 3: | ||
| import builtins | ||
| else: | ||
| import __builtin__ as builtins | ||
| import builtins | ||
| builtins._ASTROPY_SETUP_ = False | ||
@@ -21,93 +17,4 @@ | ||
| __version__ = '' | ||
| try: | ||
| from .version import githash as __githash__ | ||
| except ImportError: | ||
| __githash__ = '' | ||
| # set up the test command | ||
| def _get_test_runner(): | ||
| import os | ||
| from astropy.tests.helper import TestRunner | ||
| return TestRunner(os.path.dirname(__file__)) | ||
| def test(package=None, test_path=None, args=None, plugins=None, | ||
| verbose=False, pastebin=None, remote_data=False, pep8=False, | ||
| pdb=False, coverage=False, open_files=False, **kwargs): | ||
| """ | ||
| Run the tests using `py.test <https://docs.pytest.org/en/latest/>`__. | ||
| Parameters | ||
| ---------- | ||
| package : str, optional | ||
| The name of a specific package to test, e.g. 'io.fits' or 'utils'. | ||
| If nothing is specified all default tests are run. | ||
| test_path : str, optional | ||
| Specify location to test by path. May be a single file or | ||
| directory. Must be specified absolutely or relative to the | ||
| calling directory. | ||
| args : str, optional | ||
| Additional arguments to be passed to pytest.main in the ``args`` | ||
| keyword argument. | ||
| plugins : list, optional | ||
| Plugins to be passed to pytest.main in the ``plugins`` keyword | ||
| argument. | ||
| verbose : bool, optional | ||
| Convenience option to turn on verbose output from py.test. Passing | ||
| True is the same as specifying ``'-v'`` in ``args``. | ||
| pastebin : {'failed','all',None}, optional | ||
| Convenience option for turning on py.test pastebin output. Set to | ||
| ``'failed'`` to upload info for failed tests, or ``'all'`` to upload | ||
| info for all tests. | ||
| remote_data : bool, optional | ||
| Controls whether to run tests marked with @remote_data. These | ||
| tests use online data and are not run by default. Set to True to | ||
| run these tests. | ||
| pep8 : bool, optional | ||
| Turn on PEP8 checking via the `pytest-pep8 plugin | ||
| <https://pypi.org/project/pytest-pep8/>`_ and disable normal | ||
| tests. Same as specifying ``'--pep8 -k pep8'`` in ``args``. | ||
| pdb : bool, optional | ||
| Turn on PDB post-mortem analysis for failing tests. Same as | ||
| specifying ``'--pdb'`` in ``args``. | ||
| coverage : bool, optional | ||
| Generate a test coverage report. The result will be placed in | ||
| the directory htmlcov. | ||
| open_files : bool, optional | ||
| Fail when any tests leave files open. Off by default, because | ||
| this adds extra run time to the test suite. Requires the | ||
| `psutil <https://pypi.org/project/psutil/>`_ package. | ||
| parallel : int, optional | ||
| When provided, run the tests in parallel on the specified | ||
| number of CPUs. If parallel is negative, it will use the all | ||
| the cores on the machine. Requires the | ||
| `pytest-xdist <https://pypi.org/project/pytest-xdist/>`_ plugin | ||
| installed. Only available when using Astropy 0.3 or later. | ||
| kwargs | ||
| Any additional keywords passed into this function will be passed | ||
| on to the astropy test runner. This allows use of test-related | ||
| functionality implemented in later versions of astropy without | ||
| explicitly updating the package template. | ||
| """ | ||
| test_runner = _get_test_runner() | ||
| return test_runner.run_tests( | ||
| package=package, test_path=test_path, args=args, | ||
| plugins=plugins, verbose=verbose, pastebin=pastebin, | ||
| remote_data=remote_data, pep8=pep8, pdb=pdb, | ||
| coverage=coverage, open_files=open_files, **kwargs) | ||
| if not _ASTROPY_SETUP_: # noqa | ||
@@ -121,2 +28,8 @@ import os | ||
| # Create the test function for self test | ||
| from astropy.tests.runner import TestRunner | ||
| test = TestRunner.make_test_runner_in(os.path.dirname(__file__)) | ||
| test.__test__ = False | ||
| __all__ += ['test'] | ||
| # add these here so we only need to cleanup the namespace at the end | ||
@@ -123,0 +36,0 @@ config_dir = None |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| from astropy.wcs.utils import wcs_to_celestial_frame | ||
@@ -4,0 +2,0 @@ from astropy.coordinates import (ICRS, FK5, FK4, Galactic, |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import warnings | ||
@@ -4,0 +2,0 @@ |
+35
-49
@@ -1,26 +0,42 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| # This file is used to configure the behavior of pytest when using the Astropy | ||
| # test infrastructure. It needs to live inside the package in order for it to | ||
| # get picked up when running the tests inside an interpreter using | ||
| # packagename.test | ||
| # Force the backend to Agg when testing | ||
| import os | ||
| import matplotlib | ||
| matplotlib.use('Agg') | ||
| from astropy.version import version as astropy_version | ||
| # This file is used to configure the behavior of pytest when using the Astropy | ||
| # test infrastructure. | ||
| from astropy.version import version as astropy_version | ||
| # For Astropy 3.0 and later, we can use the standalone pytest plugin | ||
| if astropy_version < '3.0': | ||
| # With older versions of Astropy, we actually need to import the pytest | ||
| # plugins themselves in order to make them discoverable by pytest. | ||
| from astropy.tests.pytest_plugins import * # noqa | ||
| del pytest_report_header | ||
| ASTROPY_HEADER = True | ||
| else: | ||
| # As of Astropy 3.0, the pytest plugins provided by Astropy are | ||
| # automatically made available when Astropy is installed. This means it's | ||
| # not necessary to import them here, but we still need to import global | ||
| # variables that are used for configuration. | ||
| from astropy.tests.plugins.display import PYTEST_HEADER_MODULES, TESTED_VERSIONS | ||
| try: | ||
| from pytest_astropy_header.display import PYTEST_HEADER_MODULES, TESTED_VERSIONS | ||
| ASTROPY_HEADER = True | ||
| except ImportError: | ||
| ASTROPY_HEADER = False | ||
| from astropy.tests.helper import enable_deprecations_as_exceptions | ||
| # Uncomment the following line to treat all DeprecationWarnings as | ||
| def pytest_configure(config): | ||
| if ASTROPY_HEADER: | ||
| config.option.astropy_header = True | ||
| # Customize the following lines to add/remove entries from the list of | ||
| # packages for which version numbers are displayed when running the tests. | ||
| PYTEST_HEADER_MODULES['Astropy'] = 'astropy' | ||
| PYTEST_HEADER_MODULES['pyregion'] = 'pyregion' | ||
| PYTEST_HEADER_MODULES['PyAVM'] = 'PyAVM' | ||
| PYTEST_HEADER_MODULES['reproject'] = 'reproject' | ||
| del PYTEST_HEADER_MODULES['h5py'] | ||
| from . import __version__ | ||
| packagename = os.path.basename(os.path.dirname(__file__)) | ||
| TESTED_VERSIONS[packagename] = __version__ | ||
| # Uncomment the last two lines in this block to treat all DeprecationWarnings as | ||
| # exceptions. For Astropy v2.0 or later, there are 2 additional keywords, | ||
@@ -35,33 +51,3 @@ # as follow (although default should work for most cases). | ||
| # warnings_to_ignore_by_pyver={(MAJOR, MINOR): ['Message to ignore']} | ||
| enable_deprecations_as_exceptions() | ||
| # Uncomment and customize the following lines to add/remove entries from | ||
| # the list of packages for which version numbers are displayed when running | ||
| # the tests. Making it pass for KeyError is essential in some cases when | ||
| # the package uses other astropy affiliated packages. | ||
| try: | ||
| PYTEST_HEADER_MODULES['Astropy'] = 'astropy' | ||
| PYTEST_HEADER_MODULES['pyregion'] = 'pyregion' | ||
| PYTEST_HEADER_MODULES['PyAVM'] = 'PyAVM' | ||
| PYTEST_HEADER_MODULES['reproject'] = 'reproject' | ||
| del PYTEST_HEADER_MODULES['h5py'] | ||
| except (NameError, KeyError): | ||
| pass | ||
| # Uncomment the following lines to display the version number of the | ||
| # package rather than the version number of Astropy in the top line when | ||
| # running the tests. | ||
| import os | ||
| # This is to figure out the package version, rather than | ||
| # using Astropy's | ||
| try: | ||
| from .version import version | ||
| except ImportError: | ||
| version = 'dev' | ||
| try: | ||
| packagename = os.path.basename(os.path.dirname(__file__)) | ||
| TESTED_VERSIONS[packagename] = version | ||
| except NameError: # Needed to support Astropy <= 1.0.0 | ||
| pass | ||
| # from astropy.tests.helper import enable_deprecations_as_exceptions # noqa | ||
| # enable_deprecations_as_exceptions() |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import numpy as np | ||
@@ -4,0 +2,0 @@ |
@@ -1,6 +0,4 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import threading | ||
| from astropy.utils.decorators import wraps | ||
| from functools import wraps | ||
@@ -7,0 +5,0 @@ mydata = threading.local() |
+0
-2
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| from .decorators import auto_refresh | ||
@@ -4,0 +2,0 @@ |
+0
-2
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import numpy as np | ||
@@ -4,0 +2,0 @@ |
+0
-2
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| from astropy import log | ||
@@ -4,0 +2,0 @@ |
+0
-2
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| from collections import OrderedDict | ||
@@ -4,0 +2,0 @@ |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import warnings | ||
@@ -4,0 +2,0 @@ |
+0
-2
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| from astropy import log | ||
@@ -4,0 +2,0 @@ from astropy import wcs |
+0
-2
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| from distutils import version | ||
@@ -4,0 +2,0 @@ import os |
+0
-2
@@ -1,4 +0,2 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| def slice_hypercube(data, header, dimensions=[0, 1], slices=[]): | ||
@@ -5,0 +3,0 @@ """ |
@@ -7,2 +7,7 @@ import matplotlib | ||
| # The developer versions of the form 3.2.x+... contain changes that will only | ||
| # be included in the 3.3.x release, so we update this here. | ||
| if MPL_VERSION[:3] == '3.2' and '+' in MPL_VERSION: | ||
| MPL_VERSION = '3.3' | ||
| baseline_dir = ROOT + '/' + MPL_VERSION[:3] + '.x/' |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import string | ||
@@ -4,0 +2,0 @@ import random |
@@ -1,5 +0,3 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| def get_package_data(): | ||
| return {_ASTROPY_PACKAGE_NAME_ + '.tests': ['coveragerc', 'data/*.reg', 'data/*/*.hdr']} # noqa |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import numpy as np | ||
@@ -4,0 +2,0 @@ from astropy.tests.helper import pytest |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import os | ||
@@ -4,0 +2,0 @@ |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import numpy as np | ||
@@ -4,0 +2,0 @@ from astropy.tests.helper import pytest |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import pytest | ||
@@ -4,0 +2,0 @@ import numpy as np |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import numpy as np | ||
@@ -4,0 +2,0 @@ from astropy.io import fits |
@@ -1,4 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import pytest | ||
| import numpy as np | ||
@@ -5,0 +2,0 @@ |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import numpy as np | ||
@@ -4,0 +2,0 @@ |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import pytest | ||
@@ -4,0 +2,0 @@ import numpy as np |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import os | ||
@@ -4,0 +2,0 @@ import tempfile |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import os | ||
@@ -4,0 +2,0 @@ |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import os | ||
@@ -4,0 +2,0 @@ |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import pytest | ||
@@ -4,0 +2,0 @@ import numpy as np |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import numpy as np | ||
@@ -4,0 +2,0 @@ |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import os | ||
@@ -4,0 +2,0 @@ |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import os | ||
@@ -4,0 +2,0 @@ import warnings |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import os | ||
@@ -32,3 +30,3 @@ import sys | ||
| from xml.dom import minidom | ||
| return minidom.parse(f).childNodes[2].attributes['xmlns'].value == 'http://www.w3.org/2000/svg' | ||
| return minidom.parse(f).childNodes[-1].attributes['xmlns'].value == 'http://www.w3.org/2000/svg' | ||
| else: | ||
@@ -35,0 +33,0 @@ raise Exception("Unknown format: %s" % format) |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import os | ||
@@ -4,0 +2,0 @@ |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import pytest | ||
@@ -4,0 +2,0 @@ import numpy as np |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import os | ||
@@ -4,0 +2,0 @@ |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import numpy as np | ||
@@ -4,0 +2,0 @@ |
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| import pytest | ||
@@ -4,0 +2,0 @@ import numpy as np |
+2
-4
@@ -1,3 +0,1 @@ | ||
| from __future__ import absolute_import, print_function, division | ||
| from .decorators import auto_refresh | ||
@@ -22,4 +20,4 @@ | ||
| if direction in ('in', 'out'): | ||
| self.ax.coords[self.x].ticks.set_tick_out(direction == 'out') | ||
| self.ax.coords[self.y].ticks.set_tick_out(direction == 'out') | ||
| self._ax.coords[self.x].ticks.set_tick_out(direction == 'out') | ||
| self._ax.coords[self.y].ticks.set_tick_out(direction == 'out') | ||
| else: | ||
@@ -26,0 +24,0 @@ raise ValueError("direction should be 'in' or 'out'") |
+7
-226
@@ -1,227 +0,8 @@ | ||
| # Autogenerated by Astropy-affiliated package aplpy's setup.py on 2019-02-19 11:43:15 UTC | ||
| from __future__ import unicode_literals | ||
| import datetime | ||
| import locale | ||
| import os | ||
| import subprocess | ||
| import warnings | ||
| def _decode_stdio(stream): | ||
| try: | ||
| stdio_encoding = locale.getdefaultlocale()[1] or 'utf-8' | ||
| except ValueError: | ||
| stdio_encoding = 'utf-8' | ||
| try: | ||
| text = stream.decode(stdio_encoding) | ||
| except UnicodeDecodeError: | ||
| # Final fallback | ||
| text = stream.decode('latin1') | ||
| return text | ||
| def update_git_devstr(version, path=None): | ||
| """ | ||
| Updates the git revision string if and only if the path is being imported | ||
| directly from a git working copy. This ensures that the revision number in | ||
| the version string is accurate. | ||
| """ | ||
| try: | ||
| # Quick way to determine if we're in git or not - returns '' if not | ||
| devstr = get_git_devstr(sha=True, show_warning=False, path=path) | ||
| except OSError: | ||
| return version | ||
| if not devstr: | ||
| # Probably not in git so just pass silently | ||
| return version | ||
| if 'dev' in version: # update to the current git revision | ||
| version_base = version.split('.dev', 1)[0] | ||
| devstr = get_git_devstr(sha=False, show_warning=False, path=path) | ||
| return version_base + '.dev' + devstr | ||
| else: | ||
| # otherwise it's already the true/release version | ||
| return version | ||
| def get_git_devstr(sha=False, show_warning=True, path=None): | ||
| """ | ||
| Determines the number of revisions in this repository. | ||
| Parameters | ||
| ---------- | ||
| sha : bool | ||
| If True, the full SHA1 hash will be returned. Otherwise, the total | ||
| count of commits in the repository will be used as a "revision | ||
| number". | ||
| show_warning : bool | ||
| If True, issue a warning if git returns an error code, otherwise errors | ||
| pass silently. | ||
| path : str or None | ||
| If a string, specifies the directory to look in to find the git | ||
| repository. If `None`, the current working directory is used, and must | ||
| be the root of the git repository. | ||
| If given a filename it uses the directory containing that file. | ||
| Returns | ||
| ------- | ||
| devversion : str | ||
| Either a string with the revision number (if `sha` is False), the | ||
| SHA1 hash of the current commit (if `sha` is True), or an empty string | ||
| if git version info could not be identified. | ||
| """ | ||
| if path is None: | ||
| path = os.getcwd() | ||
| if not os.path.isdir(path): | ||
| path = os.path.abspath(os.path.dirname(path)) | ||
| if sha: | ||
| # Faster for getting just the hash of HEAD | ||
| cmd = ['rev-parse', 'HEAD'] | ||
| else: | ||
| cmd = ['rev-list', '--count', 'HEAD'] | ||
| def run_git(cmd): | ||
| try: | ||
| p = subprocess.Popen(['git'] + cmd, cwd=path, | ||
| stdout=subprocess.PIPE, | ||
| stderr=subprocess.PIPE, | ||
| stdin=subprocess.PIPE) | ||
| stdout, stderr = p.communicate() | ||
| except OSError as e: | ||
| if show_warning: | ||
| warnings.warn('Error running git: ' + str(e)) | ||
| return (None, b'', b'') | ||
| if p.returncode == 128: | ||
| if show_warning: | ||
| warnings.warn('No git repository present at {0!r}! Using ' | ||
| 'default dev version.'.format(path)) | ||
| return (p.returncode, b'', b'') | ||
| if p.returncode == 129: | ||
| if show_warning: | ||
| warnings.warn('Your git looks old (does it support {0}?); ' | ||
| 'consider upgrading to v1.7.2 or ' | ||
| 'later.'.format(cmd[0])) | ||
| return (p.returncode, stdout, stderr) | ||
| elif p.returncode != 0: | ||
| if show_warning: | ||
| warnings.warn('Git failed while determining revision ' | ||
| 'count: {0}'.format(_decode_stdio(stderr))) | ||
| return (p.returncode, stdout, stderr) | ||
| return p.returncode, stdout, stderr | ||
| returncode, stdout, stderr = run_git(cmd) | ||
| if not sha and returncode == 128: | ||
| # git returns 128 if the command is not run from within a git | ||
| # repository tree. In this case, a warning is produced above but we | ||
| # return the default dev version of '0'. | ||
| return '0' | ||
| elif not sha and returncode == 129: | ||
| # git returns 129 if a command option failed to parse; in | ||
| # particular this could happen in git versions older than 1.7.2 | ||
| # where the --count option is not supported | ||
| # Also use --abbrev-commit and --abbrev=0 to display the minimum | ||
| # number of characters needed per-commit (rather than the full hash) | ||
| cmd = ['rev-list', '--abbrev-commit', '--abbrev=0', 'HEAD'] | ||
| returncode, stdout, stderr = run_git(cmd) | ||
| # Fall back on the old method of getting all revisions and counting | ||
| # the lines | ||
| if returncode == 0: | ||
| return str(stdout.count(b'\n')) | ||
| else: | ||
| return '' | ||
| elif sha: | ||
| return _decode_stdio(stdout)[:40] | ||
| else: | ||
| return _decode_stdio(stdout).strip() | ||
| # This function is tested but it is only ever executed within a subprocess when | ||
| # creating a fake package, so it doesn't get picked up by coverage metrics. | ||
| def _get_repo_path(pathname, levels=None): # pragma: no cover | ||
| """ | ||
| Given a file or directory name, determine the root of the git repository | ||
| this path is under. If given, this won't look any higher than ``levels`` | ||
| (that is, if ``levels=0`` then the given path must be the root of the git | ||
| repository and is returned if so. | ||
| Returns `None` if the given path could not be determined to belong to a git | ||
| repo. | ||
| """ | ||
| if os.path.isfile(pathname): | ||
| current_dir = os.path.abspath(os.path.dirname(pathname)) | ||
| elif os.path.isdir(pathname): | ||
| current_dir = os.path.abspath(pathname) | ||
| else: | ||
| return None | ||
| current_level = 0 | ||
| while levels is None or current_level <= levels: | ||
| if os.path.exists(os.path.join(current_dir, '.git')): | ||
| return current_dir | ||
| current_level += 1 | ||
| if current_dir == os.path.dirname(current_dir): | ||
| break | ||
| current_dir = os.path.dirname(current_dir) | ||
| return None | ||
| _packagename = "aplpy" | ||
| _last_generated_version = "2.0.3" | ||
| _last_githash = "70f1a5fedfc04f8d97ef71d2078c47cf5efec9b3" | ||
| # Determine where the source code for this module | ||
| # lives. If __file__ is not a filesystem path then | ||
| # it is assumed not to live in a git repo at all. | ||
| if _get_repo_path(__file__, levels=len(_packagename.split('.'))): | ||
| version = update_git_devstr(_last_generated_version, path=__file__) | ||
| githash = get_git_devstr(sha=True, show_warning=False, | ||
| path=__file__) or _last_githash | ||
| else: | ||
| # The file does not appear to live in a git repo so don't bother | ||
| # invoking git | ||
| version = _last_generated_version | ||
| githash = _last_githash | ||
| major = 2 | ||
| minor = 0 | ||
| bugfix = 3 | ||
| version_info = (major, minor, bugfix) | ||
| release = True | ||
| timestamp = datetime.datetime(2019, 2, 19, 11, 43, 15) | ||
| debug = False | ||
| astropy_helpers_version = "3.1" | ||
| # Note that we need to fall back to the hard-coded version if either | ||
| # setuptools_scm can't be imported or setuptools_scm can't determine the | ||
| # version, so we catch the generic 'Exception'. | ||
| try: | ||
| from ._compiler import compiler | ||
| except ImportError: | ||
| compiler = "unknown" | ||
| try: | ||
| from .cython_version import cython_version | ||
| except ImportError: | ||
| cython_version = "unknown" | ||
| from setuptools_scm import get_version | ||
| version = get_version(root='..', relative_to=__file__) | ||
| except Exception: | ||
| version = '2.1.0' |
+14
-0
| CHANGES | ||
| -------- | ||
| 2.1.0 (2022-03-10) | ||
| ------------------ | ||
| - Updated package infrastructure to follow guidelines described in APE 17, namely | ||
| removing astropy-helpers and using tox for setting up testing environments. [#448] | ||
| - Fix compatibility with astropy 4.x and 5.0. [#448,#471] | ||
| - Fixed default color for ``show_lines`` (was previously transparent, now black). [#441] | ||
| - Fixed ``set_tick_direction`` to not error due to incorrect reference to ``ax``. [#450] | ||
| - Fixed compatibility with Matplotlib 3.4. [#466] | ||
| 2.0.3 (2019-02-19) | ||
@@ -5,0 +19,0 @@ ------------------ |
+56
-19
@@ -28,5 +28,6 @@ # -*- coding: utf-8 -*- | ||
| import datetime | ||
| import os | ||
| import sys | ||
| import datetime | ||
| from importlib import import_module | ||
@@ -40,7 +41,5 @@ try: | ||
| # Get configuration information from setup.cfg | ||
| try: | ||
| from ConfigParser import ConfigParser | ||
| except ImportError: | ||
| from configparser import ConfigParser | ||
| from configparser import ConfigParser | ||
| conf = ConfigParser() | ||
| conf.read([os.path.join(os.path.dirname(__file__), '..', 'setup.cfg')]) | ||
@@ -51,5 +50,12 @@ setup_cfg = dict(conf.items('metadata')) | ||
| # By default, highlight as Python 3. | ||
| highlight_language = 'python3' | ||
| # If your documentation needs a minimal Sphinx version, state it here. | ||
| #needs_sphinx = '1.1' | ||
| #needs_sphinx = '1.2' | ||
| # To perform a Sphinx version check that needs to be more specific than | ||
| # major.minor, call `check_sphinx_version("x.y.z")` here. | ||
| # check_sphinx_version("1.2.1") | ||
| # List of patterns, relative to source directory, that match files and | ||
@@ -67,3 +73,3 @@ # directories to ignore when looking for source files. | ||
| # This does not *have* to match the package name, but typically does | ||
| project = setup_cfg['package_name'] | ||
| project = setup_cfg['name'] | ||
| author = setup_cfg['author'] | ||
@@ -77,4 +83,4 @@ copyright = '{0}, {1}'.format( | ||
| __import__(setup_cfg['package_name']) | ||
| package = sys.modules[setup_cfg['package_name']] | ||
| import_module(setup_cfg['name']) | ||
| package = sys.modules[setup_cfg['name']] | ||
@@ -87,3 +93,3 @@ # The short X.Y version. | ||
| # -- Options for HTML output --------------------------------------------------- | ||
| # -- Options for HTML output -------------------------------------------------- | ||
@@ -97,2 +103,3 @@ # A NOTE ON HTML THEMES | ||
| # Add any paths that contain custom themes here, relative to this directory. | ||
@@ -110,2 +117,6 @@ # To use a different custom theme, add the directory containing the theme. | ||
| # The name of an image file (relative to this directory) to place at the top | ||
| # of the sidebar. | ||
| #html_logo = '' | ||
| # The name of an image file (within the static path) to use as favicon of the | ||
@@ -138,3 +149,3 @@ # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 | ||
| # -- Options for manual page output -------------------------------------------- | ||
| # -- Options for manual page output ------------------------------------------- | ||
@@ -147,13 +158,10 @@ # One entry per manual page. List of tuples | ||
| ## -- Options for the edit_on_github extension ---------------------------------------- | ||
| # -- Options for the edit_on_github extension --------------------------------- | ||
| if eval(setup_cfg.get('edit_on_github')): | ||
| extensions += ['astropy.sphinx.ext.edit_on_github'] | ||
| if setup_cfg.get('edit_on_github').lower() == 'true': | ||
| versionmod = __import__(setup_cfg['package_name'] + '.version') | ||
| extensions += ['sphinx_astropy.ext.edit_on_github'] | ||
| edit_on_github_project = setup_cfg['github_project'] | ||
| if versionmod.version.release: | ||
| edit_on_github_branch = "v" + versionmod.version.version | ||
| else: | ||
| edit_on_github_branch = "master" | ||
| edit_on_github_branch = "master" | ||
@@ -163,2 +171,5 @@ edit_on_github_source_root = "" | ||
| # -- Resolving issue number to links in changelog ----------------------------- | ||
| github_issues_url = 'https://github.com/{0}/issues/'.format(setup_cfg['github_project']) | ||
| # Use nitpicky mode to avoid broken links in docs | ||
@@ -168,1 +179,27 @@ nitpicky = True | ||
| ('py:class', 'aplpy.regions.Regions')] | ||
| # -- Turn on nitpicky mode for sphinx (to warn about references not found) ---- | ||
| # | ||
| # nitpicky = True | ||
| # nitpick_ignore = [] | ||
| # | ||
| # Some warnings are impossible to suppress, and you can list specific references | ||
| # that should be ignored in a nitpick-exceptions file which should be inside | ||
| # the docs/ directory. The format of the file should be: | ||
| # | ||
| # <type> <class> | ||
| # | ||
| # for example: | ||
| # | ||
| # py:class astropy.io.votable.tree.Element | ||
| # py:class astropy.io.votable.tree.SimpleElement | ||
| # py:class astropy.io.votable.tree.SimpleElementWithContent | ||
| # | ||
| # Uncomment the following lines to enable the exceptions: | ||
| # | ||
| # for line in open('nitpick-exceptions'): | ||
| # if line.strip() == "" or line.startswith("#"): | ||
| # continue | ||
| # dtype, target = line.split(None, 1) | ||
| # target = target.strip() | ||
| # nitpick_ignore.append((dtype, six.u(target))) |
+41
-53
| Metadata-Version: 2.1 | ||
| Name: APLpy | ||
| Version: 2.0.3 | ||
| Name: aplpy | ||
| Version: 2.1.0 | ||
| Summary: The Astronomical Plotting Library in Python | ||
| Home-page: http://aplpy.github.io | ||
| Author: Thomas Robitaille and Eli Bressert | ||
| Author-email: thomas.robitaille@gmail.com, elibre@users.sourceforge.net | ||
| Author-email: thomas.robitaille@gmail.com | ||
| License: MIT | ||
| Description: |Build Status| |CircleCI| |codecov| |Documentation Status| | ||
| About | ||
| ----- | ||
| APLpy (the Astronomical Plotting Library in Python) is a Python module | ||
| aimed at producing publication-quality plots of astronomical imaging | ||
| data in FITS format. | ||
| PyPI: https://pypi.python.org/pypi/APLpy | ||
| Website: http://aplpy.github.io | ||
| Documentation: http://aplpy.readthedocs.io | ||
| APLpy is released under an MIT open-source license. | ||
| Installing | ||
| ---------- | ||
| The following dependencies are required: | ||
| - `Numpy <http://www.numpy.org>`__ 1.11 or later | ||
| - `Matplotlib <http://www.matplotlib.org>`__ 2.0 or later | ||
| - `Astropy <http://www.astropy.org>`__ 3.1 or later | ||
| - `reproject <https://reproject.readthedocs.org>`__ 0.4 or later | ||
| - `PyAVM <http://astrofrog.github.io/pyavm/>`__ 0.9.4 or later | ||
| - `pyregion <http://pyregion.readthedocs.org/>`__ 2.0 or later | ||
| - `pillow <https://pypi.org/project/Pillow/>`__ 4.0 or later | ||
| - `scikit-image <https://pypi.org/project/scikit-image/>`__ 0.14 or later | ||
| - `shapely <https://shapely.readthedocs.io/en/stable/project.html>`__ 1.6 or later | ||
| You can install APLpy and all its dependencies with:: | ||
| pip install aplpy | ||
| or if you use conda:: | ||
| conda install -c astropy aplpy | ||
| .. |Build Status| image:: https://travis-ci.org/aplpy/aplpy.svg?branch=master | ||
| :target: https://travis-ci.org/aplpy/aplpy | ||
| .. |CircleCI| image:: https://circleci.com/gh/aplpy/aplpy/tree/master.svg?style=svg | ||
| :target: https://circleci.com/gh/aplpy/aplpy/tree/master | ||
| .. |codecov| image:: https://codecov.io/gh/aplpy/aplpy/branch/master/graph/badge.svg | ||
| :target: https://codecov.io/gh/aplpy/aplpy | ||
| .. |Documentation Status| image:: https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat | ||
| :target: https://aplpy.readthedocs.io/en/latest/ | ||
| Platform: UNKNOWN | ||
| Requires-Python: >=3.5 | ||
| Requires-Python: >=3.6 | ||
| Description-Content-Type: text/x-rst | ||
| Provides-Extra: test | ||
| Provides-Extra: docs | ||
| License-File: LICENSE.md | ||
| |Azure| |CircleCI| |codecov| |Documentation Status| | ||
| About | ||
| ----- | ||
| APLpy (the Astronomical Plotting Library in Python) is a Python module | ||
| aimed at producing publication-quality plots of astronomical imaging | ||
| data in FITS format. | ||
| PyPI: https://pypi.python.org/pypi/APLpy | ||
| Website: http://aplpy.github.io | ||
| Documentation: http://aplpy.readthedocs.io | ||
| APLpy is released under an MIT open-source license. | ||
| Installing | ||
| ---------- | ||
| You can install APLpy and all its dependencies with:: | ||
| pip install aplpy | ||
| .. |Azure| image:: https://dev.azure.com/thomasrobitaille/aplpy/_apis/build/status/aplpy.aplpy?repoName=aplpy%2Faplpy&branchName=refs%2Fpull%2F441%2Fmerge | ||
| :target: https://dev.azure.com/thomasrobitaille/aplpy/_build/latest?definitionId=16&repoName=aplpy%2Faplpy&branchName=refs%2Fpull%2F441%2Fmerge | ||
| .. |CircleCI| image:: https://circleci.com/gh/aplpy/aplpy/tree/main.svg?style=svg | ||
| :target: https://circleci.com/gh/aplpy/aplpy/tree/main | ||
| .. |codecov| image:: https://codecov.io/gh/aplpy/aplpy/branch/main/graph/badge.svg | ||
| :target: https://codecov.io/gh/aplpy/aplpy | ||
| .. |Documentation Status| image:: https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat | ||
| :target: https://aplpy.readthedocs.io/en/latest/ | ||
+6
-22
@@ -1,2 +0,2 @@ | ||
| |Build Status| |CircleCI| |codecov| |Documentation Status| | ||
| |Azure| |CircleCI| |codecov| |Documentation Status| | ||
@@ -21,14 +21,2 @@ About | ||
| The following dependencies are required: | ||
| - `Numpy <http://www.numpy.org>`__ 1.11 or later | ||
| - `Matplotlib <http://www.matplotlib.org>`__ 2.0 or later | ||
| - `Astropy <http://www.astropy.org>`__ 3.1 or later | ||
| - `reproject <https://reproject.readthedocs.org>`__ 0.4 or later | ||
| - `PyAVM <http://astrofrog.github.io/pyavm/>`__ 0.9.4 or later | ||
| - `pyregion <http://pyregion.readthedocs.org/>`__ 2.0 or later | ||
| - `pillow <https://pypi.org/project/Pillow/>`__ 4.0 or later | ||
| - `scikit-image <https://pypi.org/project/scikit-image/>`__ 0.14 or later | ||
| - `shapely <https://shapely.readthedocs.io/en/stable/project.html>`__ 1.6 or later | ||
| You can install APLpy and all its dependencies with:: | ||
@@ -38,13 +26,9 @@ | ||
| or if you use conda:: | ||
| conda install -c astropy aplpy | ||
| .. |Build Status| image:: https://travis-ci.org/aplpy/aplpy.svg?branch=master | ||
| :target: https://travis-ci.org/aplpy/aplpy | ||
| .. |CircleCI| image:: https://circleci.com/gh/aplpy/aplpy/tree/master.svg?style=svg | ||
| :target: https://circleci.com/gh/aplpy/aplpy/tree/master | ||
| .. |codecov| image:: https://codecov.io/gh/aplpy/aplpy/branch/master/graph/badge.svg | ||
| .. |Azure| image:: https://dev.azure.com/thomasrobitaille/aplpy/_apis/build/status/aplpy.aplpy?repoName=aplpy%2Faplpy&branchName=refs%2Fpull%2F441%2Fmerge | ||
| :target: https://dev.azure.com/thomasrobitaille/aplpy/_build/latest?definitionId=16&repoName=aplpy%2Faplpy&branchName=refs%2Fpull%2F441%2Fmerge | ||
| .. |CircleCI| image:: https://circleci.com/gh/aplpy/aplpy/tree/main.svg?style=svg | ||
| :target: https://circleci.com/gh/aplpy/aplpy/tree/main | ||
| .. |codecov| image:: https://codecov.io/gh/aplpy/aplpy/branch/main/graph/badge.svg | ||
| :target: https://codecov.io/gh/aplpy/aplpy | ||
| .. |Documentation Status| image:: https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat | ||
| :target: https://aplpy.readthedocs.io/en/latest/ |
+70
-30
@@ -1,37 +0,77 @@ | ||
| [build_sphinx] | ||
| source-dir = docs | ||
| build-dir = docs/_build | ||
| all_files = 1 | ||
| [metadata] | ||
| name = aplpy | ||
| author = Thomas Robitaille and Eli Bressert | ||
| author_email = thomas.robitaille@gmail.com | ||
| license = MIT | ||
| license_file = LICENSE.md | ||
| url = http://aplpy.github.io | ||
| description = The Astronomical Plotting Library in Python | ||
| long_description = file: README.rst | ||
| long_description_content_type = text/x-rst | ||
| edit_on_github = False | ||
| github_project = APLpy/aplpy | ||
| [build_docs] | ||
| source-dir = docs | ||
| build-dir = docs/_build | ||
| all_files = 1 | ||
| [options] | ||
| zip_safe = False | ||
| packages = find: | ||
| python_requires = >=3.6 | ||
| setup_requires = setuptools_scm | ||
| install_requires = | ||
| numpy | ||
| astropy>=3.1 | ||
| matplotlib>=2.0 | ||
| reproject>=0.4 | ||
| pyregion>=2.0 | ||
| pillow>=4.3 | ||
| pyavm>=0.9.4 | ||
| scikit-image>=0.14 | ||
| shapely>=1.7 | ||
| [upload_docs] | ||
| upload-dir = docs/_build/html | ||
| show-response = 1 | ||
| [options.extras_require] | ||
| test = | ||
| pytest-astropy | ||
| pytest-mpl | ||
| docs = | ||
| sphinx-astropy | ||
| [options.package_data] | ||
| aplpy = *.reg, *.hdr | ||
| [tool:pytest] | ||
| minversion = 3.0 | ||
| norecursedirs = build docs/_build | ||
| testpaths = "aplpy" "docs" | ||
| astropy_header = true | ||
| doctest_plus = enabled | ||
| text_file_format = rst | ||
| addopts = --doctest-rst | ||
| [ah_bootstrap] | ||
| auto_use = True | ||
| [coverage:run] | ||
| omit = | ||
| aplpy/_astropy_init* | ||
| aplpy/conftest.py | ||
| aplpy/*setup_package* | ||
| aplpy/tests/* | ||
| aplpy/*/tests/* | ||
| aplpy/extern/* | ||
| aplpy/version* | ||
| */aplpy/_astropy_init* | ||
| */aplpy/conftest.py | ||
| */aplpy/*setup_package* | ||
| */aplpy/tests/* | ||
| */aplpy/*/tests/* | ||
| */aplpy/extern/* | ||
| */aplpy/version* | ||
| [metadata] | ||
| package_name = aplpy | ||
| description = The Astronomical Plotting Library in Python | ||
| author = Thomas Robitaille and Eli Bressert | ||
| author_email = thomas.robitaille@gmail.com, elibre@users.sourceforge.net | ||
| license = MIT | ||
| url = http://aplpy.github.io | ||
| edit_on_github = False | ||
| github_project = aplpy/aplpy | ||
| install_requires = numpy, astropy>=3.1, matplotlib>=2.0, reproject>=0.4, pyregion>=2.0, pillow>=4.0, pyavm>=0.9.4, scikit-image>=0.14, shapely>=1.6 | ||
| # version should be PEP440 compatible (https://www.python.org/dev/peps/pep-0440/) | ||
| version = 2.0.3 | ||
| [coverage:report] | ||
| exclude_lines = | ||
| pragma: no cover | ||
| except ImportError | ||
| raise AssertionError | ||
| raise NotImplementedError | ||
| def main\(.*\): | ||
| pragma: py{ignore_python_version} | ||
| def _ipython_key_completions_ | ||
| [options.extras_require] | ||
| docs = sphinx-astropy | ||
| test = pytest-astropy; codecov; pytest-mpl | ||
| [egg_info] | ||
| tag_build = | ||
| tag_date = 0 | ||
+49
-112
| #!/usr/bin/env python | ||
| # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| import glob | ||
| # NOTE: The configuration for the package, including the name, version, and | ||
| # other information are set in the setup.cfg file. | ||
| import os | ||
| import sys | ||
| import ah_bootstrap | ||
| from setuptools import setup | ||
| # A dirty hack to get around some early import/configurations ambiguities | ||
| if sys.version_info[0] >= 3: | ||
| import builtins | ||
| else: | ||
| import __builtin__ as builtins | ||
| builtins._ASTROPY_SETUP_ = True | ||
| from astropy_helpers.setup_helpers import (register_commands, get_debug_option, | ||
| get_package_info) | ||
| from astropy_helpers.git_helpers import get_git_devstr | ||
| from astropy_helpers.version_helpers import generate_version_py | ||
| # First provide helpful messages if contributors try and run legacy commands | ||
| # for tests or docs. | ||
| # Get some values from the setup.cfg | ||
| try: | ||
| from ConfigParser import ConfigParser | ||
| except ImportError: | ||
| from configparser import ConfigParser | ||
| TEST_HELP = """ | ||
| Note: running tests is no longer done using 'python setup.py test'. Instead | ||
| you will need to run: | ||
| conf = ConfigParser() | ||
| conf.read(['setup.cfg']) | ||
| metadata = dict(conf.items('metadata')) | ||
| tox -e test | ||
| PACKAGENAME = metadata.get('package_name', 'packagename') | ||
| DESCRIPTION = metadata.get('description', 'packagename') | ||
| AUTHOR = metadata.get('author', 'Astropy Developers') | ||
| AUTHOR_EMAIL = metadata.get('author_email', '') | ||
| LICENSE = metadata.get('license', 'unknown') | ||
| URL = metadata.get('url', 'http://astropy.org') | ||
| If you don't already have tox installed, you can install it with: | ||
| # order of priority for long_description: | ||
| # (1) set in setup.cfg, | ||
| # (2) load LONG_DESCRIPTION.rst, | ||
| # (3) load README.rst, | ||
| # (4) package docstring | ||
| readme_glob = 'README*' | ||
| _cfg_long_description = metadata.get('long_description', '') | ||
| if _cfg_long_description: | ||
| LONG_DESCRIPTION = _cfg_long_description | ||
| pip install tox | ||
| elif os.path.exists('LONG_DESCRIPTION.rst'): | ||
| with open('LONG_DESCRIPTION.rst') as f: | ||
| LONG_DESCRIPTION = f.read() | ||
| If you only want to run part of the test suite, you can also use pytest | ||
| directly with:: | ||
| elif len(glob.glob(readme_glob)) > 0: | ||
| with open(glob.glob(readme_glob)[0]) as f: | ||
| LONG_DESCRIPTION = f.read() | ||
| pip install -e .[test] | ||
| pytest | ||
| else: | ||
| # Get the long description from the package's docstring | ||
| __import__(PACKAGENAME) | ||
| package = sys.modules[PACKAGENAME] | ||
| LONG_DESCRIPTION = package.__doc__ | ||
| For more information, see: | ||
| # Store the package name in a built-in variable so it's easy | ||
| # to get from other parts of the setup infrastructure | ||
| builtins._ASTROPY_PACKAGE_NAME_ = PACKAGENAME | ||
| http://docs.astropy.org/en/latest/development/testguide.html#running-tests | ||
| """ | ||
| # VERSION should be PEP440 compatible (http://www.python.org/dev/peps/pep-0440) | ||
| VERSION = metadata.get('version', '0.0.dev0') | ||
| if 'test' in sys.argv: | ||
| print(TEST_HELP) | ||
| sys.exit(1) | ||
| # Indicates if this version is a release version | ||
| RELEASE = 'dev' not in VERSION | ||
| DOCS_HELP = """ | ||
| Note: building the documentation is no longer done using | ||
| 'python setup.py build_docs'. Instead you will need to run: | ||
| if not RELEASE: | ||
| VERSION += get_git_devstr(False) | ||
| tox -e build_docs | ||
| # Populate the dict of setup command overrides; this should be done before | ||
| # invoking any other functionality from distutils since it can potentially | ||
| # modify distutils' behavior. | ||
| cmdclassd = register_commands(PACKAGENAME, VERSION, RELEASE) | ||
| If you don't already have tox installed, you can install it with: | ||
| # Freeze build information in version.py | ||
| generate_version_py(PACKAGENAME, VERSION, RELEASE, | ||
| get_debug_option(PACKAGENAME)) | ||
| pip install tox | ||
| # Treat everything in scripts except README* as a script to be installed | ||
| scripts = [fname for fname in glob.glob(os.path.join('scripts', '*')) | ||
| if not os.path.basename(fname).startswith('README')] | ||
| You can also build the documentation with Sphinx directly using:: | ||
| pip install -e .[docs] | ||
| cd docs | ||
| make html | ||
| # Get configuration information from all of the various subpackages. | ||
| # See the docstring for setup_helpers.update_package_files for more | ||
| # details. | ||
| package_info = get_package_info() | ||
| For more information, see: | ||
| # Add the project-global data | ||
| package_info['package_data'].setdefault(PACKAGENAME, []) | ||
| package_info['package_data'][PACKAGENAME].append('data/*') | ||
| http://docs.astropy.org/en/latest/install.html#builddocs | ||
| """ | ||
| # Define entry points for command-line scripts | ||
| entry_points = {'console_scripts': []} | ||
| if 'build_docs' in sys.argv or 'build_sphinx' in sys.argv: | ||
| print(DOCS_HELP) | ||
| sys.exit(1) | ||
| if conf.has_section('entry_points'): | ||
| entry_point_list = conf.items('entry_points') | ||
| for entry_point in entry_point_list: | ||
| entry_points['console_scripts'].append('{0} = {1}'.format( | ||
| entry_point[0], entry_point[1])) | ||
| VERSION_TEMPLATE = """ | ||
| # Note that we need to fall back to the hard-coded version if either | ||
| # setuptools_scm can't be imported or setuptools_scm can't determine the | ||
| # version, so we catch the generic 'Exception'. | ||
| try: | ||
| from setuptools_scm import get_version | ||
| version = get_version(root='..', relative_to=__file__) | ||
| except Exception: | ||
| version = '{version}' | ||
| """.lstrip() | ||
| # Include all .c files, recursively, including those generated by | ||
| # Cython, since we can not do this in MANIFEST.in with a "dynamic" | ||
| # directory name. | ||
| c_files = [] | ||
| for root, dirs, files in os.walk(PACKAGENAME): | ||
| for filename in files: | ||
| if filename.endswith('.c'): | ||
| c_files.append( | ||
| os.path.join( | ||
| os.path.relpath(root, PACKAGENAME), filename)) | ||
| package_info['package_data'][PACKAGENAME].extend(c_files) | ||
| # Note that requires and provides should not be included in the call to | ||
| # ``setup``, since these are now deprecated. See this link for more details: | ||
| # https://groups.google.com/forum/#!topic/astropy-dev/urYO8ckB2uM | ||
| setup(name='APLpy', | ||
| version=VERSION, | ||
| description=DESCRIPTION, | ||
| scripts=scripts, | ||
| install_requires=[s.strip() for s in metadata.get('install_requires', 'astropy').split(',')], | ||
| author=AUTHOR, | ||
| author_email=AUTHOR_EMAIL, | ||
| license=LICENSE, | ||
| url=URL, | ||
| long_description=LONG_DESCRIPTION, | ||
| cmdclass=cmdclassd, | ||
| zip_safe=False, | ||
| use_2to3=False, | ||
| entry_points=entry_points, | ||
| python_requires='>=3.5', | ||
| **package_info | ||
| ) | ||
| setup(use_scm_version={'write_to': os.path.join('aplpy', 'version.py'), | ||
| 'write_to_template': VERSION_TEMPLATE}) |
-975
| """ | ||
| This bootstrap module contains code for ensuring that the astropy_helpers | ||
| package will be importable by the time the setup.py script runs. It also | ||
| includes some workarounds to ensure that a recent-enough version of setuptools | ||
| is being used for the installation. | ||
| This module should be the first thing imported in the setup.py of distributions | ||
| that make use of the utilities in astropy_helpers. If the distribution ships | ||
| with its own copy of astropy_helpers, this module will first attempt to import | ||
| from the shipped copy. However, it will also check PyPI to see if there are | ||
| any bug-fix releases on top of the current version that may be useful to get | ||
| past platform-specific bugs that have been fixed. When running setup.py, use | ||
| the ``--offline`` command-line option to disable the auto-upgrade checks. | ||
| When this module is imported or otherwise executed it automatically calls a | ||
| main function that attempts to read the project's setup.cfg file, which it | ||
| checks for a configuration section called ``[ah_bootstrap]`` the presences of | ||
| that section, and options therein, determine the next step taken: If it | ||
| contains an option called ``auto_use`` with a value of ``True``, it will | ||
| automatically call the main function of this module called | ||
| `use_astropy_helpers` (see that function's docstring for full details). | ||
| Otherwise no further action is taken and by default the system-installed version | ||
| of astropy-helpers will be used (however, ``ah_bootstrap.use_astropy_helpers`` | ||
| may be called manually from within the setup.py script). | ||
| This behavior can also be controlled using the ``--auto-use`` and | ||
| ``--no-auto-use`` command-line flags. For clarity, an alias for | ||
| ``--no-auto-use`` is ``--use-system-astropy-helpers``, and we recommend using | ||
| the latter if needed. | ||
| Additional options in the ``[ah_boostrap]`` section of setup.cfg have the same | ||
| names as the arguments to `use_astropy_helpers`, and can be used to configure | ||
| the bootstrap script when ``auto_use = True``. | ||
| See https://github.com/astropy/astropy-helpers for more details, and for the | ||
| latest version of this module. | ||
| """ | ||
| import contextlib | ||
| import errno | ||
| import io | ||
| import locale | ||
| import os | ||
| import re | ||
| import subprocess as sp | ||
| import sys | ||
| __minimum_python_version__ = (3, 5) | ||
| if sys.version_info < __minimum_python_version__: | ||
| print("ERROR: Python {} or later is required by astropy-helpers".format( | ||
| __minimum_python_version__)) | ||
| sys.exit(1) | ||
| try: | ||
| from ConfigParser import ConfigParser, RawConfigParser | ||
| except ImportError: | ||
| from configparser import ConfigParser, RawConfigParser | ||
| _str_types = (str, bytes) | ||
| # What follows are several import statements meant to deal with install-time | ||
| # issues with either missing or misbehaving pacakges (including making sure | ||
| # setuptools itself is installed): | ||
| # Check that setuptools 1.0 or later is present | ||
| from distutils.version import LooseVersion | ||
| try: | ||
| import setuptools | ||
| assert LooseVersion(setuptools.__version__) >= LooseVersion('1.0') | ||
| except (ImportError, AssertionError): | ||
| print("ERROR: setuptools 1.0 or later is required by astropy-helpers") | ||
| sys.exit(1) | ||
| # typing as a dependency for 1.6.1+ Sphinx causes issues when imported after | ||
| # initializing submodule with ah_boostrap.py | ||
| # See discussion and references in | ||
| # https://github.com/astropy/astropy-helpers/issues/302 | ||
| try: | ||
| import typing # noqa | ||
| except ImportError: | ||
| pass | ||
| # Note: The following import is required as a workaround to | ||
| # https://github.com/astropy/astropy-helpers/issues/89; if we don't import this | ||
| # module now, it will get cleaned up after `run_setup` is called, but that will | ||
| # later cause the TemporaryDirectory class defined in it to stop working when | ||
| # used later on by setuptools | ||
| try: | ||
| import setuptools.py31compat # noqa | ||
| except ImportError: | ||
| pass | ||
| # matplotlib can cause problems if it is imported from within a call of | ||
| # run_setup(), because in some circumstances it will try to write to the user's | ||
| # home directory, resulting in a SandboxViolation. See | ||
| # https://github.com/matplotlib/matplotlib/pull/4165 | ||
| # Making sure matplotlib, if it is available, is imported early in the setup | ||
| # process can mitigate this (note importing matplotlib.pyplot has the same | ||
| # issue) | ||
| try: | ||
| import matplotlib | ||
| matplotlib.use('Agg') | ||
| import matplotlib.pyplot | ||
| except: | ||
| # Ignore if this fails for *any* reason* | ||
| pass | ||
| # End compatibility imports... | ||
| # In case it didn't successfully import before the ez_setup checks | ||
| import pkg_resources | ||
| from setuptools import Distribution | ||
| from setuptools.package_index import PackageIndex | ||
| from distutils import log | ||
| from distutils.debug import DEBUG | ||
| # TODO: Maybe enable checking for a specific version of astropy_helpers? | ||
| DIST_NAME = 'astropy-helpers' | ||
| PACKAGE_NAME = 'astropy_helpers' | ||
| UPPER_VERSION_EXCLUSIVE = None | ||
| # Defaults for other options | ||
| DOWNLOAD_IF_NEEDED = True | ||
| INDEX_URL = 'https://pypi.python.org/simple' | ||
| USE_GIT = True | ||
| OFFLINE = False | ||
| AUTO_UPGRADE = True | ||
| # A list of all the configuration options and their required types | ||
| CFG_OPTIONS = [ | ||
| ('auto_use', bool), ('path', str), ('download_if_needed', bool), | ||
| ('index_url', str), ('use_git', bool), ('offline', bool), | ||
| ('auto_upgrade', bool) | ||
| ] | ||
| class _Bootstrapper(object): | ||
| """ | ||
| Bootstrapper implementation. See ``use_astropy_helpers`` for parameter | ||
| documentation. | ||
| """ | ||
| def __init__(self, path=None, index_url=None, use_git=None, offline=None, | ||
| download_if_needed=None, auto_upgrade=None): | ||
| if path is None: | ||
| path = PACKAGE_NAME | ||
| if not (isinstance(path, _str_types) or path is False): | ||
| raise TypeError('path must be a string or False') | ||
| if not isinstance(path, str): | ||
| fs_encoding = sys.getfilesystemencoding() | ||
| path = path.decode(fs_encoding) # path to unicode | ||
| self.path = path | ||
| # Set other option attributes, using defaults where necessary | ||
| self.index_url = index_url if index_url is not None else INDEX_URL | ||
| self.offline = offline if offline is not None else OFFLINE | ||
| # If offline=True, override download and auto-upgrade | ||
| if self.offline: | ||
| download_if_needed = False | ||
| auto_upgrade = False | ||
| self.download = (download_if_needed | ||
| if download_if_needed is not None | ||
| else DOWNLOAD_IF_NEEDED) | ||
| self.auto_upgrade = (auto_upgrade | ||
| if auto_upgrade is not None else AUTO_UPGRADE) | ||
| # If this is a release then the .git directory will not exist so we | ||
| # should not use git. | ||
| git_dir_exists = os.path.exists(os.path.join(os.path.dirname(__file__), '.git')) | ||
| if use_git is None and not git_dir_exists: | ||
| use_git = False | ||
| self.use_git = use_git if use_git is not None else USE_GIT | ||
| # Declared as False by default--later we check if astropy-helpers can be | ||
| # upgraded from PyPI, but only if not using a source distribution (as in | ||
| # the case of import from a git submodule) | ||
| self.is_submodule = False | ||
| @classmethod | ||
| def main(cls, argv=None): | ||
| if argv is None: | ||
| argv = sys.argv | ||
| config = cls.parse_config() | ||
| config.update(cls.parse_command_line(argv)) | ||
| auto_use = config.pop('auto_use', False) | ||
| bootstrapper = cls(**config) | ||
| if auto_use: | ||
| # Run the bootstrapper, otherwise the setup.py is using the old | ||
| # use_astropy_helpers() interface, in which case it will run the | ||
| # bootstrapper manually after reconfiguring it. | ||
| bootstrapper.run() | ||
| return bootstrapper | ||
| @classmethod | ||
| def parse_config(cls): | ||
| if not os.path.exists('setup.cfg'): | ||
| return {} | ||
| cfg = ConfigParser() | ||
| try: | ||
| cfg.read('setup.cfg') | ||
| except Exception as e: | ||
| if DEBUG: | ||
| raise | ||
| log.error( | ||
| "Error reading setup.cfg: {0!r}\n{1} will not be " | ||
| "automatically bootstrapped and package installation may fail." | ||
| "\n{2}".format(e, PACKAGE_NAME, _err_help_msg)) | ||
| return {} | ||
| if not cfg.has_section('ah_bootstrap'): | ||
| return {} | ||
| config = {} | ||
| for option, type_ in CFG_OPTIONS: | ||
| if not cfg.has_option('ah_bootstrap', option): | ||
| continue | ||
| if type_ is bool: | ||
| value = cfg.getboolean('ah_bootstrap', option) | ||
| else: | ||
| value = cfg.get('ah_bootstrap', option) | ||
| config[option] = value | ||
| return config | ||
| @classmethod | ||
| def parse_command_line(cls, argv=None): | ||
| if argv is None: | ||
| argv = sys.argv | ||
| config = {} | ||
| # For now we just pop recognized ah_bootstrap options out of the | ||
| # arg list. This is imperfect; in the unlikely case that a setup.py | ||
| # custom command or even custom Distribution class defines an argument | ||
| # of the same name then we will break that. However there's a catch22 | ||
| # here that we can't just do full argument parsing right here, because | ||
| # we don't yet know *how* to parse all possible command-line arguments. | ||
| if '--no-git' in argv: | ||
| config['use_git'] = False | ||
| argv.remove('--no-git') | ||
| if '--offline' in argv: | ||
| config['offline'] = True | ||
| argv.remove('--offline') | ||
| if '--auto-use' in argv: | ||
| config['auto_use'] = True | ||
| argv.remove('--auto-use') | ||
| if '--no-auto-use' in argv: | ||
| config['auto_use'] = False | ||
| argv.remove('--no-auto-use') | ||
| if '--use-system-astropy-helpers' in argv: | ||
| config['auto_use'] = False | ||
| argv.remove('--use-system-astropy-helpers') | ||
| return config | ||
| def run(self): | ||
| strategies = ['local_directory', 'local_file', 'index'] | ||
| dist = None | ||
| # First, remove any previously imported versions of astropy_helpers; | ||
| # this is necessary for nested installs where one package's installer | ||
| # is installing another package via setuptools.sandbox.run_setup, as in | ||
| # the case of setup_requires | ||
| for key in list(sys.modules): | ||
| try: | ||
| if key == PACKAGE_NAME or key.startswith(PACKAGE_NAME + '.'): | ||
| del sys.modules[key] | ||
| except AttributeError: | ||
| # Sometimes mysterious non-string things can turn up in | ||
| # sys.modules | ||
| continue | ||
| # Check to see if the path is a submodule | ||
| self.is_submodule = self._check_submodule() | ||
| for strategy in strategies: | ||
| method = getattr(self, 'get_{0}_dist'.format(strategy)) | ||
| dist = method() | ||
| if dist is not None: | ||
| break | ||
| else: | ||
| raise _AHBootstrapSystemExit( | ||
| "No source found for the {0!r} package; {0} must be " | ||
| "available and importable as a prerequisite to building " | ||
| "or installing this package.".format(PACKAGE_NAME)) | ||
| # This is a bit hacky, but if astropy_helpers was loaded from a | ||
| # directory/submodule its Distribution object gets a "precedence" of | ||
| # "DEVELOP_DIST". However, in other cases it gets a precedence of | ||
| # "EGG_DIST". However, when activing the distribution it will only be | ||
| # placed early on sys.path if it is treated as an EGG_DIST, so always | ||
| # do that | ||
| dist = dist.clone(precedence=pkg_resources.EGG_DIST) | ||
| # Otherwise we found a version of astropy-helpers, so we're done | ||
| # Just active the found distribution on sys.path--if we did a | ||
| # download this usually happens automatically but it doesn't hurt to | ||
| # do it again | ||
| # Note: Adding the dist to the global working set also activates it | ||
| # (makes it importable on sys.path) by default. | ||
| try: | ||
| pkg_resources.working_set.add(dist, replace=True) | ||
| except TypeError: | ||
| # Some (much) older versions of setuptools do not have the | ||
| # replace=True option here. These versions are old enough that all | ||
| # bets may be off anyways, but it's easy enough to work around just | ||
| # in case... | ||
| if dist.key in pkg_resources.working_set.by_key: | ||
| del pkg_resources.working_set.by_key[dist.key] | ||
| pkg_resources.working_set.add(dist) | ||
| @property | ||
| def config(self): | ||
| """ | ||
| A `dict` containing the options this `_Bootstrapper` was configured | ||
| with. | ||
| """ | ||
| return dict((optname, getattr(self, optname)) | ||
| for optname, _ in CFG_OPTIONS if hasattr(self, optname)) | ||
| def get_local_directory_dist(self): | ||
| """ | ||
| Handle importing a vendored package from a subdirectory of the source | ||
| distribution. | ||
| """ | ||
| if not os.path.isdir(self.path): | ||
| return | ||
| log.info('Attempting to import astropy_helpers from {0} {1!r}'.format( | ||
| 'submodule' if self.is_submodule else 'directory', | ||
| self.path)) | ||
| dist = self._directory_import() | ||
| if dist is None: | ||
| log.warn( | ||
| 'The requested path {0!r} for importing {1} does not ' | ||
| 'exist, or does not contain a copy of the {1} ' | ||
| 'package.'.format(self.path, PACKAGE_NAME)) | ||
| elif self.auto_upgrade and not self.is_submodule: | ||
| # A version of astropy-helpers was found on the available path, but | ||
| # check to see if a bugfix release is available on PyPI | ||
| upgrade = self._do_upgrade(dist) | ||
| if upgrade is not None: | ||
| dist = upgrade | ||
| return dist | ||
| def get_local_file_dist(self): | ||
| """ | ||
| Handle importing from a source archive; this also uses setup_requires | ||
| but points easy_install directly to the source archive. | ||
| """ | ||
| if not os.path.isfile(self.path): | ||
| return | ||
| log.info('Attempting to unpack and import astropy_helpers from ' | ||
| '{0!r}'.format(self.path)) | ||
| try: | ||
| dist = self._do_download(find_links=[self.path]) | ||
| except Exception as e: | ||
| if DEBUG: | ||
| raise | ||
| log.warn( | ||
| 'Failed to import {0} from the specified archive {1!r}: ' | ||
| '{2}'.format(PACKAGE_NAME, self.path, str(e))) | ||
| dist = None | ||
| if dist is not None and self.auto_upgrade: | ||
| # A version of astropy-helpers was found on the available path, but | ||
| # check to see if a bugfix release is available on PyPI | ||
| upgrade = self._do_upgrade(dist) | ||
| if upgrade is not None: | ||
| dist = upgrade | ||
| return dist | ||
| def get_index_dist(self): | ||
| if not self.download: | ||
| log.warn('Downloading {0!r} disabled.'.format(DIST_NAME)) | ||
| return None | ||
| log.warn( | ||
| "Downloading {0!r}; run setup.py with the --offline option to " | ||
| "force offline installation.".format(DIST_NAME)) | ||
| try: | ||
| dist = self._do_download() | ||
| except Exception as e: | ||
| if DEBUG: | ||
| raise | ||
| log.warn( | ||
| 'Failed to download and/or install {0!r} from {1!r}:\n' | ||
| '{2}'.format(DIST_NAME, self.index_url, str(e))) | ||
| dist = None | ||
| # No need to run auto-upgrade here since we've already presumably | ||
| # gotten the most up-to-date version from the package index | ||
| return dist | ||
| def _directory_import(self): | ||
| """ | ||
| Import astropy_helpers from the given path, which will be added to | ||
| sys.path. | ||
| Must return True if the import succeeded, and False otherwise. | ||
| """ | ||
| # Return True on success, False on failure but download is allowed, and | ||
| # otherwise raise SystemExit | ||
| path = os.path.abspath(self.path) | ||
| # Use an empty WorkingSet rather than the man | ||
| # pkg_resources.working_set, since on older versions of setuptools this | ||
| # will invoke a VersionConflict when trying to install an upgrade | ||
| ws = pkg_resources.WorkingSet([]) | ||
| ws.add_entry(path) | ||
| dist = ws.by_key.get(DIST_NAME) | ||
| if dist is None: | ||
| # We didn't find an egg-info/dist-info in the given path, but if a | ||
| # setup.py exists we can generate it | ||
| setup_py = os.path.join(path, 'setup.py') | ||
| if os.path.isfile(setup_py): | ||
| # We use subprocess instead of run_setup from setuptools to | ||
| # avoid segmentation faults - see the following for more details: | ||
| # https://github.com/cython/cython/issues/2104 | ||
| sp.check_output([sys.executable, 'setup.py', 'egg_info'], cwd=path) | ||
| for dist in pkg_resources.find_distributions(path, True): | ||
| # There should be only one... | ||
| return dist | ||
| return dist | ||
| def _do_download(self, version='', find_links=None): | ||
| if find_links: | ||
| allow_hosts = '' | ||
| index_url = None | ||
| else: | ||
| allow_hosts = None | ||
| index_url = self.index_url | ||
| # Annoyingly, setuptools will not handle other arguments to | ||
| # Distribution (such as options) before handling setup_requires, so it | ||
| # is not straightforward to programmatically augment the arguments which | ||
| # are passed to easy_install | ||
| class _Distribution(Distribution): | ||
| def get_option_dict(self, command_name): | ||
| opts = Distribution.get_option_dict(self, command_name) | ||
| if command_name == 'easy_install': | ||
| if find_links is not None: | ||
| opts['find_links'] = ('setup script', find_links) | ||
| if index_url is not None: | ||
| opts['index_url'] = ('setup script', index_url) | ||
| if allow_hosts is not None: | ||
| opts['allow_hosts'] = ('setup script', allow_hosts) | ||
| return opts | ||
| if version: | ||
| req = '{0}=={1}'.format(DIST_NAME, version) | ||
| else: | ||
| if UPPER_VERSION_EXCLUSIVE is None: | ||
| req = DIST_NAME | ||
| else: | ||
| req = '{0}<{1}'.format(DIST_NAME, UPPER_VERSION_EXCLUSIVE) | ||
| attrs = {'setup_requires': [req]} | ||
| # NOTE: we need to parse the config file (e.g. setup.cfg) to make sure | ||
| # it honours the options set in the [easy_install] section, and we need | ||
| # to explicitly fetch the requirement eggs as setup_requires does not | ||
| # get honored in recent versions of setuptools: | ||
| # https://github.com/pypa/setuptools/issues/1273 | ||
| try: | ||
| context = _verbose if DEBUG else _silence | ||
| with context(): | ||
| dist = _Distribution(attrs=attrs) | ||
| try: | ||
| dist.parse_config_files(ignore_option_errors=True) | ||
| dist.fetch_build_eggs(req) | ||
| except TypeError: | ||
| # On older versions of setuptools, ignore_option_errors | ||
| # doesn't exist, and the above two lines are not needed | ||
| # so we can just continue | ||
| pass | ||
| # If the setup_requires succeeded it will have added the new dist to | ||
| # the main working_set | ||
| return pkg_resources.working_set.by_key.get(DIST_NAME) | ||
| except Exception as e: | ||
| if DEBUG: | ||
| raise | ||
| msg = 'Error retrieving {0} from {1}:\n{2}' | ||
| if find_links: | ||
| source = find_links[0] | ||
| elif index_url != INDEX_URL: | ||
| source = index_url | ||
| else: | ||
| source = 'PyPI' | ||
| raise Exception(msg.format(DIST_NAME, source, repr(e))) | ||
| def _do_upgrade(self, dist): | ||
| # Build up a requirement for a higher bugfix release but a lower minor | ||
| # release (so API compatibility is guaranteed) | ||
| next_version = _next_version(dist.parsed_version) | ||
| req = pkg_resources.Requirement.parse( | ||
| '{0}>{1},<{2}'.format(DIST_NAME, dist.version, next_version)) | ||
| package_index = PackageIndex(index_url=self.index_url) | ||
| upgrade = package_index.obtain(req) | ||
| if upgrade is not None: | ||
| return self._do_download(version=upgrade.version) | ||
| def _check_submodule(self): | ||
| """ | ||
| Check if the given path is a git submodule. | ||
| See the docstrings for ``_check_submodule_using_git`` and | ||
| ``_check_submodule_no_git`` for further details. | ||
| """ | ||
| if (self.path is None or | ||
| (os.path.exists(self.path) and not os.path.isdir(self.path))): | ||
| return False | ||
| if self.use_git: | ||
| return self._check_submodule_using_git() | ||
| else: | ||
| return self._check_submodule_no_git() | ||
| def _check_submodule_using_git(self): | ||
| """ | ||
| Check if the given path is a git submodule. If so, attempt to initialize | ||
| and/or update the submodule if needed. | ||
| This function makes calls to the ``git`` command in subprocesses. The | ||
| ``_check_submodule_no_git`` option uses pure Python to check if the given | ||
| path looks like a git submodule, but it cannot perform updates. | ||
| """ | ||
| cmd = ['git', 'submodule', 'status', '--', self.path] | ||
| try: | ||
| log.info('Running `{0}`; use the --no-git option to disable git ' | ||
| 'commands'.format(' '.join(cmd))) | ||
| returncode, stdout, stderr = run_cmd(cmd) | ||
| except _CommandNotFound: | ||
| # The git command simply wasn't found; this is most likely the | ||
| # case on user systems that don't have git and are simply | ||
| # trying to install the package from PyPI or a source | ||
| # distribution. Silently ignore this case and simply don't try | ||
| # to use submodules | ||
| return False | ||
| stderr = stderr.strip() | ||
| if returncode != 0 and stderr: | ||
| # Unfortunately the return code alone cannot be relied on, as | ||
| # earlier versions of git returned 0 even if the requested submodule | ||
| # does not exist | ||
| # This is a warning that occurs in perl (from running git submodule) | ||
| # which only occurs with a malformatted locale setting which can | ||
| # happen sometimes on OSX. See again | ||
| # https://github.com/astropy/astropy/issues/2749 | ||
| perl_warning = ('perl: warning: Falling back to the standard locale ' | ||
| '("C").') | ||
| if not stderr.strip().endswith(perl_warning): | ||
| # Some other unknown error condition occurred | ||
| log.warn('git submodule command failed ' | ||
| 'unexpectedly:\n{0}'.format(stderr)) | ||
| return False | ||
| # Output of `git submodule status` is as follows: | ||
| # | ||
| # 1: Status indicator: '-' for submodule is uninitialized, '+' if | ||
| # submodule is initialized but is not at the commit currently indicated | ||
| # in .gitmodules (and thus needs to be updated), or 'U' if the | ||
| # submodule is in an unstable state (i.e. has merge conflicts) | ||
| # | ||
| # 2. SHA-1 hash of the current commit of the submodule (we don't really | ||
| # need this information but it's useful for checking that the output is | ||
| # correct) | ||
| # | ||
| # 3. The output of `git describe` for the submodule's current commit | ||
| # hash (this includes for example what branches the commit is on) but | ||
| # only if the submodule is initialized. We ignore this information for | ||
| # now | ||
| _git_submodule_status_re = re.compile( | ||
| '^(?P<status>[+-U ])(?P<commit>[0-9a-f]{40}) ' | ||
| '(?P<submodule>\S+)( .*)?$') | ||
| # The stdout should only contain one line--the status of the | ||
| # requested submodule | ||
| m = _git_submodule_status_re.match(stdout) | ||
| if m: | ||
| # Yes, the path *is* a git submodule | ||
| self._update_submodule(m.group('submodule'), m.group('status')) | ||
| return True | ||
| else: | ||
| log.warn( | ||
| 'Unexpected output from `git submodule status`:\n{0}\n' | ||
| 'Will attempt import from {1!r} regardless.'.format( | ||
| stdout, self.path)) | ||
| return False | ||
| def _check_submodule_no_git(self): | ||
| """ | ||
| Like ``_check_submodule_using_git``, but simply parses the .gitmodules file | ||
| to determine if the supplied path is a git submodule, and does not exec any | ||
| subprocesses. | ||
| This can only determine if a path is a submodule--it does not perform | ||
| updates, etc. This function may need to be updated if the format of the | ||
| .gitmodules file is changed between git versions. | ||
| """ | ||
| gitmodules_path = os.path.abspath('.gitmodules') | ||
| if not os.path.isfile(gitmodules_path): | ||
| return False | ||
| # This is a minimal reader for gitconfig-style files. It handles a few of | ||
| # the quirks that make gitconfig files incompatible with ConfigParser-style | ||
| # files, but does not support the full gitconfig syntax (just enough | ||
| # needed to read a .gitmodules file). | ||
| gitmodules_fileobj = io.StringIO() | ||
| # Must use io.open for cross-Python-compatible behavior wrt unicode | ||
| with io.open(gitmodules_path) as f: | ||
| for line in f: | ||
| # gitconfig files are more flexible with leading whitespace; just | ||
| # go ahead and remove it | ||
| line = line.lstrip() | ||
| # comments can start with either # or ; | ||
| if line and line[0] in (':', ';'): | ||
| continue | ||
| gitmodules_fileobj.write(line) | ||
| gitmodules_fileobj.seek(0) | ||
| cfg = RawConfigParser() | ||
| try: | ||
| cfg.readfp(gitmodules_fileobj) | ||
| except Exception as exc: | ||
| log.warn('Malformatted .gitmodules file: {0}\n' | ||
| '{1} cannot be assumed to be a git submodule.'.format( | ||
| exc, self.path)) | ||
| return False | ||
| for section in cfg.sections(): | ||
| if not cfg.has_option(section, 'path'): | ||
| continue | ||
| submodule_path = cfg.get(section, 'path').rstrip(os.sep) | ||
| if submodule_path == self.path.rstrip(os.sep): | ||
| return True | ||
| return False | ||
| def _update_submodule(self, submodule, status): | ||
| if status == ' ': | ||
| # The submodule is up to date; no action necessary | ||
| return | ||
| elif status == '-': | ||
| if self.offline: | ||
| raise _AHBootstrapSystemExit( | ||
| "Cannot initialize the {0} submodule in --offline mode; " | ||
| "this requires being able to clone the submodule from an " | ||
| "online repository.".format(submodule)) | ||
| cmd = ['update', '--init'] | ||
| action = 'Initializing' | ||
| elif status == '+': | ||
| cmd = ['update'] | ||
| action = 'Updating' | ||
| if self.offline: | ||
| cmd.append('--no-fetch') | ||
| elif status == 'U': | ||
| raise _AHBootstrapSystemExit( | ||
| 'Error: Submodule {0} contains unresolved merge conflicts. ' | ||
| 'Please complete or abandon any changes in the submodule so that ' | ||
| 'it is in a usable state, then try again.'.format(submodule)) | ||
| else: | ||
| log.warn('Unknown status {0!r} for git submodule {1!r}. Will ' | ||
| 'attempt to use the submodule as-is, but try to ensure ' | ||
| 'that the submodule is in a clean state and contains no ' | ||
| 'conflicts or errors.\n{2}'.format(status, submodule, | ||
| _err_help_msg)) | ||
| return | ||
| err_msg = None | ||
| cmd = ['git', 'submodule'] + cmd + ['--', submodule] | ||
| log.warn('{0} {1} submodule with: `{2}`'.format( | ||
| action, submodule, ' '.join(cmd))) | ||
| try: | ||
| log.info('Running `{0}`; use the --no-git option to disable git ' | ||
| 'commands'.format(' '.join(cmd))) | ||
| returncode, stdout, stderr = run_cmd(cmd) | ||
| except OSError as e: | ||
| err_msg = str(e) | ||
| else: | ||
| if returncode != 0: | ||
| err_msg = stderr | ||
| if err_msg is not None: | ||
| log.warn('An unexpected error occurred updating the git submodule ' | ||
| '{0!r}:\n{1}\n{2}'.format(submodule, err_msg, | ||
| _err_help_msg)) | ||
| class _CommandNotFound(OSError): | ||
| """ | ||
| An exception raised when a command run with run_cmd is not found on the | ||
| system. | ||
| """ | ||
| def run_cmd(cmd): | ||
| """ | ||
| Run a command in a subprocess, given as a list of command-line | ||
| arguments. | ||
| Returns a ``(returncode, stdout, stderr)`` tuple. | ||
| """ | ||
| try: | ||
| p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE) | ||
| # XXX: May block if either stdout or stderr fill their buffers; | ||
| # however for the commands this is currently used for that is | ||
| # unlikely (they should have very brief output) | ||
| stdout, stderr = p.communicate() | ||
| except OSError as e: | ||
| if DEBUG: | ||
| raise | ||
| if e.errno == errno.ENOENT: | ||
| msg = 'Command not found: `{0}`'.format(' '.join(cmd)) | ||
| raise _CommandNotFound(msg, cmd) | ||
| else: | ||
| raise _AHBootstrapSystemExit( | ||
| 'An unexpected error occurred when running the ' | ||
| '`{0}` command:\n{1}'.format(' '.join(cmd), str(e))) | ||
| # Can fail of the default locale is not configured properly. See | ||
| # https://github.com/astropy/astropy/issues/2749. For the purposes under | ||
| # consideration 'latin1' is an acceptable fallback. | ||
| try: | ||
| stdio_encoding = locale.getdefaultlocale()[1] or 'latin1' | ||
| except ValueError: | ||
| # Due to an OSX oddity locale.getdefaultlocale() can also crash | ||
| # depending on the user's locale/language settings. See: | ||
| # http://bugs.python.org/issue18378 | ||
| stdio_encoding = 'latin1' | ||
| # Unlikely to fail at this point but even then let's be flexible | ||
| if not isinstance(stdout, str): | ||
| stdout = stdout.decode(stdio_encoding, 'replace') | ||
| if not isinstance(stderr, str): | ||
| stderr = stderr.decode(stdio_encoding, 'replace') | ||
| return (p.returncode, stdout, stderr) | ||
| def _next_version(version): | ||
| """ | ||
| Given a parsed version from pkg_resources.parse_version, returns a new | ||
| version string with the next minor version. | ||
| Examples | ||
| ======== | ||
| >>> _next_version(pkg_resources.parse_version('1.2.3')) | ||
| '1.3.0' | ||
| """ | ||
| if hasattr(version, 'base_version'): | ||
| # New version parsing from setuptools >= 8.0 | ||
| if version.base_version: | ||
| parts = version.base_version.split('.') | ||
| else: | ||
| parts = [] | ||
| else: | ||
| parts = [] | ||
| for part in version: | ||
| if part.startswith('*'): | ||
| break | ||
| parts.append(part) | ||
| parts = [int(p) for p in parts] | ||
| if len(parts) < 3: | ||
| parts += [0] * (3 - len(parts)) | ||
| major, minor, micro = parts[:3] | ||
| return '{0}.{1}.{2}'.format(major, minor + 1, 0) | ||
| class _DummyFile(object): | ||
| """A noop writeable object.""" | ||
| errors = '' # Required for Python 3.x | ||
| encoding = 'utf-8' | ||
| def write(self, s): | ||
| pass | ||
| def flush(self): | ||
| pass | ||
| @contextlib.contextmanager | ||
| def _verbose(): | ||
| yield | ||
| @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: | ||
| 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 | ||
| _err_help_msg = """ | ||
| If the problem persists consider installing astropy_helpers manually using pip | ||
| (`pip install astropy_helpers`) or by manually downloading the source archive, | ||
| extracting it, and installing by running `python setup.py install` from the | ||
| root of the extracted source code. | ||
| """ | ||
| class _AHBootstrapSystemExit(SystemExit): | ||
| def __init__(self, *args): | ||
| if not args: | ||
| msg = 'An unknown problem occurred bootstrapping astropy_helpers.' | ||
| else: | ||
| msg = args[0] | ||
| msg += '\n' + _err_help_msg | ||
| super(_AHBootstrapSystemExit, self).__init__(msg, *args[1:]) | ||
| BOOTSTRAPPER = _Bootstrapper.main() | ||
| def use_astropy_helpers(**kwargs): | ||
| """ | ||
| Ensure that the `astropy_helpers` module is available and is importable. | ||
| This supports automatic submodule initialization if astropy_helpers is | ||
| included in a project as a git submodule, or will download it from PyPI if | ||
| necessary. | ||
| Parameters | ||
| ---------- | ||
| path : str or None, optional | ||
| A filesystem path relative to the root of the project's source code | ||
| that should be added to `sys.path` so that `astropy_helpers` can be | ||
| imported from that path. | ||
| If the path is a git submodule it will automatically be initialized | ||
| and/or updated. | ||
| The path may also be to a ``.tar.gz`` archive of the astropy_helpers | ||
| source distribution. In this case the archive is automatically | ||
| unpacked and made temporarily available on `sys.path` as a ``.egg`` | ||
| archive. | ||
| If `None` skip straight to downloading. | ||
| download_if_needed : bool, optional | ||
| If the provided filesystem path is not found an attempt will be made to | ||
| download astropy_helpers from PyPI. It will then be made temporarily | ||
| available on `sys.path` as a ``.egg`` archive (using the | ||
| ``setup_requires`` feature of setuptools. If the ``--offline`` option | ||
| is given at the command line the value of this argument is overridden | ||
| to `False`. | ||
| index_url : str, optional | ||
| If provided, use a different URL for the Python package index than the | ||
| main PyPI server. | ||
| use_git : bool, optional | ||
| If `False` no git commands will be used--this effectively disables | ||
| support for git submodules. If the ``--no-git`` option is given at the | ||
| command line the value of this argument is overridden to `False`. | ||
| auto_upgrade : bool, optional | ||
| By default, when installing a package from a non-development source | ||
| distribution ah_boostrap will try to automatically check for patch | ||
| releases to astropy-helpers on PyPI and use the patched version over | ||
| any bundled versions. Setting this to `False` will disable that | ||
| functionality. If the ``--offline`` option is given at the command line | ||
| the value of this argument is overridden to `False`. | ||
| offline : bool, optional | ||
| If `False` disable all actions that require an internet connection, | ||
| including downloading packages from the package index and fetching | ||
| updates to any git submodule. Defaults to `True`. | ||
| """ | ||
| global BOOTSTRAPPER | ||
| config = BOOTSTRAPPER.config | ||
| config.update(**kwargs) | ||
| # Create a new bootstrapper with the updated configuration and run it | ||
| BOOTSTRAPPER = _Bootstrapper(**config) | ||
| BOOTSTRAPPER.run() |
Sorry, the diff of this file is not supported yet
| # We set the language to c because python isn't supported on the MacOS X nodes | ||
| # on Travis. However, the language ends up being irrelevant anyway, since we | ||
| # install Python ourselves using conda. | ||
| language: c | ||
| os: | ||
| - osx | ||
| - linux | ||
| # Setting sudo to false opts in to Travis-CI container-based builds. | ||
| sudo: false | ||
| env: | ||
| matrix: | ||
| - PYTHON_VERSION=3.5 | ||
| - PYTHON_VERSION=3.6 | ||
| - PYTHON_VERSION=3.7 SETUPTOOLS_VERSION=dev DEBUG=True | ||
| CONDA_DEPENDENCIES='sphinx cython numpy pytest-cov' | ||
| EVENT_TYPE='push pull_request cron' | ||
| global: | ||
| - CONDA_DEPENDENCIES="setuptools sphinx cython numpy pytest-cov" | ||
| - PIP_DEPENDENCIES="codecov" | ||
| - EVENT_TYPE='push pull_request' | ||
| matrix: | ||
| include: | ||
| # Do one build with sphinx-astropy as one of the tests bypasses the auto- | ||
| # installation but we want to make sure that test runs for coverage. | ||
| - os: linux | ||
| env: PYTHON_VERSION=3.6 PIP_DEPENDENCIES='codecov sphinx-astropy' | ||
| - os: linux | ||
| env: PYTHON_VERSION=3.6 SPHINX_VERSION='1.5' SETUPTOOLS_VERSION=27 | ||
| - os: linux | ||
| env: PYTHON_VERSION=3.5 SPHINX_VERSION='1.4' SETUPTOOLS_VERSION=27 | ||
| - os: linux | ||
| env: PYTHON_VERSION=3.6 PIP_DEPENDENCIES='git+https://github.com/sphinx-doc/sphinx.git#egg=sphinx codecov' | ||
| CONDA_DEPENDENCIES="setuptools cython numpy pytest-cov" | ||
| # Test without installing numpy beforehand to make sure everything works | ||
| # without assuming numpy is already installed | ||
| - os: linux | ||
| env: PYTHON_VERSION=3.6 CONDA_DEPENDENCIES='sphinx cython pytest-cov' | ||
| # Test conda's clang | ||
| - os: osx | ||
| env: | ||
| - PYTHON_VERSION=3.5 | ||
| - CONDA_DEPENDENCIES="setuptools sphinx cython numpy pytest-cov clang llvm-openmp" | ||
| - OPENMP_EXPECTED=True | ||
| - CCOMPILER=clang | ||
| # Test gcc on OSX | ||
| - os: osx | ||
| env: | ||
| - PYTHON_VERSION=3.5 | ||
| - CONDA_DEPENDENCIES="setuptools sphinx cython numpy pytest-cov gcc" | ||
| - OPENMP_EXPECTED=True | ||
| - CCOMPILER=gcc | ||
| # Uncomment the following if there are issues in setuptools that we | ||
| # can't work around quickly - otherwise leave uncommented so that | ||
| # we notice when things go wrong. | ||
| # | ||
| # allow_failures: | ||
| # - env: PYTHON_VERSION=3.6 SETUPTOOLS_VERSION=dev DEBUG=True | ||
| # CONDA_DEPENDENCIES='sphinx cython numpy pytest-cov' | ||
| # EVENT_TYPE='push pull_request cron' | ||
| before_install: | ||
| # Test OSX without OpenMP support | ||
| # Since the matrix OSX tests use the OS shipped version of clang, they double up | ||
| # as exploratory tests for when the shipped version has automatic OpenMP support. | ||
| # These tests will then fail and at such a time a new one should be added | ||
| # to explicitly remove OpenMP support. | ||
| - if [ -z $OPENMP_EXPECTED ]; then | ||
| if [[ $TRAVIS_OS_NAME == osx ]]; then | ||
| export OPENMP_EXPECTED=False; | ||
| else | ||
| export OPENMP_EXPECTED=True; | ||
| fi | ||
| fi | ||
| # We need to use CCOMPILER otherwise Travis overwrites CC if we define it | ||
| # in env: above. | ||
| - if [ ! -z $CCOMPILER ]; then | ||
| export CC=$CCOMPILER; | ||
| fi | ||
| # Check CC variable | ||
| - echo $CC | ||
| install: | ||
| - git clone git://github.com/astropy/ci-helpers.git | ||
| - source ci-helpers/travis/setup_conda.sh | ||
| # We cannot install the developer version of setuptools using pip because | ||
| # pip tries to remove the previous version of setuptools before the | ||
| # installation is complete, which causes issues. Instead, we just install | ||
| # setuptools manually. | ||
| - if [[ $SETUPTOOLS_VERSION == dev ]]; then git clone http://github.com/pypa/setuptools.git; cd setuptools; python bootstrap.py; python setup.py install; cd ..; fi | ||
| before_script: | ||
| # Some of the tests use git commands that require a user to be configured | ||
| - git config --global user.name "A U Thor" | ||
| - git config --global user.email "author@example.com" | ||
| script: | ||
| - py.test --cov astropy_helpers astropy_helpers | ||
| # In conftest.py we produce a .coverage.subprocess that contains coverage | ||
| # statistics for sub-processes, so we combine it with the main one here. | ||
| - mv .coverage .coverage.main | ||
| - coverage combine .coverage.main .coverage.subprocess | ||
| - coverage report | ||
| after_success: | ||
| - codecov |
| """ | ||
| This bootstrap module contains code for ensuring that the astropy_helpers | ||
| package will be importable by the time the setup.py script runs. It also | ||
| includes some workarounds to ensure that a recent-enough version of setuptools | ||
| is being used for the installation. | ||
| This module should be the first thing imported in the setup.py of distributions | ||
| that make use of the utilities in astropy_helpers. If the distribution ships | ||
| with its own copy of astropy_helpers, this module will first attempt to import | ||
| from the shipped copy. However, it will also check PyPI to see if there are | ||
| any bug-fix releases on top of the current version that may be useful to get | ||
| past platform-specific bugs that have been fixed. When running setup.py, use | ||
| the ``--offline`` command-line option to disable the auto-upgrade checks. | ||
| When this module is imported or otherwise executed it automatically calls a | ||
| main function that attempts to read the project's setup.cfg file, which it | ||
| checks for a configuration section called ``[ah_bootstrap]`` the presences of | ||
| that section, and options therein, determine the next step taken: If it | ||
| contains an option called ``auto_use`` with a value of ``True``, it will | ||
| automatically call the main function of this module called | ||
| `use_astropy_helpers` (see that function's docstring for full details). | ||
| Otherwise no further action is taken and by default the system-installed version | ||
| of astropy-helpers will be used (however, ``ah_bootstrap.use_astropy_helpers`` | ||
| may be called manually from within the setup.py script). | ||
| This behavior can also be controlled using the ``--auto-use`` and | ||
| ``--no-auto-use`` command-line flags. For clarity, an alias for | ||
| ``--no-auto-use`` is ``--use-system-astropy-helpers``, and we recommend using | ||
| the latter if needed. | ||
| Additional options in the ``[ah_boostrap]`` section of setup.cfg have the same | ||
| names as the arguments to `use_astropy_helpers`, and can be used to configure | ||
| the bootstrap script when ``auto_use = True``. | ||
| See https://github.com/astropy/astropy-helpers for more details, and for the | ||
| latest version of this module. | ||
| """ | ||
| import contextlib | ||
| import errno | ||
| import io | ||
| import locale | ||
| import os | ||
| import re | ||
| import subprocess as sp | ||
| import sys | ||
| __minimum_python_version__ = (3, 5) | ||
| if sys.version_info < __minimum_python_version__: | ||
| print("ERROR: Python {} or later is required by astropy-helpers".format( | ||
| __minimum_python_version__)) | ||
| sys.exit(1) | ||
| try: | ||
| from ConfigParser import ConfigParser, RawConfigParser | ||
| except ImportError: | ||
| from configparser import ConfigParser, RawConfigParser | ||
| _str_types = (str, bytes) | ||
| # What follows are several import statements meant to deal with install-time | ||
| # issues with either missing or misbehaving pacakges (including making sure | ||
| # setuptools itself is installed): | ||
| # Check that setuptools 1.0 or later is present | ||
| from distutils.version import LooseVersion | ||
| try: | ||
| import setuptools | ||
| assert LooseVersion(setuptools.__version__) >= LooseVersion('1.0') | ||
| except (ImportError, AssertionError): | ||
| print("ERROR: setuptools 1.0 or later is required by astropy-helpers") | ||
| sys.exit(1) | ||
| # typing as a dependency for 1.6.1+ Sphinx causes issues when imported after | ||
| # initializing submodule with ah_boostrap.py | ||
| # See discussion and references in | ||
| # https://github.com/astropy/astropy-helpers/issues/302 | ||
| try: | ||
| import typing # noqa | ||
| except ImportError: | ||
| pass | ||
| # Note: The following import is required as a workaround to | ||
| # https://github.com/astropy/astropy-helpers/issues/89; if we don't import this | ||
| # module now, it will get cleaned up after `run_setup` is called, but that will | ||
| # later cause the TemporaryDirectory class defined in it to stop working when | ||
| # used later on by setuptools | ||
| try: | ||
| import setuptools.py31compat # noqa | ||
| except ImportError: | ||
| pass | ||
| # matplotlib can cause problems if it is imported from within a call of | ||
| # run_setup(), because in some circumstances it will try to write to the user's | ||
| # home directory, resulting in a SandboxViolation. See | ||
| # https://github.com/matplotlib/matplotlib/pull/4165 | ||
| # Making sure matplotlib, if it is available, is imported early in the setup | ||
| # process can mitigate this (note importing matplotlib.pyplot has the same | ||
| # issue) | ||
| try: | ||
| import matplotlib | ||
| matplotlib.use('Agg') | ||
| import matplotlib.pyplot | ||
| except: | ||
| # Ignore if this fails for *any* reason* | ||
| pass | ||
| # End compatibility imports... | ||
| # In case it didn't successfully import before the ez_setup checks | ||
| import pkg_resources | ||
| from setuptools import Distribution | ||
| from setuptools.package_index import PackageIndex | ||
| from distutils import log | ||
| from distutils.debug import DEBUG | ||
| # TODO: Maybe enable checking for a specific version of astropy_helpers? | ||
| DIST_NAME = 'astropy-helpers' | ||
| PACKAGE_NAME = 'astropy_helpers' | ||
| UPPER_VERSION_EXCLUSIVE = None | ||
| # Defaults for other options | ||
| DOWNLOAD_IF_NEEDED = True | ||
| INDEX_URL = 'https://pypi.python.org/simple' | ||
| USE_GIT = True | ||
| OFFLINE = False | ||
| AUTO_UPGRADE = True | ||
| # A list of all the configuration options and their required types | ||
| CFG_OPTIONS = [ | ||
| ('auto_use', bool), ('path', str), ('download_if_needed', bool), | ||
| ('index_url', str), ('use_git', bool), ('offline', bool), | ||
| ('auto_upgrade', bool) | ||
| ] | ||
| class _Bootstrapper(object): | ||
| """ | ||
| Bootstrapper implementation. See ``use_astropy_helpers`` for parameter | ||
| documentation. | ||
| """ | ||
| def __init__(self, path=None, index_url=None, use_git=None, offline=None, | ||
| download_if_needed=None, auto_upgrade=None): | ||
| if path is None: | ||
| path = PACKAGE_NAME | ||
| if not (isinstance(path, _str_types) or path is False): | ||
| raise TypeError('path must be a string or False') | ||
| if not isinstance(path, str): | ||
| fs_encoding = sys.getfilesystemencoding() | ||
| path = path.decode(fs_encoding) # path to unicode | ||
| self.path = path | ||
| # Set other option attributes, using defaults where necessary | ||
| self.index_url = index_url if index_url is not None else INDEX_URL | ||
| self.offline = offline if offline is not None else OFFLINE | ||
| # If offline=True, override download and auto-upgrade | ||
| if self.offline: | ||
| download_if_needed = False | ||
| auto_upgrade = False | ||
| self.download = (download_if_needed | ||
| if download_if_needed is not None | ||
| else DOWNLOAD_IF_NEEDED) | ||
| self.auto_upgrade = (auto_upgrade | ||
| if auto_upgrade is not None else AUTO_UPGRADE) | ||
| # If this is a release then the .git directory will not exist so we | ||
| # should not use git. | ||
| git_dir_exists = os.path.exists(os.path.join(os.path.dirname(__file__), '.git')) | ||
| if use_git is None and not git_dir_exists: | ||
| use_git = False | ||
| self.use_git = use_git if use_git is not None else USE_GIT | ||
| # Declared as False by default--later we check if astropy-helpers can be | ||
| # upgraded from PyPI, but only if not using a source distribution (as in | ||
| # the case of import from a git submodule) | ||
| self.is_submodule = False | ||
| @classmethod | ||
| def main(cls, argv=None): | ||
| if argv is None: | ||
| argv = sys.argv | ||
| config = cls.parse_config() | ||
| config.update(cls.parse_command_line(argv)) | ||
| auto_use = config.pop('auto_use', False) | ||
| bootstrapper = cls(**config) | ||
| if auto_use: | ||
| # Run the bootstrapper, otherwise the setup.py is using the old | ||
| # use_astropy_helpers() interface, in which case it will run the | ||
| # bootstrapper manually after reconfiguring it. | ||
| bootstrapper.run() | ||
| return bootstrapper | ||
| @classmethod | ||
| def parse_config(cls): | ||
| if not os.path.exists('setup.cfg'): | ||
| return {} | ||
| cfg = ConfigParser() | ||
| try: | ||
| cfg.read('setup.cfg') | ||
| except Exception as e: | ||
| if DEBUG: | ||
| raise | ||
| log.error( | ||
| "Error reading setup.cfg: {0!r}\n{1} will not be " | ||
| "automatically bootstrapped and package installation may fail." | ||
| "\n{2}".format(e, PACKAGE_NAME, _err_help_msg)) | ||
| return {} | ||
| if not cfg.has_section('ah_bootstrap'): | ||
| return {} | ||
| config = {} | ||
| for option, type_ in CFG_OPTIONS: | ||
| if not cfg.has_option('ah_bootstrap', option): | ||
| continue | ||
| if type_ is bool: | ||
| value = cfg.getboolean('ah_bootstrap', option) | ||
| else: | ||
| value = cfg.get('ah_bootstrap', option) | ||
| config[option] = value | ||
| return config | ||
| @classmethod | ||
| def parse_command_line(cls, argv=None): | ||
| if argv is None: | ||
| argv = sys.argv | ||
| config = {} | ||
| # For now we just pop recognized ah_bootstrap options out of the | ||
| # arg list. This is imperfect; in the unlikely case that a setup.py | ||
| # custom command or even custom Distribution class defines an argument | ||
| # of the same name then we will break that. However there's a catch22 | ||
| # here that we can't just do full argument parsing right here, because | ||
| # we don't yet know *how* to parse all possible command-line arguments. | ||
| if '--no-git' in argv: | ||
| config['use_git'] = False | ||
| argv.remove('--no-git') | ||
| if '--offline' in argv: | ||
| config['offline'] = True | ||
| argv.remove('--offline') | ||
| if '--auto-use' in argv: | ||
| config['auto_use'] = True | ||
| argv.remove('--auto-use') | ||
| if '--no-auto-use' in argv: | ||
| config['auto_use'] = False | ||
| argv.remove('--no-auto-use') | ||
| if '--use-system-astropy-helpers' in argv: | ||
| config['auto_use'] = False | ||
| argv.remove('--use-system-astropy-helpers') | ||
| return config | ||
| def run(self): | ||
| strategies = ['local_directory', 'local_file', 'index'] | ||
| dist = None | ||
| # First, remove any previously imported versions of astropy_helpers; | ||
| # this is necessary for nested installs where one package's installer | ||
| # is installing another package via setuptools.sandbox.run_setup, as in | ||
| # the case of setup_requires | ||
| for key in list(sys.modules): | ||
| try: | ||
| if key == PACKAGE_NAME or key.startswith(PACKAGE_NAME + '.'): | ||
| del sys.modules[key] | ||
| except AttributeError: | ||
| # Sometimes mysterious non-string things can turn up in | ||
| # sys.modules | ||
| continue | ||
| # Check to see if the path is a submodule | ||
| self.is_submodule = self._check_submodule() | ||
| for strategy in strategies: | ||
| method = getattr(self, 'get_{0}_dist'.format(strategy)) | ||
| dist = method() | ||
| if dist is not None: | ||
| break | ||
| else: | ||
| raise _AHBootstrapSystemExit( | ||
| "No source found for the {0!r} package; {0} must be " | ||
| "available and importable as a prerequisite to building " | ||
| "or installing this package.".format(PACKAGE_NAME)) | ||
| # This is a bit hacky, but if astropy_helpers was loaded from a | ||
| # directory/submodule its Distribution object gets a "precedence" of | ||
| # "DEVELOP_DIST". However, in other cases it gets a precedence of | ||
| # "EGG_DIST". However, when activing the distribution it will only be | ||
| # placed early on sys.path if it is treated as an EGG_DIST, so always | ||
| # do that | ||
| dist = dist.clone(precedence=pkg_resources.EGG_DIST) | ||
| # Otherwise we found a version of astropy-helpers, so we're done | ||
| # Just active the found distribution on sys.path--if we did a | ||
| # download this usually happens automatically but it doesn't hurt to | ||
| # do it again | ||
| # Note: Adding the dist to the global working set also activates it | ||
| # (makes it importable on sys.path) by default. | ||
| try: | ||
| pkg_resources.working_set.add(dist, replace=True) | ||
| except TypeError: | ||
| # Some (much) older versions of setuptools do not have the | ||
| # replace=True option here. These versions are old enough that all | ||
| # bets may be off anyways, but it's easy enough to work around just | ||
| # in case... | ||
| if dist.key in pkg_resources.working_set.by_key: | ||
| del pkg_resources.working_set.by_key[dist.key] | ||
| pkg_resources.working_set.add(dist) | ||
| @property | ||
| def config(self): | ||
| """ | ||
| A `dict` containing the options this `_Bootstrapper` was configured | ||
| with. | ||
| """ | ||
| return dict((optname, getattr(self, optname)) | ||
| for optname, _ in CFG_OPTIONS if hasattr(self, optname)) | ||
| def get_local_directory_dist(self): | ||
| """ | ||
| Handle importing a vendored package from a subdirectory of the source | ||
| distribution. | ||
| """ | ||
| if not os.path.isdir(self.path): | ||
| return | ||
| log.info('Attempting to import astropy_helpers from {0} {1!r}'.format( | ||
| 'submodule' if self.is_submodule else 'directory', | ||
| self.path)) | ||
| dist = self._directory_import() | ||
| if dist is None: | ||
| log.warn( | ||
| 'The requested path {0!r} for importing {1} does not ' | ||
| 'exist, or does not contain a copy of the {1} ' | ||
| 'package.'.format(self.path, PACKAGE_NAME)) | ||
| elif self.auto_upgrade and not self.is_submodule: | ||
| # A version of astropy-helpers was found on the available path, but | ||
| # check to see if a bugfix release is available on PyPI | ||
| upgrade = self._do_upgrade(dist) | ||
| if upgrade is not None: | ||
| dist = upgrade | ||
| return dist | ||
| def get_local_file_dist(self): | ||
| """ | ||
| Handle importing from a source archive; this also uses setup_requires | ||
| but points easy_install directly to the source archive. | ||
| """ | ||
| if not os.path.isfile(self.path): | ||
| return | ||
| log.info('Attempting to unpack and import astropy_helpers from ' | ||
| '{0!r}'.format(self.path)) | ||
| try: | ||
| dist = self._do_download(find_links=[self.path]) | ||
| except Exception as e: | ||
| if DEBUG: | ||
| raise | ||
| log.warn( | ||
| 'Failed to import {0} from the specified archive {1!r}: ' | ||
| '{2}'.format(PACKAGE_NAME, self.path, str(e))) | ||
| dist = None | ||
| if dist is not None and self.auto_upgrade: | ||
| # A version of astropy-helpers was found on the available path, but | ||
| # check to see if a bugfix release is available on PyPI | ||
| upgrade = self._do_upgrade(dist) | ||
| if upgrade is not None: | ||
| dist = upgrade | ||
| return dist | ||
| def get_index_dist(self): | ||
| if not self.download: | ||
| log.warn('Downloading {0!r} disabled.'.format(DIST_NAME)) | ||
| return None | ||
| log.warn( | ||
| "Downloading {0!r}; run setup.py with the --offline option to " | ||
| "force offline installation.".format(DIST_NAME)) | ||
| try: | ||
| dist = self._do_download() | ||
| except Exception as e: | ||
| if DEBUG: | ||
| raise | ||
| log.warn( | ||
| 'Failed to download and/or install {0!r} from {1!r}:\n' | ||
| '{2}'.format(DIST_NAME, self.index_url, str(e))) | ||
| dist = None | ||
| # No need to run auto-upgrade here since we've already presumably | ||
| # gotten the most up-to-date version from the package index | ||
| return dist | ||
| def _directory_import(self): | ||
| """ | ||
| Import astropy_helpers from the given path, which will be added to | ||
| sys.path. | ||
| Must return True if the import succeeded, and False otherwise. | ||
| """ | ||
| # Return True on success, False on failure but download is allowed, and | ||
| # otherwise raise SystemExit | ||
| path = os.path.abspath(self.path) | ||
| # Use an empty WorkingSet rather than the man | ||
| # pkg_resources.working_set, since on older versions of setuptools this | ||
| # will invoke a VersionConflict when trying to install an upgrade | ||
| ws = pkg_resources.WorkingSet([]) | ||
| ws.add_entry(path) | ||
| dist = ws.by_key.get(DIST_NAME) | ||
| if dist is None: | ||
| # We didn't find an egg-info/dist-info in the given path, but if a | ||
| # setup.py exists we can generate it | ||
| setup_py = os.path.join(path, 'setup.py') | ||
| if os.path.isfile(setup_py): | ||
| # We use subprocess instead of run_setup from setuptools to | ||
| # avoid segmentation faults - see the following for more details: | ||
| # https://github.com/cython/cython/issues/2104 | ||
| sp.check_output([sys.executable, 'setup.py', 'egg_info'], cwd=path) | ||
| for dist in pkg_resources.find_distributions(path, True): | ||
| # There should be only one... | ||
| return dist | ||
| return dist | ||
| def _do_download(self, version='', find_links=None): | ||
| if find_links: | ||
| allow_hosts = '' | ||
| index_url = None | ||
| else: | ||
| allow_hosts = None | ||
| index_url = self.index_url | ||
| # Annoyingly, setuptools will not handle other arguments to | ||
| # Distribution (such as options) before handling setup_requires, so it | ||
| # is not straightforward to programmatically augment the arguments which | ||
| # are passed to easy_install | ||
| class _Distribution(Distribution): | ||
| def get_option_dict(self, command_name): | ||
| opts = Distribution.get_option_dict(self, command_name) | ||
| if command_name == 'easy_install': | ||
| if find_links is not None: | ||
| opts['find_links'] = ('setup script', find_links) | ||
| if index_url is not None: | ||
| opts['index_url'] = ('setup script', index_url) | ||
| if allow_hosts is not None: | ||
| opts['allow_hosts'] = ('setup script', allow_hosts) | ||
| return opts | ||
| if version: | ||
| req = '{0}=={1}'.format(DIST_NAME, version) | ||
| else: | ||
| if UPPER_VERSION_EXCLUSIVE is None: | ||
| req = DIST_NAME | ||
| else: | ||
| req = '{0}<{1}'.format(DIST_NAME, UPPER_VERSION_EXCLUSIVE) | ||
| attrs = {'setup_requires': [req]} | ||
| # NOTE: we need to parse the config file (e.g. setup.cfg) to make sure | ||
| # it honours the options set in the [easy_install] section, and we need | ||
| # to explicitly fetch the requirement eggs as setup_requires does not | ||
| # get honored in recent versions of setuptools: | ||
| # https://github.com/pypa/setuptools/issues/1273 | ||
| try: | ||
| context = _verbose if DEBUG else _silence | ||
| with context(): | ||
| dist = _Distribution(attrs=attrs) | ||
| try: | ||
| dist.parse_config_files(ignore_option_errors=True) | ||
| dist.fetch_build_eggs(req) | ||
| except TypeError: | ||
| # On older versions of setuptools, ignore_option_errors | ||
| # doesn't exist, and the above two lines are not needed | ||
| # so we can just continue | ||
| pass | ||
| # If the setup_requires succeeded it will have added the new dist to | ||
| # the main working_set | ||
| return pkg_resources.working_set.by_key.get(DIST_NAME) | ||
| except Exception as e: | ||
| if DEBUG: | ||
| raise | ||
| msg = 'Error retrieving {0} from {1}:\n{2}' | ||
| if find_links: | ||
| source = find_links[0] | ||
| elif index_url != INDEX_URL: | ||
| source = index_url | ||
| else: | ||
| source = 'PyPI' | ||
| raise Exception(msg.format(DIST_NAME, source, repr(e))) | ||
| def _do_upgrade(self, dist): | ||
| # Build up a requirement for a higher bugfix release but a lower minor | ||
| # release (so API compatibility is guaranteed) | ||
| next_version = _next_version(dist.parsed_version) | ||
| req = pkg_resources.Requirement.parse( | ||
| '{0}>{1},<{2}'.format(DIST_NAME, dist.version, next_version)) | ||
| package_index = PackageIndex(index_url=self.index_url) | ||
| upgrade = package_index.obtain(req) | ||
| if upgrade is not None: | ||
| return self._do_download(version=upgrade.version) | ||
| def _check_submodule(self): | ||
| """ | ||
| Check if the given path is a git submodule. | ||
| See the docstrings for ``_check_submodule_using_git`` and | ||
| ``_check_submodule_no_git`` for further details. | ||
| """ | ||
| if (self.path is None or | ||
| (os.path.exists(self.path) and not os.path.isdir(self.path))): | ||
| return False | ||
| if self.use_git: | ||
| return self._check_submodule_using_git() | ||
| else: | ||
| return self._check_submodule_no_git() | ||
| def _check_submodule_using_git(self): | ||
| """ | ||
| Check if the given path is a git submodule. If so, attempt to initialize | ||
| and/or update the submodule if needed. | ||
| This function makes calls to the ``git`` command in subprocesses. The | ||
| ``_check_submodule_no_git`` option uses pure Python to check if the given | ||
| path looks like a git submodule, but it cannot perform updates. | ||
| """ | ||
| cmd = ['git', 'submodule', 'status', '--', self.path] | ||
| try: | ||
| log.info('Running `{0}`; use the --no-git option to disable git ' | ||
| 'commands'.format(' '.join(cmd))) | ||
| returncode, stdout, stderr = run_cmd(cmd) | ||
| except _CommandNotFound: | ||
| # The git command simply wasn't found; this is most likely the | ||
| # case on user systems that don't have git and are simply | ||
| # trying to install the package from PyPI or a source | ||
| # distribution. Silently ignore this case and simply don't try | ||
| # to use submodules | ||
| return False | ||
| stderr = stderr.strip() | ||
| if returncode != 0 and stderr: | ||
| # Unfortunately the return code alone cannot be relied on, as | ||
| # earlier versions of git returned 0 even if the requested submodule | ||
| # does not exist | ||
| # This is a warning that occurs in perl (from running git submodule) | ||
| # which only occurs with a malformatted locale setting which can | ||
| # happen sometimes on OSX. See again | ||
| # https://github.com/astropy/astropy/issues/2749 | ||
| perl_warning = ('perl: warning: Falling back to the standard locale ' | ||
| '("C").') | ||
| if not stderr.strip().endswith(perl_warning): | ||
| # Some other unknown error condition occurred | ||
| log.warn('git submodule command failed ' | ||
| 'unexpectedly:\n{0}'.format(stderr)) | ||
| return False | ||
| # Output of `git submodule status` is as follows: | ||
| # | ||
| # 1: Status indicator: '-' for submodule is uninitialized, '+' if | ||
| # submodule is initialized but is not at the commit currently indicated | ||
| # in .gitmodules (and thus needs to be updated), or 'U' if the | ||
| # submodule is in an unstable state (i.e. has merge conflicts) | ||
| # | ||
| # 2. SHA-1 hash of the current commit of the submodule (we don't really | ||
| # need this information but it's useful for checking that the output is | ||
| # correct) | ||
| # | ||
| # 3. The output of `git describe` for the submodule's current commit | ||
| # hash (this includes for example what branches the commit is on) but | ||
| # only if the submodule is initialized. We ignore this information for | ||
| # now | ||
| _git_submodule_status_re = re.compile( | ||
| '^(?P<status>[+-U ])(?P<commit>[0-9a-f]{40}) ' | ||
| '(?P<submodule>\S+)( .*)?$') | ||
| # The stdout should only contain one line--the status of the | ||
| # requested submodule | ||
| m = _git_submodule_status_re.match(stdout) | ||
| if m: | ||
| # Yes, the path *is* a git submodule | ||
| self._update_submodule(m.group('submodule'), m.group('status')) | ||
| return True | ||
| else: | ||
| log.warn( | ||
| 'Unexpected output from `git submodule status`:\n{0}\n' | ||
| 'Will attempt import from {1!r} regardless.'.format( | ||
| stdout, self.path)) | ||
| return False | ||
| def _check_submodule_no_git(self): | ||
| """ | ||
| Like ``_check_submodule_using_git``, but simply parses the .gitmodules file | ||
| to determine if the supplied path is a git submodule, and does not exec any | ||
| subprocesses. | ||
| This can only determine if a path is a submodule--it does not perform | ||
| updates, etc. This function may need to be updated if the format of the | ||
| .gitmodules file is changed between git versions. | ||
| """ | ||
| gitmodules_path = os.path.abspath('.gitmodules') | ||
| if not os.path.isfile(gitmodules_path): | ||
| return False | ||
| # This is a minimal reader for gitconfig-style files. It handles a few of | ||
| # the quirks that make gitconfig files incompatible with ConfigParser-style | ||
| # files, but does not support the full gitconfig syntax (just enough | ||
| # needed to read a .gitmodules file). | ||
| gitmodules_fileobj = io.StringIO() | ||
| # Must use io.open for cross-Python-compatible behavior wrt unicode | ||
| with io.open(gitmodules_path) as f: | ||
| for line in f: | ||
| # gitconfig files are more flexible with leading whitespace; just | ||
| # go ahead and remove it | ||
| line = line.lstrip() | ||
| # comments can start with either # or ; | ||
| if line and line[0] in (':', ';'): | ||
| continue | ||
| gitmodules_fileobj.write(line) | ||
| gitmodules_fileobj.seek(0) | ||
| cfg = RawConfigParser() | ||
| try: | ||
| cfg.readfp(gitmodules_fileobj) | ||
| except Exception as exc: | ||
| log.warn('Malformatted .gitmodules file: {0}\n' | ||
| '{1} cannot be assumed to be a git submodule.'.format( | ||
| exc, self.path)) | ||
| return False | ||
| for section in cfg.sections(): | ||
| if not cfg.has_option(section, 'path'): | ||
| continue | ||
| submodule_path = cfg.get(section, 'path').rstrip(os.sep) | ||
| if submodule_path == self.path.rstrip(os.sep): | ||
| return True | ||
| return False | ||
| def _update_submodule(self, submodule, status): | ||
| if status == ' ': | ||
| # The submodule is up to date; no action necessary | ||
| return | ||
| elif status == '-': | ||
| if self.offline: | ||
| raise _AHBootstrapSystemExit( | ||
| "Cannot initialize the {0} submodule in --offline mode; " | ||
| "this requires being able to clone the submodule from an " | ||
| "online repository.".format(submodule)) | ||
| cmd = ['update', '--init'] | ||
| action = 'Initializing' | ||
| elif status == '+': | ||
| cmd = ['update'] | ||
| action = 'Updating' | ||
| if self.offline: | ||
| cmd.append('--no-fetch') | ||
| elif status == 'U': | ||
| raise _AHBootstrapSystemExit( | ||
| 'Error: Submodule {0} contains unresolved merge conflicts. ' | ||
| 'Please complete or abandon any changes in the submodule so that ' | ||
| 'it is in a usable state, then try again.'.format(submodule)) | ||
| else: | ||
| log.warn('Unknown status {0!r} for git submodule {1!r}. Will ' | ||
| 'attempt to use the submodule as-is, but try to ensure ' | ||
| 'that the submodule is in a clean state and contains no ' | ||
| 'conflicts or errors.\n{2}'.format(status, submodule, | ||
| _err_help_msg)) | ||
| return | ||
| err_msg = None | ||
| cmd = ['git', 'submodule'] + cmd + ['--', submodule] | ||
| log.warn('{0} {1} submodule with: `{2}`'.format( | ||
| action, submodule, ' '.join(cmd))) | ||
| try: | ||
| log.info('Running `{0}`; use the --no-git option to disable git ' | ||
| 'commands'.format(' '.join(cmd))) | ||
| returncode, stdout, stderr = run_cmd(cmd) | ||
| except OSError as e: | ||
| err_msg = str(e) | ||
| else: | ||
| if returncode != 0: | ||
| err_msg = stderr | ||
| if err_msg is not None: | ||
| log.warn('An unexpected error occurred updating the git submodule ' | ||
| '{0!r}:\n{1}\n{2}'.format(submodule, err_msg, | ||
| _err_help_msg)) | ||
| class _CommandNotFound(OSError): | ||
| """ | ||
| An exception raised when a command run with run_cmd is not found on the | ||
| system. | ||
| """ | ||
| def run_cmd(cmd): | ||
| """ | ||
| Run a command in a subprocess, given as a list of command-line | ||
| arguments. | ||
| Returns a ``(returncode, stdout, stderr)`` tuple. | ||
| """ | ||
| try: | ||
| p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE) | ||
| # XXX: May block if either stdout or stderr fill their buffers; | ||
| # however for the commands this is currently used for that is | ||
| # unlikely (they should have very brief output) | ||
| stdout, stderr = p.communicate() | ||
| except OSError as e: | ||
| if DEBUG: | ||
| raise | ||
| if e.errno == errno.ENOENT: | ||
| msg = 'Command not found: `{0}`'.format(' '.join(cmd)) | ||
| raise _CommandNotFound(msg, cmd) | ||
| else: | ||
| raise _AHBootstrapSystemExit( | ||
| 'An unexpected error occurred when running the ' | ||
| '`{0}` command:\n{1}'.format(' '.join(cmd), str(e))) | ||
| # Can fail of the default locale is not configured properly. See | ||
| # https://github.com/astropy/astropy/issues/2749. For the purposes under | ||
| # consideration 'latin1' is an acceptable fallback. | ||
| try: | ||
| stdio_encoding = locale.getdefaultlocale()[1] or 'latin1' | ||
| except ValueError: | ||
| # Due to an OSX oddity locale.getdefaultlocale() can also crash | ||
| # depending on the user's locale/language settings. See: | ||
| # http://bugs.python.org/issue18378 | ||
| stdio_encoding = 'latin1' | ||
| # Unlikely to fail at this point but even then let's be flexible | ||
| if not isinstance(stdout, str): | ||
| stdout = stdout.decode(stdio_encoding, 'replace') | ||
| if not isinstance(stderr, str): | ||
| stderr = stderr.decode(stdio_encoding, 'replace') | ||
| return (p.returncode, stdout, stderr) | ||
| def _next_version(version): | ||
| """ | ||
| Given a parsed version from pkg_resources.parse_version, returns a new | ||
| version string with the next minor version. | ||
| Examples | ||
| ======== | ||
| >>> _next_version(pkg_resources.parse_version('1.2.3')) | ||
| '1.3.0' | ||
| """ | ||
| if hasattr(version, 'base_version'): | ||
| # New version parsing from setuptools >= 8.0 | ||
| if version.base_version: | ||
| parts = version.base_version.split('.') | ||
| else: | ||
| parts = [] | ||
| else: | ||
| parts = [] | ||
| for part in version: | ||
| if part.startswith('*'): | ||
| break | ||
| parts.append(part) | ||
| parts = [int(p) for p in parts] | ||
| if len(parts) < 3: | ||
| parts += [0] * (3 - len(parts)) | ||
| major, minor, micro = parts[:3] | ||
| return '{0}.{1}.{2}'.format(major, minor + 1, 0) | ||
| class _DummyFile(object): | ||
| """A noop writeable object.""" | ||
| errors = '' # Required for Python 3.x | ||
| encoding = 'utf-8' | ||
| def write(self, s): | ||
| pass | ||
| def flush(self): | ||
| pass | ||
| @contextlib.contextmanager | ||
| def _verbose(): | ||
| yield | ||
| @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: | ||
| 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 | ||
| _err_help_msg = """ | ||
| If the problem persists consider installing astropy_helpers manually using pip | ||
| (`pip install astropy_helpers`) or by manually downloading the source archive, | ||
| extracting it, and installing by running `python setup.py install` from the | ||
| root of the extracted source code. | ||
| """ | ||
| class _AHBootstrapSystemExit(SystemExit): | ||
| def __init__(self, *args): | ||
| if not args: | ||
| msg = 'An unknown problem occurred bootstrapping astropy_helpers.' | ||
| else: | ||
| msg = args[0] | ||
| msg += '\n' + _err_help_msg | ||
| super(_AHBootstrapSystemExit, self).__init__(msg, *args[1:]) | ||
| BOOTSTRAPPER = _Bootstrapper.main() | ||
| def use_astropy_helpers(**kwargs): | ||
| """ | ||
| Ensure that the `astropy_helpers` module is available and is importable. | ||
| This supports automatic submodule initialization if astropy_helpers is | ||
| included in a project as a git submodule, or will download it from PyPI if | ||
| necessary. | ||
| Parameters | ||
| ---------- | ||
| path : str or None, optional | ||
| A filesystem path relative to the root of the project's source code | ||
| that should be added to `sys.path` so that `astropy_helpers` can be | ||
| imported from that path. | ||
| If the path is a git submodule it will automatically be initialized | ||
| and/or updated. | ||
| The path may also be to a ``.tar.gz`` archive of the astropy_helpers | ||
| source distribution. In this case the archive is automatically | ||
| unpacked and made temporarily available on `sys.path` as a ``.egg`` | ||
| archive. | ||
| If `None` skip straight to downloading. | ||
| download_if_needed : bool, optional | ||
| If the provided filesystem path is not found an attempt will be made to | ||
| download astropy_helpers from PyPI. It will then be made temporarily | ||
| available on `sys.path` as a ``.egg`` archive (using the | ||
| ``setup_requires`` feature of setuptools. If the ``--offline`` option | ||
| is given at the command line the value of this argument is overridden | ||
| to `False`. | ||
| index_url : str, optional | ||
| If provided, use a different URL for the Python package index than the | ||
| main PyPI server. | ||
| use_git : bool, optional | ||
| If `False` no git commands will be used--this effectively disables | ||
| support for git submodules. If the ``--no-git`` option is given at the | ||
| command line the value of this argument is overridden to `False`. | ||
| auto_upgrade : bool, optional | ||
| By default, when installing a package from a non-development source | ||
| distribution ah_boostrap will try to automatically check for patch | ||
| releases to astropy-helpers on PyPI and use the patched version over | ||
| any bundled versions. Setting this to `False` will disable that | ||
| functionality. If the ``--offline`` option is given at the command line | ||
| the value of this argument is overridden to `False`. | ||
| offline : bool, optional | ||
| If `False` disable all actions that require an internet connection, | ||
| including downloading packages from the package index and fetching | ||
| updates to any git submodule. Defaults to `True`. | ||
| """ | ||
| global BOOTSTRAPPER | ||
| config = BOOTSTRAPPER.config | ||
| config.update(**kwargs) | ||
| # Create a new bootstrapper with the updated configuration and run it | ||
| BOOTSTRAPPER = _Bootstrapper(**config) | ||
| BOOTSTRAPPER.run() |
| # AppVeyor.com is a Continuous Integration service to build and run tests under | ||
| # Windows | ||
| environment: | ||
| global: | ||
| PYTHON: "C:\\conda" | ||
| MINICONDA_VERSION: "latest" | ||
| OPENMP_EXPECTED: "True" | ||
| CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\ci-helpers\\appveyor\\windows_sdk.cmd" | ||
| PYTHON_ARCH: "64" # needs to be set for CMD_IN_ENV to succeed. If a mix | ||
| # of 32 bit and 64 bit builds are needed, move this | ||
| # to the matrix section. | ||
| # babel 2.0 is known to break on Windows: | ||
| # https://github.com/python-babel/babel/issues/174 | ||
| CONDA_DEPENDENCIES: "numpy Cython sphinx pytest babel!=2.0 setuptools pytest-cov" | ||
| PIP_DEPENDENCIES: "codecov" | ||
| matrix: | ||
| - PYTHON_VERSION: "3.5" | ||
| - PYTHON_VERSION: "3.6" | ||
| - PYTHON_VERSION: "3.7" | ||
| platform: | ||
| -x64 | ||
| install: | ||
| # Set up ci-helpers | ||
| - "git clone git://github.com/astropy/ci-helpers.git" | ||
| - "powershell ci-helpers/appveyor/install-miniconda.ps1" | ||
| - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" | ||
| - "activate test" | ||
| # Some of the tests use git commands that require a user to be configured | ||
| - git config --global user.name "A U Thor" | ||
| - git config --global user.email "author@example.com" | ||
| # Not a .NET project, we build the package in the install step instead | ||
| build: false | ||
| test_script: | ||
| # Specify basetemp explicitly otherwise some of the filenames exceed | ||
| # 260 characters when fetch_build_eggs runs. | ||
| - "%CMD_IN_ENV% py.test --cov --basetemp=\\pytest-tmp astropy_helpers" | ||
| - "%CMD_IN_ENV% codecov" |
Sorry, the diff of this file is not supported yet
| Metadata-Version: 1.2 | ||
| Name: astropy-helpers | ||
| Version: 3.1 | ||
| Summary: Utilities for building and installing Astropy, Astropy affiliated packages, and their respective documentation. | ||
| Home-page: https://github.com/astropy/astropy-helpers | ||
| Author: The Astropy Developers | ||
| Author-email: astropy.team@gmail.com | ||
| License: BSD | ||
| Description: astropy-helpers | ||
| =============== | ||
| .. image:: https://travis-ci.org/astropy/astropy-helpers.svg | ||
| :target: https://travis-ci.org/astropy/astropy-helpers | ||
| .. image:: https://ci.appveyor.com/api/projects/status/rt9161t9mhx02xp7/branch/master?svg=true | ||
| :target: https://ci.appveyor.com/project/Astropy/astropy-helpers | ||
| .. image:: https://codecov.io/gh/astropy/astropy-helpers/branch/master/graph/badge.svg | ||
| :target: https://codecov.io/gh/astropy/astropy-helpers | ||
| **Warning:** Please note that version ``v3.0`` and later of ``astropy-helpers`` | ||
| requires Python 3.5 or later. If you wish to maintain Python 2 support | ||
| for your package that uses ``astropy-helpers``, then do not upgrade the | ||
| helpers to ``v3.0+``. We will still provide Python 2.7 compatible | ||
| releases on the ``v2.0.x`` branch during the lifetime of the ``astropy`` | ||
| core package LTS of ``v2.0.x``. | ||
| About | ||
| ----- | ||
| This project provides a Python package, ``astropy_helpers``, which includes | ||
| many build, installation, and documentation-related tools used by the Astropy | ||
| project, but packaged separately for use by other projects that wish to | ||
| leverage this work. The motivation behind this package and details of its | ||
| implementation are in the accepted | ||
| `Astropy Proposal for Enhancement (APE) 4 <https://github.com/astropy/astropy-APEs/blob/master/APE4.rst>`_. | ||
| ``astropy_helpers`` includes a special "bootstrap" module called | ||
| ``ah_bootstrap.py`` which is intended to be used by a project's setup.py in | ||
| order to ensure that the ``astropy_helpers`` package is available for | ||
| build/installation. | ||
| As described in `APE4 <https://github.com/astropy/astropy-APEs/blob/master/APE4.rst>`_, the version | ||
| numbers for ``astropy_helpers`` follow the corresponding major/minor version of | ||
| the `astropy core package <http://www.astropy.org/>`_, but with an independent | ||
| sequence of micro (bugfix) version numbers. Hence, the initial release is 0.4, | ||
| in parallel with Astropy v0.4, which will be the first version of Astropy to | ||
| use ``astropy-helpers``. | ||
| For examples of how to implement ``astropy-helpers`` in a project, | ||
| see the ``setup.py`` and ``setup.cfg`` files of the | ||
| `Affiliated package template <https://github.com/astropy/package-template>`_. | ||
| What does astropy-helpers provide? | ||
| ---------------------------------- | ||
| Astropy-helpers' big-picture purpose is to provide customization to Python's | ||
| packaging infrastructure process in ways that the Astropy Project has found to | ||
| help simplifying the developint and releasing packages. This is primarily | ||
| build around ``setup.py`` commands, as outlined below, as well as code to help | ||
| manage version numbers and better control the build process of larger packages. | ||
| Custom setup.py commands | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| The main part of astropy-helpers is to provide customized setuptools commands. | ||
| For example, in a package that uses astropy-helpers, the following command | ||
| will be available:: | ||
| python setup.py build_docs | ||
| and this command is implemented in astropy-helpers. To use the custom | ||
| commands described here, add:: | ||
| from astropy_helpers.setup_helpers import register_commands | ||
| to your ``setup.py`` file, then do:: | ||
| cmdclassd = register_commands(NAME, VERSION, RELEASE) | ||
| where ``NAME`` is the name of your package, ``VERSION`` is the full version | ||
| string, and ``RELEASE`` is a boolean value indicating whether the version is | ||
| a stable released version (``True``) or a developer version (``False``). | ||
| Finally, pass ``cmdclassd`` to the ``setup`` function:: | ||
| setup(..., | ||
| cmdclass=cmdclassd) | ||
| The commands we provide or customize are: | ||
| **python setup.py test** | ||
| This command will automatically build the package, install it to a temporary | ||
| directory, and run the tests using `pytest <http://pytest.org/>`_ on this | ||
| installed version. Note that the bulk of this command is actually defined | ||
| in ``astropy.tests.command.AstropyTest``, because that allows the test | ||
| machinery to operate outside a setuptools command. This, here we | ||
| simply define the custom | ||
| setuptools command. | ||
| **python setup.py sdist** | ||
| We redefine ``sdist`` to use the version from distutils rather than from | ||
| setuptools, as the setuptools version requires duplication of information | ||
| in ``MANIFEST.in``. | ||
| **python setup.py build_docs** | ||
| This command will automatically build the package, then run sphinx to build | ||
| the documentation. This makes development much easier because it ensures | ||
| sphinx extensions that use the package's code to make documentation are | ||
| actually using the in-development version of the code. Sphinx itself | ||
| provides a custom setuptools command, which we | ||
| expand with the following options: | ||
| * ``-w``: set the return code to 1 if there are any warnings during the build | ||
| process. | ||
| * ``-l``: completely clean previous builds, including files generated by | ||
| the sphinx-automodapi package (which creates API pages for different | ||
| functions/classes). | ||
| * ``-n``: disable the intersphinx option. | ||
| * ``-o``: open the documentation in a browser if a build finishes successfully. | ||
| In addition, ``build_docs`` will automatically download and temporarily install | ||
| sphinx-astropy (which is a meta-package that | ||
| provides standardized configuration and documentation dependencies for astropy | ||
| packages) if it isn't already installed. Temporary installation means that the | ||
| package will be installed into an ``.eggs`` directory in the current working | ||
| directory, and it will only be available for the duration of the call to | ||
| ``build_docs``. | ||
| **python setup.py build_ext** | ||
| This is also used when running ``build`` or ``install``. We add several features | ||
| compared to the default ``build_ext`` command: | ||
| * For packages with C/Cython extensions, we create a ``packagename._compiler`` | ||
| submodule that contains information about the compilers used. | ||
| * Packages that need to build C extensions using the Numpy C API, we allow | ||
| those packages to define the include path as ``'numpy'`` as opposed to having | ||
| to import Numpy and call ``get_include``. The goal is to solve the issue that | ||
| if one has to import Numpy to define extensions, then Numpy has to be | ||
| installed/available before the package is installed, which means that one | ||
| needs to install Numpy in a separate installation step. | ||
| * We detect broken compilers and replace them with other compilers on-the-fly | ||
| unless the compiler is explicitly specified with the ``CC`` environment | ||
| variable. | ||
| * If Cython is not installed, then we automatically check for generated C files | ||
| (which are normally present in the stable releases) and give a nice error | ||
| if these are not found. | ||
| Version helpers | ||
| ^^^^^^^^^^^^^^^^ | ||
| Another piece of functionality we provide in astropy-helpers is the ability | ||
| to generate a ``packagename.version`` file that includes functions that | ||
| automatically set the version string for developer versions, to e.g. | ||
| ``3.2.dev22213`` so that each developer version has a unique number (although | ||
| note that branches an equal number of commits away from the master branch will | ||
| share the same version number). To use this, import:: | ||
| from astropy_helpers.git_helpers import get_git_devstr | ||
| in your ``setup.py`` file, and you will then be able to use:: | ||
| VERSION += get_git_devstr() | ||
| where ``VERSION`` is a version string without any developer version suffix. | ||
| We then also provide a function that generates a ``version.py`` file inside your | ||
| package (which can then be imported as ``packagename.version``) that contains | ||
| variables such as ``major``, ``minor``, and ``bugfix``, as well as | ||
| ``version_info`` (a tuple of the previous three values), a ``release`` flag that | ||
| indicates whether we are using a stable release, and several other complementary | ||
| variables. To use this, import:: | ||
| from astropy_helpers.version_helpers import generate_version_py | ||
| in your ``setup.py`` file, and call:: | ||
| generate_version_py(NAME, VERSION, RELEASE, uses_git=not RELEASE) | ||
| where ``NAME`` is the name of your package, ``VERSION`` is the full version string | ||
| (including any developer suffix), ``RELEASE`` indicates whether the version is a | ||
| stable or developer version, and ``uses_git`` indicates whether we are in a git | ||
| repository (using ``not RELEASE`` is sensible since git is not available in a | ||
| stable release). | ||
| Collecting package information | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| The ``setup`` function from setuptools can take a number of options that indicate | ||
| for example what extensions to build, and what package data to include. However, | ||
| for large packages this can become cumbersome. We therefore provide a mechanism | ||
| for defining extensions and package data inside individual sub-packages. To do | ||
| this, you can create ``setup_package.py`` files anywhere in your package, and | ||
| these files can include one or more of the following functions: | ||
| * ``get_package_data``: | ||
| This function, if defined, should return a dictionary mapping the name of | ||
| the subpackage(s) that need package data to a list of data file paths | ||
| (possibly including wildcards) relative to the path of the package's source | ||
| code. e.g. if the source distribution has a needed data file | ||
| ``astropy/wcs/tests/data/3d_cd.hdr``, this function should return | ||
| ``{'astropy.wcs.tests':['data/3d_cd.hdr']}``. See the ``package_data`` | ||
| option of the :func:`distutils.core.setup` function. | ||
| It is recommended that all such data be in a directory named ``data`` inside | ||
| the package within which it is supposed to be used. This package data | ||
| should be accessed via the ``astropy.utils.data.get_pkg_data_filename`` and | ||
| ``astropy.utils.data.get_pkg_data_fileobj`` functions. | ||
| * ``get_extensions``: | ||
| This provides information for building C or Cython extensions. If defined, | ||
| it should return a list of ``distutils.core.Extension`` objects. | ||
| * ``get_build_options``: | ||
| This function allows a package to add extra build options. It | ||
| should return a list of tuples, where each element has: | ||
| - *name*: The name of the option as it would appear on the | ||
| commandline or in the ``setup.cfg`` file. | ||
| - *doc*: A short doc string for the option, displayed by | ||
| ``setup.py build --help``. | ||
| - *is_bool* (optional): When `True`, the option is a boolean | ||
| option and doesn't have an associated value. | ||
| Once an option has been added, its value can be looked up using | ||
| ``astropy_helpers.setup_helpers.get_distutils_build_option``. | ||
| * ``get_external_libraries``: | ||
| This function declares that the package uses libraries that are | ||
| included in the astropy distribution that may also be distributed | ||
| elsewhere on the users system. It should return a list of library | ||
| names. For each library, a new build option is created, | ||
| ``'--use-system-X'`` which allows the user to request to use the | ||
| system's copy of the library. The package would typically call | ||
| ``astropy_helpers.setup_helpers.use_system_library`` from its | ||
| ``get_extensions`` function to determine if the package should use | ||
| the system library or the included one. | ||
| * ``get_entry_points()``: | ||
| This function can returns a dict formatted as required by | ||
| the ``entry_points`` argument to ``setup()``. | ||
| With these files in place, you can then add the following to your ``setup.py`` | ||
| file:: | ||
| from astropy_helpers.setup_helpers import get_package_info | ||
| ... | ||
| package_info = get_package_info() | ||
| ... | ||
| setup(..., **package_info) | ||
| OpenMP helpers | ||
| ^^^^^^^^^^^^^^ | ||
| We provide a helper function ``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 astropy_helpers.openmp_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] | ||
| Using astropy-helpers | ||
| --------------------- | ||
| The easiest way to get set up with astropy-helpers in a new package is to use | ||
| the `package-template <http://docs.astropy.org/projects/package-template>`_ | ||
| that we provide. This template is specifically designed for use with the helpers, | ||
| so using it avoids some of the tedium of setting up the heleprs. | ||
| However, we now go through the steps of adding astropy-helpers | ||
| as a submodule to a package in case you wish to do so manually. First, add | ||
| astropy-helpers as a submodule at the root of your repository:: | ||
| git submodule add git://github.com/astropy/astropy-helpers astropy_helpers | ||
| Then go inside the submodule and check out a stable version of astropy-helpers. | ||
| You can see the available versions by running:: | ||
| $ cd astropy_helpers | ||
| $ git tag | ||
| ... | ||
| v2.0.6 | ||
| v2.0.7 | ||
| ... | ||
| v3.0.1 | ||
| v3.0.2 | ||
| If you want to support Python 2, pick the latest v2.0.x version (in the above | ||
| case ``v2.0.7``) and if you don't need to support Python 2, just pick the latest | ||
| stable version (in the above case ``v3.0.2``). Check out this version with e.g.:: | ||
| $ git checkout v3.0.2 | ||
| Then go back up to the root of your repository and copy the ``ah_bootstrap.py`` | ||
| file from the submodule to the root of your repository:: | ||
| $ cd .. | ||
| $ cp astropy_helpers/ah_bootstrap.py . | ||
| Finally, add:: | ||
| import ah_bootstrap | ||
| at the top of your ``setup.py`` file. This will ensure that ``astropy_helpers`` | ||
| is now available to use in your ``setup.py`` file. Finally, add then commit your | ||
| changes:: | ||
| git add astropy_helpers ah_bootstrap.py setup.py | ||
| git commit -m "Added astropy-helpers" | ||
| Updating astropy-helpers | ||
| ------------------------ | ||
| If you would like the Astropy team to automatically open pull requests to update | ||
| astropy-helpers in your package, then see the instructions `here | ||
| <https://github.com/astropy/astropy-procedures/blob/master/update-packages/README.md>`_. | ||
| To instead update astropy-helpers manually, go inside the submodule and do:: | ||
| cd astropy_helpers | ||
| git fetch origin | ||
| Then checkout the version you want to use, e.g.:: | ||
| git checkout v3.0.3 | ||
| Go back up to the root of the repository and update the ``ah_bootstap.py`` file | ||
| too, then add your changes:: | ||
| cp astropy_helpers/ah_bootstrap.py . | ||
| git add astropy_helpers ah_bootstrap.py | ||
| git commit ... | ||
| Platform: UNKNOWN | ||
| Classifier: Development Status :: 5 - Production/Stable | ||
| Classifier: Intended Audience :: Developers | ||
| Classifier: Framework :: Setuptools Plugin | ||
| Classifier: Framework :: Sphinx :: Extension | ||
| Classifier: Framework :: Sphinx :: Theme | ||
| 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 | ||
| Requires-Python: >=3.5 |
| CHANGES.rst | ||
| LICENSE.rst | ||
| MANIFEST.in | ||
| README.rst | ||
| ah_bootstrap.py | ||
| setup.cfg | ||
| setup.py | ||
| astropy_helpers/__init__.py | ||
| astropy_helpers/conftest.py | ||
| astropy_helpers/distutils_helpers.py | ||
| astropy_helpers/git_helpers.py | ||
| astropy_helpers/openmp_helpers.py | ||
| astropy_helpers/setup_helpers.py | ||
| astropy_helpers/utils.py | ||
| astropy_helpers/version.py | ||
| astropy_helpers/version_helpers.py | ||
| astropy_helpers.egg-info/PKG-INFO | ||
| astropy_helpers.egg-info/SOURCES.txt | ||
| astropy_helpers.egg-info/dependency_links.txt | ||
| astropy_helpers.egg-info/not-zip-safe | ||
| astropy_helpers.egg-info/top_level.txt | ||
| astropy_helpers/commands/__init__.py | ||
| astropy_helpers/commands/_dummy.py | ||
| astropy_helpers/commands/build_ext.py | ||
| astropy_helpers/commands/build_sphinx.py | ||
| astropy_helpers/commands/setup_package.py | ||
| astropy_helpers/commands/test.py | ||
| astropy_helpers/commands/src/compiler.c | ||
| astropy_helpers/sphinx/__init__.py | ||
| astropy_helpers/sphinx/conf.py | ||
| licenses/LICENSE_ASTROSCRAPPY.rst |
| astropy_helpers |
| try: | ||
| from .version import version as __version__ | ||
| from .version import githash as __githash__ | ||
| except ImportError: | ||
| __version__ = '' | ||
| __githash__ = '' | ||
| # If we've made it as far as importing astropy_helpers, we don't need | ||
| # ah_bootstrap in sys.modules anymore. Getting rid of it is actually necessary | ||
| # if the package we're installing has a setup_requires of another package that | ||
| # uses astropy_helpers (and possibly a different version at that) | ||
| # See https://github.com/astropy/astropy/issues/3541 | ||
| import sys | ||
| if 'ah_bootstrap' in sys.modules: | ||
| del sys.modules['ah_bootstrap'] | ||
| # Note, this is repeated from ah_bootstrap.py, but is here too in case this | ||
| # astropy-helpers was upgraded to from an older version that did not have this | ||
| # check in its ah_bootstrap. | ||
| # matplotlib can cause problems if it is imported from within a call of | ||
| # run_setup(), because in some circumstances it will try to write to the user's | ||
| # home directory, resulting in a SandboxViolation. See | ||
| # https://github.com/matplotlib/matplotlib/pull/4165 | ||
| # Making sure matplotlib, if it is available, is imported early in the setup | ||
| # process can mitigate this (note importing matplotlib.pyplot has the same | ||
| # issue) | ||
| try: | ||
| import matplotlib | ||
| matplotlib.use('Agg') | ||
| import matplotlib.pyplot | ||
| except: | ||
| # Ignore if this fails for *any* reason* | ||
| pass | ||
| import os | ||
| # Ensure that all module-level code in astropy or other packages know that | ||
| # we're in setup mode: | ||
| if ('__main__' in sys.modules and | ||
| hasattr(sys.modules['__main__'], '__file__')): | ||
| filename = os.path.basename(sys.modules['__main__'].__file__) | ||
| if filename.rstrip('co') == 'setup.py': | ||
| import builtins | ||
| builtins._ASTROPY_SETUP_ = True | ||
| del filename |
| """ | ||
| Provides a base class for a 'dummy' setup.py command that has no functionality | ||
| (probably due to a missing requirement). This dummy command can raise an | ||
| exception when it is run, explaining to the user what dependencies must be met | ||
| to use this command. | ||
| The reason this is at all tricky is that we want the command to be able to | ||
| provide this message even when the user passes arguments to the command. If we | ||
| don't know ahead of time what arguments the command can take, this is | ||
| difficult, because distutils does not allow unknown arguments to be passed to a | ||
| setup.py command. This hacks around that restriction to provide a useful error | ||
| message even when a user passes arguments to the dummy implementation of a | ||
| command. | ||
| Use this like: | ||
| try: | ||
| from some_dependency import SetupCommand | ||
| except ImportError: | ||
| from ._dummy import _DummyCommand | ||
| class SetupCommand(_DummyCommand): | ||
| description = \ | ||
| 'Implementation of SetupCommand from some_dependency; ' | ||
| 'some_dependency must be installed to run this command' | ||
| # This is the message that will be raised when a user tries to | ||
| # run this command--define it as a class attribute. | ||
| error_msg = \ | ||
| "The 'setup_command' command requires the some_dependency " | ||
| "package to be installed and importable." | ||
| """ | ||
| import sys | ||
| from setuptools import Command | ||
| from distutils.errors import DistutilsArgError | ||
| from textwrap import dedent | ||
| class _DummyCommandMeta(type): | ||
| """ | ||
| Causes an exception to be raised on accessing attributes of a command class | ||
| so that if ``./setup.py command_name`` is run with additional command-line | ||
| options we can provide a useful error message instead of the default that | ||
| tells users the options are unrecognized. | ||
| """ | ||
| def __init__(cls, name, bases, members): | ||
| if bases == (Command, object): | ||
| # This is the _DummyCommand base class, presumably | ||
| return | ||
| if not hasattr(cls, 'description'): | ||
| raise TypeError( | ||
| "_DummyCommand subclass must have a 'description' " | ||
| "attribute.") | ||
| if not hasattr(cls, 'error_msg'): | ||
| raise TypeError( | ||
| "_DummyCommand subclass must have an 'error_msg' " | ||
| "attribute.") | ||
| def __getattribute__(cls, attr): | ||
| if attr in ('description', 'error_msg'): | ||
| # Allow cls.description to work so that `./setup.py | ||
| # --help-commands` still works | ||
| return super(_DummyCommandMeta, cls).__getattribute__(attr) | ||
| raise DistutilsArgError(cls.error_msg) | ||
| class _DummyCommand(Command, object, metaclass=_DummyCommandMeta): | ||
| pass |
| import errno | ||
| import os | ||
| import re | ||
| import shlex | ||
| import shutil | ||
| import subprocess | ||
| import sys | ||
| import textwrap | ||
| from distutils import log, ccompiler, sysconfig | ||
| from distutils.core import Extension | ||
| from distutils.ccompiler import get_default_compiler | ||
| from importlib import invalidate_caches | ||
| from setuptools.command.build_ext import build_ext as SetuptoolsBuildExt | ||
| from setuptools.command import build_py | ||
| from ..utils import get_numpy_include_path, classproperty | ||
| from ..version_helpers import get_pkg_version_module | ||
| def should_build_with_cython(package, release=None): | ||
| """Returns the previously used Cython version (or 'unknown' if not | ||
| previously built) if Cython should be used to build extension modules from | ||
| pyx files. If the ``release`` parameter is not specified an attempt is | ||
| made to determine the release flag from `astropy.version`. | ||
| """ | ||
| try: | ||
| version_module = __import__(package + '.cython_version', | ||
| fromlist=['release', 'cython_version']) | ||
| except ImportError: | ||
| version_module = None | ||
| if release is None and version_module is not None: | ||
| try: | ||
| release = version_module.release | ||
| except AttributeError: | ||
| pass | ||
| try: | ||
| cython_version = version_module.cython_version | ||
| except AttributeError: | ||
| cython_version = 'unknown' | ||
| # Only build with Cython if, of course, Cython is installed, we're in a | ||
| # development version (i.e. not release) or the Cython-generated source | ||
| # files haven't been created yet (cython_version == 'unknown'). The latter | ||
| # case can happen even when release is True if checking out a release tag | ||
| # from the repository | ||
| have_cython = False | ||
| try: | ||
| import Cython # noqa | ||
| have_cython = True | ||
| except ImportError: | ||
| pass | ||
| if have_cython and (not release or cython_version == 'unknown'): | ||
| return cython_version | ||
| else: | ||
| return False | ||
| _compiler_versions = {} | ||
| def get_compiler_version(compiler): | ||
| if compiler in _compiler_versions: | ||
| return _compiler_versions[compiler] | ||
| # Different flags to try to get the compiler version | ||
| # TODO: It might be worth making this configurable to support | ||
| # arbitrary odd compilers; though all bets may be off in such | ||
| # cases anyway | ||
| flags = ['--version', '--Version', '-version', '-Version', | ||
| '-v', '-V'] | ||
| def try_get_version(flag): | ||
| process = subprocess.Popen( | ||
| shlex.split(compiler, posix=('win' not in sys.platform)) + [flag], | ||
| stdout=subprocess.PIPE, stderr=subprocess.PIPE) | ||
| stdout, stderr = process.communicate() | ||
| if process.returncode != 0: | ||
| return 'unknown' | ||
| output = stdout.strip().decode('latin-1') # Safest bet | ||
| if not output: | ||
| # Some compilers return their version info on stderr | ||
| output = stderr.strip().decode('latin-1') | ||
| if not output: | ||
| output = 'unknown' | ||
| return output | ||
| for flag in flags: | ||
| version = try_get_version(flag) | ||
| if version != 'unknown': | ||
| break | ||
| # Cache results to speed up future calls | ||
| _compiler_versions[compiler] = version | ||
| return version | ||
| # TODO: I think this can be reworked without having to create the class | ||
| # programmatically. | ||
| def generate_build_ext_command(packagename, release): | ||
| """ | ||
| Creates a custom 'build_ext' command that allows for manipulating some of | ||
| the C extension options at build time. We use a function to build the | ||
| class since the base class for build_ext may be different depending on | ||
| certain build-time parameters (for example, we may use Cython's build_ext | ||
| instead of the default version in distutils). | ||
| Uses the default distutils.command.build_ext by default. | ||
| """ | ||
| class build_ext(SetuptoolsBuildExt, object): | ||
| package_name = packagename | ||
| is_release = release | ||
| _user_options = SetuptoolsBuildExt.user_options[:] | ||
| _boolean_options = SetuptoolsBuildExt.boolean_options[:] | ||
| _help_options = SetuptoolsBuildExt.help_options[:] | ||
| force_rebuild = False | ||
| _broken_compiler_mapping = [ | ||
| ('i686-apple-darwin[0-9]*-llvm-gcc-4.2', 'clang') | ||
| ] | ||
| # Warning: Spaghetti code ahead. | ||
| # During setup.py, the setup_helpers module needs the ability to add | ||
| # items to a command's user_options list. At this stage we don't know | ||
| # whether or not we can build with Cython, and so don't know for sure | ||
| # what base class will be used for build_ext; nevertheless we want to | ||
| # be able to provide a list to add options into. | ||
| # | ||
| # Later, once setup() has been called we should have all build | ||
| # dependencies included via setup_requires available. distutils needs | ||
| # to be able to access the user_options as a *class* attribute before | ||
| # the class has been initialized, but we do need to be able to | ||
| # enumerate the options for the correct base class at that point | ||
| @classproperty | ||
| def user_options(cls): | ||
| from distutils import core | ||
| if core._setup_distribution is None: | ||
| # We haven't gotten into setup() yet, and the Distribution has | ||
| # not yet been initialized | ||
| return cls._user_options | ||
| return cls._final_class.user_options | ||
| @classproperty | ||
| def boolean_options(cls): | ||
| # Similar to user_options above | ||
| from distutils import core | ||
| if core._setup_distribution is None: | ||
| # We haven't gotten into setup() yet, and the Distribution has | ||
| # not yet been initialized | ||
| return cls._boolean_options | ||
| return cls._final_class.boolean_options | ||
| @classproperty | ||
| def help_options(cls): | ||
| # Similar to user_options above | ||
| from distutils import core | ||
| if core._setup_distribution is None: | ||
| # We haven't gotten into setup() yet, and the Distribution has | ||
| # not yet been initialized | ||
| return cls._help_options | ||
| return cls._final_class.help_options | ||
| @classproperty(lazy=True) | ||
| def _final_class(cls): | ||
| """ | ||
| Late determination of what the build_ext base class should be, | ||
| depending on whether or not Cython is available. | ||
| """ | ||
| uses_cython = should_build_with_cython(cls.package_name, | ||
| cls.is_release) | ||
| if uses_cython: | ||
| # We need to decide late on whether or not to use Cython's | ||
| # build_ext (since Cython may not be available earlier in the | ||
| # setup.py if it was brought in via setup_requires) | ||
| try: | ||
| from Cython.Distutils.old_build_ext import old_build_ext as base_cls | ||
| except ImportError: | ||
| from Cython.Distutils import build_ext as base_cls | ||
| else: | ||
| base_cls = SetuptoolsBuildExt | ||
| # Create and return an instance of a new class based on this class | ||
| # using one of the above possible base classes | ||
| def merge_options(attr): | ||
| base = getattr(base_cls, attr) | ||
| ours = getattr(cls, '_' + attr) | ||
| all_base = set(opt[0] for opt in base) | ||
| return base + [opt for opt in ours if opt[0] not in all_base] | ||
| boolean_options = (base_cls.boolean_options + | ||
| [opt for opt in cls._boolean_options | ||
| if opt not in base_cls.boolean_options]) | ||
| members = dict(cls.__dict__) | ||
| members.update({ | ||
| 'user_options': merge_options('user_options'), | ||
| 'help_options': merge_options('help_options'), | ||
| 'boolean_options': boolean_options, | ||
| 'uses_cython': uses_cython, | ||
| }) | ||
| # Update the base class for the original build_ext command | ||
| build_ext.__bases__ = (base_cls, object) | ||
| # Create a new class for the existing class, but now with the | ||
| # appropriate base class depending on whether or not to use Cython. | ||
| # Ensure that object is one of the bases to make a new-style class. | ||
| return type(cls.__name__, (build_ext,), members) | ||
| def __new__(cls, *args, **kwargs): | ||
| # By the time the command is actually instantialized, the | ||
| # Distribution instance for the build has been instantiated, which | ||
| # means setup_requires has been processed--now we can determine | ||
| # what base class we can use for the actual build, and return an | ||
| # instance of a build_ext command that uses that base class (right | ||
| # now the options being Cython.Distutils.build_ext, or the stock | ||
| # setuptools build_ext) | ||
| new_cls = super(build_ext, cls._final_class).__new__(cls._final_class) | ||
| # Since the new cls is not a subclass of the original cls, we must | ||
| # manually call its __init__ | ||
| new_cls.__init__(*args, **kwargs) | ||
| return new_cls | ||
| def finalize_options(self): | ||
| # Add a copy of the _compiler.so module as well, but only if there | ||
| # are in fact C modules to compile (otherwise there's no reason to | ||
| # include a record of the compiler used) | ||
| # Note, self.extensions may not be set yet, but | ||
| # self.distribution.ext_modules is where any extension modules | ||
| # passed to setup() can be found | ||
| self._adjust_compiler() | ||
| extensions = self.distribution.ext_modules | ||
| if extensions: | ||
| build_py = self.get_finalized_command('build_py') | ||
| package_dir = build_py.get_package_dir(packagename) | ||
| src_path = os.path.relpath( | ||
| os.path.join(os.path.dirname(__file__), 'src')) | ||
| shutil.copy(os.path.join(src_path, 'compiler.c'), | ||
| os.path.join(package_dir, '_compiler.c')) | ||
| ext = Extension(self.package_name + '._compiler', | ||
| [os.path.join(package_dir, '_compiler.c')]) | ||
| extensions.insert(0, ext) | ||
| super(build_ext, self).finalize_options() | ||
| # Generate | ||
| if self.uses_cython: | ||
| try: | ||
| from Cython import __version__ as cython_version | ||
| except ImportError: | ||
| # This shouldn't happen if we made it this far | ||
| cython_version = None | ||
| if (cython_version is not None and | ||
| cython_version != self.uses_cython): | ||
| self.force_rebuild = True | ||
| # Update the used cython version | ||
| self.uses_cython = cython_version | ||
| # Regardless of the value of the '--force' option, force a rebuild | ||
| # if the debug flag changed from the last build | ||
| if self.force_rebuild: | ||
| self.force = True | ||
| def run(self): | ||
| # For extensions that require 'numpy' in their include dirs, | ||
| # replace 'numpy' with the actual paths | ||
| np_include = None | ||
| for extension in self.extensions: | ||
| if 'numpy' in extension.include_dirs: | ||
| if np_include is None: | ||
| np_include = get_numpy_include_path() | ||
| idx = extension.include_dirs.index('numpy') | ||
| extension.include_dirs.insert(idx, np_include) | ||
| extension.include_dirs.remove('numpy') | ||
| self._check_cython_sources(extension) | ||
| super(build_ext, self).run() | ||
| # Update cython_version.py if building with Cython | ||
| try: | ||
| cython_version = get_pkg_version_module( | ||
| packagename, fromlist=['cython_version'])[0] | ||
| except (AttributeError, ImportError): | ||
| cython_version = 'unknown' | ||
| if self.uses_cython and self.uses_cython != cython_version: | ||
| build_py = self.get_finalized_command('build_py') | ||
| package_dir = build_py.get_package_dir(packagename) | ||
| cython_py = os.path.join(package_dir, 'cython_version.py') | ||
| with open(cython_py, 'w') as f: | ||
| f.write('# Generated file; do not modify\n') | ||
| f.write('cython_version = {0!r}\n'.format(self.uses_cython)) | ||
| if os.path.isdir(self.build_lib): | ||
| # The build/lib directory may not exist if the build_py | ||
| # command was not previously run, which may sometimes be | ||
| # the case | ||
| self.copy_file(cython_py, | ||
| os.path.join(self.build_lib, cython_py), | ||
| preserve_mode=False) | ||
| invalidate_caches() | ||
| def _adjust_compiler(self): | ||
| """ | ||
| This function detects broken compilers and switches to another. If | ||
| the environment variable CC is explicitly set, or a compiler is | ||
| specified on the commandline, no override is performed -- the | ||
| purpose here is to only override a default compiler. | ||
| The specific compilers with problems are: | ||
| * The default compiler in XCode-4.2, llvm-gcc-4.2, | ||
| segfaults when compiling wcslib. | ||
| The set of broken compilers can be updated by changing the | ||
| compiler_mapping variable. It is a list of 2-tuples where the | ||
| first in the pair is a regular expression matching the version of | ||
| the broken compiler, and the second is the compiler to change to. | ||
| """ | ||
| if 'CC' in os.environ: | ||
| # Check that CC is not set to llvm-gcc-4.2 | ||
| c_compiler = os.environ['CC'] | ||
| try: | ||
| version = get_compiler_version(c_compiler) | ||
| except OSError: | ||
| msg = textwrap.dedent( | ||
| """ | ||
| The C compiler set by the CC environment variable: | ||
| {compiler:s} | ||
| cannot be found or executed. | ||
| """.format(compiler=c_compiler)) | ||
| log.warn(msg) | ||
| sys.exit(1) | ||
| for broken, fixed in self._broken_compiler_mapping: | ||
| if re.match(broken, version): | ||
| msg = textwrap.dedent( | ||
| """Compiler specified by CC environment variable | ||
| ({compiler:s}:{version:s}) will fail to compile | ||
| {pkg:s}. | ||
| Please set CC={fixed:s} and try again. | ||
| You can do this, for example, by running: | ||
| CC={fixed:s} python setup.py <command> | ||
| where <command> is the command you ran. | ||
| """.format(compiler=c_compiler, version=version, | ||
| pkg=self.package_name, fixed=fixed)) | ||
| log.warn(msg) | ||
| sys.exit(1) | ||
| # If C compiler is set via CC, and isn't broken, we are good to go. We | ||
| # should definitely not try accessing the compiler specified by | ||
| # ``sysconfig.get_config_var('CC')`` lower down, because this may fail | ||
| # if the compiler used to compile Python is missing (and maybe this is | ||
| # why the user is setting CC). For example, the official Python 2.7.3 | ||
| # MacOS X binary was compiled with gcc-4.2, which is no longer available | ||
| # in XCode 4. | ||
| return | ||
| if self.compiler is not None: | ||
| # At this point, self.compiler will be set only if a compiler | ||
| # was specified in the command-line or via setup.cfg, in which | ||
| # case we don't do anything | ||
| return | ||
| compiler_type = ccompiler.get_default_compiler() | ||
| if compiler_type == 'unix': | ||
| # We have to get the compiler this way, as this is the one that is | ||
| # used if os.environ['CC'] is not set. It is actually read in from | ||
| # the Python Makefile. Note that this is not necessarily the same | ||
| # compiler as returned by ccompiler.new_compiler() | ||
| c_compiler = sysconfig.get_config_var('CC') | ||
| try: | ||
| version = get_compiler_version(c_compiler) | ||
| except OSError: | ||
| msg = textwrap.dedent( | ||
| """ | ||
| The C compiler used to compile Python {compiler:s}, and | ||
| which is normally used to compile C extensions, is not | ||
| available. You can explicitly specify which compiler to | ||
| use by setting the CC environment variable, for example: | ||
| CC=gcc python setup.py <command> | ||
| or if you are using MacOS X, you can try: | ||
| CC=clang python setup.py <command> | ||
| """.format(compiler=c_compiler)) | ||
| log.warn(msg) | ||
| sys.exit(1) | ||
| for broken, fixed in self._broken_compiler_mapping: | ||
| if re.match(broken, version): | ||
| os.environ['CC'] = fixed | ||
| break | ||
| def _check_cython_sources(self, extension): | ||
| """ | ||
| Where relevant, make sure that the .c files associated with .pyx | ||
| modules are present (if building without Cython installed). | ||
| """ | ||
| # Determine the compiler we'll be using | ||
| if self.compiler is None: | ||
| compiler = get_default_compiler() | ||
| else: | ||
| compiler = self.compiler | ||
| # Replace .pyx with C-equivalents, unless c files are missing | ||
| for jdx, src in enumerate(extension.sources): | ||
| base, ext = os.path.splitext(src) | ||
| pyxfn = base + '.pyx' | ||
| cfn = base + '.c' | ||
| cppfn = base + '.cpp' | ||
| if not os.path.isfile(pyxfn): | ||
| continue | ||
| if self.uses_cython: | ||
| extension.sources[jdx] = pyxfn | ||
| else: | ||
| if os.path.isfile(cfn): | ||
| extension.sources[jdx] = cfn | ||
| elif os.path.isfile(cppfn): | ||
| extension.sources[jdx] = cppfn | ||
| else: | ||
| msg = ( | ||
| 'Could not find C/C++ file {0}.(c/cpp) for Cython ' | ||
| 'file {1} when building extension {2}. Cython ' | ||
| 'must be installed to build from a git ' | ||
| 'checkout.'.format(base, pyxfn, extension.name)) | ||
| raise IOError(errno.ENOENT, msg, cfn) | ||
| # Current versions of Cython use deprecated Numpy API features | ||
| # the use of which produces a few warnings when compiling. | ||
| # These additional flags should squelch those warnings. | ||
| # TODO: Feel free to remove this if/when a Cython update | ||
| # removes use of the deprecated Numpy API | ||
| if compiler == 'unix': | ||
| extension.extra_compile_args.extend([ | ||
| '-Wp,-w', '-Wno-unused-function']) | ||
| return build_ext |
| from __future__ import print_function | ||
| import os | ||
| import pkgutil | ||
| import re | ||
| import shutil | ||
| import subprocess | ||
| import sys | ||
| import glob | ||
| import warnings | ||
| from distutils.version import LooseVersion | ||
| from distutils import log | ||
| from sphinx import __version__ as sphinx_version | ||
| from sphinx.setup_command import BuildDoc as SphinxBuildDoc | ||
| from ..utils import AstropyDeprecationWarning | ||
| SPHINX_LT_17 = LooseVersion(sphinx_version) < LooseVersion('1.7') | ||
| SUBPROCESS_TEMPLATE = """ | ||
| import os | ||
| import sys | ||
| {build_main} | ||
| os.chdir({srcdir!r}) | ||
| {sys_path_inserts} | ||
| for builder in {builders!r}: | ||
| retcode = build_main(argv={argv!r} + ['-b', builder, '.', os.path.join({output_dir!r}, builder)]) | ||
| if retcode != 0: | ||
| sys.exit(retcode) | ||
| """ | ||
| def ensure_sphinx_astropy_installed(): | ||
| """ | ||
| Make sure that sphinx-astropy is available, installing it temporarily if not. | ||
| This returns the available version of sphinx-astropy as well as any | ||
| paths that should be added to sys.path for sphinx-astropy to be available. | ||
| """ | ||
| # We've split out the Sphinx part of astropy-helpers into sphinx-astropy | ||
| # but we want it to be auto-installed seamlessly for anyone using | ||
| # build_docs. We check if it's already installed, and if not, we install | ||
| # it to a local .eggs directory and add the eggs to the path (these | ||
| # have to each be added to the path, we can't add them by simply adding | ||
| # .eggs to the path) | ||
| sys_path_inserts = [] | ||
| sphinx_astropy_version = None | ||
| try: | ||
| from sphinx_astropy import __version__ as sphinx_astropy_version # noqa | ||
| except ImportError: | ||
| from setuptools import Distribution | ||
| dist = Distribution() | ||
| eggs = dist.fetch_build_eggs('sphinx-astropy') | ||
| # Find out the version of sphinx-astropy if possible. For some old | ||
| # setuptools version, eggs will be None even if sphinx-astropy was | ||
| # successfully installed. | ||
| if eggs is not None: | ||
| for egg in eggs: | ||
| if egg.project_name == 'sphinx-astropy': | ||
| sphinx_astropy_version = egg.parsed_version.public | ||
| break | ||
| eggs_path = os.path.abspath('.eggs') | ||
| for egg in glob.glob(os.path.join(eggs_path, '*.egg')): | ||
| sys_path_inserts.append(egg) | ||
| return sphinx_astropy_version, sys_path_inserts | ||
| class AstropyBuildDocs(SphinxBuildDoc): | ||
| """ | ||
| A version of the ``build_docs`` command that uses the version of Astropy | ||
| that is built by the setup ``build`` command, rather than whatever is | ||
| installed on the system. To build docs against the installed version, run | ||
| ``make html`` in the ``astropy/docs`` directory. | ||
| """ | ||
| description = 'Build Sphinx documentation for Astropy environment' | ||
| user_options = SphinxBuildDoc.user_options[:] | ||
| user_options.append( | ||
| ('warnings-returncode', 'w', | ||
| 'Parses the sphinx output and sets the return code to 1 if there ' | ||
| 'are any warnings. Note that this will cause the sphinx log to ' | ||
| 'only update when it completes, rather than continuously as is ' | ||
| 'normally the case.')) | ||
| user_options.append( | ||
| ('clean-docs', 'l', | ||
| 'Completely clean previous builds, including ' | ||
| 'automodapi-generated files before building new ones')) | ||
| user_options.append( | ||
| ('no-intersphinx', 'n', | ||
| 'Skip intersphinx, even if conf.py says to use it')) | ||
| user_options.append( | ||
| ('open-docs-in-browser', 'o', | ||
| 'Open the docs in a browser (using the webbrowser module) if the ' | ||
| 'build finishes successfully.')) | ||
| boolean_options = SphinxBuildDoc.boolean_options[:] | ||
| boolean_options.append('warnings-returncode') | ||
| boolean_options.append('clean-docs') | ||
| boolean_options.append('no-intersphinx') | ||
| boolean_options.append('open-docs-in-browser') | ||
| _self_iden_rex = re.compile(r"self\.([^\d\W][\w]+)", re.UNICODE) | ||
| def initialize_options(self): | ||
| SphinxBuildDoc.initialize_options(self) | ||
| self.clean_docs = False | ||
| self.no_intersphinx = False | ||
| self.open_docs_in_browser = False | ||
| self.warnings_returncode = False | ||
| self.traceback = False | ||
| def finalize_options(self): | ||
| # This has to happen before we call the parent class's finalize_options | ||
| if self.build_dir is None: | ||
| self.build_dir = 'docs/_build' | ||
| SphinxBuildDoc.finalize_options(self) | ||
| # Clear out previous sphinx builds, if requested | ||
| if self.clean_docs: | ||
| dirstorm = [os.path.join(self.source_dir, 'api'), | ||
| os.path.join(self.source_dir, 'generated')] | ||
| dirstorm.append(self.build_dir) | ||
| for d in dirstorm: | ||
| if os.path.isdir(d): | ||
| log.info('Cleaning directory ' + d) | ||
| shutil.rmtree(d) | ||
| else: | ||
| log.info('Not cleaning directory ' + d + ' because ' | ||
| 'not present or not a directory') | ||
| def run(self): | ||
| # TODO: Break this method up into a few more subroutines and | ||
| # document them better | ||
| import webbrowser | ||
| from urllib.request import pathname2url | ||
| # This is used at the very end of `run` to decide if sys.exit should | ||
| # be called. If it's None, it won't be. | ||
| retcode = None | ||
| # Now make sure Astropy is built and determine where it was built | ||
| build_cmd = self.reinitialize_command('build') | ||
| build_cmd.inplace = 0 | ||
| self.run_command('build') | ||
| build_cmd = self.get_finalized_command('build') | ||
| build_cmd_path = os.path.abspath(build_cmd.build_lib) | ||
| ah_importer = pkgutil.get_importer('astropy_helpers') | ||
| if ah_importer is None: | ||
| ah_path = '.' | ||
| else: | ||
| ah_path = os.path.abspath(ah_importer.path) | ||
| if SPHINX_LT_17: | ||
| build_main = 'from sphinx import build_main' | ||
| else: | ||
| build_main = 'from sphinx.cmd.build import build_main' | ||
| # We need to make sure sphinx-astropy is installed and install it | ||
| # temporarily if not | ||
| sphinx_astropy_version, extra_paths = ensure_sphinx_astropy_installed() | ||
| sys_path_inserts = [build_cmd_path, ah_path] + extra_paths | ||
| sys_path_inserts = os.linesep.join(['sys.path.insert(0, {0!r})'.format(path) for path in sys_path_inserts]) | ||
| argv = [] | ||
| if self.warnings_returncode: | ||
| argv.append('-W') | ||
| if self.no_intersphinx: | ||
| # Note, if sphinx_astropy_version is None, this could indicate an | ||
| # old version of setuptools, but sphinx-astropy is likely ok, so | ||
| # we can proceed. | ||
| if sphinx_astropy_version is None or LooseVersion(sphinx_astropy_version) >= LooseVersion('1.1'): | ||
| argv.extend(['-D', 'disable_intersphinx=1']) | ||
| else: | ||
| log.warn('The -n option to disable intersphinx requires ' | ||
| 'sphinx-astropy>=1.1. Ignoring.') | ||
| # We now need to adjust the flags based on the parent class's options | ||
| if self.fresh_env: | ||
| argv.append('-E') | ||
| if self.all_files: | ||
| argv.append('-a') | ||
| if getattr(self, 'pdb', False): | ||
| argv.append('-P') | ||
| if getattr(self, 'nitpicky', False): | ||
| argv.append('-n') | ||
| if self.traceback: | ||
| argv.append('-T') | ||
| # The default verbosity level is 1, so in that case we just don't add a flag | ||
| if self.verbose == 0: | ||
| argv.append('-q') | ||
| elif self.verbose > 1: | ||
| argv.append('-v') | ||
| if SPHINX_LT_17: | ||
| argv.insert(0, 'sphinx-build') | ||
| if isinstance(self.builder, str): | ||
| builders = [self.builder] | ||
| else: | ||
| builders = self.builder | ||
| subproccode = SUBPROCESS_TEMPLATE.format(build_main=build_main, | ||
| srcdir=self.source_dir, | ||
| sys_path_inserts=sys_path_inserts, | ||
| builders=builders, | ||
| argv=argv, | ||
| output_dir=os.path.abspath(self.build_dir)) | ||
| log.debug('Starting subprocess of {0} with python code:\n{1}\n' | ||
| '[CODE END])'.format(sys.executable, subproccode)) | ||
| proc = subprocess.Popen([sys.executable], stdin=subprocess.PIPE) | ||
| proc.communicate(subproccode.encode('utf-8')) | ||
| if proc.returncode != 0: | ||
| retcode = proc.returncode | ||
| if retcode is None: | ||
| if self.open_docs_in_browser: | ||
| if self.builder == 'html': | ||
| absdir = os.path.abspath(self.builder_target_dir) | ||
| index_path = os.path.join(absdir, 'index.html') | ||
| fileurl = 'file://' + pathname2url(index_path) | ||
| webbrowser.open(fileurl) | ||
| else: | ||
| log.warn('open-docs-in-browser option was given, but ' | ||
| 'the builder is not html! Ignoring.') | ||
| # Here we explicitly check proc.returncode since we only want to output | ||
| # this for cases where the return code really wasn't 0. | ||
| if proc.returncode: | ||
| log.warn('Sphinx Documentation subprocess failed with return ' | ||
| 'code ' + str(proc.returncode)) | ||
| if retcode is not None: | ||
| # this is potentially dangerous in that there might be something | ||
| # after the call to `setup` in `setup.py`, and exiting here will | ||
| # prevent that from running. But there's no other apparent way | ||
| # to signal what the return code should be. | ||
| sys.exit(retcode) | ||
| class AstropyBuildSphinx(AstropyBuildDocs): # pragma: no cover | ||
| description = 'deprecated alias to the build_docs command' | ||
| def run(self): | ||
| warnings.warn( | ||
| 'The "build_sphinx" command is now deprecated. Use' | ||
| '"build_docs" instead.', AstropyDeprecationWarning) | ||
| AstropyBuildDocs.run(self) |
| from os.path import join | ||
| def get_package_data(): | ||
| return {'astropy_helpers.commands': [join('src', 'compiler.c')]} |
| #include <Python.h> | ||
| /*************************************************************************** | ||
| * Macros for determining the compiler version. | ||
| * | ||
| * These are borrowed from boost, and majorly abridged to include only | ||
| * the compilers we care about. | ||
| ***************************************************************************/ | ||
| #ifndef PY3K | ||
| #if PY_MAJOR_VERSION >= 3 | ||
| #define PY3K 1 | ||
| #else | ||
| #define PY3K 0 | ||
| #endif | ||
| #endif | ||
| #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 | ||
| }; | ||
| #if PY3K | ||
| static struct PyModuleDef moduledef = { | ||
| PyModuleDef_HEAD_INIT, | ||
| "_compiler", | ||
| NULL, | ||
| sizeof(struct module_state), | ||
| NULL, | ||
| NULL, | ||
| NULL, | ||
| NULL, | ||
| NULL | ||
| }; | ||
| #define INITERROR return NULL | ||
| PyMODINIT_FUNC | ||
| PyInit__compiler(void) | ||
| #else | ||
| #define INITERROR return | ||
| PyMODINIT_FUNC | ||
| init_compiler(void) | ||
| #endif | ||
| { | ||
| PyObject* m; | ||
| #if PY3K | ||
| m = PyModule_Create(&moduledef); | ||
| #else | ||
| m = Py_InitModule3("_compiler", NULL, NULL); | ||
| #endif | ||
| if (m == NULL) | ||
| INITERROR; | ||
| PyModule_AddStringConstant(m, "compiler", COMPILER); | ||
| #if PY3K | ||
| return m; | ||
| #endif | ||
| } |
| """ | ||
| Different implementations of the ``./setup.py test`` command depending on | ||
| what's locally available. | ||
| If Astropy v1.1.0.dev or later is available it should be possible to import | ||
| AstropyTest from ``astropy.tests.command``. If ``astropy`` can be imported | ||
| but not ``astropy.tests.command`` (i.e. an older version of Astropy), we can | ||
| use the backwards-compat implementation of the command. | ||
| If Astropy can't be imported at all then there is a skeleton implementation | ||
| that allows users to at least discover the ``./setup.py test`` command and | ||
| learn that they need Astropy to run it. | ||
| """ | ||
| # Previously these except statements caught only ImportErrors, but there are | ||
| # some other obscure exceptional conditions that can occur when importing | ||
| # astropy.tests (at least on older versions) that can cause these imports to | ||
| # fail | ||
| try: | ||
| import astropy # noqa | ||
| from astropy.tests.command import AstropyTest | ||
| except Exception: | ||
| # No astropy at all--provide the dummy implementation | ||
| from ._dummy import _DummyCommand | ||
| class AstropyTest(_DummyCommand): | ||
| command_name = 'test' | ||
| description = 'Run the tests for this package' | ||
| error_msg = ( | ||
| "The 'test' command requires the astropy package to be " | ||
| "installed and importable.") |
| # This file contains settings for pytest that are specific to astropy-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 astropy-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 astropy_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('astropy_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('astropy_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') |
| """ | ||
| This module contains various utilities for introspecting the distutils | ||
| module and the setup process. | ||
| Some of these utilities require the | ||
| `astropy_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, log | ||
| from distutils.dist import Distribution | ||
| from distutils.errors import DistutilsError | ||
| from .utils import silence | ||
| # This function, and any functions that call it, require the setup in | ||
| # `astropy_helpers.setup_helpers.register_commands` to be run first. | ||
| def get_dummy_distribution(): | ||
| """ | ||
| Returns a distutils Distribution object used to instrument the setup | ||
| environment before calling the actual setup() function. | ||
| """ | ||
| from .setup_helpers import _module_state | ||
| if _module_state['registered_commands'] is None: | ||
| raise RuntimeError( | ||
| 'astropy_helpers.setup_helpers.register_commands() must be ' | ||
| 'called before using ' | ||
| 'astropy_helpers.setup_helpers.get_dummy_distribution()') | ||
| # 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:]}) | ||
| dist.cmdclass.update(_module_state['registered_commands']) | ||
| 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_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_distutils_install_option(option): | ||
| """ Returns the value of the given distutils install 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, ['install']) | ||
| def get_distutils_build_or_install_option(option): | ||
| """ Returns the value of the given distutils build or install option. | ||
| Parameters | ||
| ---------- | ||
| option : str | ||
| The name of the option | ||
| Returns | ||
| ------- | ||
| val : str or None | ||
| The value of the given distutils build or install option. If the | ||
| option is not set, returns None. | ||
| """ | ||
| return get_distutils_option(option, ['build', 'build_ext', 'build_clib', | ||
| 'install']) | ||
| def get_compiler_option(): | ||
| """ 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 | ||
| def add_command_option(command, name, doc, is_bool=False): | ||
| """ | ||
| Add a custom option to a setup command. | ||
| Issues a warning if the option already exists on that command. | ||
| Parameters | ||
| ---------- | ||
| command : str | ||
| The name of the command as given on the command line | ||
| name : str | ||
| The name of the build option | ||
| doc : str | ||
| A short description of the option, for the `--help` message | ||
| is_bool : bool, optional | ||
| When `True`, the option is a boolean option and doesn't | ||
| require an associated value. | ||
| """ | ||
| dist = get_dummy_distribution() | ||
| cmdcls = dist.get_command_class(command) | ||
| if (hasattr(cmdcls, '_astropy_helpers_options') and | ||
| name in cmdcls._astropy_helpers_options): | ||
| return | ||
| attr = name.replace('-', '_') | ||
| if hasattr(cmdcls, attr): | ||
| raise RuntimeError( | ||
| '{0!r} already has a {1!r} class attribute, barring {2!r} from ' | ||
| 'being usable as a custom option name.'.format(cmdcls, attr, name)) | ||
| for idx, cmd in enumerate(cmdcls.user_options): | ||
| if cmd[0] == name: | ||
| log.warn('Overriding existing {0!r} option ' | ||
| '{1!r}'.format(command, name)) | ||
| del cmdcls.user_options[idx] | ||
| if name in cmdcls.boolean_options: | ||
| cmdcls.boolean_options.remove(name) | ||
| break | ||
| cmdcls.user_options.append((name, None, doc)) | ||
| if is_bool: | ||
| cmdcls.boolean_options.append(name) | ||
| # Distutils' command parsing requires that a command object have an | ||
| # attribute with the same name as the option (with '-' replaced with '_') | ||
| # in order for that option to be recognized as valid | ||
| setattr(cmdcls, attr, None) | ||
| # This caches the options added through add_command_option so that if it is | ||
| # run multiple times in the same interpreter repeated adds are ignored | ||
| # (this way we can still raise a RuntimeError if a custom option overrides | ||
| # a built-in option) | ||
| if not hasattr(cmdcls, '_astropy_helpers_options'): | ||
| cmdcls._astropy_helpers_options = set([name]) | ||
| else: | ||
| cmdcls._astropy_helpers_options.add(name) | ||
| def get_distutils_display_options(): | ||
| """ Returns a set of all the distutils display options in their long and | ||
| short forms. These are the setup.py arguments such as --name or --version | ||
| which print the project's metadata and then exit. | ||
| Returns | ||
| ------- | ||
| opts : set | ||
| The long and short form display option arguments, including the - or -- | ||
| """ | ||
| short_display_opts = set('-' + o[1] for o in Distribution.display_options | ||
| if o[1]) | ||
| long_display_opts = set('--' + o[0] for o in Distribution.display_options) | ||
| # Include -h and --help which are not explicitly listed in | ||
| # Distribution.display_options (as they are handled by optparse) | ||
| short_display_opts.add('-h') | ||
| long_display_opts.add('--help') | ||
| # This isn't the greatest approach to hardcode these commands. | ||
| # However, there doesn't seem to be a good way to determine | ||
| # whether build *will be* run as part of the command at this | ||
| # phase. | ||
| display_commands = set([ | ||
| 'clean', 'register', 'setopt', 'saveopts', 'egg_info', | ||
| 'alias']) | ||
| return short_display_opts.union(long_display_opts.union(display_commands)) | ||
| def is_distutils_display_option(): | ||
| """ Returns True if sys.argv contains any of the distutils display options | ||
| such as --version or --name. | ||
| """ | ||
| display_options = get_distutils_display_options() | ||
| return bool(set(sys.argv[1:]).intersection(display_options)) |
| # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| """ | ||
| Utilities for retrieving revision information from a project's git repository. | ||
| """ | ||
| # Do not remove the following comment; it is used by | ||
| # astropy_helpers.version_helpers to determine the beginning of the code in | ||
| # this module | ||
| # BEGIN | ||
| import locale | ||
| import os | ||
| import subprocess | ||
| import warnings | ||
| def _decode_stdio(stream): | ||
| try: | ||
| stdio_encoding = locale.getdefaultlocale()[1] or 'utf-8' | ||
| except ValueError: | ||
| stdio_encoding = 'utf-8' | ||
| try: | ||
| text = stream.decode(stdio_encoding) | ||
| except UnicodeDecodeError: | ||
| # Final fallback | ||
| text = stream.decode('latin1') | ||
| return text | ||
| def update_git_devstr(version, path=None): | ||
| """ | ||
| Updates the git revision string if and only if the path is being imported | ||
| directly from a git working copy. This ensures that the revision number in | ||
| the version string is accurate. | ||
| """ | ||
| try: | ||
| # Quick way to determine if we're in git or not - returns '' if not | ||
| devstr = get_git_devstr(sha=True, show_warning=False, path=path) | ||
| except OSError: | ||
| return version | ||
| if not devstr: | ||
| # Probably not in git so just pass silently | ||
| return version | ||
| if 'dev' in version: # update to the current git revision | ||
| version_base = version.split('.dev', 1)[0] | ||
| devstr = get_git_devstr(sha=False, show_warning=False, path=path) | ||
| return version_base + '.dev' + devstr | ||
| else: | ||
| # otherwise it's already the true/release version | ||
| return version | ||
| def get_git_devstr(sha=False, show_warning=True, path=None): | ||
| """ | ||
| Determines the number of revisions in this repository. | ||
| Parameters | ||
| ---------- | ||
| sha : bool | ||
| If True, the full SHA1 hash will be returned. Otherwise, the total | ||
| count of commits in the repository will be used as a "revision | ||
| number". | ||
| show_warning : bool | ||
| If True, issue a warning if git returns an error code, otherwise errors | ||
| pass silently. | ||
| path : str or None | ||
| If a string, specifies the directory to look in to find the git | ||
| repository. If `None`, the current working directory is used, and must | ||
| be the root of the git repository. | ||
| If given a filename it uses the directory containing that file. | ||
| Returns | ||
| ------- | ||
| devversion : str | ||
| Either a string with the revision number (if `sha` is False), the | ||
| SHA1 hash of the current commit (if `sha` is True), or an empty string | ||
| if git version info could not be identified. | ||
| """ | ||
| if path is None: | ||
| path = os.getcwd() | ||
| if not os.path.isdir(path): | ||
| path = os.path.abspath(os.path.dirname(path)) | ||
| if sha: | ||
| # Faster for getting just the hash of HEAD | ||
| cmd = ['rev-parse', 'HEAD'] | ||
| else: | ||
| cmd = ['rev-list', '--count', 'HEAD'] | ||
| def run_git(cmd): | ||
| try: | ||
| p = subprocess.Popen(['git'] + cmd, cwd=path, | ||
| stdout=subprocess.PIPE, | ||
| stderr=subprocess.PIPE, | ||
| stdin=subprocess.PIPE) | ||
| stdout, stderr = p.communicate() | ||
| except OSError as e: | ||
| if show_warning: | ||
| warnings.warn('Error running git: ' + str(e)) | ||
| return (None, b'', b'') | ||
| if p.returncode == 128: | ||
| if show_warning: | ||
| warnings.warn('No git repository present at {0!r}! Using ' | ||
| 'default dev version.'.format(path)) | ||
| return (p.returncode, b'', b'') | ||
| if p.returncode == 129: | ||
| if show_warning: | ||
| warnings.warn('Your git looks old (does it support {0}?); ' | ||
| 'consider upgrading to v1.7.2 or ' | ||
| 'later.'.format(cmd[0])) | ||
| return (p.returncode, stdout, stderr) | ||
| elif p.returncode != 0: | ||
| if show_warning: | ||
| warnings.warn('Git failed while determining revision ' | ||
| 'count: {0}'.format(_decode_stdio(stderr))) | ||
| return (p.returncode, stdout, stderr) | ||
| return p.returncode, stdout, stderr | ||
| returncode, stdout, stderr = run_git(cmd) | ||
| if not sha and returncode == 128: | ||
| # git returns 128 if the command is not run from within a git | ||
| # repository tree. In this case, a warning is produced above but we | ||
| # return the default dev version of '0'. | ||
| return '0' | ||
| elif not sha and returncode == 129: | ||
| # git returns 129 if a command option failed to parse; in | ||
| # particular this could happen in git versions older than 1.7.2 | ||
| # where the --count option is not supported | ||
| # Also use --abbrev-commit and --abbrev=0 to display the minimum | ||
| # number of characters needed per-commit (rather than the full hash) | ||
| cmd = ['rev-list', '--abbrev-commit', '--abbrev=0', 'HEAD'] | ||
| returncode, stdout, stderr = run_git(cmd) | ||
| # Fall back on the old method of getting all revisions and counting | ||
| # the lines | ||
| if returncode == 0: | ||
| return str(stdout.count(b'\n')) | ||
| else: | ||
| return '' | ||
| elif sha: | ||
| return _decode_stdio(stdout)[:40] | ||
| else: | ||
| return _decode_stdio(stdout).strip() | ||
| # This function is tested but it is only ever executed within a subprocess when | ||
| # creating a fake package, so it doesn't get picked up by coverage metrics. | ||
| def _get_repo_path(pathname, levels=None): # pragma: no cover | ||
| """ | ||
| Given a file or directory name, determine the root of the git repository | ||
| this path is under. If given, this won't look any higher than ``levels`` | ||
| (that is, if ``levels=0`` then the given path must be the root of the git | ||
| repository and is returned if so. | ||
| Returns `None` if the given path could not be determined to belong to a git | ||
| repo. | ||
| """ | ||
| if os.path.isfile(pathname): | ||
| current_dir = os.path.abspath(os.path.dirname(pathname)) | ||
| elif os.path.isdir(pathname): | ||
| current_dir = os.path.abspath(pathname) | ||
| else: | ||
| return None | ||
| current_level = 0 | ||
| while levels is None or current_level <= levels: | ||
| if os.path.exists(os.path.join(current_dir, '.git')): | ||
| return current_dir | ||
| current_level += 1 | ||
| if current_dir == os.path.dirname(current_dir): | ||
| break | ||
| current_dir = os.path.dirname(current_dir) | ||
| return None |
| # 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 astropy_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. | ||
| from __future__ import absolute_import, print_function | ||
| import os | ||
| import sys | ||
| import glob | ||
| import time | ||
| import datetime | ||
| import tempfile | ||
| import subprocess | ||
| from distutils import log | ||
| from distutils.ccompiler import new_compiler | ||
| from distutils.sysconfig import customize_compiler, get_config_var | ||
| from distutils.errors import CompileError, LinkError | ||
| from .setup_helpers import get_compiler_option | ||
| __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 <omp.h> | ||
| #include <stdio.h> | ||
| 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':<flags>, 'linker_flags':<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_option() == '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<path> 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) |
| # 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. | ||
| """ | ||
| from __future__ import absolute_import, print_function | ||
| import collections | ||
| import os | ||
| import re | ||
| import subprocess | ||
| import sys | ||
| import traceback | ||
| import warnings | ||
| from distutils import log | ||
| from distutils.errors import DistutilsOptionError, DistutilsModuleError | ||
| from distutils.core import Extension | ||
| from distutils.core import Command | ||
| from distutils.command.sdist import sdist as DistutilsSdist | ||
| from setuptools import find_packages as _find_packages | ||
| from .distutils_helpers import (add_command_option, get_compiler_option, | ||
| get_dummy_distribution, get_distutils_build_option, | ||
| get_distutils_build_or_install_option) | ||
| from .version_helpers import get_pkg_version_module | ||
| from .utils import (walk_skip_hidden, import_file, extends_doc, | ||
| resolve_name, AstropyDeprecationWarning) | ||
| from .commands.build_ext import generate_build_ext_command | ||
| from .commands.test import AstropyTest | ||
| # These imports are not used in this module, but are included for backwards | ||
| # compat with older versions of this module | ||
| from .utils import get_numpy_include_path, write_if_different # noqa | ||
| from .commands.build_ext import should_build_with_cython, get_compiler_version # noqa | ||
| _module_state = {'registered_commands': None, | ||
| 'have_sphinx': False, | ||
| 'package_cache': None, | ||
| 'exclude_packages': set(), | ||
| 'excludes_too_late': False} | ||
| try: | ||
| import sphinx # noqa | ||
| _module_state['have_sphinx'] = True | ||
| except ValueError as e: | ||
| # This can occur deep in the bowels of Sphinx's imports by way of docutils | ||
| # and an occurrence of this bug: http://bugs.python.org/issue18378 | ||
| # In this case sphinx is effectively unusable | ||
| if 'unknown locale' in e.args[0]: | ||
| log.warn( | ||
| "Possible misconfiguration of one of the environment variables " | ||
| "LC_ALL, LC_CTYPES, LANG, or LANGUAGE. For an example of how to " | ||
| "configure your system's language environment on OSX see " | ||
| "http://blog.remibergsma.com/2012/07/10/" | ||
| "setting-locales-correctly-on-mac-osx-terminal-application/") | ||
| except ImportError: | ||
| pass | ||
| except SyntaxError: | ||
| # occurs if markupsafe is recent version, which doesn't support Python 3.2 | ||
| pass | ||
| def adjust_compiler(package): | ||
| """ | ||
| This function detects broken compilers and switches to another. If | ||
| the environment variable CC is explicitly set, or a compiler is | ||
| specified on the commandline, no override is performed -- the purpose | ||
| here is to only override a default compiler. | ||
| The specific compilers with problems are: | ||
| * The default compiler in XCode-4.2, llvm-gcc-4.2, | ||
| segfaults when compiling wcslib. | ||
| The set of broken compilers can be updated by changing the | ||
| compiler_mapping variable. It is a list of 2-tuples where the | ||
| first in the pair is a regular expression matching the version | ||
| of the broken compiler, and the second is the compiler to change | ||
| to. | ||
| """ | ||
| warnings.warn( | ||
| 'Direct use of the adjust_compiler function in setup.py is ' | ||
| 'deprecated and can be removed from your setup.py. This ' | ||
| 'functionality is now incorporated directly into the build_ext ' | ||
| 'command.', AstropyDeprecationWarning) | ||
| def get_debug_option(packagename): | ||
| """ Determines if the build is in debug mode. | ||
| Returns | ||
| ------- | ||
| debug : bool | ||
| True if the current build was started with the debug option, False | ||
| otherwise. | ||
| """ | ||
| try: | ||
| current_debug = get_pkg_version_module(packagename, | ||
| fromlist=['debug'])[0] | ||
| except (ImportError, AttributeError): | ||
| current_debug = None | ||
| # Only modify the debug flag if one of the build commands was explicitly | ||
| # run (i.e. not as a sub-command of something else) | ||
| dist = get_dummy_distribution() | ||
| if any(cmd in dist.commands for cmd in ['build', 'build_ext']): | ||
| debug = bool(get_distutils_build_option('debug')) | ||
| else: | ||
| debug = bool(current_debug) | ||
| if current_debug is not None and current_debug != debug: | ||
| build_ext_cmd = dist.get_command_class('build_ext') | ||
| build_ext_cmd.force_rebuild = True | ||
| return debug | ||
| def add_exclude_packages(excludes): | ||
| if _module_state['excludes_too_late']: | ||
| raise RuntimeError( | ||
| "add_package_excludes must be called before all other setup helper " | ||
| "functions in order to properly handle excluded packages") | ||
| _module_state['exclude_packages'].update(set(excludes)) | ||
| def register_commands(package, version, release, srcdir='.'): | ||
| if _module_state['registered_commands'] is not None: | ||
| return _module_state['registered_commands'] | ||
| if _module_state['have_sphinx']: | ||
| try: | ||
| from .commands.build_sphinx import (AstropyBuildSphinx, | ||
| AstropyBuildDocs) | ||
| except ImportError: | ||
| AstropyBuildSphinx = AstropyBuildDocs = FakeBuildSphinx | ||
| else: | ||
| AstropyBuildSphinx = AstropyBuildDocs = FakeBuildSphinx | ||
| _module_state['registered_commands'] = registered_commands = { | ||
| 'test': generate_test_command(package), | ||
| # Use distutils' sdist because it respects package_data. | ||
| # setuptools/distributes sdist requires duplication of information in | ||
| # MANIFEST.in | ||
| 'sdist': DistutilsSdist, | ||
| # The exact form of the build_ext command depends on whether or not | ||
| # we're building a release version | ||
| 'build_ext': generate_build_ext_command(package, release), | ||
| 'build_sphinx': AstropyBuildSphinx, | ||
| 'build_docs': AstropyBuildDocs | ||
| } | ||
| # Need to override the __name__ here so that the commandline options are | ||
| # presented as being related to the "build" command, for example; normally | ||
| # this wouldn't be necessary since commands also have a command_name | ||
| # attribute, but there is a bug in distutils' help display code that it | ||
| # uses __name__ instead of command_name. Yay distutils! | ||
| for name, cls in registered_commands.items(): | ||
| cls.__name__ = name | ||
| # Add a few custom options; more of these can be added by specific packages | ||
| # later | ||
| for option in [ | ||
| ('use-system-libraries', | ||
| "Use system libraries whenever possible", True)]: | ||
| add_command_option('build', *option) | ||
| add_command_option('install', *option) | ||
| add_command_hooks(registered_commands, srcdir=srcdir) | ||
| return registered_commands | ||
| def add_command_hooks(commands, srcdir='.'): | ||
| """ | ||
| Look through setup_package.py modules for functions with names like | ||
| ``pre_<command_name>_hook`` and ``post_<command_name>_hook`` where | ||
| ``<command_name>`` is the name of a ``setup.py`` command (e.g. build_ext). | ||
| If either hook is present this adds a wrapped version of that command to | ||
| the passed in ``commands`` `dict`. ``commands`` may be pre-populated with | ||
| other custom distutils command classes that should be wrapped if there are | ||
| hooks for them (e.g. `AstropyBuildPy`). | ||
| """ | ||
| hook_re = re.compile(r'^(pre|post)_(.+)_hook$') | ||
| # Distutils commands have a method of the same name, but it is not a | ||
| # *classmethod* (which probably didn't exist when distutils was first | ||
| # written) | ||
| def get_command_name(cmdcls): | ||
| if hasattr(cmdcls, 'command_name'): | ||
| return cmdcls.command_name | ||
| else: | ||
| return cmdcls.__name__ | ||
| packages = find_packages(srcdir) | ||
| dist = get_dummy_distribution() | ||
| hooks = collections.defaultdict(dict) | ||
| for setuppkg in iter_setup_packages(srcdir, packages): | ||
| for name, obj in vars(setuppkg).items(): | ||
| match = hook_re.match(name) | ||
| if not match: | ||
| continue | ||
| hook_type = match.group(1) | ||
| cmd_name = match.group(2) | ||
| if hook_type not in hooks[cmd_name]: | ||
| hooks[cmd_name][hook_type] = [] | ||
| hooks[cmd_name][hook_type].append((setuppkg.__name__, obj)) | ||
| for cmd_name, cmd_hooks in hooks.items(): | ||
| commands[cmd_name] = generate_hooked_command( | ||
| cmd_name, dist.get_command_class(cmd_name), cmd_hooks) | ||
| def generate_hooked_command(cmd_name, cmd_cls, hooks): | ||
| """ | ||
| Returns a generated subclass of ``cmd_cls`` that runs the pre- and | ||
| post-command hooks for that command before and after the ``cmd_cls.run`` | ||
| method. | ||
| """ | ||
| def run(self, orig_run=cmd_cls.run): | ||
| self.run_command_hooks('pre_hooks') | ||
| orig_run(self) | ||
| self.run_command_hooks('post_hooks') | ||
| return type(cmd_name, (cmd_cls, object), | ||
| {'run': run, 'run_command_hooks': run_command_hooks, | ||
| 'pre_hooks': hooks.get('pre', []), | ||
| 'post_hooks': hooks.get('post', [])}) | ||
| def run_command_hooks(cmd_obj, hook_kind): | ||
| """Run hooks registered for that command and phase. | ||
| *cmd_obj* is a finalized command object; *hook_kind* is either | ||
| 'pre_hook' or 'post_hook'. | ||
| """ | ||
| hooks = getattr(cmd_obj, hook_kind, None) | ||
| if not hooks: | ||
| return | ||
| for modname, hook in hooks: | ||
| if isinstance(hook, str): | ||
| try: | ||
| hook_obj = resolve_name(hook) | ||
| except ImportError as exc: | ||
| raise DistutilsModuleError( | ||
| 'cannot find hook {0}: {1}'.format(hook, exc)) | ||
| else: | ||
| hook_obj = hook | ||
| if not callable(hook_obj): | ||
| raise DistutilsOptionError('hook {0!r} is not callable' % hook) | ||
| log.info('running {0} from {1} for {2} command'.format( | ||
| hook_kind.rstrip('s'), modname, cmd_obj.get_command_name())) | ||
| try: | ||
| hook_obj(cmd_obj) | ||
| except Exception: | ||
| log.error('{0} command hook {1} raised an exception: %s\n'.format( | ||
| hook_obj.__name__, cmd_obj.get_command_name())) | ||
| log.error(traceback.format_exc()) | ||
| sys.exit(1) | ||
| def generate_test_command(package_name): | ||
| """ | ||
| Creates a custom 'test' command for the given package which sets the | ||
| command's ``package_name`` class attribute to the name of the package being | ||
| tested. | ||
| """ | ||
| return type(package_name.title() + 'Test', (AstropyTest,), | ||
| {'package_name': package_name}) | ||
| def update_package_files(srcdir, extensions, package_data, packagenames, | ||
| package_dirs): | ||
| """ | ||
| This function is deprecated and maintained for backward compatibility | ||
| with affiliated packages. Affiliated packages should update their | ||
| setup.py to use `get_package_info` instead. | ||
| """ | ||
| info = get_package_info(srcdir) | ||
| extensions.extend(info['ext_modules']) | ||
| package_data.update(info['package_data']) | ||
| packagenames = list(set(packagenames + info['packages'])) | ||
| package_dirs.update(info['package_dir']) | ||
| def get_package_info(srcdir='.', exclude=()): | ||
| """ | ||
| Collates all of the information for building all subpackages | ||
| and returns a dictionary of keyword arguments that can | ||
| be passed directly to `distutils.setup`. | ||
| The purpose of this function is to allow subpackages to update the | ||
| arguments to the package's ``setup()`` function in its setup.py | ||
| script, rather than having to specify all extensions/package data | ||
| directly in the ``setup.py``. See Astropy's own | ||
| ``setup.py`` for example usage and the Astropy development docs | ||
| for more details. | ||
| This function obtains that information by iterating through all | ||
| packages in ``srcdir`` and locating a ``setup_package.py`` module. | ||
| This module can contain the following functions: | ||
| ``get_extensions()``, ``get_package_data()``, | ||
| ``get_build_options()``, and ``get_external_libraries()``. | ||
| Each of those functions take no arguments. | ||
| - ``get_extensions`` returns a list of | ||
| `distutils.extension.Extension` objects. | ||
| - ``get_package_data()`` returns a dict formatted as required by | ||
| the ``package_data`` argument to ``setup()``. | ||
| - ``get_build_options()`` returns a list of tuples describing the | ||
| extra build options to add. | ||
| - ``get_external_libraries()`` returns | ||
| a list of libraries that can optionally be built using external | ||
| dependencies. | ||
| - ``get_entry_points()`` returns a dict formatted as required by | ||
| the ``entry_points`` argument to ``setup()``. | ||
| """ | ||
| ext_modules = [] | ||
| packages = [] | ||
| package_data = {} | ||
| package_dir = {} | ||
| if exclude: | ||
| warnings.warn( | ||
| "Use of the exclude parameter is no longer supported since it does " | ||
| "not work as expected. Use add_exclude_packages instead. Note that " | ||
| "it must be called prior to any other calls from setup helpers.", | ||
| AstropyDeprecationWarning) | ||
| # Use the find_packages tool to locate all packages and modules | ||
| packages = find_packages(srcdir, exclude=exclude) | ||
| # Update package_dir if the package lies in a subdirectory | ||
| if srcdir != '.': | ||
| package_dir[''] = srcdir | ||
| # For each of the setup_package.py modules, extract any | ||
| # information that is needed to install them. The build options | ||
| # are extracted first, so that their values will be available in | ||
| # subsequent calls to `get_extensions`, etc. | ||
| for setuppkg in iter_setup_packages(srcdir, packages): | ||
| if hasattr(setuppkg, 'get_build_options'): | ||
| options = setuppkg.get_build_options() | ||
| for option in options: | ||
| add_command_option('build', *option) | ||
| if hasattr(setuppkg, 'get_external_libraries'): | ||
| libraries = setuppkg.get_external_libraries() | ||
| for library in libraries: | ||
| add_external_library(library) | ||
| 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()) | ||
| if hasattr(setuppkg, 'get_package_data'): | ||
| package_data.update(setuppkg.get_package_data()) | ||
| # Locate any .pyx files not already specified, and add their extensions in. | ||
| # The default include dirs include numpy to facilitate numerical work. | ||
| ext_modules.extend(get_cython_extensions(srcdir, packages, ext_modules, | ||
| ['numpy'])) | ||
| # 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_option() == 'msvc': | ||
| for ext in ext_modules: | ||
| ext.extra_link_args.append('/MANIFEST') | ||
| return { | ||
| 'ext_modules': ext_modules, | ||
| 'packages': packages, | ||
| 'package_dir': package_dir, | ||
| 'package_data': package_data, | ||
| } | ||
| 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.relpath( | ||
| 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.relpath(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 of `~distutils.core.Extension` objects | ||
| The extensions that are already defined. Any .pyx files already here | ||
| will be ignored. | ||
| extincludedirs : list of str or None | ||
| Directories to include as the `include_dirs` argument to the generated | ||
| `~distutils.core.Extension` objects. | ||
| Returns | ||
| ------- | ||
| exts : list of `~distutils.core.Extension` objects | ||
| 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 | ||
| class DistutilsExtensionArgs(collections.defaultdict): | ||
| """ | ||
| A special dictionary whose default values are the empty list. | ||
| This is useful for building up a set of arguments for | ||
| `distutils.Extension` without worrying whether the entry is | ||
| already present. | ||
| """ | ||
| def __init__(self, *args, **kwargs): | ||
| def default_factory(): | ||
| return [] | ||
| super(DistutilsExtensionArgs, self).__init__( | ||
| default_factory, *args, **kwargs) | ||
| def update(self, other): | ||
| for key, val in other.items(): | ||
| self[key].extend(val) | ||
| 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 of str | ||
| A list of pkg-config packages to look up. | ||
| default_libraries : list of str | ||
| A list of library names to use if the pkg-config lookup fails. | ||
| Returns | ||
| ------- | ||
| config : dict | ||
| A dictionary containing keyword arguments to | ||
| `distutils.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 = DistutilsExtensionArgs() | ||
| 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 | ||
| def add_external_library(library): | ||
| """ | ||
| Add a build option for selecting the internal or system copy of a library. | ||
| Parameters | ||
| ---------- | ||
| library : str | ||
| The name of the library. If the library is `foo`, the build | ||
| option will be called `--use-system-foo`. | ||
| """ | ||
| for command in ['build', 'build_ext', 'install']: | ||
| add_command_option(command, str('use-system-' + library), | ||
| 'Use the system {0} library'.format(library), | ||
| is_bool=True) | ||
| def use_system_library(library): | ||
| """ | ||
| Returns `True` if the build configuration indicates that the given | ||
| library should use the system copy of the library rather than the | ||
| internal one. | ||
| For the given library `foo`, this will be `True` if | ||
| `--use-system-foo` or `--use-system-libraries` was provided at the | ||
| commandline or in `setup.cfg`. | ||
| Parameters | ||
| ---------- | ||
| library : str | ||
| The name of the library | ||
| Returns | ||
| ------- | ||
| use_system : bool | ||
| `True` if the build should use the system copy of the library. | ||
| """ | ||
| return ( | ||
| get_distutils_build_or_install_option('use_system_{0}'.format(library)) or | ||
| get_distutils_build_or_install_option('use_system_libraries')) | ||
| @extends_doc(_find_packages) | ||
| def find_packages(where='.', exclude=(), invalidate_cache=False): | ||
| """ | ||
| This version of ``find_packages`` caches previous results to speed up | ||
| subsequent calls. Use ``invalide_cache=True`` to ignore cached results | ||
| from previous ``find_packages`` calls, and repeat the package search. | ||
| """ | ||
| if exclude: | ||
| warnings.warn( | ||
| "Use of the exclude parameter is no longer supported since it does " | ||
| "not work as expected. Use add_exclude_packages instead. Note that " | ||
| "it must be called prior to any other calls from setup helpers.", | ||
| AstropyDeprecationWarning) | ||
| # Calling add_exclude_packages after this point will have no effect | ||
| _module_state['excludes_too_late'] = True | ||
| if not invalidate_cache and _module_state['package_cache'] is not None: | ||
| return _module_state['package_cache'] | ||
| packages = _find_packages( | ||
| where=where, exclude=list(_module_state['exclude_packages'])) | ||
| _module_state['package_cache'] = packages | ||
| return packages | ||
| class FakeBuildSphinx(Command): | ||
| """ | ||
| A dummy build_sphinx command that is called if Sphinx is not | ||
| installed and displays a relevant error message | ||
| """ | ||
| # user options inherited from sphinx.setup_command.BuildDoc | ||
| user_options = [ | ||
| ('fresh-env', 'E', ''), | ||
| ('all-files', 'a', ''), | ||
| ('source-dir=', 's', ''), | ||
| ('build-dir=', None, ''), | ||
| ('config-dir=', 'c', ''), | ||
| ('builder=', 'b', ''), | ||
| ('project=', None, ''), | ||
| ('version=', None, ''), | ||
| ('release=', None, ''), | ||
| ('today=', None, ''), | ||
| ('link-index', 'i', '')] | ||
| # user options appended in astropy.setup_helpers.AstropyBuildSphinx | ||
| user_options.append(('warnings-returncode', 'w', '')) | ||
| user_options.append(('clean-docs', 'l', '')) | ||
| user_options.append(('no-intersphinx', 'n', '')) | ||
| user_options.append(('open-docs-in-browser', 'o', '')) | ||
| def initialize_options(self): | ||
| try: | ||
| raise RuntimeError("Sphinx and its dependencies must be installed " | ||
| "for build_docs.") | ||
| except: | ||
| log.error('error: Sphinx and its dependencies must be installed ' | ||
| 'for build_docs.') | ||
| sys.exit(1) |
| import warnings | ||
| from sphinx_astropy.conf import * | ||
| warnings.warn("Note that astropy_helpers.sphinx.conf is deprecated - use sphinx_astropy.conf instead") |
| import os | ||
| import subprocess as sp | ||
| import sys | ||
| 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_setup_helpers(request): | ||
| """ | ||
| Saves and restores the global state of the astropy_helpers.setup_helpers | ||
| module between tests. | ||
| """ | ||
| mod = __import__('astropy_helpers.setup_helpers', fromlist=['']) | ||
| old_state = mod._module_state.copy() | ||
| def finalizer(old_state=old_state): | ||
| mod = sys.modules.get('astropy_helpers.setup_helpers') | ||
| if mod is not None: | ||
| mod._module_state.update(old_state) | ||
| request.addfinalizer(finalizer) | ||
| @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 = 'astropy-helpers-test' | ||
| VERSION = {version!r} | ||
| setup(name=NAME, version=VERSION, | ||
| packages=['_astropy_helpers_test_'], | ||
| zip_safe=False) | ||
| """ | ||
| def create_testpackage(tmpdir, version='0.1'): | ||
| source = tmpdir.mkdir('testpkg') | ||
| with source.as_cwd(): | ||
| source.mkdir('_astropy_helpers_test_') | ||
| init = source.join('_astropy_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 _astropy_helpers_test_ | ||
| used primarily for testing ah_boostrap, but without using the | ||
| astropy_helpers package directly and getting it confused with the | ||
| astropy_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('astropy_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] |
| # -*- coding: utf-8 -*- | ||
| import glob | ||
| import os | ||
| import json | ||
| import textwrap | ||
| from distutils.version import LooseVersion | ||
| import setuptools | ||
| import pytest | ||
| from . import reset_setup_helpers, reset_distutils_log # noqa | ||
| from . import run_cmd, run_setup, testpackage, create_testpackage | ||
| from ..utils import silence | ||
| TEST_SETUP_PY = """\ | ||
| #!/usr/bin/env python | ||
| from __future__ import print_function | ||
| import os | ||
| import sys | ||
| # This import is not the real run of ah_bootstrap for the purposes of the test, | ||
| # so we need to preserve the command-line arguments otherwise these get eaten | ||
| # up by this import | ||
| args = sys.argv[:] | ||
| import ah_bootstrap | ||
| sys.argv = args | ||
| {extra} | ||
| # reset the name of the package installed by ah_boostrap to | ||
| # _astropy_helpers_test_--this will prevent any confusion by pkg_resources with | ||
| # any already installed packages named astropy_helpers | ||
| # We also disable auto-upgrade by default | ||
| ah_bootstrap.DIST_NAME = 'astropy-helpers-test' | ||
| ah_bootstrap.PACKAGE_NAME = '_astropy_helpers_test_' | ||
| ah_bootstrap.AUTO_UPGRADE = False | ||
| ah_bootstrap.DOWNLOAD_IF_NEEDED = False | ||
| try: | ||
| ah_bootstrap.BOOTSTRAPPER = ah_bootstrap._Bootstrapper.main() | ||
| ah_bootstrap.use_astropy_helpers({args}) | ||
| finally: | ||
| ah_bootstrap.DIST_NAME = 'astropy-helpers' | ||
| ah_bootstrap.PACKAGE_NAME = 'astropy_helpers' | ||
| ah_bootstrap.AUTO_UPGRADE = True | ||
| ah_bootstrap.DOWNLOAD_IF_NEEDED = True | ||
| # Kind of a hacky way to do this, but this assertion is specifically | ||
| # for test_check_submodule_no_git | ||
| # TODO: Rework the tests in this module so that it's easier to test specific | ||
| # behaviors of ah_bootstrap for each test | ||
| assert '--no-git' not in sys.argv | ||
| import _astropy_helpers_test_ | ||
| filename = os.path.abspath(_astropy_helpers_test_.__file__) | ||
| filename = filename.replace('.pyc', '.py') # More consistent this way | ||
| # We print out variables that are needed in tests below in JSON | ||
| import json | ||
| data = {{}} | ||
| data['filename'] = filename | ||
| data['ah_bootstrap.BOOTSTRAPPER.use_git'] = ah_bootstrap.BOOTSTRAPPER.use_git | ||
| print(json.dumps(data)) | ||
| """ | ||
| AH_BOOTSTRAP_FILE = os.path.join(os.path.dirname(__file__), '..', '..', 'ah_bootstrap.py') | ||
| with open(AH_BOOTSTRAP_FILE) as f: | ||
| AH_BOOTSTRAP = f.read() | ||
| # The behavior checked in some of the tests depends on the version of | ||
| # setuptools | ||
| try: | ||
| # We need to use LooseVersion here instead of StrictVersion since developer | ||
| # versions of setuptools ('35.0.2.post20170530') don't satisfy the | ||
| # StrictVersion criteria even though they satisfy PEP440 | ||
| SETUPTOOLS_VERSION = LooseVersion(setuptools.__version__).version | ||
| except: | ||
| # Broken setuptools? ¯\_(ツ)_/¯ | ||
| SETUPTOOLS_VERSION = (0, 0, 0) | ||
| def test_bootstrap_from_submodule(tmpdir, testpackage, capsys): | ||
| """ | ||
| Tests importing _astropy_helpers_test_ from a submodule in a git | ||
| repository. This tests actually performing a fresh clone of the repository | ||
| without the submodule initialized, and that importing astropy_helpers in | ||
| that context works transparently after calling | ||
| `ah_boostrap.use_astropy_helpers`. | ||
| """ | ||
| orig_repo = tmpdir.mkdir('orig') | ||
| with orig_repo.as_cwd(): | ||
| run_cmd('git', ['init']) | ||
| orig_repo.join('ah_bootstrap.py').write(AH_BOOTSTRAP) | ||
| run_cmd('git', ['add', 'ah_bootstrap.py']) | ||
| # Write a test setup.py that uses ah_bootstrap; it also ensures that | ||
| # any previous reference to astropy_helpers is first wiped from | ||
| # sys.modules | ||
| orig_repo.join('setup.py').write(TEST_SETUP_PY.format(args='', extra='')) | ||
| run_cmd('git', ['add', 'setup.py']) | ||
| # Add our own clone of the astropy_helpers repo as a submodule named | ||
| # astropy_helpers | ||
| run_cmd('git', ['submodule', 'add', str(testpackage), | ||
| '_astropy_helpers_test_']) | ||
| run_cmd('git', ['commit', '-m', 'test repository']) | ||
| os.chdir(str(tmpdir)) | ||
| # Creates a clone of our test repo in the directory 'clone' | ||
| run_cmd('git', ['clone', 'orig', 'clone']) | ||
| os.chdir('clone') | ||
| run_setup('setup.py', []) | ||
| stdout, stderr = capsys.readouterr() | ||
| path = json.loads(stdout.strip())['filename'] | ||
| # Ensure that the astropy_helpers used by the setup.py is the one that | ||
| # was imported from git submodule | ||
| a = os.path.normcase(path) | ||
| b = os.path.normcase(str(tmpdir.join('clone', '_astropy_helpers_test_', | ||
| '_astropy_helpers_test_', | ||
| '__init__.py'))) | ||
| assert a == b | ||
| def test_bootstrap_from_submodule_no_locale(tmpdir, testpackage, capsys, | ||
| monkeypatch): | ||
| """ | ||
| Regression test for https://github.com/astropy/astropy/issues/2749 | ||
| Runs test_bootstrap_from_submodule but with missing locale/language | ||
| settings. | ||
| """ | ||
| for varname in ('LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE'): | ||
| monkeypatch.delenv(varname, raising=False) | ||
| test_bootstrap_from_submodule(tmpdir, testpackage, capsys) | ||
| def test_bootstrap_from_submodule_bad_locale(tmpdir, testpackage, capsys, | ||
| monkeypatch): | ||
| """ | ||
| Additional regression test for | ||
| https://github.com/astropy/astropy/issues/2749 | ||
| """ | ||
| for varname in ('LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE'): | ||
| monkeypatch.delenv(varname, raising=False) | ||
| # Test also with bad LC_CTYPE a la http://bugs.python.org/issue18378 | ||
| monkeypatch.setenv('LC_CTYPE', 'UTF-8') | ||
| test_bootstrap_from_submodule(tmpdir, testpackage, capsys) | ||
| UPDATE_ERROR_PATCH = """ | ||
| class UpgradeError(Exception): | ||
| pass | ||
| def _do_upgrade(*args, **kwargs): | ||
| raise UpgradeError() | ||
| ah_bootstrap._Bootstrapper._do_upgrade = _do_upgrade | ||
| """ | ||
| def test_check_submodule_no_git(capsys, tmpdir, testpackage): | ||
| """ | ||
| Tests that when importing astropy_helpers from a submodule, it is still | ||
| recognized as a submodule even when using the --no-git option. | ||
| In particular this ensures that the auto-upgrade feature is not activated. | ||
| """ | ||
| orig_repo = tmpdir.mkdir('orig') | ||
| with orig_repo.as_cwd(): | ||
| orig_repo.join('ah_bootstrap.py').write(AH_BOOTSTRAP) | ||
| run_cmd('git', ['init']) | ||
| # Write a test setup.py that uses ah_bootstrap; it also ensures that | ||
| # any previous reference to astropy_helpers is first wiped from | ||
| # sys.modules | ||
| args = 'auto_upgrade=True' | ||
| orig_repo.join('setup.py').write(TEST_SETUP_PY.format(args=args, extra=UPDATE_ERROR_PATCH)) | ||
| run_cmd('git', ['add', 'setup.py']) | ||
| # Add our own clone of the astropy_helpers repo as a submodule named | ||
| # astropy_helpers | ||
| run_cmd('git', ['submodule', 'add', str(testpackage), | ||
| '_astropy_helpers_test_']) | ||
| run_cmd('git', ['commit', '-m', 'test repository']) | ||
| run_setup('setup.py', ['--no-git']) | ||
| stdout, stderr = capsys.readouterr() | ||
| use_git = bool(json.loads(stdout.strip())['ah_bootstrap.BOOTSTRAPPER.use_git']) | ||
| if 'UpgradeError' in stderr: | ||
| pytest.fail('Attempted to run auto-upgrade despite importing ' | ||
| '_astropy_helpers_test_ from a git submodule') | ||
| # Ensure that the no-git option was in fact set | ||
| assert not use_git | ||
| def test_bootstrap_from_directory(tmpdir, testpackage, capsys): | ||
| """ | ||
| Tests simply bundling a copy of the astropy_helpers source code in its | ||
| entirety bundled directly in the source package and not in an archive. | ||
| """ | ||
| source = tmpdir.mkdir('source') | ||
| testpackage.copy(source.join('_astropy_helpers_test_')) | ||
| with source.as_cwd(): | ||
| source.join('ah_bootstrap.py').write(AH_BOOTSTRAP) | ||
| source.join('setup.py').write(TEST_SETUP_PY.format(args='', extra='')) | ||
| run_setup('setup.py', []) | ||
| stdout, stderr = capsys.readouterr() | ||
| path = json.loads(stdout.strip())['filename'] | ||
| # Ensure that the astropy_helpers used by the setup.py is the one that | ||
| # was imported from git submodule | ||
| a = os.path.normcase(path) | ||
| b = os.path.normcase(str(source.join('_astropy_helpers_test_', | ||
| '_astropy_helpers_test_', | ||
| '__init__.py'))) | ||
| assert a == b | ||
| def test_bootstrap_from_archive(tmpdir, testpackage, capsys): | ||
| """ | ||
| Tests importing _astropy_helpers_test_ from a .tar.gz source archive | ||
| shipped alongside the package that uses it. | ||
| """ | ||
| orig_repo = tmpdir.mkdir('orig') | ||
| # Make a source distribution of the test package | ||
| with silence(): | ||
| run_setup(str(testpackage.join('setup.py')), | ||
| ['sdist', '--dist-dir=dist', '--formats=gztar']) | ||
| dist_dir = testpackage.join('dist') | ||
| for dist_file in dist_dir.visit('*.tar.gz'): | ||
| dist_file.copy(orig_repo) | ||
| with orig_repo.as_cwd(): | ||
| orig_repo.join('ah_bootstrap.py').write(AH_BOOTSTRAP) | ||
| # Write a test setup.py that uses ah_bootstrap; it also ensures that | ||
| # any previous reference to astropy_helpers is first wiped from | ||
| # sys.modules | ||
| args = 'path={0!r}'.format(os.path.basename(str(dist_file))) | ||
| orig_repo.join('setup.py').write(TEST_SETUP_PY.format(args=args, extra='')) | ||
| run_setup('setup.py', []) | ||
| stdout, stderr = capsys.readouterr() | ||
| path = json.loads(stdout.strip())['filename'] | ||
| # Installation from the .tar.gz should have resulted in a .egg | ||
| # directory that the _astropy_helpers_test_ package was imported from | ||
| eggs = _get_local_eggs() | ||
| assert eggs | ||
| egg = orig_repo.join(eggs[0]) | ||
| assert os.path.isdir(str(egg)) | ||
| a = os.path.normcase(path) | ||
| b = os.path.normcase(str(egg.join('_astropy_helpers_test_', | ||
| '__init__.py'))) | ||
| assert a == b | ||
| def test_download_if_needed(tmpdir, testpackage, capsys): | ||
| """ | ||
| Tests the case where astropy_helpers was not actually included in a | ||
| package, or is otherwise missing, and we need to "download" it. | ||
| This does not test actually downloading from the internet--this is normally | ||
| done through setuptools' easy_install command which can also install from a | ||
| source archive. From the point of view of ah_boostrap the two actions are | ||
| equivalent, so we can just as easily simulate this by providing a setup.cfg | ||
| giving the path to a source archive to "download" (as though it were a | ||
| URL). | ||
| """ | ||
| source = tmpdir.mkdir('source') | ||
| # Ensure ah_bootstrap is imported from the local directory | ||
| import ah_bootstrap # noqa | ||
| # Make a source distribution of the test package | ||
| with silence(): | ||
| run_setup(str(testpackage.join('setup.py')), | ||
| ['sdist', '--dist-dir=dist', '--formats=gztar']) | ||
| dist_dir = testpackage.join('dist') | ||
| with source.as_cwd(): | ||
| source.join('ah_bootstrap.py').write(AH_BOOTSTRAP) | ||
| source.join('setup.py').write(TEST_SETUP_PY.format( | ||
| args='download_if_needed=True', extra='')) | ||
| source.join('setup.cfg').write(textwrap.dedent("""\ | ||
| [easy_install] | ||
| find_links = {find_links} | ||
| """.format(find_links=str(dist_dir)))) | ||
| run_setup('setup.py', []) | ||
| stdout, stderr = capsys.readouterr() | ||
| path = json.loads(stdout.strip())['filename'] | ||
| # easy_install should have worked by 'installing' astropy_helpers as a | ||
| # .egg in the current directory | ||
| eggs = _get_local_eggs() | ||
| assert eggs | ||
| egg = source.join(eggs[0]) | ||
| assert os.path.isdir(str(egg)) | ||
| a = os.path.normcase(path) | ||
| b = os.path.normcase(str(egg.join('_astropy_helpers_test_', | ||
| '__init__.py'))) | ||
| assert a == b | ||
| EXTRA_PACKAGE_INDEX = """ | ||
| from setuptools.package_index import PackageIndex | ||
| class FakePackageIndex(PackageIndex): | ||
| def __init__(self, *args, **kwargs): | ||
| PackageIndex.__init__(self, *args, **kwargs) | ||
| self.to_scan = {dists} | ||
| def find_packages(self, requirement): | ||
| # no-op | ||
| pass | ||
| ah_bootstrap.PackageIndex = FakePackageIndex | ||
| """ | ||
| def test_upgrade(tmpdir, capsys): | ||
| orig_dir = create_testpackage(tmpdir.mkdir('orig')) | ||
| # Make a test package that uses _astropy_helpers_test_ | ||
| source = tmpdir.mkdir('source') | ||
| dist_dir = source.mkdir('dists') | ||
| orig_dir.copy(source.join('_astropy_helpers_test_')) | ||
| with source.as_cwd(): | ||
| source.join('ah_bootstrap.py').write(AH_BOOTSTRAP) | ||
| setup_py = TEST_SETUP_PY.format(args='auto_upgrade=True', extra='') | ||
| source.join('setup.py').write(setup_py) | ||
| # This will be used to later to fake downloading the upgrade package | ||
| source.join('setup.cfg').write(textwrap.dedent("""\ | ||
| [easy_install] | ||
| find_links = {find_links} | ||
| """.format(find_links=str(dist_dir)))) | ||
| # Make additional "upgrade" versions of the _astropy_helpers_test_ | ||
| # package--one of them is version 0.2 and the other is version 0.1.1. The | ||
| # auto-upgrade should ignore version 0.2 but use version 0.1.1. | ||
| upgrade_dir_1 = create_testpackage(tmpdir.mkdir('upgrade_1'), version='0.2') | ||
| upgrade_dir_2 = create_testpackage(tmpdir.mkdir('upgrade_2'), version='0.1.1') | ||
| dists = [] | ||
| # For each upgrade package go ahead and build a source distribution of it | ||
| # and copy that source distribution to a dist directory we'll use later to | ||
| # simulate a 'download' | ||
| for upgrade_dir in [upgrade_dir_1, upgrade_dir_2]: | ||
| with silence(): | ||
| run_setup(str(upgrade_dir.join('setup.py')), | ||
| ['sdist', '--dist-dir=dist', '--formats=gztar']) | ||
| dists.append(str(upgrade_dir.join('dist'))) | ||
| for dist_file in upgrade_dir.visit('*.tar.gz'): | ||
| dist_file.copy(source.join('dists')) | ||
| with source.as_cwd(): | ||
| setup_py = TEST_SETUP_PY.format(args='auto_upgrade=True', | ||
| extra=EXTRA_PACKAGE_INDEX.format(dists=dists)) | ||
| source.join('setup.py').write(setup_py) | ||
| # Now run the source setup.py; this test is similar to | ||
| # test_download_if_needed, but we explicitly check that the correct | ||
| # *version* of _astropy_helpers_test_ was used | ||
| run_setup('setup.py', []) | ||
| stdout, stderr = capsys.readouterr() | ||
| path = json.loads(stdout.strip())['filename'] | ||
| eggs = _get_local_eggs() | ||
| assert eggs | ||
| egg = source.join(eggs[0]) | ||
| assert os.path.isdir(str(egg)) | ||
| a = os.path.normcase(path) | ||
| b = os.path.normcase(str(egg.join('_astropy_helpers_test_', | ||
| '__init__.py'))) | ||
| assert a == b | ||
| assert 'astropy_helpers_test-0.1.1-' in str(egg) | ||
| def _get_local_eggs(path='.'): | ||
| """ | ||
| Helper utility used by some tests to get the list of egg archive files | ||
| in a local directory. | ||
| """ | ||
| if SETUPTOOLS_VERSION[0] >= 7: | ||
| eggs = glob.glob(os.path.join(path, '.eggs', '*.egg')) | ||
| else: | ||
| eggs = glob.glob('*.egg') | ||
| return eggs |
| import glob | ||
| import imp | ||
| import os | ||
| import pkgutil | ||
| import re | ||
| import sys | ||
| import tarfile | ||
| import pytest | ||
| from warnings import catch_warnings | ||
| from . import reset_setup_helpers, reset_distutils_log # noqa | ||
| from . import run_cmd, run_setup, cleanup_import | ||
| from astropy_helpers.git_helpers import get_git_devstr | ||
| _DEV_VERSION_RE = re.compile(r'\d+\.\d+(?:\.\d+)?\.dev(\d+)') | ||
| ASTROPY_HELPERS_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) | ||
| TEST_VERSION_SETUP_PY = """\ | ||
| #!/usr/bin/env python | ||
| import sys | ||
| from setuptools import setup | ||
| NAME = 'apyhtest_eva' | ||
| VERSION = {version!r} | ||
| RELEASE = 'dev' not in VERSION | ||
| sys.path.insert(0, r'{astropy_helpers_path}') | ||
| from astropy_helpers.git_helpers import get_git_devstr | ||
| from astropy_helpers.version_helpers import generate_version_py | ||
| if not RELEASE: | ||
| VERSION += get_git_devstr(False) | ||
| generate_version_py(NAME, VERSION, RELEASE, False, uses_git=not RELEASE) | ||
| setup(name=NAME, version=VERSION, packages=['apyhtest_eva']) | ||
| """ | ||
| TEST_VERSION_INIT = """\ | ||
| try: | ||
| from .version import version as __version__ | ||
| from .version import githash as __githash__ | ||
| except ImportError: | ||
| __version__ = __githash__ = '' | ||
| """ | ||
| @pytest.fixture | ||
| def version_test_package(tmpdir, request): | ||
| def make_test_package(version='42.42.dev'): | ||
| test_package = tmpdir.mkdir('test_package') | ||
| test_package.join('setup.py').write( | ||
| TEST_VERSION_SETUP_PY.format(version=version, | ||
| astropy_helpers_path=ASTROPY_HELPERS_PATH)) | ||
| test_package.mkdir('apyhtest_eva').join('__init__.py').write(TEST_VERSION_INIT) | ||
| with test_package.as_cwd(): | ||
| run_cmd('git', ['init']) | ||
| run_cmd('git', ['add', '--all']) | ||
| run_cmd('git', ['commit', '-m', 'test package']) | ||
| if '' in sys.path: | ||
| sys.path.remove('') | ||
| sys.path.insert(0, '') | ||
| def finalize(): | ||
| cleanup_import('apyhtest_eva') | ||
| request.addfinalizer(finalize) | ||
| return test_package | ||
| return make_test_package | ||
| def test_update_git_devstr(version_test_package, capsys): | ||
| """Tests that the commit number in the package's version string updates | ||
| after git commits even without re-running setup.py. | ||
| """ | ||
| # We have to call version_test_package to actually create the package | ||
| test_pkg = version_test_package() | ||
| with test_pkg.as_cwd(): | ||
| run_setup('setup.py', ['--version']) | ||
| stdout, stderr = capsys.readouterr() | ||
| version = stdout.strip() | ||
| m = _DEV_VERSION_RE.match(version) | ||
| assert m, ( | ||
| "Stdout did not match the version string pattern:" | ||
| "\n\n{0}\n\nStderr:\n\n{1}".format(stdout, stderr)) | ||
| revcount = int(m.group(1)) | ||
| import apyhtest_eva | ||
| assert apyhtest_eva.__version__ == version | ||
| # Make a silly git commit | ||
| with open('.test', 'w'): | ||
| pass | ||
| run_cmd('git', ['add', '.test']) | ||
| run_cmd('git', ['commit', '-m', 'test']) | ||
| import apyhtest_eva.version | ||
| imp.reload(apyhtest_eva.version) | ||
| # Previously this checked packagename.__version__, but in order for that to | ||
| # be updated we also have to re-import _astropy_init which could be tricky. | ||
| # Checking directly that the packagename.version module was updated is | ||
| # sufficient: | ||
| m = _DEV_VERSION_RE.match(apyhtest_eva.version.version) | ||
| assert m | ||
| assert int(m.group(1)) == revcount + 1 | ||
| # This doesn't test astropy_helpers.get_helpers.update_git_devstr directly | ||
| # since a copy of that function is made in packagename.version (so that it | ||
| # can work without astropy_helpers installed). In order to get test | ||
| # coverage on the actual astropy_helpers copy of that function just call it | ||
| # directly and compare to the value in packagename | ||
| from astropy_helpers.git_helpers import update_git_devstr | ||
| newversion = update_git_devstr(version, path=str(test_pkg)) | ||
| assert newversion == apyhtest_eva.version.version | ||
| def test_version_update_in_other_repos(version_test_package, tmpdir): | ||
| """ | ||
| Regression test for https://github.com/astropy/astropy-helpers/issues/114 | ||
| and for https://github.com/astropy/astropy-helpers/issues/107 | ||
| """ | ||
| test_pkg = version_test_package() | ||
| with test_pkg.as_cwd(): | ||
| run_setup('setup.py', ['build']) | ||
| # Add the path to the test package to sys.path for now | ||
| sys.path.insert(0, str(test_pkg)) | ||
| try: | ||
| import apyhtest_eva | ||
| m = _DEV_VERSION_RE.match(apyhtest_eva.__version__) | ||
| assert m | ||
| correct_revcount = int(m.group(1)) | ||
| with tmpdir.as_cwd(): | ||
| testrepo = tmpdir.mkdir('testrepo') | ||
| testrepo.chdir() | ||
| # Create an empty git repo | ||
| run_cmd('git', ['init']) | ||
| import apyhtest_eva.version | ||
| imp.reload(apyhtest_eva.version) | ||
| m = _DEV_VERSION_RE.match(apyhtest_eva.version.version) | ||
| assert m | ||
| assert int(m.group(1)) == correct_revcount | ||
| correct_revcount = int(m.group(1)) | ||
| # Add several commits--more than the revcount for the apyhtest_eva package | ||
| for idx in range(correct_revcount + 5): | ||
| test_filename = '.test' + str(idx) | ||
| testrepo.ensure(test_filename) | ||
| run_cmd('git', ['add', test_filename]) | ||
| run_cmd('git', ['commit', '-m', 'A message']) | ||
| import apyhtest_eva.version | ||
| imp.reload(apyhtest_eva.version) | ||
| m = _DEV_VERSION_RE.match(apyhtest_eva.version.version) | ||
| assert m | ||
| assert int(m.group(1)) == correct_revcount | ||
| correct_revcount = int(m.group(1)) | ||
| finally: | ||
| sys.path.remove(str(test_pkg)) | ||
| @pytest.mark.parametrize('version', ['1.0.dev', '1.0']) | ||
| def test_installed_git_version(version_test_package, version, tmpdir, capsys): | ||
| """ | ||
| Test for https://github.com/astropy/astropy-helpers/issues/87 | ||
| Ensures that packages installed with astropy_helpers have a correct copy | ||
| of the git hash of the installed commit. | ||
| """ | ||
| # To test this, it should suffice to build a source dist, unpack it | ||
| # somewhere outside the git repository, and then do a build and import | ||
| # from the build directory--no need to "install" as such | ||
| test_pkg = version_test_package(version) | ||
| with test_pkg.as_cwd(): | ||
| run_setup('setup.py', ['build']) | ||
| try: | ||
| import apyhtest_eva | ||
| githash = apyhtest_eva.__githash__ | ||
| assert githash and isinstance(githash, str) | ||
| # Ensure that it does in fact look like a git hash and not some | ||
| # other arbitrary string | ||
| assert re.match(r'[0-9a-f]{40}', githash) | ||
| finally: | ||
| cleanup_import('apyhtest_eva') | ||
| run_setup('setup.py', ['sdist', '--dist-dir=dist', '--formats=gztar']) | ||
| tgzs = glob.glob(os.path.join('dist', '*.tar.gz')) | ||
| assert len(tgzs) == 1 | ||
| tgz = test_pkg.join(tgzs[0]) | ||
| build_dir = tmpdir.mkdir('build_dir') | ||
| tf = tarfile.open(str(tgz), mode='r:gz') | ||
| tf.extractall(str(build_dir)) | ||
| with build_dir.as_cwd(): | ||
| pkg_dir = glob.glob('apyhtest_eva-*')[0] | ||
| os.chdir(pkg_dir) | ||
| with catch_warnings(record=True) as w: | ||
| run_setup('setup.py', ['build']) | ||
| try: | ||
| import apyhtest_eva | ||
| loader = pkgutil.get_loader('apyhtest_eva') | ||
| # Ensure we are importing the 'packagename' that was just unpacked | ||
| # into the build_dir | ||
| assert loader.get_filename().startswith(str(build_dir)) | ||
| assert apyhtest_eva.__githash__ == githash | ||
| finally: | ||
| cleanup_import('apyhtest_eva') | ||
| def test_get_git_devstr(tmpdir): | ||
| dirpath = str(tmpdir) | ||
| warn_msg = "No git repository present at" | ||
| # Verify as much as possible, but avoid dealing with paths on windows | ||
| if not sys.platform.startswith('win'): | ||
| warn_msg += " '{}'".format(dirpath) | ||
| with catch_warnings(record=True) as w: | ||
| devstr = get_git_devstr(path=dirpath) | ||
| assert devstr == '0' | ||
| assert len(w) == 1 | ||
| assert str(w[0].message).startswith(warn_msg) |
| import os | ||
| import sys | ||
| import types | ||
| from copy import deepcopy | ||
| from importlib import machinery | ||
| from distutils.core import Extension | ||
| from ..openmp_helpers import add_openmp_flags_if_available, generate_openmp_enabled_py | ||
| from ..setup_helpers import _module_state, register_commands | ||
| IS_TRAVIS_LINUX = os.environ.get('TRAVIS_OS_NAME', None) == 'linux' | ||
| IS_TRAVIS_OSX = os.environ.get('TRAVIS_OS_NAME', None) == 'osx' | ||
| IS_APPVEYOR = os.environ.get('APPVEYOR', None) == 'True' | ||
| PY3_LT_35 = sys.version_info[0] == 3 and sys.version_info[1] < 5 | ||
| _state = None | ||
| try: | ||
| OPENMP_EXPECTED = 'True' == os.environ['OPENMP_EXPECTED'] | ||
| except KeyError: | ||
| raise Exception("The OPENMP_EXPECTED environment variable should be set to " | ||
| "True or False and should indicate whether OpenMP should " | ||
| "work on your platform.") | ||
| def setup_function(function): | ||
| global state | ||
| state = deepcopy(_module_state) | ||
| def teardown_function(function): | ||
| _module_state.clear() | ||
| _module_state.update(state) | ||
| def test_add_openmp_flags_if_available(): | ||
| register_commands('openmp_testing', '0.0', False) | ||
| 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: | ||
| assert using_openmp | ||
| else: | ||
| assert not using_openmp | ||
| def test_generate_openmp_enabled_py(): | ||
| register_commands('openmp_autogeneration_testing', '0.0', False) | ||
| # 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: | ||
| assert is_openmp_enabled | ||
| else: | ||
| assert not is_openmp_enabled |
| import os | ||
| import sys | ||
| import stat | ||
| import shutil | ||
| import importlib | ||
| import contextlib | ||
| import pytest | ||
| from textwrap import dedent | ||
| from setuptools import Distribution | ||
| from ..setup_helpers import get_package_info, register_commands | ||
| from ..commands import build_ext | ||
| from . import reset_setup_helpers, reset_distutils_log # noqa | ||
| from . import run_setup, cleanup_import | ||
| ASTROPY_HELPERS_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) | ||
| 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 <Python.h> | ||
| #ifndef PY3K | ||
| #if PY_MAJOR_VERSION >= 3 | ||
| #define PY3K 1 | ||
| #else | ||
| #define PY3K 0 | ||
| #endif | ||
| #endif | ||
| #if PY3K | ||
| static struct PyModuleDef moduledef = { | ||
| PyModuleDef_HEAD_INIT, | ||
| "unit01", | ||
| NULL, | ||
| -1, | ||
| NULL | ||
| }; | ||
| PyMODINIT_FUNC | ||
| PyInit_unit01(void) { | ||
| return PyModule_Create(&moduledef); | ||
| } | ||
| #else | ||
| PyMODINIT_FUNC | ||
| initunit01(void) { | ||
| Py_InitModule3("unit01", NULL, NULL); | ||
| } | ||
| #endif | ||
| """)) | ||
| 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 | ||
| sys.path.insert(0, r'{astropy_helpers_path}') | ||
| from astropy_helpers.setup_helpers import register_commands | ||
| from astropy_helpers.setup_helpers import get_package_info | ||
| from astropy_helpers.version_helpers import generate_version_py | ||
| if '--no-cython' in sys.argv: | ||
| from astropy_helpers.commands import build_ext | ||
| build_ext.should_build_with_cython = lambda *args: False | ||
| sys.argv.remove('--no-cython') | ||
| NAME = 'apyhtest_eva' | ||
| VERSION = '0.1' | ||
| RELEASE = True | ||
| cmdclassd = register_commands(NAME, VERSION, RELEASE) | ||
| generate_version_py(NAME, VERSION, RELEASE, False, False) | ||
| package_info = get_package_info() | ||
| setup( | ||
| name=NAME, | ||
| version=VERSION, | ||
| cmdclass=cmdclassd, | ||
| **package_info | ||
| ) | ||
| """.format(astropy_helpers_path=ASTROPY_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/astropy-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_package_info to work | ||
| register_commands('yoda', '0.0', False, srcdir=str(test_pkg)) | ||
| package_info = get_package_info(str(test_pkg)) | ||
| assert len(package_info['ext_modules']) == 1 | ||
| assert package_info['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'))]) | ||
| stdout, stderr = capsys.readouterr() | ||
| assert "No git repository present at" in stderr | ||
| 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 | ||
| import apyhtest_eva.version | ||
| assert apyhtest_eva.version.compiler == apyhtest_eva._compiler.compiler | ||
| assert apyhtest_eva.version.compiler != 'unknown' | ||
| def test_no_cython_buildext(capsys, c_extension_test_package, monkeypatch): | ||
| """ | ||
| Regression test for https://github.com/astropy/astropy-helpers/pull/35 | ||
| This tests the custom build_ext command installed by astropy_helpers when | ||
| used with a project that has no Cython extensions (but does have one or | ||
| more normal C extensions). | ||
| """ | ||
| test_pkg = c_extension_test_package | ||
| with test_pkg.as_cwd(): | ||
| run_setup('setup.py', ['build_ext', '--inplace', '--no-cython']) | ||
| stdout, stderr = capsys.readouterr() | ||
| assert "No git repository present at" in stderr | ||
| sys.path.insert(0, str(test_pkg)) | ||
| try: | ||
| import apyhtest_eva.unit01 | ||
| dirname = os.path.abspath(os.path.dirname(apyhtest_eva.unit01.__file__)) | ||
| assert dirname == str(test_pkg.join('apyhtest_eva')) | ||
| finally: | ||
| sys.path.remove(str(test_pkg)) | ||
| def test_missing_cython_c_files(capsys, pyx_extension_test_package, monkeypatch): | ||
| """ | ||
| Regression test for https://github.com/astropy/astropy-helpers/pull/181 | ||
| Test failure mode when building a package that has Cython modules, but | ||
| where Cython is not installed and the generated C files are missing. | ||
| """ | ||
| test_pkg = pyx_extension_test_package | ||
| with test_pkg.as_cwd(): | ||
| with pytest.raises(SystemExit): | ||
| run_setup('setup.py', ['build_ext', '--inplace', '--no-cython']) | ||
| stdout, stderr = capsys.readouterr() | ||
| assert "No git repository present at" in stderr | ||
| msg = ('Could not find C/C++ file ' | ||
| '{0}.(c/cpp)'.format('apyhtest_eva/unit02'.replace('/', os.sep))) | ||
| assert msg in stderr | ||
| @pytest.mark.parametrize('mode', ['cli', 'cli-w', 'deprecated', 'cli-l']) | ||
| def test_build_docs(capsys, tmpdir, mode): | ||
| """ | ||
| Test for build_docs | ||
| """ | ||
| test_pkg = tmpdir.mkdir('test_pkg') | ||
| test_pkg.mkdir('mypackage') | ||
| test_pkg.join('mypackage').join('__init__.py').write(dedent("""\ | ||
| def test_function(): | ||
| pass | ||
| class A(): | ||
| pass | ||
| class B(A): | ||
| pass | ||
| """)) | ||
| test_pkg.mkdir('docs') | ||
| docs_dir = test_pkg.join('docs') | ||
| docs_dir.join('conf.py').write(dedent("""\ | ||
| import warnings | ||
| with warnings.catch_warnings(): # ignore matplotlib warning | ||
| warnings.simplefilter("ignore") | ||
| from sphinx_astropy.conf import * | ||
| exclude_patterns.append('_templates') | ||
| suppress_warnings = ['app.add_directive', 'app.add_node', 'app.add_role'] | ||
| """)) | ||
| docs_dir.join('index.rst').write(dedent("""\ | ||
| .. automodapi:: mypackage | ||
| :no-inheritance-diagram: | ||
| """)) | ||
| test_pkg.join('setup.py').write(dedent("""\ | ||
| import sys | ||
| sys.path.insert(0, r'{astropy_helpers_path}') | ||
| from os.path import join | ||
| from setuptools import setup, Extension | ||
| from astropy_helpers.setup_helpers import register_commands, get_package_info | ||
| NAME = 'mypackage' | ||
| VERSION = 0.1 | ||
| RELEASE = True | ||
| cmdclassd = register_commands(NAME, VERSION, RELEASE) | ||
| setup( | ||
| name=NAME, | ||
| version=VERSION, | ||
| cmdclass=cmdclassd, | ||
| **get_package_info() | ||
| ) | ||
| """.format(astropy_helpers_path=ASTROPY_HELPERS_PATH))) | ||
| with test_pkg.as_cwd(): | ||
| if mode == 'cli': | ||
| run_setup('setup.py', ['build_docs']) | ||
| elif mode == 'cli-w': | ||
| run_setup('setup.py', ['build_docs', '-w']) | ||
| elif mode == 'cli-l': | ||
| run_setup('setup.py', ['build_docs', '-l']) | ||
| elif mode == 'deprecated': | ||
| run_setup('setup.py', ['build_sphinx']) | ||
| stdout, stderr = capsys.readouterr() | ||
| assert 'AstropyDeprecationWarning' in stderr | ||
| assert os.path.exists(docs_dir.join('_build', 'html', 'index.html').strpath) | ||
| def test_command_hooks(tmpdir, capsys): | ||
| """A basic test for pre- and post-command hooks.""" | ||
| test_pkg = tmpdir.mkdir('test_pkg') | ||
| test_pkg.mkdir('_welltall_') | ||
| test_pkg.join('_welltall_', '__init__.py').ensure() | ||
| # Create a setup_package module with a couple of command hooks in it | ||
| test_pkg.join('_welltall_', 'setup_package.py').write(dedent("""\ | ||
| def pre_build_hook(cmd_obj): | ||
| print('Hello build!') | ||
| def post_build_hook(cmd_obj): | ||
| print('Goodbye build!') | ||
| """)) | ||
| # A simple setup.py for the test package--running register_commands should | ||
| # discover and enable the command hooks | ||
| test_pkg.join('setup.py').write(dedent("""\ | ||
| import sys | ||
| from os.path import join | ||
| from setuptools import setup, Extension | ||
| sys.path.insert(0, r'{astropy_helpers_path}') | ||
| from astropy_helpers.setup_helpers import register_commands, get_package_info | ||
| NAME = '_welltall_' | ||
| VERSION = 0.1 | ||
| RELEASE = True | ||
| cmdclassd = register_commands(NAME, VERSION, RELEASE) | ||
| setup( | ||
| name=NAME, | ||
| version=VERSION, | ||
| cmdclass=cmdclassd | ||
| ) | ||
| """.format(astropy_helpers_path=ASTROPY_HELPERS_PATH))) | ||
| with test_pkg.as_cwd(): | ||
| try: | ||
| run_setup('setup.py', ['build']) | ||
| finally: | ||
| cleanup_import('_welltall_') | ||
| stdout, stderr = capsys.readouterr() | ||
| want = dedent("""\ | ||
| running build | ||
| running pre_hook from _welltall_.setup_package for build command | ||
| Hello build! | ||
| running post_hook from _welltall_.setup_package for build command | ||
| Goodbye build! | ||
| """).strip() | ||
| assert want in stdout.replace('\r\n', '\n').replace('\r', '\n') | ||
| def test_adjust_compiler(monkeypatch, tmpdir): | ||
| """ | ||
| Regression test for https://github.com/astropy/astropy-helpers/issues/182 | ||
| """ | ||
| from distutils import ccompiler, sysconfig | ||
| class MockLog(object): | ||
| def __init__(self): | ||
| self.messages = [] | ||
| def warn(self, message): | ||
| self.messages.append(message) | ||
| good = tmpdir.join('gcc-good') | ||
| good.write(dedent("""\ | ||
| #!{python} | ||
| import sys | ||
| print('gcc 4.10') | ||
| sys.exit(0) | ||
| """.format(python=sys.executable))) | ||
| good.chmod(stat.S_IRUSR | stat.S_IEXEC) | ||
| # A "compiler" that reports itself to be a version of Apple's llvm-gcc | ||
| # which is broken | ||
| bad = tmpdir.join('gcc-bad') | ||
| bad.write(dedent("""\ | ||
| #!{python} | ||
| import sys | ||
| print('i686-apple-darwin-llvm-gcc-4.2') | ||
| sys.exit(0) | ||
| """.format(python=sys.executable))) | ||
| bad.chmod(stat.S_IRUSR | stat.S_IEXEC) | ||
| # A "compiler" that doesn't even know its identity (this reproduces the bug | ||
| # in #182) | ||
| ugly = tmpdir.join('gcc-ugly') | ||
| ugly.write(dedent("""\ | ||
| #!{python} | ||
| import sys | ||
| sys.exit(1) | ||
| """.format(python=sys.executable))) | ||
| ugly.chmod(stat.S_IRUSR | stat.S_IEXEC) | ||
| # Scripts with shebang lines don't work implicitly in Windows when passed | ||
| # to subprocess.Popen, so... | ||
| if 'win' in sys.platform: | ||
| good = ' '.join((sys.executable, str(good))) | ||
| bad = ' '.join((sys.executable, str(bad))) | ||
| ugly = ' '.join((sys.executable, str(ugly))) | ||
| dist = Distribution({}) | ||
| cmd_cls = build_ext.generate_build_ext_command('astropy', False) | ||
| cmd = cmd_cls(dist) | ||
| adjust_compiler = cmd._adjust_compiler | ||
| @contextlib.contextmanager | ||
| def test_setup(): | ||
| log = MockLog() | ||
| monkeypatch.setattr(build_ext, 'log', log) | ||
| yield log | ||
| monkeypatch.undo() | ||
| @contextlib.contextmanager | ||
| def compiler_setter_with_environ(compiler): | ||
| monkeypatch.setenv('CC', compiler) | ||
| with test_setup() as log: | ||
| yield log | ||
| monkeypatch.undo() | ||
| @contextlib.contextmanager | ||
| def compiler_setter_with_sysconfig(compiler): | ||
| monkeypatch.setattr(ccompiler, 'get_default_compiler', lambda: 'unix') | ||
| monkeypatch.setattr(sysconfig, 'get_config_var', lambda v: compiler) | ||
| old_cc = os.environ.get('CC') | ||
| if old_cc is not None: | ||
| del os.environ['CC'] | ||
| with test_setup() as log: | ||
| yield log | ||
| monkeypatch.undo() | ||
| monkeypatch.undo() | ||
| monkeypatch.undo() | ||
| if old_cc is not None: | ||
| os.environ['CC'] = old_cc | ||
| compiler_setters = (compiler_setter_with_environ, | ||
| compiler_setter_with_sysconfig) | ||
| for compiler_setter in compiler_setters: | ||
| with compiler_setter(str(good)): | ||
| # Should have no side-effects | ||
| adjust_compiler() | ||
| with compiler_setter(str(ugly)): | ||
| # Should just pass without complaint, since we can't determine | ||
| # anything about the compiler anyways | ||
| adjust_compiler() | ||
| # In the following tests we check the log messages just to ensure that the | ||
| # failures occur on the correct code paths for these cases | ||
| with compiler_setter_with_environ(str(bad)) as log: | ||
| with pytest.raises(SystemExit): | ||
| adjust_compiler() | ||
| assert len(log.messages) == 1 | ||
| assert 'will fail to compile' in log.messages[0] | ||
| with compiler_setter_with_sysconfig(str(bad)): | ||
| adjust_compiler() | ||
| assert 'CC' in os.environ and os.environ['CC'] == 'clang' | ||
| with compiler_setter_with_environ('bogus') as log: | ||
| with pytest.raises(SystemExit): | ||
| # Missing compiler? | ||
| adjust_compiler() | ||
| assert len(log.messages) == 1 | ||
| assert 'cannot be found or executed' in log.messages[0] | ||
| with compiler_setter_with_sysconfig('bogus') as log: | ||
| with pytest.raises(SystemExit): | ||
| # Missing compiler? | ||
| adjust_compiler() | ||
| assert len(log.messages) == 1 | ||
| assert 'The C compiler used to compile Python' in log.messages[0] | ||
| def test_invalid_package_exclusion(tmpdir, capsys): | ||
| module_name = 'foobar' | ||
| setup_header = dedent("""\ | ||
| import sys | ||
| from os.path import join | ||
| from setuptools import setup, Extension | ||
| sys.path.insert(0, r'{astropy_helpers_path}') | ||
| from astropy_helpers.setup_helpers import register_commands, \\ | ||
| get_package_info, add_exclude_packages | ||
| NAME = {module_name!r} | ||
| VERSION = 0.1 | ||
| RELEASE = True | ||
| """.format(module_name=module_name, astropy_helpers_path=ASTROPY_HELPERS_PATH)) | ||
| setup_footer = dedent("""\ | ||
| setup( | ||
| name=NAME, | ||
| version=VERSION, | ||
| cmdclass=cmdclassd, | ||
| **package_info | ||
| ) | ||
| """) | ||
| # Test error when using add_package_excludes out of order | ||
| error_commands = dedent("""\ | ||
| cmdclassd = register_commands(NAME, VERSION, RELEASE) | ||
| package_info = get_package_info() | ||
| add_exclude_packages(['tests*']) | ||
| """) | ||
| error_pkg = tmpdir.mkdir('error_pkg') | ||
| error_pkg.join('setup.py').write( | ||
| setup_header + error_commands + setup_footer) | ||
| with error_pkg.as_cwd(): | ||
| with pytest.raises(SystemExit): | ||
| run_setup('setup.py', ['build']) | ||
| stdout, stderr = capsys.readouterr() | ||
| assert "RuntimeError" in stderr | ||
| # Test warning when using deprecated exclude parameter | ||
| warn_commands = dedent("""\ | ||
| cmdclassd = register_commands(NAME, VERSION, RELEASE) | ||
| package_info = get_package_info(exclude=['test*']) | ||
| """) | ||
| warn_pkg = tmpdir.mkdir('warn_pkg') | ||
| warn_pkg.join('setup.py').write( | ||
| setup_header + warn_commands + setup_footer) | ||
| with warn_pkg.as_cwd(): | ||
| run_setup('setup.py', ['build']) | ||
| stdout, stderr = capsys.readouterr() | ||
| assert 'AstropyDeprecationWarning' in stderr |
| import os | ||
| from ..utils import find_data_files | ||
| def test_find_data_files(tmpdir): | ||
| data = tmpdir.mkdir('data') | ||
| sub1 = data.mkdir('sub1') | ||
| sub2 = data.mkdir('sub2') | ||
| sub3 = sub1.mkdir('sub3') | ||
| for directory in (data, sub1, sub2, sub3): | ||
| filename = directory.join('data.dat').strpath | ||
| with open(filename, 'w') as f: | ||
| f.write('test') | ||
| filenames = find_data_files(data.strpath, '**/*.dat') | ||
| filenames = sorted(os.path.relpath(x, data.strpath) for x in filenames) | ||
| assert filenames[0] == os.path.join('data.dat') | ||
| assert filenames[1] == os.path.join('sub1', 'data.dat') | ||
| assert filenames[2] == os.path.join('sub1', 'sub3', 'data.dat') | ||
| assert filenames[3] == os.path.join('sub2', 'data.dat') |
| # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| from __future__ import absolute_import, unicode_literals | ||
| import contextlib | ||
| import functools | ||
| import imp | ||
| import inspect | ||
| import os | ||
| import sys | ||
| import glob | ||
| import textwrap | ||
| import types | ||
| import warnings | ||
| from importlib import machinery as import_machinery | ||
| # 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. | ||
| """ | ||
| def _get_platlib_dir(cmd): | ||
| """ | ||
| Given a build command, return the name of the appropriate platform-specific | ||
| build subdirectory directory (e.g. build/lib.linux-x86_64-2.7) | ||
| """ | ||
| plat_specifier = '.{0}-{1}'.format(cmd.plat_name, sys.version[0:3]) | ||
| return os.path.join(cmd.build_base, 'lib' + plat_specifier) | ||
| def get_numpy_include_path(): | ||
| """ | ||
| Gets the path to the numpy headers. | ||
| """ | ||
| # We need to go through this nonsense in case setuptools | ||
| # downloaded and installed Numpy for us as part of the build or | ||
| # install, since Numpy may still think it's in "setup mode", when | ||
| # in fact we're ready to use it to build astropy now. | ||
| import builtins | ||
| if hasattr(builtins, '__NUMPY_SETUP__'): | ||
| del builtins.__NUMPY_SETUP__ | ||
| import imp | ||
| import numpy | ||
| imp.reload(numpy) | ||
| try: | ||
| numpy_include = numpy.get_include() | ||
| except AttributeError: | ||
| numpy_include = numpy.get_numpy_include() | ||
| return numpy_include | ||
| 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: | ||
| 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. | ||
| 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 as if it doesn't belong to a | ||
| particular 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 import_machinery: | ||
| loader = import_machinery.SourceFileLoader(name, filename) | ||
| mod = loader.load_module() | ||
| else: | ||
| with open(filename, mode) as fd: | ||
| mod = imp.load_module(name, fd, filename, ('.py', mode, 1)) | ||
| return mod | ||
| def resolve_name(name): | ||
| """Resolve a name like ``module.object`` to an object and return it. | ||
| Raise `ImportError` if the module or name is not found. | ||
| """ | ||
| parts = name.split('.') | ||
| cursor = len(parts) - 1 | ||
| module_name = parts[:cursor] | ||
| attr_name = parts[-1] | ||
| while cursor > 0: | ||
| try: | ||
| ret = __import__('.'.join(module_name), fromlist=[attr_name]) | ||
| break | ||
| except ImportError: | ||
| if cursor == 0: | ||
| raise | ||
| cursor -= 1 | ||
| module_name = parts[:cursor] | ||
| attr_name = parts[cursor] | ||
| ret = '' | ||
| for part in parts[cursor:]: | ||
| try: | ||
| ret = getattr(ret, part) | ||
| except AttributeError: | ||
| raise ImportError(name) | ||
| return ret | ||
| def extends_doc(extended_func): | ||
| """ | ||
| A function decorator for use when wrapping an existing function but adding | ||
| additional functionality. This copies the docstring from the original | ||
| function, and appends to it (along with a newline) the docstring of the | ||
| wrapper function. | ||
| Examples | ||
| -------- | ||
| >>> def foo(): | ||
| ... '''Hello.''' | ||
| ... | ||
| >>> @extends_doc(foo) | ||
| ... def bar(): | ||
| ... '''Goodbye.''' | ||
| ... | ||
| >>> print(bar.__doc__) | ||
| Hello. | ||
| Goodbye. | ||
| """ | ||
| def decorator(func): | ||
| if not (extended_func.__doc__ is None or func.__doc__ is None): | ||
| func.__doc__ = '\n\n'.join([extended_func.__doc__.rstrip('\n'), | ||
| func.__doc__.lstrip('\n')]) | ||
| return func | ||
| return decorator | ||
| # Duplicated from astropy.utils.decorators.deprecated | ||
| # When fixing issues in this function fix them in astropy first, then | ||
| # port the fixes over to astropy-helpers | ||
| def deprecated(since, message='', name='', alternative='', pending=False, | ||
| obj_type=None): | ||
| """ | ||
| Used to mark a function or class as deprecated. | ||
| To mark an attribute as deprecated, use `deprecated_attribute`. | ||
| Parameters | ||
| ---------- | ||
| since : str | ||
| The release at which this API became deprecated. This is | ||
| required. | ||
| message : str, optional | ||
| Override the default deprecation message. The format | ||
| specifier ``func`` may be used for the name of the function, | ||
| and ``alternative`` may be used in the deprecation message | ||
| to insert the name of an alternative to the deprecated | ||
| function. ``obj_type`` may be used to insert a friendly name | ||
| for the type of object being deprecated. | ||
| name : str, optional | ||
| The name of the deprecated function or class; if not provided | ||
| the name is automatically determined from the passed in | ||
| function or class, though this is useful in the case of | ||
| renamed functions, where the new function is just assigned to | ||
| the name of the deprecated function. For example:: | ||
| def new_function(): | ||
| ... | ||
| oldFunction = new_function | ||
| alternative : str, optional | ||
| An alternative function or class name that the user may use in | ||
| place of the deprecated object. The deprecation warning will | ||
| tell the user about this alternative if provided. | ||
| pending : bool, optional | ||
| If True, uses a AstropyPendingDeprecationWarning instead of a | ||
| AstropyDeprecationWarning. | ||
| obj_type : str, optional | ||
| The type of this object, if the automatically determined one | ||
| needs to be overridden. | ||
| """ | ||
| method_types = (classmethod, staticmethod, types.MethodType) | ||
| def deprecate_doc(old_doc, message): | ||
| """ | ||
| Returns a given docstring with a deprecation message prepended | ||
| to it. | ||
| """ | ||
| if not old_doc: | ||
| old_doc = '' | ||
| old_doc = textwrap.dedent(old_doc).strip('\n') | ||
| new_doc = (('\n.. deprecated:: {since}' | ||
| '\n {message}\n\n'.format( | ||
| **{'since': since, 'message': message.strip()})) + old_doc) | ||
| if not old_doc: | ||
| # This is to prevent a spurious 'unexpected unindent' warning from | ||
| # docutils when the original docstring was blank. | ||
| new_doc += r'\ ' | ||
| return new_doc | ||
| def get_function(func): | ||
| """ | ||
| Given a function or classmethod (or other function wrapper type), get | ||
| the function object. | ||
| """ | ||
| if isinstance(func, method_types): | ||
| func = func.__func__ | ||
| return func | ||
| def deprecate_function(func, message): | ||
| """ | ||
| Returns a wrapped function that displays an | ||
| ``AstropyDeprecationWarning`` when it is called. | ||
| """ | ||
| if isinstance(func, method_types): | ||
| func_wrapper = type(func) | ||
| else: | ||
| func_wrapper = lambda f: f | ||
| func = get_function(func) | ||
| def deprecated_func(*args, **kwargs): | ||
| if pending: | ||
| category = AstropyPendingDeprecationWarning | ||
| else: | ||
| category = AstropyDeprecationWarning | ||
| warnings.warn(message, category, stacklevel=2) | ||
| return func(*args, **kwargs) | ||
| # If this is an extension function, we can't call | ||
| # functools.wraps on it, but we normally don't care. | ||
| # This crazy way to get the type of a wrapper descriptor is | ||
| # straight out of the Python 3.3 inspect module docs. | ||
| if type(func) is not type(str.__dict__['__add__']): # nopep8 | ||
| deprecated_func = functools.wraps(func)(deprecated_func) | ||
| deprecated_func.__doc__ = deprecate_doc( | ||
| deprecated_func.__doc__, message) | ||
| return func_wrapper(deprecated_func) | ||
| def deprecate_class(cls, message): | ||
| """ | ||
| Update the docstring and wrap the ``__init__`` in-place (or ``__new__`` | ||
| if the class or any of the bases overrides ``__new__``) so it will give | ||
| a deprecation warning when an instance is created. | ||
| This won't work for extension classes because these can't be modified | ||
| in-place and the alternatives don't work in the general case: | ||
| - Using a new class that looks and behaves like the original doesn't | ||
| work because the __new__ method of extension types usually makes sure | ||
| that it's the same class or a subclass. | ||
| - Subclassing the class and return the subclass can lead to problems | ||
| with pickle and will look weird in the Sphinx docs. | ||
| """ | ||
| cls.__doc__ = deprecate_doc(cls.__doc__, message) | ||
| if cls.__new__ is object.__new__: | ||
| cls.__init__ = deprecate_function(get_function(cls.__init__), message) | ||
| else: | ||
| cls.__new__ = deprecate_function(get_function(cls.__new__), message) | ||
| return cls | ||
| def deprecate(obj, message=message, name=name, alternative=alternative, | ||
| pending=pending): | ||
| if obj_type is None: | ||
| if isinstance(obj, type): | ||
| obj_type_name = 'class' | ||
| elif inspect.isfunction(obj): | ||
| obj_type_name = 'function' | ||
| elif inspect.ismethod(obj) or isinstance(obj, method_types): | ||
| obj_type_name = 'method' | ||
| else: | ||
| obj_type_name = 'object' | ||
| else: | ||
| obj_type_name = obj_type | ||
| if not name: | ||
| name = get_function(obj).__name__ | ||
| altmessage = '' | ||
| if not message or type(message) is type(deprecate): | ||
| if pending: | ||
| message = ('The {func} {obj_type} will be deprecated in a ' | ||
| 'future version.') | ||
| else: | ||
| message = ('The {func} {obj_type} is deprecated and may ' | ||
| 'be removed in a future version.') | ||
| if alternative: | ||
| altmessage = '\n Use {} instead.'.format(alternative) | ||
| message = ((message.format(**{ | ||
| 'func': name, | ||
| 'name': name, | ||
| 'alternative': alternative, | ||
| 'obj_type': obj_type_name})) + | ||
| altmessage) | ||
| if isinstance(obj, type): | ||
| return deprecate_class(obj, message) | ||
| else: | ||
| return deprecate_function(obj, message) | ||
| if type(message) is type(deprecate): | ||
| return deprecate(message) | ||
| return deprecate | ||
| def deprecated_attribute(name, since, message=None, alternative=None, | ||
| pending=False): | ||
| """ | ||
| Used to mark a public attribute as deprecated. This creates a | ||
| property that will warn when the given attribute name is accessed. | ||
| To prevent the warning (i.e. for internal code), use the private | ||
| name for the attribute by prepending an underscore | ||
| (i.e. ``self._name``). | ||
| Parameters | ||
| ---------- | ||
| name : str | ||
| The name of the deprecated attribute. | ||
| since : str | ||
| The release at which this API became deprecated. This is | ||
| required. | ||
| message : str, optional | ||
| Override the default deprecation message. The format | ||
| specifier ``name`` may be used for the name of the attribute, | ||
| and ``alternative`` may be used in the deprecation message | ||
| to insert the name of an alternative to the deprecated | ||
| function. | ||
| alternative : str, optional | ||
| An alternative attribute that the user may use in place of the | ||
| deprecated attribute. The deprecation warning will tell the | ||
| user about this alternative if provided. | ||
| pending : bool, optional | ||
| If True, uses a AstropyPendingDeprecationWarning instead of a | ||
| AstropyDeprecationWarning. | ||
| Examples | ||
| -------- | ||
| :: | ||
| class MyClass: | ||
| # Mark the old_name as deprecated | ||
| old_name = misc.deprecated_attribute('old_name', '0.1') | ||
| def method(self): | ||
| self._old_name = 42 | ||
| """ | ||
| private_name = '_' + name | ||
| @deprecated(since, name=name, obj_type='attribute') | ||
| def get(self): | ||
| return getattr(self, private_name) | ||
| @deprecated(since, name=name, obj_type='attribute') | ||
| def set(self, val): | ||
| setattr(self, private_name, val) | ||
| @deprecated(since, name=name, obj_type='attribute') | ||
| def delete(self): | ||
| delattr(self, private_name) | ||
| return property(get, set, delete) | ||
| def minversion(module, version, inclusive=True, version_path='__version__'): | ||
| """ | ||
| Returns `True` if the specified Python module satisfies a minimum version | ||
| requirement, and `False` if not. | ||
| By default this uses `pkg_resources.parse_version` to do the version | ||
| comparison if available. Otherwise it falls back on | ||
| `distutils.version.LooseVersion`. | ||
| Parameters | ||
| ---------- | ||
| module : module or `str` | ||
| An imported module of which to check the version, or the name of | ||
| that module (in which case an import of that module is attempted-- | ||
| if this fails `False` is returned). | ||
| version : `str` | ||
| The version as a string that this module must have at a minimum (e.g. | ||
| ``'0.12'``). | ||
| inclusive : `bool` | ||
| The specified version meets the requirement inclusively (i.e. ``>=``) | ||
| as opposed to strictly greater than (default: `True`). | ||
| version_path : `str` | ||
| A dotted attribute path to follow in the module for the version. | ||
| Defaults to just ``'__version__'``, which should work for most Python | ||
| modules. | ||
| Examples | ||
| -------- | ||
| >>> import astropy | ||
| >>> minversion(astropy, '0.4.4') | ||
| True | ||
| """ | ||
| if isinstance(module, types.ModuleType): | ||
| module_name = module.__name__ | ||
| elif isinstance(module, str): | ||
| module_name = module | ||
| try: | ||
| module = resolve_name(module_name) | ||
| except ImportError: | ||
| return False | ||
| else: | ||
| raise ValueError('module argument must be an actual imported ' | ||
| 'module, or the import name of the module; ' | ||
| 'got {0!r}'.format(module)) | ||
| if '.' not in version_path: | ||
| have_version = getattr(module, version_path) | ||
| else: | ||
| have_version = resolve_name('.'.join([module.__name__, version_path])) | ||
| try: | ||
| from pkg_resources import parse_version | ||
| except ImportError: | ||
| from distutils.version import LooseVersion as parse_version | ||
| if inclusive: | ||
| return parse_version(have_version) >= parse_version(version) | ||
| else: | ||
| return parse_version(have_version) > parse_version(version) | ||
| # Copy of the classproperty decorator from astropy.utils.decorators | ||
| class classproperty(property): | ||
| """ | ||
| Similar to `property`, but allows class-level properties. That is, | ||
| a property whose getter is like a `classmethod`. | ||
| The wrapped method may explicitly use the `classmethod` decorator (which | ||
| must become before this decorator), or the `classmethod` may be omitted | ||
| (it is implicit through use of this decorator). | ||
| .. note:: | ||
| classproperty only works for *read-only* properties. It does not | ||
| currently allow writeable/deleteable properties, due to subtleties of how | ||
| Python descriptors work. In order to implement such properties on a class | ||
| a metaclass for that class must be implemented. | ||
| Parameters | ||
| ---------- | ||
| fget : callable | ||
| The function that computes the value of this property (in particular, | ||
| the function when this is used as a decorator) a la `property`. | ||
| doc : str, optional | ||
| The docstring for the property--by default inherited from the getter | ||
| function. | ||
| lazy : bool, optional | ||
| If True, caches the value returned by the first call to the getter | ||
| function, so that it is only called once (used for lazy evaluation | ||
| of an attribute). This is analogous to `lazyproperty`. The ``lazy`` | ||
| argument can also be used when `classproperty` is used as a decorator | ||
| (see the third example below). When used in the decorator syntax this | ||
| *must* be passed in as a keyword argument. | ||
| Examples | ||
| -------- | ||
| :: | ||
| >>> class Foo: | ||
| ... _bar_internal = 1 | ||
| ... @classproperty | ||
| ... def bar(cls): | ||
| ... return cls._bar_internal + 1 | ||
| ... | ||
| >>> Foo.bar | ||
| 2 | ||
| >>> foo_instance = Foo() | ||
| >>> foo_instance.bar | ||
| 2 | ||
| >>> foo_instance._bar_internal = 2 | ||
| >>> foo_instance.bar # Ignores instance attributes | ||
| 2 | ||
| As previously noted, a `classproperty` is limited to implementing | ||
| read-only attributes:: | ||
| >>> class Foo: | ||
| ... _bar_internal = 1 | ||
| ... @classproperty | ||
| ... def bar(cls): | ||
| ... return cls._bar_internal | ||
| ... @bar.setter | ||
| ... def bar(cls, value): | ||
| ... cls._bar_internal = value | ||
| ... | ||
| Traceback (most recent call last): | ||
| ... | ||
| NotImplementedError: classproperty can only be read-only; use a | ||
| metaclass to implement modifiable class-level properties | ||
| When the ``lazy`` option is used, the getter is only called once:: | ||
| >>> class Foo: | ||
| ... @classproperty(lazy=True) | ||
| ... def bar(cls): | ||
| ... print("Performing complicated calculation") | ||
| ... return 1 | ||
| ... | ||
| >>> Foo.bar | ||
| Performing complicated calculation | ||
| 1 | ||
| >>> Foo.bar | ||
| 1 | ||
| If a subclass inherits a lazy `classproperty` the property is still | ||
| re-evaluated for the subclass:: | ||
| >>> class FooSub(Foo): | ||
| ... pass | ||
| ... | ||
| >>> FooSub.bar | ||
| Performing complicated calculation | ||
| 1 | ||
| >>> FooSub.bar | ||
| 1 | ||
| """ | ||
| def __new__(cls, fget=None, doc=None, lazy=False): | ||
| if fget is None: | ||
| # Being used as a decorator--return a wrapper that implements | ||
| # decorator syntax | ||
| def wrapper(func): | ||
| return cls(func, lazy=lazy) | ||
| return wrapper | ||
| return super().__new__(cls) | ||
| def __init__(self, fget, doc=None, lazy=False): | ||
| self._lazy = lazy | ||
| if lazy: | ||
| self._cache = {} | ||
| fget = self._wrap_fget(fget) | ||
| super().__init__(fget=fget, doc=doc) | ||
| # There is a buglet in Python where self.__doc__ doesn't | ||
| # get set properly on instances of property subclasses if | ||
| # the doc argument was used rather than taking the docstring | ||
| # from fget | ||
| # Related Python issue: https://bugs.python.org/issue24766 | ||
| if doc is not None: | ||
| self.__doc__ = doc | ||
| def __get__(self, obj, objtype): | ||
| if self._lazy and objtype in self._cache: | ||
| return self._cache[objtype] | ||
| # The base property.__get__ will just return self here; | ||
| # instead we pass objtype through to the original wrapped | ||
| # function (which takes the class as its sole argument) | ||
| val = self.fget.__wrapped__(objtype) | ||
| if self._lazy: | ||
| self._cache[objtype] = val | ||
| return val | ||
| def getter(self, fget): | ||
| return super().getter(self._wrap_fget(fget)) | ||
| def setter(self, fset): | ||
| raise NotImplementedError( | ||
| "classproperty can only be read-only; use a metaclass to " | ||
| "implement modifiable class-level properties") | ||
| def deleter(self, fdel): | ||
| raise NotImplementedError( | ||
| "classproperty can only be read-only; use a metaclass to " | ||
| "implement modifiable class-level properties") | ||
| @staticmethod | ||
| def _wrap_fget(orig_fget): | ||
| if isinstance(orig_fget, classmethod): | ||
| orig_fget = orig_fget.__func__ | ||
| # Using stock functools.wraps instead of the fancier version | ||
| # found later in this module, which is overkill for this purpose | ||
| @functools.wraps(orig_fget) | ||
| def fget(obj): | ||
| return orig_fget(obj.__class__) | ||
| return fget | ||
| def find_data_files(package, pattern): | ||
| """ | ||
| Include files matching ``pattern`` inside ``package``. | ||
| Parameters | ||
| ---------- | ||
| package : str | ||
| The package inside which to look for data files | ||
| pattern : str | ||
| Pattern (glob-style) to match for the data files (e.g. ``*.dat``). | ||
| This supports the``**``recursive syntax. For example, ``**/*.fits`` | ||
| matches all files ending with ``.fits`` recursively. Only one | ||
| instance of ``**`` can be included in the pattern. | ||
| """ | ||
| return glob.glob(os.path.join(package, pattern), recursive=True) |
| # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| """ | ||
| Utilities for generating the version string for Astropy (or an affiliated | ||
| package) and the version.py module, which contains version info for the | ||
| package. | ||
| Within the generated astropy.version module, the `major`, `minor`, and `bugfix` | ||
| variables hold the respective parts of the version number (bugfix is '0' if | ||
| absent). The `release` variable is True if this is a release, and False if this | ||
| is a development version of astropy. For the actual version string, use:: | ||
| from astropy.version import version | ||
| or:: | ||
| from astropy import __version__ | ||
| """ | ||
| from __future__ import division | ||
| import datetime | ||
| import imp | ||
| import os | ||
| import pkgutil | ||
| import sys | ||
| import time | ||
| from distutils import log | ||
| from importlib import invalidate_caches | ||
| import pkg_resources | ||
| from . import git_helpers | ||
| from .distutils_helpers import is_distutils_display_option | ||
| def _version_split(version): | ||
| """ | ||
| Split a version string into major, minor, and bugfix numbers. If any of | ||
| those numbers are missing the default is zero. Any pre/post release | ||
| modifiers are ignored. | ||
| Examples | ||
| ======== | ||
| >>> _version_split('1.2.3') | ||
| (1, 2, 3) | ||
| >>> _version_split('1.2') | ||
| (1, 2, 0) | ||
| >>> _version_split('1.2rc1') | ||
| (1, 2, 0) | ||
| >>> _version_split('1') | ||
| (1, 0, 0) | ||
| >>> _version_split('') | ||
| (0, 0, 0) | ||
| """ | ||
| parsed_version = pkg_resources.parse_version(version) | ||
| if hasattr(parsed_version, 'base_version'): | ||
| # New version parsing for setuptools >= 8.0 | ||
| if parsed_version.base_version: | ||
| parts = [int(part) | ||
| for part in parsed_version.base_version.split('.')] | ||
| else: | ||
| parts = [] | ||
| else: | ||
| parts = [] | ||
| for part in parsed_version: | ||
| if part.startswith('*'): | ||
| # Ignore any .dev, a, b, rc, etc. | ||
| break | ||
| parts.append(int(part)) | ||
| if len(parts) < 3: | ||
| parts += [0] * (3 - len(parts)) | ||
| # In principle a version could have more parts (like 1.2.3.4) but we only | ||
| # support <major>.<minor>.<micro> | ||
| return tuple(parts[:3]) | ||
| # This is used by setup.py to create a new version.py - see that file for | ||
| # details. Note that the imports have to be absolute, since this is also used | ||
| # by affiliated packages. | ||
| _FROZEN_VERSION_PY_TEMPLATE = """ | ||
| # Autogenerated by {packagetitle}'s setup.py on {timestamp!s} UTC | ||
| from __future__ import unicode_literals | ||
| import datetime | ||
| {header} | ||
| major = {major} | ||
| minor = {minor} | ||
| bugfix = {bugfix} | ||
| version_info = (major, minor, bugfix) | ||
| release = {rel} | ||
| timestamp = {timestamp!r} | ||
| debug = {debug} | ||
| astropy_helpers_version = "{ahver}" | ||
| try: | ||
| from ._compiler import compiler | ||
| except ImportError: | ||
| compiler = "unknown" | ||
| try: | ||
| from .cython_version import cython_version | ||
| except ImportError: | ||
| cython_version = "unknown" | ||
| """[1:] | ||
| _FROZEN_VERSION_PY_WITH_GIT_HEADER = """ | ||
| {git_helpers} | ||
| _packagename = "{packagename}" | ||
| _last_generated_version = "{verstr}" | ||
| _last_githash = "{githash}" | ||
| # Determine where the source code for this module | ||
| # lives. If __file__ is not a filesystem path then | ||
| # it is assumed not to live in a git repo at all. | ||
| if _get_repo_path(__file__, levels=len(_packagename.split('.'))): | ||
| version = update_git_devstr(_last_generated_version, path=__file__) | ||
| githash = get_git_devstr(sha=True, show_warning=False, | ||
| path=__file__) or _last_githash | ||
| else: | ||
| # The file does not appear to live in a git repo so don't bother | ||
| # invoking git | ||
| version = _last_generated_version | ||
| githash = _last_githash | ||
| """[1:] | ||
| _FROZEN_VERSION_PY_STATIC_HEADER = """ | ||
| version = "{verstr}" | ||
| githash = "{githash}" | ||
| """[1:] | ||
| def _get_version_py_str(packagename, version, githash, release, debug, | ||
| uses_git=True): | ||
| try: | ||
| from astropy_helpers import __version__ as ahver | ||
| except ImportError: | ||
| ahver = "unknown" | ||
| epoch = int(os.environ.get('SOURCE_DATE_EPOCH', time.time())) | ||
| timestamp = datetime.datetime.utcfromtimestamp(epoch) | ||
| major, minor, bugfix = _version_split(version) | ||
| if packagename.lower() == 'astropy': | ||
| packagetitle = 'Astropy' | ||
| else: | ||
| packagetitle = 'Astropy-affiliated package ' + packagename | ||
| header = '' | ||
| if uses_git: | ||
| header = _generate_git_header(packagename, version, githash) | ||
| elif not githash: | ||
| # _generate_git_header will already generate a new git has for us, but | ||
| # for creating a new version.py for a release (even if uses_git=False) | ||
| # we still need to get the githash to include in the version.py | ||
| # See https://github.com/astropy/astropy-helpers/issues/141 | ||
| githash = git_helpers.get_git_devstr(sha=True, show_warning=True) | ||
| if not header: # If _generate_git_header fails it returns an empty string | ||
| header = _FROZEN_VERSION_PY_STATIC_HEADER.format(verstr=version, | ||
| githash=githash) | ||
| return _FROZEN_VERSION_PY_TEMPLATE.format(packagetitle=packagetitle, | ||
| timestamp=timestamp, | ||
| header=header, | ||
| major=major, | ||
| minor=minor, | ||
| bugfix=bugfix, | ||
| ahver=ahver, | ||
| rel=release, debug=debug) | ||
| def _generate_git_header(packagename, version, githash): | ||
| """ | ||
| Generates a header to the version.py module that includes utilities for | ||
| probing the git repository for updates (to the current git hash, etc.) | ||
| These utilities should only be available in development versions, and not | ||
| in release builds. | ||
| If this fails for any reason an empty string is returned. | ||
| """ | ||
| loader = pkgutil.get_loader(git_helpers) | ||
| source = loader.get_source(git_helpers.__name__) or '' | ||
| source_lines = source.splitlines() | ||
| if not source_lines: | ||
| log.warn('Cannot get source code for astropy_helpers.git_helpers; ' | ||
| 'git support disabled.') | ||
| return '' | ||
| idx = 0 | ||
| for idx, line in enumerate(source_lines): | ||
| if line.startswith('# BEGIN'): | ||
| break | ||
| git_helpers_py = '\n'.join(source_lines[idx + 1:]) | ||
| verstr = version | ||
| new_githash = git_helpers.get_git_devstr(sha=True, show_warning=False) | ||
| if new_githash: | ||
| githash = new_githash | ||
| return _FROZEN_VERSION_PY_WITH_GIT_HEADER.format( | ||
| git_helpers=git_helpers_py, packagename=packagename, | ||
| verstr=verstr, githash=githash) | ||
| def generate_version_py(packagename, version, release=None, debug=None, | ||
| uses_git=True, srcdir='.'): | ||
| """Regenerate the version.py module if necessary.""" | ||
| try: | ||
| version_module = get_pkg_version_module(packagename) | ||
| try: | ||
| last_generated_version = version_module._last_generated_version | ||
| except AttributeError: | ||
| last_generated_version = version_module.version | ||
| try: | ||
| last_githash = version_module._last_githash | ||
| except AttributeError: | ||
| last_githash = version_module.githash | ||
| current_release = version_module.release | ||
| current_debug = version_module.debug | ||
| except ImportError: | ||
| version_module = None | ||
| last_generated_version = None | ||
| last_githash = None | ||
| current_release = None | ||
| current_debug = None | ||
| if release is None: | ||
| # Keep whatever the current value is, if it exists | ||
| release = bool(current_release) | ||
| if debug is None: | ||
| # Likewise, keep whatever the current value is, if it exists | ||
| debug = bool(current_debug) | ||
| package_srcdir = os.path.join(srcdir, *packagename.split('.')) | ||
| version_py = os.path.join(package_srcdir, 'version.py') | ||
| if (last_generated_version != version or current_release != release or | ||
| current_debug != debug): | ||
| if '-q' not in sys.argv and '--quiet' not in sys.argv: | ||
| log.set_threshold(log.INFO) | ||
| if is_distutils_display_option(): | ||
| # Always silence unnecessary log messages when display options are | ||
| # being used | ||
| log.set_threshold(log.WARN) | ||
| log.info('Freezing version number to {0}'.format(version_py)) | ||
| with open(version_py, 'w') as f: | ||
| # This overwrites the actual version.py | ||
| f.write(_get_version_py_str(packagename, version, last_githash, | ||
| release, debug, uses_git=uses_git)) | ||
| invalidate_caches() | ||
| if version_module: | ||
| imp.reload(version_module) | ||
| def get_pkg_version_module(packagename, fromlist=None): | ||
| """Returns the package's .version module generated by | ||
| `astropy_helpers.version_helpers.generate_version_py`. Raises an | ||
| ImportError if the version module is not found. | ||
| If ``fromlist`` is an iterable, return a tuple of the members of the | ||
| version module corresponding to the member names given in ``fromlist``. | ||
| Raises an `AttributeError` if any of these module members are not found. | ||
| """ | ||
| if not fromlist: | ||
| # Due to a historical quirk of Python's import implementation, | ||
| # __import__ will not return submodules of a package if 'fromlist' is | ||
| # empty. | ||
| # TODO: For Python 3.1 and up it may be preferable to use importlib | ||
| # instead of the __import__ builtin | ||
| return __import__(packagename + '.version', fromlist=['']) | ||
| else: | ||
| mod = __import__(packagename + '.version', fromlist=fromlist) | ||
| return tuple(getattr(mod, member) for member in fromlist) |
| # Autogenerated by Astropy-affiliated package astropy_helpers's setup.py on 2019-02-17 11:49:47 UTC | ||
| from __future__ import unicode_literals | ||
| import datetime | ||
| version = "3.1" | ||
| githash = "9f82aac6c2141b425e2d639560f7260189d90b54" | ||
| major = 3 | ||
| minor = 1 | ||
| bugfix = 0 | ||
| version_info = (major, minor, bugfix) | ||
| release = True | ||
| timestamp = datetime.datetime(2019, 2, 17, 11, 49, 47) | ||
| debug = False | ||
| astropy_helpers_version = "" | ||
| try: | ||
| from ._compiler import compiler | ||
| except ImportError: | ||
| compiler = "unknown" | ||
| try: | ||
| from .cython_version import cython_version | ||
| except ImportError: | ||
| cython_version = "unknown" |
| astropy-helpers Changelog | ||
| ************************* | ||
| 3.1 (2018-12-04) | ||
| ---------------- | ||
| - Added extensive documentation about astropy-helpers to the README.rst file. [#416] | ||
| - Fixed the compatibility of the build_docs command with Sphinx 1.8 and above. [#413] | ||
| - Removing deprecated test_helpers.py file. [#369] | ||
| - Removing ez_setup.py file and requiring setuptools 1.0 or later. [#384] | ||
| - Remove all sphinx components from ``astropy-helpers``. These are now replaced | ||
| by the ``sphinx-astropy`` package in conjunction with the ``astropy-theme-sphinx``, | ||
| ``sphinx-automodapi``, and ``numpydoc`` packages. [#368] | ||
| - openmp_helpers.py: Make add_openmp_flags_if_available() work for clang. | ||
| The necessary include, library, and runtime paths now get added to the C test code | ||
| used to determine if openmp works. | ||
| Autogenerator utility added ``openmp_enabled.is_openmp_enabled()`` | ||
| which can be called post build to determine state of OpenMP support. | ||
| [#382] | ||
| - Add version_info tuple to autogenerated version.py. Allows for simple | ||
| version checking, i.e. version_info > (2,0,1). [#385] | ||
| 3.0.2 (2018-06-01) | ||
| ------------------ | ||
| - Nothing changed. | ||
| 3.0.1 (2018-02-22) | ||
| ------------------ | ||
| - Nothing changed. | ||
| 3.0 (2018-02-09) | ||
| ---------------- | ||
| - Removing Python 2 support, including 2to3. Packages wishing to keep Python | ||
| 2 support should NOT update to this version. [#340] | ||
| - Removing deprecated _test_compat making astropy a hard dependency for | ||
| packages wishing to use the astropy tests machinery. [#314] | ||
| - Removing unused 'register' command since packages should be uploaded | ||
| with twine and get registered automatically. [#332] | ||
| 2.0.8 (2018-12-04) | ||
| ------------------ | ||
| - Fixed compatibility with Sphinx 1.8+. [#428] | ||
| - Fixed error that occurs when installing a package in an environment where | ||
| ``numpy`` is not already installed. [#404] | ||
| - Updated bundled version of sphinx-automodapi to v0.9. [#422] | ||
| - Updated bundled version of numpydoc to v0.8.0. [#423] | ||
| 2.0.7 (2018-06-01) | ||
| ------------------ | ||
| - Removing ez_setup.py file and requiring setuptools 1.0 or later. [#384] | ||
| 2.0.6 (2018-02-24) | ||
| ------------------ | ||
| - Avoid deprecation warning due to ``exclude=`` keyword in ``setup.py``. [#379] | ||
| 2.0.5 (2018-02-22) | ||
| ------------------ | ||
| - Fix segmentation faults that occurred when the astropy-helpers submodule | ||
| was first initialized in packages that also contained Cython code. [#375] | ||
| 2.0.4 (2018-02-09) | ||
| ------------------ | ||
| - Support dotted package names as namespace packages in generate_version_py. | ||
| [#370] | ||
| - Fix compatibility with setuptools 36.x and above. [#372] | ||
| - Fix false negative in add_openmp_flags_if_available when measuring code | ||
| coverage with gcc. [#374] | ||
| 2.0.3 (2018-01-20) | ||
| ------------------ | ||
| - Make sure that astropy-helpers 3.x.x is not downloaded on Python 2. [#362, #363] | ||
| - The bundled version of sphinx-automodapi has been updated to v0.7. [#365] | ||
| - Add --auto-use and --no-auto-use command-line flags to match the | ||
| ``auto_use`` configuration option, and add an alias | ||
| ``--use-system-astropy-helpers`` for ``--no-auto-use``. [#366] | ||
| 2.0.2 (2017-10-13) | ||
| ------------------ | ||
| - Added new helper function add_openmp_flags_if_available that can add | ||
| OpenMP compilation flags to a C/Cython extension if needed. [#346] | ||
| - Update numpydoc to v0.7. [#343] | ||
| - The function ``get_git_devstr`` now returns ``'0'`` instead of ``None`` when | ||
| no git repository is present. This allows generation of development version | ||
| strings that are in a format that ``setuptools`` expects (e.g. "1.1.3.dev0" | ||
| instead of "1.1.3.dev"). [#330] | ||
| - It is now possible to override generated timestamps to make builds | ||
| reproducible by setting the ``SOURCE_DATE_EPOCH`` environment variable [#341] | ||
| - Mark Sphinx extensions as parallel-safe. [#344] | ||
| - Switch to using mathjax instead of imgmath for local builds. [#342] | ||
| - Deprecate ``exclude`` parameter of various functions in setup_helpers since | ||
| it could not work as intended. Add new function ``add_exclude_packages`` to | ||
| provide intended behavior. [#331] | ||
| - Allow custom Sphinx doctest extension to recognize and process standard | ||
| doctest directives ``testsetup`` and ``doctest``. [#335] | ||
| 2.0.1 (2017-07-28) | ||
| ------------------ | ||
| - Fix compatibility with Sphinx <1.5. [#326] | ||
| 2.0 (2017-07-06) | ||
| ---------------- | ||
| - Add support for package that lies in a subdirectory. [#249] | ||
| - Removing ``compat.subprocess``. [#298] | ||
| - Python 3.3 is no longer supported. [#300] | ||
| - The 'automodapi' Sphinx extension (and associated dependencies) has now | ||
| been moved to a standalone package which can be found at | ||
| https://github.com/astropy/sphinx-automodapi - this is now bundled in | ||
| astropy-helpers under astropy_helpers.extern.automodapi for | ||
| convenience. Version shipped with astropy-helpers is v0.6. | ||
| [#278, #303, #309, #323] | ||
| - The ``numpydoc`` Sphinx extension has now been moved to | ||
| ``astropy_helpers.extern``. [#278] | ||
| - Fix ``build_docs`` error catching, so it doesn't hide Sphinx errors. [#292] | ||
| - Fix compatibility with Sphinx 1.6. [#318] | ||
| - Updating ez_setup.py to the last version before it's removal. [#321] | ||
| 1.3.1 (2017-03-18) | ||
| ------------------ | ||
| - Fixed the missing button to hide output in documentation code | ||
| blocks. [#287] | ||
| - Fixed bug when ``build_docs`` when running with the clean (-l) option. [#289] | ||
| - Add alternative location for various intersphinx inventories to fall back | ||
| to. [#293] | ||
| 1.3 (2016-12-16) | ||
| ---------------- | ||
| - ``build_sphinx`` has been deprecated in favor of the ``build_docs`` command. | ||
| [#246] | ||
| - Force the use of Cython's old ``build_ext`` command. A new ``build_ext`` | ||
| command was added in Cython 0.25, but it does not work with astropy-helpers | ||
| currently. [#261] | ||
| 1.2 (2016-06-18) | ||
| ---------------- | ||
| - Added sphinx configuration value ``automodsumm_inherited_members``. | ||
| If ``True`` this will include members that are inherited from a base | ||
| class in the generated API docs. Defaults to ``False`` which matches | ||
| the previous behavior. [#215] | ||
| - Fixed ``build_sphinx`` to recognize builds that succeeded but have output | ||
| *after* the "build succeeded." statement. This only applies when | ||
| ``--warnings-returncode`` is given (which is primarily relevant for Travis | ||
| documentation builds). [#223] | ||
| - Fixed ``build_sphinx`` the sphinx extensions to not output a spurious warning | ||
| for sphinx versions > 1.4. [#229] | ||
| - Add Python version dependent local sphinx inventories that contain | ||
| otherwise missing references. [#216] | ||
| - ``astropy_helpers`` now require Sphinx 1.3 or later. [#226] | ||
| 1.1.2 (2016-03-9) | ||
| ----------------- | ||
| - The CSS for the sphinx documentation was altered to prevent some text overflow | ||
| problems. [#217] | ||
| 1.1.1 (2015-12-23) | ||
| ------------------ | ||
| - Fixed crash in build with ``AttributeError: cython_create_listing`` with | ||
| older versions of setuptools. [#209, #210] | ||
| 1.1 (2015-12-10) | ||
| ---------------- | ||
| - The original ``AstropyTest`` class in ``astropy_helpers``, which implements | ||
| the ``setup.py test`` command, is deprecated in favor of moving the | ||
| implementation of that command closer to the actual Astropy test runner in | ||
| ``astropy.tests``. Now a dummy ``test`` command is provided solely for | ||
| informing users that they need ``astropy`` installed to run the tests | ||
| (however, the previous, now deprecated implementation is still provided and | ||
| continues to work with older versions of Astropy). See the related issue for | ||
| more details. [#184] | ||
| - Added a useful new utility function to ``astropy_helpers.utils`` called | ||
| ``find_data_files``. This is similar to the ``find_packages`` function in | ||
| setuptools in that it can be used to search a package for data files | ||
| (matching a pattern) that can be passed to the ``package_data`` argument for | ||
| ``setup()``. See the docstring to ``astropy_helpers.utils.find_data_files`` | ||
| for more details. [#42] | ||
| - The ``astropy_helpers`` module now sets the global ``_ASTROPY_SETUP_`` | ||
| flag upon import (from within a ``setup.py``) script, so it's not necessary | ||
| to have this in the ``setup.py`` script explicitly. If in doubt though, | ||
| there's no harm in setting it twice. Putting it in ``astropy_helpers`` | ||
| just ensures that any other imports that occur during build will have this | ||
| flag set. [#191] | ||
| - It is now possible to use Cython as a ``setup_requires`` build requirement, | ||
| and still build Cython extensions even if Cython wasn't available at the | ||
| beginning of the build processes (that is, is automatically downloaded via | ||
| setuptools' processing of ``setup_requires``). [#185] | ||
| - Moves the ``adjust_compiler`` check into the ``build_ext`` command itself, | ||
| so it's only used when actually building extension modules. This also | ||
| deprecates the stand-alone ``adjust_compiler`` function. [#76] | ||
| - When running the ``build_sphinx`` / ``build_docs`` command with the ``-w`` | ||
| option, the output from Sphinx is streamed as it runs instead of silently | ||
| buffering until the doc build is complete. [#197] | ||
| 1.0.7 (unreleased) | ||
| ------------------ | ||
| - Fix missing import in ``astropy_helpers/utils.py``. [#196] | ||
| 1.0.6 (2015-12-04) | ||
| ------------------ | ||
| - Fixed bug where running ``./setup.py build_sphinx`` could return successfully | ||
| even when the build was not successful (and should have returned a non-zero | ||
| error code). [#199] | ||
| 1.0.5 (2015-10-02) | ||
| ------------------ | ||
| - Fixed a regression in the ``./setup.py test`` command that was introduced in | ||
| v1.0.4. | ||
| 1.0.4 (2015-10-02) | ||
| ------------------ | ||
| - Fixed issue with the sphinx documentation css where the line numbers for code | ||
| blocks were not aligned with the code. [#179, #180] | ||
| - Fixed crash that could occur when trying to build Cython extension modules | ||
| when Cython isn't installed. Normally this still results in a failed build, | ||
| but was supposed to provide a useful error message rather than crash | ||
| outright (this was a regression introduced in v1.0.3). [#181] | ||
| - Fixed a crash that could occur on Python 3 when a working C compiler isn't | ||
| found. [#182] | ||
| - Quieted warnings about deprecated Numpy API in Cython extensions, when | ||
| building Cython extensions against Numpy >= 1.7. [#183, #186] | ||
| - Improved support for py.test >= 2.7--running the ``./setup.py test`` command | ||
| now copies all doc pages into the temporary test directory as well, so that | ||
| all test files have a "common root directory". [#189, #190] | ||
| 1.0.3 (2015-07-22) | ||
| ------------------ | ||
| - Added workaround for sphinx-doc/sphinx#1843, a but in Sphinx which | ||
| prevented descriptor classes with a custom metaclass from being documented | ||
| correctly. [#158] | ||
| - Added an alias for the ``./setup.py build_sphinx`` command as | ||
| ``./setup.py build_docs`` which, to a new contributor, should hopefully be | ||
| less cryptic. [#161] | ||
| - The fonts in graphviz diagrams now match the font of the HTML content. [#169] | ||
| - When the documentation is built on readthedocs.org, MathJax will be | ||
| used for math rendering. When built elsewhere, the "pngmath" | ||
| extension is still used for math rendering. [#170] | ||
| - Fix crash when importing astropy_helpers when running with ``python -OO`` | ||
| [#171] | ||
| - The ``build`` and ``build_ext`` stages now correctly recognize the presence | ||
| of C++ files in Cython extensions (previously only vanilla C worked). [#173] | ||
| 1.0.2 (2015-04-02) | ||
| ------------------ | ||
| - Various fixes enabling the astropy-helpers Sphinx build command and | ||
| Sphinx extensions to work with Sphinx 1.3. [#148] | ||
| - More improvement to the ability to handle multiple versions of | ||
| astropy-helpers being imported in the same Python interpreter session | ||
| in the (somewhat rare) case of nested installs. [#147] | ||
| - To better support high resolution displays, use SVG for the astropy | ||
| logo and linkout image, falling back to PNGs for browsers that | ||
| support it. [#150, #151] | ||
| - Improve ``setup_helpers.get_compiler_version`` to work with more compilers, | ||
| and to return more info. This will help fix builds of Astropy on less | ||
| common compilers, like Sun C. [#153] | ||
| 1.0.1 (2015-03-04) | ||
| ------------------ | ||
| - Released in concert with v0.4.8 to address the same issues. | ||
| 0.4.8 (2015-03-04) | ||
| ------------------ | ||
| - Improved the ``ah_bootstrap`` script's ability to override existing | ||
| installations of astropy-helpers with new versions in the context of | ||
| installing multiple packages simultaneously within the same Python | ||
| interpreter (e.g. when one package has in its ``setup_requires`` another | ||
| package that uses a different version of astropy-helpers. [#144] | ||
| - Added a workaround to an issue in matplotlib that can, in rare cases, lead | ||
| to a crash when installing packages that import matplotlib at build time. | ||
| [#144] | ||
| 1.0 (2015-02-17) | ||
| ---------------- | ||
| - Added new pre-/post-command hook points for ``setup.py`` commands. Now any | ||
| package can define code to run before and/or after any ``setup.py`` command | ||
| without having to manually subclass that command by adding | ||
| ``pre_<command_name>_hook`` and ``post_<command_name>_hook`` callables to | ||
| the package's ``setup_package.py`` module. See the PR for more details. | ||
| [#112] | ||
| - The following objects in the ``astropy_helpers.setup_helpers`` module have | ||
| been relocated: | ||
| - ``get_dummy_distribution``, ``get_distutils_*``, ``get_compiler_option``, | ||
| ``add_command_option``, ``is_distutils_display_option`` -> | ||
| ``astropy_helpers.distutils_helpers`` | ||
| - ``should_build_with_cython``, ``generate_build_ext_command`` -> | ||
| ``astropy_helpers.commands.build_ext`` | ||
| - ``AstropyBuildPy`` -> ``astropy_helpers.commands.build_py`` | ||
| - ``AstropyBuildSphinx`` -> ``astropy_helpers.commands.build_sphinx`` | ||
| - ``AstropyInstall`` -> ``astropy_helpers.commands.install`` | ||
| - ``AstropyInstallLib`` -> ``astropy_helpers.commands.install_lib`` | ||
| - ``AstropyRegister`` -> ``astropy_helpers.commands.register`` | ||
| - ``get_pkg_version_module`` -> ``astropy_helpers.version_helpers`` | ||
| - ``write_if_different``, ``import_file``, ``get_numpy_include_path`` -> | ||
| ``astropy_helpers.utils`` | ||
| All of these are "soft" deprecations in the sense that they are still | ||
| importable from ``astropy_helpers.setup_helpers`` for now, and there is | ||
| no (easy) way to produce deprecation warnings when importing these objects | ||
| from ``setup_helpers`` rather than directly from the modules they are | ||
| defined in. But please consider updating any imports to these objects. | ||
| [#110] | ||
| - Use of the ``astropy.sphinx.ext.astropyautosummary`` extension is deprecated | ||
| for use with Sphinx < 1.2. Instead it should suffice to remove this | ||
| extension for the ``extensions`` list in your ``conf.py`` and add the stock | ||
| ``sphinx.ext.autosummary`` instead. [#131] | ||
| 0.4.7 (2015-02-17) | ||
| ------------------ | ||
| - Fixed incorrect/missing git hash being added to the generated ``version.py`` | ||
| when creating a release. [#141] | ||
| 0.4.6 (2015-02-16) | ||
| ------------------ | ||
| - Fixed problems related to the automatically generated _compiler | ||
| module not being created properly. [#139] | ||
| 0.4.5 (2015-02-11) | ||
| ------------------ | ||
| - Fixed an issue where ah_bootstrap.py could blow up when astropy_helper's | ||
| version number is 1.0. | ||
| - Added a workaround for documentation of properties in the rare case | ||
| where the class's metaclass has a property of the same name. [#130] | ||
| - Fixed an issue on Python 3 where importing a package using astropy-helper's | ||
| generated version.py module would crash when the current working directory | ||
| is an empty git repository. [#114, #137] | ||
| - Fixed an issue where the "revision count" appended to .dev versions by | ||
| the generated version.py did not accurately reflect the revision count for | ||
| the package it belongs to, and could be invalid if the current working | ||
| directory is an unrelated git repository. [#107, #137] | ||
| - Likewise, fixed a confusing warning message that could occur in the same | ||
| circumstances as the above issue. [#121, #137] | ||
| 0.4.4 (2014-12-31) | ||
| ------------------ | ||
| - More improvements for building the documentation using Python 3.x. [#100] | ||
| - Additional minor fixes to Python 3 support. [#115] | ||
| - Updates to support new test features in Astropy [#92, #106] | ||
| 0.4.3 (2014-10-22) | ||
| ------------------ | ||
| - The generated ``version.py`` file now preserves the git hash of installed | ||
| copies of the package as well as when building a source distribution. That | ||
| is, the git hash of the changeset that was installed/released is preserved. | ||
| [#87] | ||
| - In smart resolver add resolution for class links when they exist in the | ||
| intersphinx inventory, but not the mapping of the current package | ||
| (e.g. when an affiliated package uses an astropy core class of which | ||
| "actual" and "documented" location differs) [#88] | ||
| - Fixed a bug that could occur when running ``setup.py`` for the first time | ||
| in a repository that uses astropy-helpers as a submodule: | ||
| ``AttributeError: 'NoneType' object has no attribute 'mkdtemp'`` [#89] | ||
| - Fixed a bug where optional arguments to the ``doctest-skip`` Sphinx | ||
| directive were sometimes being left in the generated documentation output. | ||
| [#90] | ||
| - Improved support for building the documentation using Python 3.x. [#96] | ||
| - Avoid error message if .git directory is not present. [#91] | ||
| 0.4.2 (2014-08-09) | ||
| ------------------ | ||
| - Fixed some CSS issues in generated API docs. [#69] | ||
| - Fixed the warning message that could be displayed when generating a | ||
| version number with some older versions of git. [#77] | ||
| - Fixed automodsumm to work with new versions of Sphinx (>= 1.2.2). [#80] | ||
| 0.4.1 (2014-08-08) | ||
| ------------------ | ||
| - Fixed git revision count on systems with git versions older than v1.7.2. | ||
| [#70] | ||
| - Fixed display of warning text when running a git command fails (previously | ||
| the output of stderr was not being decoded properly). [#70] | ||
| - The ``--offline`` flag to ``setup.py`` understood by ``ah_bootstrap.py`` | ||
| now also prevents git from going online to fetch submodule updates. [#67] | ||
| - The Sphinx extension for converting issue numbers to links in the changelog | ||
| now supports working on arbitrary pages via a new ``conf.py`` setting: | ||
| ``changelog_links_docpattern``. By default it affects the ``changelog`` | ||
| and ``whatsnew`` pages in one's Sphinx docs. [#61] | ||
| - Fixed crash that could result from users with missing/misconfigured | ||
| locale settings. [#58] | ||
| - The font used for code examples in the docs is now the | ||
| system-defined ``monospace`` font, rather than ``Minaco``, which is | ||
| not available on all platforms. [#50] | ||
| 0.4 (2014-07-15) | ||
| ---------------- | ||
| - Initial release of astropy-helpers. See `APE4 | ||
| <https://github.com/astropy/astropy-APEs/blob/master/APE4.rst>`_ for | ||
| details of the motivation and design of this package. | ||
| - The ``astropy_helpers`` package replaces the following modules in the | ||
| ``astropy`` package: | ||
| - ``astropy.setup_helpers`` -> ``astropy_helpers.setup_helpers`` | ||
| - ``astropy.version_helpers`` -> ``astropy_helpers.version_helpers`` | ||
| - ``astropy.sphinx`` - > ``astropy_helpers.sphinx`` | ||
| These modules should be considered deprecated in ``astropy``, and any new, | ||
| non-critical changes to those modules will be made in ``astropy_helpers`` | ||
| instead. Affiliated packages wishing to make use those modules (as in the | ||
| Astropy package-template) should use the versions from ``astropy_helpers`` | ||
| instead, and include the ``ah_bootstrap.py`` script in their project, for | ||
| bootstrapping the ``astropy_helpers`` package in their setup.py script. |
| Contributing to astropy-helpers | ||
| =============================== | ||
| The guidelines for contributing to ``astropy-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 ``astropy-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 | ||
| ``astropy-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 | ||
| ``astropy-helpers``) should be addressed in the github repository for that | ||
| package. |
| Copyright (c) 2014, 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. |
| # 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. |
| include README.rst | ||
| include CHANGES.rst | ||
| include LICENSE.rst | ||
| recursive-include licenses * | ||
| include ah_bootstrap.py | ||
| exclude *.pyc *.o | ||
| prune build | ||
| prune astropy_helpers/tests |
| astropy-helpers | ||
| =============== | ||
| .. image:: https://travis-ci.org/astropy/astropy-helpers.svg | ||
| :target: https://travis-ci.org/astropy/astropy-helpers | ||
| .. image:: https://ci.appveyor.com/api/projects/status/rt9161t9mhx02xp7/branch/master?svg=true | ||
| :target: https://ci.appveyor.com/project/Astropy/astropy-helpers | ||
| .. image:: https://codecov.io/gh/astropy/astropy-helpers/branch/master/graph/badge.svg | ||
| :target: https://codecov.io/gh/astropy/astropy-helpers | ||
| **Warning:** Please note that version ``v3.0`` and later of ``astropy-helpers`` | ||
| requires Python 3.5 or later. If you wish to maintain Python 2 support | ||
| for your package that uses ``astropy-helpers``, then do not upgrade the | ||
| helpers to ``v3.0+``. We will still provide Python 2.7 compatible | ||
| releases on the ``v2.0.x`` branch during the lifetime of the ``astropy`` | ||
| core package LTS of ``v2.0.x``. | ||
| About | ||
| ----- | ||
| This project provides a Python package, ``astropy_helpers``, which includes | ||
| many build, installation, and documentation-related tools used by the Astropy | ||
| project, but packaged separately for use by other projects that wish to | ||
| leverage this work. The motivation behind this package and details of its | ||
| implementation are in the accepted | ||
| `Astropy Proposal for Enhancement (APE) 4 <https://github.com/astropy/astropy-APEs/blob/master/APE4.rst>`_. | ||
| ``astropy_helpers`` includes a special "bootstrap" module called | ||
| ``ah_bootstrap.py`` which is intended to be used by a project's setup.py in | ||
| order to ensure that the ``astropy_helpers`` package is available for | ||
| build/installation. | ||
| As described in `APE4 <https://github.com/astropy/astropy-APEs/blob/master/APE4.rst>`_, the version | ||
| numbers for ``astropy_helpers`` follow the corresponding major/minor version of | ||
| the `astropy core package <http://www.astropy.org/>`_, but with an independent | ||
| sequence of micro (bugfix) version numbers. Hence, the initial release is 0.4, | ||
| in parallel with Astropy v0.4, which will be the first version of Astropy to | ||
| use ``astropy-helpers``. | ||
| For examples of how to implement ``astropy-helpers`` in a project, | ||
| see the ``setup.py`` and ``setup.cfg`` files of the | ||
| `Affiliated package template <https://github.com/astropy/package-template>`_. | ||
| What does astropy-helpers provide? | ||
| ---------------------------------- | ||
| Astropy-helpers' big-picture purpose is to provide customization to Python's | ||
| packaging infrastructure process in ways that the Astropy Project has found to | ||
| help simplifying the developint and releasing packages. This is primarily | ||
| build around ``setup.py`` commands, as outlined below, as well as code to help | ||
| manage version numbers and better control the build process of larger packages. | ||
| Custom setup.py commands | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| The main part of astropy-helpers is to provide customized setuptools commands. | ||
| For example, in a package that uses astropy-helpers, the following command | ||
| will be available:: | ||
| python setup.py build_docs | ||
| and this command is implemented in astropy-helpers. To use the custom | ||
| commands described here, add:: | ||
| from astropy_helpers.setup_helpers import register_commands | ||
| to your ``setup.py`` file, then do:: | ||
| cmdclassd = register_commands(NAME, VERSION, RELEASE) | ||
| where ``NAME`` is the name of your package, ``VERSION`` is the full version | ||
| string, and ``RELEASE`` is a boolean value indicating whether the version is | ||
| a stable released version (``True``) or a developer version (``False``). | ||
| Finally, pass ``cmdclassd`` to the ``setup`` function:: | ||
| setup(..., | ||
| cmdclass=cmdclassd) | ||
| The commands we provide or customize are: | ||
| **python setup.py test** | ||
| This command will automatically build the package, install it to a temporary | ||
| directory, and run the tests using `pytest <http://pytest.org/>`_ on this | ||
| installed version. Note that the bulk of this command is actually defined | ||
| in ``astropy.tests.command.AstropyTest``, because that allows the test | ||
| machinery to operate outside a setuptools command. This, here we | ||
| simply define the custom | ||
| setuptools command. | ||
| **python setup.py sdist** | ||
| We redefine ``sdist`` to use the version from distutils rather than from | ||
| setuptools, as the setuptools version requires duplication of information | ||
| in ``MANIFEST.in``. | ||
| **python setup.py build_docs** | ||
| This command will automatically build the package, then run sphinx to build | ||
| the documentation. This makes development much easier because it ensures | ||
| sphinx extensions that use the package's code to make documentation are | ||
| actually using the in-development version of the code. Sphinx itself | ||
| provides a custom setuptools command, which we | ||
| expand with the following options: | ||
| * ``-w``: set the return code to 1 if there are any warnings during the build | ||
| process. | ||
| * ``-l``: completely clean previous builds, including files generated by | ||
| the sphinx-automodapi package (which creates API pages for different | ||
| functions/classes). | ||
| * ``-n``: disable the intersphinx option. | ||
| * ``-o``: open the documentation in a browser if a build finishes successfully. | ||
| In addition, ``build_docs`` will automatically download and temporarily install | ||
| sphinx-astropy (which is a meta-package that | ||
| provides standardized configuration and documentation dependencies for astropy | ||
| packages) if it isn't already installed. Temporary installation means that the | ||
| package will be installed into an ``.eggs`` directory in the current working | ||
| directory, and it will only be available for the duration of the call to | ||
| ``build_docs``. | ||
| **python setup.py build_ext** | ||
| This is also used when running ``build`` or ``install``. We add several features | ||
| compared to the default ``build_ext`` command: | ||
| * For packages with C/Cython extensions, we create a ``packagename._compiler`` | ||
| submodule that contains information about the compilers used. | ||
| * Packages that need to build C extensions using the Numpy C API, we allow | ||
| those packages to define the include path as ``'numpy'`` as opposed to having | ||
| to import Numpy and call ``get_include``. The goal is to solve the issue that | ||
| if one has to import Numpy to define extensions, then Numpy has to be | ||
| installed/available before the package is installed, which means that one | ||
| needs to install Numpy in a separate installation step. | ||
| * We detect broken compilers and replace them with other compilers on-the-fly | ||
| unless the compiler is explicitly specified with the ``CC`` environment | ||
| variable. | ||
| * If Cython is not installed, then we automatically check for generated C files | ||
| (which are normally present in the stable releases) and give a nice error | ||
| if these are not found. | ||
| Version helpers | ||
| ^^^^^^^^^^^^^^^^ | ||
| Another piece of functionality we provide in astropy-helpers is the ability | ||
| to generate a ``packagename.version`` file that includes functions that | ||
| automatically set the version string for developer versions, to e.g. | ||
| ``3.2.dev22213`` so that each developer version has a unique number (although | ||
| note that branches an equal number of commits away from the master branch will | ||
| share the same version number). To use this, import:: | ||
| from astropy_helpers.git_helpers import get_git_devstr | ||
| in your ``setup.py`` file, and you will then be able to use:: | ||
| VERSION += get_git_devstr() | ||
| where ``VERSION`` is a version string without any developer version suffix. | ||
| We then also provide a function that generates a ``version.py`` file inside your | ||
| package (which can then be imported as ``packagename.version``) that contains | ||
| variables such as ``major``, ``minor``, and ``bugfix``, as well as | ||
| ``version_info`` (a tuple of the previous three values), a ``release`` flag that | ||
| indicates whether we are using a stable release, and several other complementary | ||
| variables. To use this, import:: | ||
| from astropy_helpers.version_helpers import generate_version_py | ||
| in your ``setup.py`` file, and call:: | ||
| generate_version_py(NAME, VERSION, RELEASE, uses_git=not RELEASE) | ||
| where ``NAME`` is the name of your package, ``VERSION`` is the full version string | ||
| (including any developer suffix), ``RELEASE`` indicates whether the version is a | ||
| stable or developer version, and ``uses_git`` indicates whether we are in a git | ||
| repository (using ``not RELEASE`` is sensible since git is not available in a | ||
| stable release). | ||
| Collecting package information | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| The ``setup`` function from setuptools can take a number of options that indicate | ||
| for example what extensions to build, and what package data to include. However, | ||
| for large packages this can become cumbersome. We therefore provide a mechanism | ||
| for defining extensions and package data inside individual sub-packages. To do | ||
| this, you can create ``setup_package.py`` files anywhere in your package, and | ||
| these files can include one or more of the following functions: | ||
| * ``get_package_data``: | ||
| This function, if defined, should return a dictionary mapping the name of | ||
| the subpackage(s) that need package data to a list of data file paths | ||
| (possibly including wildcards) relative to the path of the package's source | ||
| code. e.g. if the source distribution has a needed data file | ||
| ``astropy/wcs/tests/data/3d_cd.hdr``, this function should return | ||
| ``{'astropy.wcs.tests':['data/3d_cd.hdr']}``. See the ``package_data`` | ||
| option of the :func:`distutils.core.setup` function. | ||
| It is recommended that all such data be in a directory named ``data`` inside | ||
| the package within which it is supposed to be used. This package data | ||
| should be accessed via the ``astropy.utils.data.get_pkg_data_filename`` and | ||
| ``astropy.utils.data.get_pkg_data_fileobj`` functions. | ||
| * ``get_extensions``: | ||
| This provides information for building C or Cython extensions. If defined, | ||
| it should return a list of ``distutils.core.Extension`` objects. | ||
| * ``get_build_options``: | ||
| This function allows a package to add extra build options. It | ||
| should return a list of tuples, where each element has: | ||
| - *name*: The name of the option as it would appear on the | ||
| commandline or in the ``setup.cfg`` file. | ||
| - *doc*: A short doc string for the option, displayed by | ||
| ``setup.py build --help``. | ||
| - *is_bool* (optional): When `True`, the option is a boolean | ||
| option and doesn't have an associated value. | ||
| Once an option has been added, its value can be looked up using | ||
| ``astropy_helpers.setup_helpers.get_distutils_build_option``. | ||
| * ``get_external_libraries``: | ||
| This function declares that the package uses libraries that are | ||
| included in the astropy distribution that may also be distributed | ||
| elsewhere on the users system. It should return a list of library | ||
| names. For each library, a new build option is created, | ||
| ``'--use-system-X'`` which allows the user to request to use the | ||
| system's copy of the library. The package would typically call | ||
| ``astropy_helpers.setup_helpers.use_system_library`` from its | ||
| ``get_extensions`` function to determine if the package should use | ||
| the system library or the included one. | ||
| * ``get_entry_points()``: | ||
| This function can returns a dict formatted as required by | ||
| the ``entry_points`` argument to ``setup()``. | ||
| With these files in place, you can then add the following to your ``setup.py`` | ||
| file:: | ||
| from astropy_helpers.setup_helpers import get_package_info | ||
| ... | ||
| package_info = get_package_info() | ||
| ... | ||
| setup(..., **package_info) | ||
| OpenMP helpers | ||
| ^^^^^^^^^^^^^^ | ||
| We provide a helper function ``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 astropy_helpers.openmp_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] | ||
| Using astropy-helpers | ||
| --------------------- | ||
| The easiest way to get set up with astropy-helpers in a new package is to use | ||
| the `package-template <http://docs.astropy.org/projects/package-template>`_ | ||
| that we provide. This template is specifically designed for use with the helpers, | ||
| so using it avoids some of the tedium of setting up the heleprs. | ||
| However, we now go through the steps of adding astropy-helpers | ||
| as a submodule to a package in case you wish to do so manually. First, add | ||
| astropy-helpers as a submodule at the root of your repository:: | ||
| git submodule add git://github.com/astropy/astropy-helpers astropy_helpers | ||
| Then go inside the submodule and check out a stable version of astropy-helpers. | ||
| You can see the available versions by running:: | ||
| $ cd astropy_helpers | ||
| $ git tag | ||
| ... | ||
| v2.0.6 | ||
| v2.0.7 | ||
| ... | ||
| v3.0.1 | ||
| v3.0.2 | ||
| If you want to support Python 2, pick the latest v2.0.x version (in the above | ||
| case ``v2.0.7``) and if you don't need to support Python 2, just pick the latest | ||
| stable version (in the above case ``v3.0.2``). Check out this version with e.g.:: | ||
| $ git checkout v3.0.2 | ||
| Then go back up to the root of your repository and copy the ``ah_bootstrap.py`` | ||
| file from the submodule to the root of your repository:: | ||
| $ cd .. | ||
| $ cp astropy_helpers/ah_bootstrap.py . | ||
| Finally, add:: | ||
| import ah_bootstrap | ||
| at the top of your ``setup.py`` file. This will ensure that ``astropy_helpers`` | ||
| is now available to use in your ``setup.py`` file. Finally, add then commit your | ||
| changes:: | ||
| git add astropy_helpers ah_bootstrap.py setup.py | ||
| git commit -m "Added astropy-helpers" | ||
| Updating astropy-helpers | ||
| ------------------------ | ||
| If you would like the Astropy team to automatically open pull requests to update | ||
| astropy-helpers in your package, then see the instructions `here | ||
| <https://github.com/astropy/astropy-procedures/blob/master/update-packages/README.md>`_. | ||
| To instead update astropy-helpers manually, go inside the submodule and do:: | ||
| cd astropy_helpers | ||
| git fetch origin | ||
| Then checkout the version you want to use, e.g.:: | ||
| git checkout v3.0.3 | ||
| Go back up to the root of the repository and update the ``ah_bootstap.py`` file | ||
| too, then add your changes:: | ||
| cp astropy_helpers/ah_bootstrap.py . | ||
| git add astropy_helpers ah_bootstrap.py | ||
| git commit ... |
| [tool:pytest] | ||
| norecursedirs = | ||
| .tox | ||
| astropy_helpers/tests/package_template | ||
| python_functions = test_ |
| #!/usr/bin/env python | ||
| # Licensed under a 3-clause BSD style license - see LICENSE.rst | ||
| import ah_bootstrap | ||
| import pkg_resources | ||
| from setuptools import setup | ||
| from astropy_helpers.setup_helpers import (register_commands, get_package_info, | ||
| add_exclude_packages) | ||
| from astropy_helpers.version_helpers import generate_version_py | ||
| NAME = 'astropy_helpers' | ||
| VERSION = '3.1' | ||
| RELEASE = 'dev' not in VERSION | ||
| generate_version_py(NAME, VERSION, RELEASE, False, uses_git=not RELEASE) | ||
| # Use the updated version including the git rev count | ||
| from astropy_helpers.version import version as VERSION | ||
| add_exclude_packages(['astropy_helpers.tests']) | ||
| cmdclass = register_commands(NAME, VERSION, RELEASE) | ||
| # This package actually doesn't use the Astropy test command | ||
| del cmdclass['test'] | ||
| setup( | ||
| name=pkg_resources.safe_name(NAME), # astropy_helpers -> astropy-helpers | ||
| version=VERSION, | ||
| description='Utilities for building and installing Astropy, Astropy ' | ||
| 'affiliated packages, and their respective documentation.', | ||
| author='The Astropy Developers', | ||
| author_email='astropy.team@gmail.com', | ||
| license='BSD', | ||
| url=' https://github.com/astropy/astropy-helpers', | ||
| long_description=open('README.rst').read(), | ||
| classifiers=[ | ||
| 'Development Status :: 5 - Production/Stable', | ||
| 'Intended Audience :: Developers', | ||
| 'Framework :: Setuptools Plugin', | ||
| 'Framework :: Sphinx :: Extension', | ||
| 'Framework :: Sphinx :: Theme', | ||
| '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' | ||
| ], | ||
| cmdclass=cmdclass, | ||
| python_requires='>=3.5', | ||
| zip_safe=False, | ||
| **get_package_info() | ||
| ) |
Sorry, the diff of this file is too big to display
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
1582782
-16.59%118
-19.73%5574
-51.42%