diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml new file mode 100644 index 00000000..4abd3eb0 --- /dev/null +++ b/.github/workflows/build_wheels.yml @@ -0,0 +1,155 @@ +name: Wheels + +on: [ push, pull_request ] + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + if: ${{ startsWith(github.ref, 'refs/tags/') || contains(github.event.head_commit.message, '[pypi]') }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ macos-11, macos-12, macos-13, flyci-macos-large-latest-m2, macos-14, ubuntu-latest, windows-latest ] + + steps: + - uses: awvwgk/setup-fortran@main + if: matrix.os == 'windows-latest' + id: setup-fortran + with: + compiler: gcc + version: 11 + + - run: ln -s $(which gfortran-11) /usr/local/bin/gfortran + if: matrix.os != 'windows-latest' + + - run: gfortran --version + + - uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: actions/setup-python@v5 + + - name: Install cibuildwheel + run: python -m pip install cibuildwheel==2.18.1 + + - name: Build macos-13 wheels + if: matrix.os == 'macos-13' || matrix.os == 'macos-13-xlarge' || matrix.os == 'flyci-macos-large-latest-m2' + env: + MACOSX_DEPLOYMENT_TARGET: 13 + CIBW_BUILD: cp311-* + CIBW_SKIP: pp* + CIBW_BUILD_VERBOSITY: 1 + run: python -m cibuildwheel --output-dir wheelhouse + + - name: Build macos-12 wheels + if: matrix.os == 'macos-12' + env: + MACOSX_DEPLOYMENT_TARGET: 12 + CIBW_BUILD: cp311-* + CIBW_SKIP: pp* + CIBW_BUILD_VERBOSITY: 1 + run: python -m cibuildwheel --output-dir wheelhouse + + - name: Build macos-11 wheels + if: matrix.os == 'macos-11' + env: + # all cp3xx, since old macs seem to only use osx 11+ builds if this is set not "none" + # see consistency with get_tag() in setup.py + MACOSX_DEPLOYMENT_TARGET: 11 + CIBW_SKIP: pp* + CIBW_BUILD_VERBOSITY: 1 + run: python -m cibuildwheel --output-dir wheelhouse + + - name: Build wheels + if: matrix.os == 'macos-14' || matrix.os == 'ubuntu-latest' || matrix.os == 'windows-latest' + env: + MACOSX_DEPLOYMENT_TARGET: 14 + CIBW_BUILD: cp311-* + CIBW_SKIP: pp* *-win32 *-manylinux_i686 *musllinux* + CIBW_BUILD_VERBOSITY: 1 + run: python -m cibuildwheel --output-dir wheelhouse + + - uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.os }} + path: | + wheelhouse/*.whl + + build-sdist-and-upload: + runs-on: ubuntu-latest + needs: [ 'build_wheels' ] + environment: wheels + if: github.repository_owner == 'cmbant' + permissions: + id-token: write + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + cache: pip + cache-dependency-path: "setup.py" + + - name: Install dependencies + run: | + python -m pip install -U pip + python -m pip install -U build twine + + - name: Download wheels from build artifacts + uses: actions/download-artifact@v4 + with: + pattern: wheels-* + merge-multiple: true + path: dist-wheels/ + + - name: Build package + run: | + python -m build --sdist + twine check --strict dist/* + twine check --strict dist-wheels/* + + - name: Publish wheels to PyPI Test + if: ${{ !startsWith(github.ref, 'refs/tags/') }} + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + packages-dir: dist-wheels/ + + - name: Publish sdist to PyPI Test + if: ${{ !startsWith(github.ref, 'refs/tags/') }} + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + - name: Publish wheels to PyPI + if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: dist-wheels/ + + - name: Publish sdist to PyPI + if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: pypa/gh-action-pypi-publish@release/v1 + + test_wheels: + name: Test wheels on ${{ matrix.os }} + if: contains(github.event.head_commit.message, '[testpypi]') + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ macos-11, macos-12, macos-13, flyci-macos-large-latest-m2, macos-14, ubuntu-latest, windows-latest ] + + steps: + - uses: actions/setup-python@v5 + + - name: install + run: python -m pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ camb + + - name: test + run: python -m unittest camb.tests.camb_test diff --git a/.travis.yml b/.travis.yml index d0808e61..60e6f7bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -88,16 +88,3 @@ script: after_failure: - test $TRAVIS_PULL_REQUEST == "false" && test $PYPI_DIST == "true" && test $TRAVIS_REPO_SLUG == "cmbant/CAMB" && [ -d fortran/testfiles ] && bash fortran/tests/upload_tests.sh -before_deploy: - - pushd fortran; make clean delete; popd - -deploy: - - provider: pypi - distributions: sdist - username: "__token__" - password: $PYPI_PASSWORD - on: - branch: master - repo: cmbant/CAMB - tags: true - condition: $PYPI_DIST = true diff --git a/README.rst b/README.rst index 38773749..8e0a601f 100644 --- a/README.rst +++ b/README.rst @@ -39,9 +39,9 @@ Then install using:: pip install -e ./CAMB [--user] -You will need gfortran 6 or higher installed to compile. Binary files for Windows are also provided, so these are used instead if no -gfortran installation is found on Windows machines. If you have gfortran installed, "python setup.py make" -(and other standard setup commands) will build the Fortran library on all systems (including Windows without directly using a Makefile). +You will need gfortran 6 or higher installed to compile (usually included with gcc by default). +If you have gfortran installed, "python setup.py make" (and other standard setup commands) will build the Fortran +library on all systems (including Windows without directly using a Makefile). The python wrapper provides a module called "camb" documented in the Python `CAMB documentation `_. diff --git a/camb/__init__.py b/camb/__init__.py index 8f66592b..54450a67 100644 --- a/camb/__init__.py +++ b/camb/__init__.py @@ -7,7 +7,7 @@ __author__ = "Antony Lewis" __contact__ = "antony at cosmologist dot info" __url__ = "https://camb.readthedocs.io" -__version__ = "1.5.4" +__version__ = "1.5.5" from . import baseconfig diff --git a/camb/cambdll.dll b/camb/cambdll.dll deleted file mode 100644 index d7fbe665..00000000 Binary files a/camb/cambdll.dll and /dev/null differ diff --git a/camb/sources.py b/camb/sources.py index 373b75a5..0314d1bb 100644 --- a/camb/sources.py +++ b/camb/sources.py @@ -57,7 +57,7 @@ def set_table(self, z, W, bias_z=None, k_bias=None, bias_kz=None): :param z: array of redshift values (monotonically increasing) :param W: array of window function values. It must be well enough sampled to smoothly cubic-spline interpolate :param bias_z: optional array of bias values at each z for scale-independent bias - :param k_bias: optional array of k values for bias + :param k_bias: optional array of k values for bias (Mpc^-1) :param bias_kz: optional 2D contiguous array for space-dependent bias(k, z). Must ensure range of k is large enough to cover required values. diff --git a/camb/tests/camb_test.py b/camb/tests/camb_test.py index cfc6b41e..03b57edb 100644 --- a/camb/tests/camb_test.py +++ b/camb/tests/camb_test.py @@ -13,7 +13,7 @@ from camb import model, correlations, bbn, dark_energy, initialpower from camb.baseconfig import CAMBParamRangeError, CAMBValueError -fast = 'ci fast' in os.getenv("TRAVIS_COMMIT_MESSAGE", "") +fast = 'ci fast' in os.getenv("TRAVIS_COMMIT_MESSAGE", "") or os.getenv("GITHUB_ACTIONS") class CambTest(unittest.TestCase): diff --git a/docs/source/fortran_compilers.rst b/docs/source/fortran_compilers.rst index f90b557b..311d178a 100644 --- a/docs/source/fortran_compilers.rst +++ b/docs/source/fortran_compilers.rst @@ -45,7 +45,7 @@ This includes in Jupyter notebooks; just re-start the kernel or use:: import IPython IPython.Application.instance().kernel.do_shutdown(True) -If you want to automamatically rebuild the library from Jupyter you can do something like this:: +If you want to automatically rebuild the library from Jupyter you can do something like this:: import subprocess import sys diff --git a/docs/source/index.rst b/docs/source/index.rst index 8af74a21..426a710e 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -21,17 +21,17 @@ If you want to work on the code from `GitHub `_, pip install -e ./CAMB [--user] You will need ifort or gfortran 6 or higher installed (and on your path) to compile; see :ref:`fortran-compilers` for -compiler installation details if needed. A compiled library for Windows is also provided, and is used if no -gfortran installation is found on Windows machines. If you have gfortran installed, "python setup.py make" will build +compiler installation details if needed. If you have gfortran installed, "python setup.py make" will build the Fortran library on all systems (including Windows without directly using a Makefile), and can be used to update a source installation after changes or pulling an updated version. +The standard pip installation includes binary pre-compiled code, so no need for a Fortran compiler +(unless you want to use custom sources/symbolic compilation features). Anaconda users can also install from conda-forge, best making a new clean environment using:: - conda create -n camb -c conda-forge python=3.9 camb + conda create -n camb -c conda-forge python=3.11 camb activate camb -with no need for a Fortran compiler (unless you want to use custom sources/symbolic compilation features). Check that conda installs the latest version, if not try installing in a new clean conda environment as above. diff --git a/fortran/config.f90 b/fortran/config.f90 index 70d5ee6c..7f56d42e 100644 --- a/fortran/config.f90 +++ b/fortran/config.f90 @@ -3,7 +3,7 @@ module config use constants, only: const_twopi implicit none - character(LEN=*), parameter :: version = '1.5.4' + character(LEN=*), parameter :: version = '1.5.5' integer :: FeedbackLevel = 0 !if >0 print out useful information about the model diff --git a/pyproject.toml b/pyproject.toml index f4030e80..64dd5822 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,11 +8,11 @@ authors = [ { name = "Antony Lewis" }, ] description = "Code for Anisotropies in the Microwave Background" -keywords= ['cosmology', 'CAMB', 'CMB'] +keywords = ['cosmology', 'CAMB', 'CMB'] readme = "docs/README_pypi.rst" license = { file = "LICENCE.txt" } dynamic = ["version"] -requires-python = ">=3.6.0" +requires-python = ">=3.7.0" classifiers = [ "Development Status :: 5 - Production/Stable", "Operating System :: OS Independent", @@ -20,7 +20,6 @@ classifiers = [ "Intended Audience :: Science/Research", "Topic :: Scientific/Engineering :: Astronomy", "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", @@ -46,4 +45,4 @@ Tracker = "https://github.com/cmbant/camb/issues" Licensing = "https://github.com/cmbant/camb/blob/master/LICENCE.txt" [tool.setuptools.dynamic] -version = {attr = "camb.__version__"} +version = { attr = "camb.__version__" } \ No newline at end of file diff --git a/setup.py b/setup.py index 874592c7..752ad950 100644 --- a/setup.py +++ b/setup.py @@ -3,10 +3,12 @@ import os import shutil from typing import Any -from setuptools import setup +from setuptools import setup, Command, Extension +from setuptools.command.build_ext import build_ext from setuptools.command.build_py import build_py from setuptools.command.develop import develop -from setuptools import Command +from setuptools.command.install import install +from wheel.bdist_wheel import bdist_wheel as _bdist_wheel file_dir = os.path.abspath(os.path.dirname(__file__)) os.chdir(file_dir) @@ -19,6 +21,7 @@ else: DLLNAME = 'camblib.so' + def get_forutils(): fpath = os.getenv('FORUTILSPATH') @@ -177,7 +180,7 @@ def make_library(cluster=False): get_forutils() print("Compiling source...") subprocess.call("make python PYCAMB_OUTPUT_DIR=%s/camb/ CLUSTER_SAFE=%d" % - (pycamb_path, int(cluster)), shell=True) + (pycamb_path, int(cluster if not os.getenv("GITHUB_ACTIONS") else 1)), shell=True) subprocess.call("chmod 755 %s" % lib_file, shell=True) if not os.path.isfile(os.path.join(pycamb_path, 'camb', DLLNAME)): @@ -246,12 +249,41 @@ def run(self): subprocess.call("make clean", shell=True, cwd=os.path.join(file_dir, 'fortran')) +class BDistWheelNonPure(_bdist_wheel): + def finalize_options(self): + super().finalize_options() + self.root_is_pure = False + + def get_tag(self): + _, _, plat = super().get_tag() + if "osx_11" in plat: + return _, _, plat + return "py3", "none", plat + + +class InstallPlatlib(install): + def finalize_options(self): + super().finalize_options() + if self.distribution.has_ext_modules(): + self.install_lib = self.install_platlib + + +class BuildExtCommand(build_ext): + """Ensure built extensions are added to the correct path in the wheel.""" + + def run(self): + pass + + if __name__ == "__main__": setup(name=os.getenv('CAMB_PACKAGE_NAME', 'camb'), zip_safe=False, cmdclass={'build_py': SharedLibrary, 'build_cluster': SharedLibraryCluster, 'make': MakeLibrary, 'make_cluster': MakeLibraryCluster, 'clean': CleanLibrary, - 'develop': DevelopLibrary, 'develop_cluster': DevelopLibraryCluster}, + 'develop': DevelopLibrary, 'develop_cluster': DevelopLibraryCluster, + 'bdist_wheel': BDistWheelNonPure, 'install': InstallPlatlib, + "build_ext": BuildExtCommand}, + ext_modules=[Extension("camb.camblib", [])], packages=['camb', 'camb.tests'], platforms="any", package_data={'camb': [DLLNAME, 'HighLExtrapTemplate_lenspotentialCls.dat',