diff --git a/.github/environment.yml b/.github/environment.yml index 262417a0e..337a5e931 100644 --- a/.github/environment.yml +++ b/.github/environment.yml @@ -25,6 +25,7 @@ dependencies: - wheel # for classy - cython < 3 # for classy - pip: + - scipy<1.14 - classy # Note the benchmarks were generated with 2.9.4 - isitgr # Note the benchmarks were generated with 1.0.2 - velocileptors @ git+https://github.com/sfschen/velocileptors diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 509e653a6..da5ebc251 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,35 +1,111 @@ -name: Upload to PyPI +name: Build and publish to PyPI on: release: types: [published] + workflow_dispatch: jobs: - Upload: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + cibw_archs: x86_64 + - os: macos-latest + cibw_archs: arm64 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build wheels + uses: pypa/cibuildwheel@v3.3.1 + env: + CIBW_ARCHS: ${{ matrix.cibw_archs }} + + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }}-${{ matrix.cibw_archs }} + path: ./wheelhouse/*.whl + + build_sdist: + name: Build source distribution runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install build dependencies + run: python -m pip install --upgrade pip build + + - name: Build sdist + run: python -m build --sdist + + - name: Upload sdist + uses: actions/upload-artifact@v4 + with: + name: cibw-sdist + path: dist/*.tar.gz + + publish_testpypi: + name: Publish to TestPyPI + needs: [build_wheels, build_sdist] + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' + environment: + name: testpypi + url: https://test.pypi.org/p/pyccl + permissions: + contents: read + id-token: write + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + pattern: cibw-* + path: dist + merge-multiple: true + + - name: Publish to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + publish: + name: Publish to PyPI + needs: [build_wheels, build_sdist] + runs-on: ubuntu-latest + if: github.event_name == 'release' + environment: + name: pypi + url: https://pypi.org/p/pyccl permissions: contents: read - id-token: write # Required for trusted publishing + id-token: write steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup python - uses: actions/setup-python@v5 - with: - python-version: '3.10' - - - name: Install build dependencies - run: | - python -m pip install --upgrade pip - python -m pip install build - - - name: Build source tarball only - run: | - python -m build --sdist - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.PYPI_PYCCL_UPLOAD }} + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + pattern: cibw-* + path: dist + merge-multiple: true + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/cmake/Modules/BuildFFTW.cmake b/cmake/Modules/BuildFFTW.cmake index fafcb2eb7..198221737 100644 --- a/cmake/Modules/BuildFFTW.cmake +++ b/cmake/Modules/BuildFFTW.cmake @@ -11,7 +11,7 @@ if(NOT FFTW_FOUND ) ExternalProject_Add(FFTW PREFIX FFTW URL http://www.fftw.org/fftw-${FFTWVersion}.tar.gz - URL_MD5 ${GSLMD5} + URL_MD5 ${FFTWMD5} DOWNLOAD_NO_PROGRESS 1 CONFIGURE_COMMAND ./configure --prefix=${CMAKE_BINARY_DIR}/extern --enable-shared=no --with-pic=yes BUILD_COMMAND make -j8 diff --git a/cmake/Modules/FindPythonLibsNew.cmake b/cmake/Modules/FindPythonLibsNew.cmake index b29b287de..ff04a8ab5 100644 --- a/cmake/Modules/FindPythonLibsNew.cmake +++ b/cmake/Modules/FindPythonLibsNew.cmake @@ -74,12 +74,12 @@ endif() # The library suffix is from the config var LDVERSION sometimes, otherwise # VERSION. VERSION will typically be like "2.7" on unix, and "27" on windows. execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-c" - "from distutils import sysconfig as s;import sys;import struct; + "import sysconfig as s;import sys;import struct; print('.'.join(str(v) for v in sys.version_info)); print(sys.prefix); -print(s.get_python_inc(plat_specific=True)); -print(s.get_python_lib(plat_specific=True)); -print(s.get_config_var('SO')); +print(s.get_path('platinclude')); +print(s.get_path('platlib')); +print(s.get_config_var('EXT_SUFFIX') or s.get_config_var('SO') or ''); print(hasattr(sys, 'gettotalrefcount')+0); print(struct.calcsize('@P')); print(s.get_config_var('LDVERSION') or s.get_config_var('VERSION')); diff --git a/pyccl/CMakeLists.txt b/pyccl/CMakeLists.txt index a255b7b53..c25db88c5 100644 --- a/pyccl/CMakeLists.txt +++ b/pyccl/CMakeLists.txt @@ -29,14 +29,12 @@ else() endif() swig_link_libraries(ccllib ccl_static) +# SWIG>=4.4 triggers int-conversion warning/error while calling numpy's import_array() +# GCC 14+ and Clang both treat this as an error. +target_compile_options(${SWIG_MODULE_ccllib_REAL_NAME} PRIVATE "-Wno-error=int-conversion") + if(APPLE) - # SWIG>=4.4 triggers int-conversion warning/error while calling numpy's import_array() - # GCC seems to be raising warnings but clang raises an error, instead. - # We make clang to treat them as warnings with next line. - target_compile_options(${SWIG_MODULE_ccllib_REAL_NAME} PRIVATE "-Wno-error=int-conversion") - # Unpleasant subtelty for linking on osx + # On macOS, allow undefined symbols to be resolved at runtime by the Python interpreter. set_target_properties(${SWIG_MODULE_ccllib_REAL_NAME} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") -else(APPLE) - swig_link_libraries(ccllib ${PYTHON_LIBRARIES}) endif() set_target_properties(${SWIG_MODULE_ccllib_REAL_NAME} PROPERTIES SUFFIX .so) diff --git a/pyproject.toml b/pyproject.toml index f78dfb4ae..5a99c5762 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ classifiers = [ dependencies = [ "numpy", "packaging", - "scipy", + "scipy<1.14", "pyyaml", ] @@ -37,10 +37,7 @@ documentation = "https://ccl.readthedocs.io/en/latest/" repository = "https://github.com/LSSTDESC/CCL" [project.optional-dependencies] -dev = [ - "pytest", - "pytest-cov", -] +dev = ["pytest", "pytest-cov"] [tool.setuptools.packages.find] include = ["pyccl*"] @@ -49,3 +46,16 @@ include = ["pyccl*"] pyccl = ["_ccllib.so", "emulators/data/*.npz"] [tool.setuptools_scm] + +[tool.cibuildwheel] +build = "cp310-* cp311-* cp312-* cp313-* cp314-*" +skip = "pp* *-musllinux_* *-win* *-manylinux_i686 *-manylinux_s390x *-manylinux_ppc64le" +test-command = "python -c \"import pyccl; print(pyccl.__version__)\"" + +[tool.cibuildwheel.linux] +manylinux-x86_64-image = "manylinux_2_28" +manylinux-aarch64-image = "manylinux_2_28" +before-all = "dnf install -y pcre-devel automake autoconf libtool bison" + +[tool.cibuildwheel.macos] +environment = { MACOSX_DEPLOYMENT_TARGET = "11.0" } diff --git a/setup.py b/setup.py index 312972df7..a424e4240 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,5 @@ import os +import shutil import sys from subprocess import call @@ -9,10 +10,15 @@ def _compile_ccl(debug=False): - call(["mkdir", "-p", "build"]) + # Clean stale CMake cache to avoid cross-version contamination + # (e.g. when cibuildwheel builds multiple Python versions sequentially). + if os.path.exists("build/CMakeCache.txt"): + shutil.rmtree("build") + os.makedirs("build", exist_ok=True) v = sys.version_info cmd = ["cmake", "-H.", "-Bbuild", - "-DPYTHON_VERSION=%d.%d.%d" % (v.major, v.minor, v.micro)] + "-DPYTHON_VERSION=%d.%d.%d" % (v.major, v.minor, v.micro), + "-DPYTHON_EXECUTABLE=%s" % sys.executable] if debug: cmd += ["-DCMAKE_BUILD_TYPE=Debug"] if call(cmd) != 0: @@ -45,6 +51,9 @@ def __init__(self, attr=None): self.debug = False super().__init__(attr) + def has_ext_modules(self): + return True + class Build(_build): """Specialized Python source builder."""