diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..fba5cbcd3 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,7 @@ +/.github/workflows/ @skilledwolf +/.github/scripts/ @skilledwolf +/.github/ci-matrix/ @skilledwolf +/.github/dependabot.yml @skilledwolf +/CMakeLists.txt @skilledwolf +/pyproject.toml @skilledwolf +/config/FindBoostForALPS.cmake @skilledwolf diff --git a/.github/RELEASING.md b/.github/RELEASING.md new file mode 100644 index 000000000..1bc2ad585 --- /dev/null +++ b/.github/RELEASING.md @@ -0,0 +1,15 @@ +# Releasing pyalps + +1. Update `project.version` in `pyproject.toml` and merge the change through CI. +2. Create a GitHub release whose tag is exactly `v`. +3. Publish the GitHub release. Draft releases do not publish packages. +4. Approve the protected `pypi` environment deployment after the source + distribution and all wheels pass verification. + +The release workflow builds one source distribution, then builds every wheel +from that exact archive. Manual dispatch is build-only and can be used to test +release packaging without obtaining a PyPI identity token. + +PyPI Trusted Publishing must be restricted to repository `skilledwolf/ALPS`, +workflow `build_wheels.yml`, and environment `pypi`. The publish action emits +PEP 740 attestations by default. diff --git a/.github/ci-matrix/README.md b/.github/ci-matrix/README.md index 85f199325..081df98fd 100644 --- a/.github/ci-matrix/README.md +++ b/.github/ci-matrix/README.md @@ -1,43 +1,31 @@ # CI build matrix -`linux.json` and `macos.json` are the single source of truth for the ALPS build -matrix. They are consumed by the reusable workflow -[`build-alps.yml`](../workflows/build-alps.yml), which filters entries by tier: - -- **`"tier": "quick"`** — runs on every pull request and push to master - ([`ci.yml`](../workflows/ci.yml)). These entries cover the *boundaries* of each - axis: oldest/newest GCC, oldest/newest Clang, newest C++ standard, a combined - all-minimums job (oldest Boost + oldest Python + the C++17 floor), and one - macOS arm64 + one macOS x86_64 job. -- **`"tier": "full"`** — additionally runs in the weekly sweep and on manual - dispatch ([`full-matrix.yml`](../workflows/full-matrix.yml)): the interior of - each version axis (intermediate compilers, Boost, Python, C++ standards) plus - extra macOS targets. The full tier is a superset — it includes the quick - entries too. - -Rationale: intermediate versions almost never break independently — if the -oldest and newest compiler both pass, the ones in between failing alone is -rare. Boundaries gate PRs; the exhaustive sweep catches ecosystem drift -(new compiler/Boost/Python releases) on a schedule instead of taxing every PR. - -Entry fields: `label` (job name + ccache key — keep unique), `tier`, `os`, -`comp_pack` (apt/brew package spec), `c_compiler`/`cxx_compiler`, `c_version` -(Linux only), `cxx_stdlib` (macOS only), `py_version`, `boost_version` -(e.g. `91` = Boost 1.91.0), optional `cxx_standard` (defaults to 17, the -floor; passed to CMake as -DCMAKE_CXX_STANDARD). - -Optional per-entry extras (Linux only): - -- `"sanitize": "undefined"` or `"address,undefined"` — build and test with - GCC sanitizers (-fno-sanitize-recover=all, so findings fail tests). -- `"permissive": false` — drop -fpermissive; the canary tracking its removal. -- `"experimental": true` — the job reports failures without failing the run - (continue-on-error). Used for sanitizers and canaries while their findings - are being burned down; flip to strict once green. - -Canary entries (`py-next-canary`, `no-fpermissive`) live in the full tier to -catch ecosystem drift early: a yellow weekly job instead of a red release -week. When a Boost beta is available, add a canary entry for it the same way. - -Axis sweeps hold everything else at the baseline: ubuntu-24.04, gcc-14, -Python 3.14, Boost 1.91, C++17. +`linux.json` and `macos.json` are the single source of truth for source-build +coverage. The reusable workflow combines both files and filters entries by tier. + +- **`quick`** is the pull-request gate: one all-minimums Linux build, current + GCC, current Clang with the newest C++ mode, and current macOS arm64. +- **`full`** runs weekly and on demand. It adds the supported Clang floor, + next compilers, Linux arm64, macOS Intel/current-OS canaries, sanitizers, + next Python, and a real build without legacy permissive mode. + +The quick tier contains four builds; the full tier contains fourteen. We test +support boundaries and meaningful platform combinations rather than every +interior compiler, Boost, Python, and C++ version. Interior versions share the +same language and ABI boundaries and previously consumed most runner capacity +without independently useful signal. + +Every Boost entry includes the SHA-256 published beside the archive on +`archives.boost.io`. The build passes the extracted directory as +`Boost_SRC_DIR`, which is the variable consumed by `FindBoostForALPS.cmake`. +The all-minimums combination constrains NumPy below 2 because Boost releases +before 1.87 do not support the NumPy 2 C API. + +Only quick entries set `remote_cache: true`. Their ccache archives are restored +on pull requests but saved only from `master`, with one immutable generation per +ISO week. Scheduled compatibility entries intentionally build cold, preventing +the full matrix from creating dozens of disposable caches per run. + +Canaries are strict. A scheduled sanitizer, next-toolchain, next-Python, or +no-permissive failure fails the compatibility workflow so it cannot remain a +silent yellow result. diff --git a/.github/ci-matrix/linux.json b/.github/ci-matrix/linux.json index 14d654c1a..184d738a8 100644 --- a/.github/ci-matrix/linux.json +++ b/.github/ci-matrix/linux.json @@ -1,43 +1,201 @@ [ - { "label": "gcc-12-ubuntu22", "tier": "full", "os": "ubuntu-22.04", "comp_pack": "gcc-12 g++-12", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 12, "py_version": "3.14", "boost_version": 91 }, - { "label": "gcc-14-ubuntu24", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "gcc-14 g++-14", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 14, "py_version": "3.14", "boost_version": 91 }, - { "label": "clang-15-ubuntu22","tier": "full", "os": "ubuntu-22.04", "comp_pack": "clang-15", "c_compiler": "clang", "cxx_compiler": "clang++", "c_version": 15, "py_version": "3.14", "boost_version": 91 }, - { "label": "clang-18-ubuntu24","tier": "full", "os": "ubuntu-24.04", "comp_pack": "clang-18", "c_compiler": "clang", "cxx_compiler": "clang++", "c_version": 18, "py_version": "3.14", "boost_version": 91 }, - - { "label": "gcc-11", "tier": "quick", "os": "ubuntu-22.04", "comp_pack": "gcc-11 g++-11", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 11, "py_version": "3.14", "boost_version": 91 }, - { "label": "gcc-13", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "gcc-13 g++-13", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 13, "py_version": "3.14", "boost_version": 91 }, - { "label": "gcc-15", "tier": "quick", "os": "ubuntu-24.04", "comp_pack": "gcc-15 g++-15", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 15, "py_version": "3.14", "boost_version": 91 }, - { "label": "clang-14", "tier": "quick", "os": "ubuntu-22.04", "comp_pack": "clang-14", "c_compiler": "clang", "cxx_compiler": "clang++", "c_version": 14, "py_version": "3.14", "boost_version": 91 }, - { "label": "clang-16", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "clang-16", "c_compiler": "clang", "cxx_compiler": "clang++", "c_version": 16, "py_version": "3.14", "boost_version": 91 }, - { "label": "clang-17", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "clang-17", "c_compiler": "clang", "cxx_compiler": "clang++", "c_version": 17, "py_version": "3.14", "boost_version": 91 }, - { "label": "clang-19", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "clang-19", "c_compiler": "clang", "cxx_compiler": "clang++", "c_version": 19, "py_version": "3.14", "boost_version": 91 }, - { "label": "clang-20", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "clang-20", "c_compiler": "clang", "cxx_compiler": "clang++", "c_version": 20, "py_version": "3.14", "boost_version": 91 }, - { "label": "clang-21", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "clang-21", "c_compiler": "clang", "cxx_compiler": "clang++", "c_version": 21, "py_version": "3.14", "boost_version": 91 }, - { "label": "clang-22", "tier": "quick", "os": "ubuntu-24.04", "comp_pack": "clang-22", "c_compiler": "clang", "cxx_compiler": "clang++", "c_version": 22, "py_version": "3.14", "boost_version": 91 }, - - { "label": "boost-1.76", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "gcc-14 g++-14", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 14, "py_version": "3.14", "boost_version": 76 }, - { "label": "boost-1.81", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "gcc-14 g++-14", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 14, "py_version": "3.14", "boost_version": 81 }, - { "label": "boost-1.86", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "gcc-14 g++-14", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 14, "py_version": "3.14", "boost_version": 86 }, - { "label": "boost-1.87", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "gcc-14 g++-14", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 14, "py_version": "3.14", "boost_version": 87 }, - { "label": "boost-1.88", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "gcc-14 g++-14", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 14, "py_version": "3.14", "boost_version": 88 }, - { "label": "boost-1.89", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "gcc-14 g++-14", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 14, "py_version": "3.14", "boost_version": 89 }, - { "label": "boost-1.90", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "gcc-14 g++-14", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 14, "py_version": "3.14", "boost_version": 90 }, - - { "label": "py3.10", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "gcc-14 g++-14", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 14, "py_version": "3.10", "boost_version": 91 }, - { "label": "py3.11", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "gcc-14 g++-14", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 14, "py_version": "3.11", "boost_version": 91 }, - { "label": "py3.12", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "gcc-14 g++-14", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 14, "py_version": "3.12", "boost_version": 91 }, - { "label": "py3.13", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "gcc-14 g++-14", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 14, "py_version": "3.13", "boost_version": 91 }, - - { "label": "cxx20", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "gcc-14 g++-14", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 14, "py_version": "3.14", "boost_version": 91, "cxx_standard": 20 }, - { "label": "cxx23", "tier": "quick", "os": "ubuntu-24.04", "comp_pack": "gcc-14 g++-14", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 14, "py_version": "3.14", "boost_version": 91, "cxx_standard": 23 }, - - { "label": "minimums", "tier": "quick", "os": "ubuntu-24.04", "comp_pack": "gcc-14 g++-14", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 14, "py_version": "3.10", "boost_version": 76, "cxx_standard": 17 }, - - { "label": "arm64-gcc-14", "tier": "full", "os": "ubuntu-24.04-arm", "comp_pack": "gcc-14 g++-14", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 14, "py_version": "3.14", "boost_version": 91 }, - - { "label": "ubsan", "tier": "quick", "os": "ubuntu-24.04", "comp_pack": "gcc-14 g++-14", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 14, "py_version": "3.14", "boost_version": 91, "sanitize": "undefined", "experimental": true }, - { "label": "asan-ubsan", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "gcc-14 g++-14", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 14, "py_version": "3.14", "boost_version": 91, "sanitize": "address,undefined", "experimental": true }, - - { "label": "py-next-canary", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "gcc-14 g++-14", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 14, "py_version": "3.15-dev", "boost_version": 91, "experimental": true }, - { "label": "no-fpermissive", "tier": "full", "os": "ubuntu-24.04", "comp_pack": "gcc-14 g++-14", "c_compiler": "gcc", "cxx_compiler": "g++", "c_version": 14, "py_version": "3.14", "boost_version": 91, "permissive": false, "experimental": true } + { + "label": "minimums", + "tier": "quick", + "platform": "linux", + "os": "ubuntu-22.04", + "comp_pack": "gcc-11 g++-11", + "c_compiler": "gcc", + "cxx_compiler": "g++", + "c_version": 11, + "py_version": "3.10", + "numpy_spec": "numpy>=1.26,<2", + "boost_version": 76, + "boost_sha256": "7bd7ddceec1a1dfdcbdb3e609b60d01739c38390a5f956385a12f3122049f0ca", + "cxx_standard": 17, + "sanitize": "", + "permissive": true, + "allow_prereleases": false, + "remote_cache": true + }, + { + "label": "gcc-current", + "tier": "quick", + "platform": "linux", + "os": "ubuntu-24.04", + "comp_pack": "gcc-14 g++-14", + "c_compiler": "gcc", + "cxx_compiler": "g++", + "c_version": 14, + "py_version": "3.14", + "boost_version": 91, + "boost_sha256": "5734305f40a76c30f951c9abd409a45a2a19fb546efe4162119250bbe4d3a463", + "cxx_standard": 17, + "sanitize": "", + "permissive": true, + "allow_prereleases": false, + "remote_cache": true + }, + { + "label": "clang-current-cxx23", + "tier": "quick", + "platform": "linux", + "os": "ubuntu-24.04", + "comp_pack": "clang-18", + "c_compiler": "clang", + "cxx_compiler": "clang++", + "c_version": 18, + "py_version": "3.14", + "boost_version": 91, + "boost_sha256": "5734305f40a76c30f951c9abd409a45a2a19fb546efe4162119250bbe4d3a463", + "cxx_standard": 23, + "sanitize": "", + "permissive": true, + "allow_prereleases": false, + "remote_cache": true + }, + { + "label": "clang-floor", + "tier": "full", + "platform": "linux", + "os": "ubuntu-22.04", + "comp_pack": "clang-14", + "c_compiler": "clang", + "cxx_compiler": "clang++", + "c_version": 14, + "py_version": "3.14", + "boost_version": 91, + "boost_sha256": "5734305f40a76c30f951c9abd409a45a2a19fb546efe4162119250bbe4d3a463", + "cxx_standard": 17, + "sanitize": "", + "permissive": true, + "allow_prereleases": false, + "remote_cache": false + }, + { + "label": "gcc-next", + "tier": "full", + "platform": "linux", + "os": "ubuntu-24.04", + "comp_pack": "gcc-15 g++-15", + "c_compiler": "gcc", + "cxx_compiler": "g++", + "c_version": 15, + "py_version": "3.14", + "boost_version": 91, + "boost_sha256": "5734305f40a76c30f951c9abd409a45a2a19fb546efe4162119250bbe4d3a463", + "cxx_standard": 17, + "sanitize": "", + "permissive": true, + "allow_prereleases": false, + "remote_cache": false + }, + { + "label": "clang-next", + "tier": "full", + "platform": "linux", + "os": "ubuntu-24.04", + "comp_pack": "clang-22", + "c_compiler": "clang", + "cxx_compiler": "clang++", + "c_version": 22, + "py_version": "3.14", + "boost_version": 91, + "boost_sha256": "5734305f40a76c30f951c9abd409a45a2a19fb546efe4162119250bbe4d3a463", + "cxx_standard": 17, + "sanitize": "", + "permissive": true, + "allow_prereleases": false, + "remote_cache": false + }, + { + "label": "linux-arm64", + "tier": "full", + "platform": "linux", + "os": "ubuntu-24.04-arm", + "comp_pack": "gcc-14 g++-14", + "c_compiler": "gcc", + "cxx_compiler": "g++", + "c_version": 14, + "py_version": "3.14", + "boost_version": 91, + "boost_sha256": "5734305f40a76c30f951c9abd409a45a2a19fb546efe4162119250bbe4d3a463", + "cxx_standard": 17, + "sanitize": "", + "permissive": true, + "allow_prereleases": false, + "remote_cache": false + }, + { + "label": "ubsan", + "tier": "full", + "platform": "linux", + "os": "ubuntu-24.04", + "comp_pack": "gcc-14 g++-14", + "c_compiler": "gcc", + "cxx_compiler": "g++", + "c_version": 14, + "py_version": "3.14", + "boost_version": 91, + "boost_sha256": "5734305f40a76c30f951c9abd409a45a2a19fb546efe4162119250bbe4d3a463", + "cxx_standard": 17, + "sanitize": "undefined", + "permissive": true, + "allow_prereleases": false, + "remote_cache": false + }, + { + "label": "asan-ubsan", + "tier": "full", + "platform": "linux", + "os": "ubuntu-24.04", + "comp_pack": "gcc-14 g++-14", + "c_compiler": "gcc", + "cxx_compiler": "g++", + "c_version": 14, + "py_version": "3.14", + "boost_version": 91, + "boost_sha256": "5734305f40a76c30f951c9abd409a45a2a19fb546efe4162119250bbe4d3a463", + "cxx_standard": 17, + "sanitize": "address,undefined", + "permissive": true, + "allow_prereleases": false, + "remote_cache": false + }, + { + "label": "py-next", + "tier": "full", + "platform": "linux", + "os": "ubuntu-24.04", + "comp_pack": "gcc-14 g++-14", + "c_compiler": "gcc", + "cxx_compiler": "g++", + "c_version": 14, + "py_version": "3.15-dev", + "boost_version": 91, + "boost_sha256": "5734305f40a76c30f951c9abd409a45a2a19fb546efe4162119250bbe4d3a463", + "cxx_standard": 17, + "sanitize": "", + "permissive": true, + "allow_prereleases": true, + "remote_cache": false + }, + { + "label": "no-fpermissive", + "tier": "full", + "platform": "linux", + "os": "ubuntu-24.04", + "comp_pack": "gcc-14 g++-14", + "c_compiler": "gcc", + "cxx_compiler": "g++", + "c_version": 14, + "py_version": "3.14", + "boost_version": 91, + "boost_sha256": "5734305f40a76c30f951c9abd409a45a2a19fb546efe4162119250bbe4d3a463", + "cxx_standard": 17, + "sanitize": "", + "permissive": false, + "allow_prereleases": false, + "remote_cache": false + } ] diff --git a/.github/ci-matrix/macos.json b/.github/ci-matrix/macos.json index cf4d7d1b3..8f4af4ecd 100644 --- a/.github/ci-matrix/macos.json +++ b/.github/ci-matrix/macos.json @@ -1,8 +1,50 @@ [ - { "label": "macos-14-appleclang", "tier": "full", "os": "macos-14", "c_compiler": "/usr/bin/clang", "cxx_compiler": "/usr/bin/clang++", "comp_pack": "", "cxx_stdlib": "-stdlib=libc++", "py_version": "3.14", "boost_version": 91 }, - { "label": "macos-15-appleclang", "tier": "quick", "os": "macos-15", "c_compiler": "/usr/bin/clang", "cxx_compiler": "/usr/bin/clang++", "comp_pack": "", "cxx_stdlib": "-stdlib=libc++", "py_version": "3.14", "boost_version": 91 }, - { "label": "macos-15-intel-appleclang", "tier": "quick", "os": "macos-15-intel", "c_compiler": "/usr/bin/clang", "cxx_compiler": "/usr/bin/clang++", "comp_pack": "", "cxx_stdlib": "-stdlib=libc++", "py_version": "3.14", "boost_version": 91 }, - { "label": "macos-26-appleclang", "tier": "full", "os": "macos-26", "c_compiler": "/usr/bin/clang", "cxx_compiler": "/usr/bin/clang++", "comp_pack": "", "cxx_stdlib": "-stdlib=libc++", "py_version": "3.14", "boost_version": 91 }, - { "label": "macos-15-gcc-13", "tier": "full", "os": "macos-15", "c_compiler": "gcc-13", "cxx_compiler": "g++-13", "comp_pack": "gcc@13", "cxx_stdlib": "", "py_version": "3.14", "boost_version": 91 }, - { "label": "macos-15-gcc-14", "tier": "full", "os": "macos-15", "c_compiler": "gcc-14", "cxx_compiler": "g++-14", "comp_pack": "gcc@14", "cxx_stdlib": "", "py_version": "3.14", "boost_version": 91 } + { + "label": "macos-15-arm", + "tier": "quick", + "platform": "macos", + "os": "macos-15", + "c_compiler": "/usr/bin/clang", + "cxx_compiler": "/usr/bin/clang++", + "py_version": "3.14", + "boost_version": 91, + "boost_sha256": "5734305f40a76c30f951c9abd409a45a2a19fb546efe4162119250bbe4d3a463", + "cxx_standard": 17, + "sanitize": "", + "permissive": true, + "allow_prereleases": false, + "remote_cache": true + }, + { + "label": "macos-15-intel", + "tier": "full", + "platform": "macos", + "os": "macos-15-intel", + "c_compiler": "/usr/bin/clang", + "cxx_compiler": "/usr/bin/clang++", + "py_version": "3.14", + "boost_version": 91, + "boost_sha256": "5734305f40a76c30f951c9abd409a45a2a19fb546efe4162119250bbe4d3a463", + "cxx_standard": 17, + "sanitize": "", + "permissive": true, + "allow_prereleases": false, + "remote_cache": false + }, + { + "label": "macos-26-arm", + "tier": "full", + "platform": "macos", + "os": "macos-26", + "c_compiler": "/usr/bin/clang", + "cxx_compiler": "/usr/bin/clang++", + "py_version": "3.14", + "boost_version": 91, + "boost_sha256": "5734305f40a76c30f951c9abd409a45a2a19fb546efe4162119250bbe4d3a463", + "cxx_standard": 17, + "sanitize": "", + "permissive": true, + "allow_prereleases": false, + "remote_cache": false + } ] diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..f585c44c2 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,22 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + target-branch: master + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 5 + groups: + actions: + patterns: ['*'] + + - package-ecosystem: pip + directory: / + target-branch: master + schedule: + interval: monthly + open-pull-requests-limit: 5 + groups: + python-build-and-runtime: + patterns: ['*'] diff --git a/.github/scripts/build-alps.sh b/.github/scripts/build-alps.sh new file mode 100644 index 000000000..9b8cc0674 --- /dev/null +++ b/.github/scripts/build-alps.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${PLATFORM:?PLATFORM is required}" +: "${C_COMPILER:?C_COMPILER is required}" +: "${CXX_COMPILER:?CXX_COMPILER is required}" +: "${CXX_STANDARD:?CXX_STANDARD is required}" +: "${BOOST_VERSION:?BOOST_VERSION is required}" +: "${PERMISSIVE:?PERMISSIVE is required}" + +workspace="${GITHUB_WORKSPACE:-$PWD}" +cc="$C_COMPILER" +cxx="$CXX_COMPILER" +if [[ "$PLATFORM" == "linux" ]]; then + : "${C_VERSION:?C_VERSION is required for Linux}" + cc="${C_COMPILER}-${C_VERSION}" + cxx="${CXX_COMPILER}-${C_VERSION}" +fi + +permissive_option=OFF +if [[ "$PERMISSIVE" == "true" ]]; then + permissive_option=ON +fi + +sanitizer_flags="" +if [[ -n "${SANITIZE:-}" ]]; then + sanitizer_flags="-fsanitize=${SANITIZE} -fno-sanitize-recover=all -fno-omit-frame-pointer" +fi + +cmake_args=( + -S "$workspace" + -B build + -DCMAKE_BUILD_TYPE=Release + -DCMAKE_C_COMPILER="$cc" + -DCMAKE_CXX_COMPILER="$cxx" + -DCMAKE_C_COMPILER_LAUNCHER=ccache + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + -DCMAKE_CXX_STANDARD="$CXX_STANDARD" + -DALPS_ENABLE_LEGACY_PERMISSIVE="$permissive_option" + -DBoost_SRC_DIR="$workspace/boost_1_${BOOST_VERSION}_0" + -DPython_ROOT_DIR="$(python -c 'import sys; print(sys.prefix)')" +) + +if [[ -n "$sanitizer_flags" ]]; then + cmake_args+=( + -DCMAKE_CXX_FLAGS="$sanitizer_flags" + -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=${SANITIZE}" + -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=${SANITIZE}" + -DCMAKE_MODULE_LINKER_FLAGS="-fsanitize=${SANITIZE}" + ) +fi + +cmake "${cmake_args[@]}" + +if [[ "$PLATFORM" == "macos" ]]; then + parallelism="$(sysctl -n hw.ncpu)" +else + parallelism="$(nproc)" +fi +cmake --build build --parallel "$parallelism" +ccache --show-stats diff --git a/.github/scripts/check-release-version.py b/.github/scripts/check-release-version.py new file mode 100644 index 000000000..303fafa15 --- /dev/null +++ b/.github/scripts/check-release-version.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +"""Require a v-prefixed release tag to match project.version exactly.""" + +from __future__ import annotations + +import sys +import tomllib +from pathlib import Path + + +def main() -> int: + if len(sys.argv) != 2: + print("usage: check-release-version.py v", file=sys.stderr) + return 2 + + tag = sys.argv[1] + if not tag.startswith("v"): + print(f"release tag must start with 'v': {tag}", file=sys.stderr) + return 1 + + with Path("pyproject.toml").open("rb") as stream: + version = tomllib.load(stream)["project"]["version"] + + expected = f"v{version}" + if tag != expected: + print(f"release tag {tag!r} does not match project version {expected!r}", file=sys.stderr) + return 1 + + print(f"release tag {tag} matches project version {version}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.github/scripts/install-linux-deps.sh b/.github/scripts/install-linux-deps.sh new file mode 100644 index 000000000..23a128e49 --- /dev/null +++ b/.github/scripts/install-linux-deps.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${COMP_PACK:?COMP_PACK is required}" +: "${C_COMPILER:?C_COMPILER is required}" +: "${C_VERSION:?C_VERSION is required}" +: "${BOOST_VERSION:?BOOST_VERSION is required}" +: "${BOOST_SHA256:?BOOST_SHA256 is required}" + +printf 'Acquire::Retries "2";\nAcquire::http::Timeout "15";\nAcquire::https::Timeout "15";\n' \ + | sudo tee /etc/apt/apt.conf.d/99ci-timeouts > /dev/null + +# Hosted runners prefer a regional Azure mirror that can stall under a large +# matrix. Use the canonical Ubuntu mirror list and put a hard wall-clock bound +# around every index refresh. +if [[ -f /etc/apt/apt-mirrors.txt ]]; then + if [[ "$(dpkg --print-architecture)" == "arm64" ]]; then + ubuntu_mirror=https://ports.ubuntu.com/ubuntu-ports + else + ubuntu_mirror=https://archive.ubuntu.com/ubuntu + fi + printf '%s\n' "$ubuntu_mirror" | sudo tee /etc/apt/apt-mirrors.txt > /dev/null +fi + +sudo timeout 300 apt-get update +sudo apt-get install -y --no-install-recommends \ + ca-certificates ccache curl libhdf5-serial-dev liblapack-dev libopenblas-dev libopenmpi-dev + +python -m pip install --upgrade pip +python -m pip install "${NUMPY_SPEC:-numpy>=1.26}" "scipy>=1.13" + +if [[ "$C_COMPILER" == "clang" ]] && (( C_VERSION >= 19 )); then + key_file="${RUNNER_TEMP:-/tmp}/apt.llvm.org.asc" + curl --fail --location --retry 3 --retry-delay 5 \ + https://apt.llvm.org/llvm-snapshot.gpg.key --output "$key_file" + printf '%s %s\n' \ + '8b2a587ffd672c4687e7581dad4b2f6c1bb2ad6b480cd9771ba2ff48e0b8c75d' \ + "$key_file" | sha256sum --check --strict - + sudo install -m 0644 "$key_file" /usr/share/keyrings/apt.llvm.org.asc + + # /etc/os-release is standard on every supported Ubuntu runner. + # shellcheck disable=SC1091 + source /etc/os-release + repo_suite="llvm-toolchain-${VERSION_CODENAME}-${C_VERSION}" + printf 'deb [signed-by=/usr/share/keyrings/apt.llvm.org.asc] https://apt.llvm.org/%s/ %s main\n' \ + "$VERSION_CODENAME" "$repo_suite" \ + | sudo tee /etc/apt/sources.list.d/apt.llvm.org.list > /dev/null + sudo timeout 300 apt-get update +elif [[ "$C_COMPILER" == "gcc" ]] && (( C_VERSION >= 15 )); then + sudo apt-get install -y --no-install-recommends software-properties-common + sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test + sudo timeout 300 apt-get update +fi + +read -r -a compiler_packages <<< "$COMP_PACK" +sudo apt-get install -y --no-install-recommends "${compiler_packages[@]}" + +archive="boost_1_${BOOST_VERSION}_0.tar.gz" +if [[ ! -f "$archive" ]]; then + curl --fail --location --retry 3 --retry-delay 5 \ + "https://archives.boost.io/release/1.${BOOST_VERSION}.0/source/${archive}" \ + --output "$archive" +fi +printf '%s %s\n' "$BOOST_SHA256" "$archive" | sha256sum --check --strict - +tar -xzf "$archive" diff --git a/.github/scripts/install-macos-deps.sh b/.github/scripts/install-macos-deps.sh new file mode 100644 index 000000000..62fe8db37 --- /dev/null +++ b/.github/scripts/install-macos-deps.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${BOOST_VERSION:?BOOST_VERSION is required}" +: "${BOOST_SHA256:?BOOST_SHA256 is required}" + +python -m pip install --upgrade pip +python -m pip install "numpy>=1.26" "scipy>=1.13" +brew install ccache hdf5 openmpi + +archive="boost_1_${BOOST_VERSION}_0.tar.gz" +if [[ ! -f "$archive" ]]; then + curl --fail --location --retry 3 --retry-delay 5 \ + "https://archives.boost.io/release/1.${BOOST_VERSION}.0/source/${archive}" \ + --output "$archive" +fi +printf '%s %s\n' "$BOOST_SHA256" "$archive" | shasum -a 256 --check +tar -xzf "$archive" diff --git a/.github/scripts/run-tests.sh b/.github/scripts/run-tests.sh new file mode 100644 index 000000000..ae3ee9bd8 --- /dev/null +++ b/.github/scripts/run-tests.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${PLATFORM:?PLATFORM is required}" +: "${C_COMPILER:?C_COMPILER is required}" +: "${CXX_COMPILER:?CXX_COMPILER is required}" + +if [[ "$PLATFORM" == "linux" ]] && [[ "${SANITIZE:-}" == *address* ]]; then + : "${C_VERSION:?C_VERSION is required for Linux sanitizers}" + asan_runtime="$("${C_COMPILER}-${C_VERSION}" -print-file-name=libasan.so)" + cxx_runtime="$("${CXX_COMPILER}-${C_VERSION}" -print-file-name=libstdc++.so.6)" + # Python is a C executable. Load libstdc++ before it dlopens the instrumented + # extension modules so ASan can install its C++ exception interceptors. + export LD_PRELOAD="${asan_runtime}:${cxx_runtime}${LD_PRELOAD:+:${LD_PRELOAD}}" + # matrix_unit_tests deliberately probes allocation failure with max_size(). + export ASAN_OPTIONS=allocator_may_return_null=1:detect_leaks=0 +fi + +export UBSAN_OPTIONS=print_stacktrace=1 +ctest --test-dir build --output-on-failure --output-junit junit.xml diff --git a/.github/workflows/build-alps.yml b/.github/workflows/build-alps.yml index 713d32051..c791b1ea3 100644 --- a/.github/workflows/build-alps.yml +++ b/.github/workflows/build-alps.yml @@ -1,218 +1,156 @@ name: Build ALPS (reusable) -# Reusable build workflow. Callers pick a tier: -# quick — matrix entries tagged "quick" in .github/ci-matrix/*.json (PR gate) -# full — every matrix entry (weekly sweep / manual dispatch) -# See .github/ci-matrix/README.md for the tiering rationale. - on: workflow_call: inputs: tier: - description: "Matrix slice to run: 'quick' or 'full'" + description: "Matrix slice: quick or full" required: true type: string + max_parallel: + description: "Maximum source builds allowed to run concurrently" + required: true + type: number + +permissions: + contents: read + +concurrency: + group: source-build-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ inputs.tier }} + cancel-in-progress: true jobs: matrix: name: Select ${{ inputs.tier }} matrix runs-on: ubuntu-latest + timeout-minutes: 5 outputs: - linux: ${{ steps.pick.outputs.linux }} - macos: ${{ steps.pick.outputs.macos }} + platforms: ${{ steps.pick.outputs.platforms }} + cache_epoch: ${{ steps.pick.outputs.cache_epoch }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: + persist-credentials: false sparse-checkout: .github/ci-matrix - id: pick + name: Select matrix entries + env: + TIER: ${{ inputs.tier }} run: | - for plat in linux macos; do - echo "$plat=$(jq -c --arg tier "${{ inputs.tier }}" \ - '[.[] | select($tier == "full" or .tier == "quick") | del(.tier)]' \ - .github/ci-matrix/$plat.json)" >> "$GITHUB_OUTPUT" - done - - linux-build: - name: Linux ${{ matrix.plat.label }} + case "$TIER" in + quick|full) ;; + *) echo "Unsupported tier: $TIER" >&2; exit 2 ;; + esac + platforms="$(jq -sc --arg tier "$TIER" \ + '[.[][] | select($tier == "full" or .tier == "quick") | del(.tier)]' \ + .github/ci-matrix/linux.json .github/ci-matrix/macos.json)" + echo "platforms=$platforms" >> "$GITHUB_OUTPUT" + echo "cache_epoch=$(date -u +%G-%V)" >> "$GITHUB_OUTPUT" + + build: + name: Build ${{ matrix.plat.label }} needs: matrix runs-on: ${{ matrix.plat.os }} - # Worst observed healthy job is ~60 min cold; a hang otherwise burns the - # 6-hour GitHub default. - timeout-minutes: 90 - # Experimental entries (sanitizers, canaries, no-fpermissive) report - # failures without failing the run. - continue-on-error: ${{ matrix.plat.experimental || false }} + timeout-minutes: 120 strategy: fail-fast: false + max-parallel: ${{ inputs.max_parallel }} matrix: - plat: ${{ fromJSON(needs.matrix.outputs.linux) }} + plat: ${{ fromJSON(needs.matrix.outputs.platforms) }} env: CCACHE_DIR: ${{ github.workspace }}/.ccache - CCACHE_MAXSIZE: 500M + CCACHE_MAXSIZE: 150M + PLATFORM: ${{ matrix.plat.platform }} + COMP_PACK: ${{ matrix.plat.comp_pack }} + C_COMPILER: ${{ matrix.plat.c_compiler }} + CXX_COMPILER: ${{ matrix.plat.cxx_compiler }} + C_VERSION: ${{ matrix.plat.c_version }} + CXX_STANDARD: ${{ matrix.plat.cxx_standard }} + NUMPY_SPEC: ${{ matrix.plat.numpy_spec }} + BOOST_VERSION: ${{ matrix.plat.boost_version }} + BOOST_SHA256: ${{ matrix.plat.boost_sha256 }} + SANITIZE: ${{ matrix.plat.sanitize }} + PERMISSIVE: ${{ matrix.plat.permissive }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - name: Set up Python uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 with: python-version: ${{ matrix.plat.py_version }} - - name: Cache Boost sources - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + allow-prereleases: ${{ matrix.plat.allow_prereleases }} + + - name: Restore Boost archive + id: boost-cache + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: boost_1_${{ matrix.plat.boost_version }}_0.tar.gz - key: boost-src-1.${{ matrix.plat.boost_version }}.0 - - name: Cache compiled objects (ccache) - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + key: boost-src-${{ runner.os }}-1.${{ matrix.plat.boost_version }}.0-${{ matrix.plat.boost_sha256 }} + + - name: Restore compiled objects + id: ccache + if: matrix.plat.remote_cache + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: ${{ github.workspace }}/.ccache - key: ccache-${{ matrix.plat.label }}-${{ github.run_id }} + key: ccache-v2-${{ runner.os }}-${{ matrix.plat.label }}-${{ needs.matrix.outputs.cache_epoch }} restore-keys: | - ccache-${{ matrix.plat.label }}- - - name: Install dependencies - # A wedged mirror must fail the step in minutes, not hang the job to - # its 90-minute cap (observed multi-hour stalls in this step). + ccache-v2-${{ runner.os }}-${{ matrix.plat.label }}- + + - name: Install Linux dependencies + if: matrix.plat.platform == 'linux' timeout-minutes: 20 - run: | - # Bound every apt network operation: 30 s socket timeout, 3 retries. - printf 'Acquire::Retries "3";\nAcquire::http::Timeout "30";\nAcquire::https::Timeout "30";\n' \ - | sudo tee /etc/apt/apt.conf.d/99ci-timeouts > /dev/null - sudo apt-get update - python -m pip install --upgrade pip - pip install "numpy>=1.26" "scipy>=1.13" - sudo apt install ccache liblapack-dev libblas-dev libopenmpi-dev libhdf5-serial-dev - # Clang >= 19: not in Ubuntu's default apt repos; use apt.llvm.org. - # GCC >= 15: not in Ubuntu's default apt repos; use ubuntu-toolchain-r PPA. - if [[ "${{ matrix.plat.c_compiler }}" == "clang" && "${{ matrix.plat.c_version }}" -ge 19 ]]; then - wget -nv --tries=3 --timeout=60 -O /tmp/llvm.sh https://apt.llvm.org/llvm.sh - sudo bash /tmp/llvm.sh ${{ matrix.plat.c_version }} all - else - if [[ "${{ matrix.plat.c_compiler }}" == "gcc" && "${{ matrix.plat.c_version }}" -ge 15 ]]; then - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test - sudo apt-get update - fi - sudo apt install ${{ matrix.plat.comp_pack }} - fi - if [[ ! -f boost_1_${{ matrix.plat.boost_version }}_0.tar.gz ]]; then - wget -nv --tries=3 --timeout=60 --waitretry=10 https://archives.boost.io/release/1.${{ matrix.plat.boost_version }}.0/source/boost_1_${{ matrix.plat.boost_version }}_0.tar.gz - fi - tar -xzf boost_1_${{ matrix.plat.boost_version }}_0.tar.gz - - name: Build ALPS - env: - CC: ${{ matrix.plat.c_compiler }}-${{ matrix.plat.c_version }} - CXX: ${{ matrix.plat.cxx_compiler }}-${{ matrix.plat.c_version }} - run: | - # Per-entry extras: sanitizers, and the no-fpermissive canary. - EXTRA_CXX="" - LINKER="" - if [[ -n "${{ matrix.plat.sanitize }}" ]]; then - EXTRA_CXX="-fsanitize=${{ matrix.plat.sanitize }} -fno-sanitize-recover=all -fno-omit-frame-pointer" - LINKER="-fsanitize=${{ matrix.plat.sanitize }}" - fi - PERMISSIVE="-fpermissive" - if [[ "${{ matrix.plat.permissive }}" == "false" ]]; then - PERMISSIVE="" - fi - cmake -S $GITHUB_WORKSPACE -B build \ - -DCMAKE_C_COMPILER_LAUNCHER=ccache \ - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -DBoost_ROOT_DIR=`pwd`/boost_1_${{ matrix.plat.boost_version }}_0 \ - -DCMAKE_CXX_STANDARD=${{ matrix.plat.cxx_standard || '17' }} \ - -DCMAKE_CXX_FLAGS="$PERMISSIVE -DBOOST_NO_AUTO_PTR -DBOOST_FILESYSTEM_NO_CXX20_ATOMIC_REF -DBOOST_TIMER_ENABLE_DEPRECATED $EXTRA_CXX" \ - -DCMAKE_EXE_LINKER_FLAGS="$LINKER" \ - -DCMAKE_SHARED_LINKER_FLAGS="$LINKER" \ - -DCMAKE_MODULE_LINKER_FLAGS="$LINKER" - cmake --build build -j $(nproc) - ccache -s - - name: Run tests - run: | - if [[ "${{ matrix.plat.sanitize }}" == *address* ]]; then - # Sanitized extension modules loaded by an unsanitized python - # need the ASan runtime preloaded. Leak detection is off until - # the (large) existing leak backlog is triaged. - export LD_PRELOAD=$(${{ matrix.plat.c_compiler }}-${{ matrix.plat.c_version }} -print-file-name=libasan.so) - export ASAN_OPTIONS=detect_leaks=0 - fi - export UBSAN_OPTIONS=print_stacktrace=1 - ctest --test-dir build --output-on-failure --output-junit junit.xml - - name: Test summary - if: always() - run: python3 .github/scripts/junit-summary.py build/junit.xml >> "$GITHUB_STEP_SUMMARY" - - name: Upload test logs on failure - if: failure() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: test-logs-linux-${{ matrix.plat.label }}-${{ github.run_id }} - path: | - build/Testing/Temporary/** - build/junit.xml + run: bash .github/scripts/install-linux-deps.sh - macos-build: - name: macOS ${{ matrix.plat.label }} - needs: matrix - runs-on: ${{ matrix.plat.os }} - # Intel runners are slow and variable; worst observed healthy job ~85 min. - timeout-minutes: 120 - continue-on-error: ${{ matrix.plat.experimental || false }} - strategy: - fail-fast: false - matrix: - plat: ${{ fromJSON(needs.matrix.outputs.macos) }} - env: - CCACHE_DIR: ${{ github.workspace }}/.ccache - CCACHE_MAXSIZE: 500M - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - name: Cache Boost sources - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + - name: Install macOS dependencies + if: matrix.plat.platform == 'macos' + timeout-minutes: 30 + run: bash .github/scripts/install-macos-deps.sh + + - name: Save Boost archive + if: >- + github.ref == 'refs/heads/master' && + steps.boost-cache.outputs.cache-hit != 'true' && + (matrix.plat.label == 'minimums' || + matrix.plat.label == 'gcc-current' || + matrix.plat.label == 'macos-15-arm') + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 with: path: boost_1_${{ matrix.plat.boost_version }}_0.tar.gz - key: boost-src-1.${{ matrix.plat.boost_version }}.0 - - name: Cache compiled objects (ccache) - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 - with: - path: ${{ github.workspace }}/.ccache - key: ccache-${{ matrix.plat.label }}-${{ github.run_id }} - restore-keys: | - ccache-${{ matrix.plat.label }}- - - name: Install dependencies - timeout-minutes: 30 - run: | - brew install ccache python@${{ matrix.plat.py_version }} gfortran - pip${{ matrix.plat.py_version }} install "numpy>=1.26" "scipy>=1.13" --break-system-packages - brew install openmpi hdf5 - if [[ -n "${{ matrix.plat.comp_pack }}" ]]; then - brew install ${{ matrix.plat.comp_pack }} - fi - if [[ ! -f boost_1_${{ matrix.plat.boost_version }}_0.tar.gz ]]; then - wget -nv --tries=3 --timeout=60 --waitretry=10 https://archives.boost.io/release/1.${{ matrix.plat.boost_version }}.0/source/boost_1_${{ matrix.plat.boost_version }}_0.tar.gz - fi - tar -xzf boost_1_${{ matrix.plat.boost_version }}_0.tar.gz - ln -sf $(brew --prefix)/bin/python${{ matrix.plat.py_version }} $(brew --prefix)/bin/python - - name: Build ALPS - env: - FC: gfortran - run: | - cmake -S $GITHUB_WORKSPACE -B build \ - -DCMAKE_C_COMPILER=${{ matrix.plat.c_compiler }} \ - -DCMAKE_CXX_COMPILER=${{ matrix.plat.cxx_compiler }} \ - -DCMAKE_C_COMPILER_LAUNCHER=ccache \ - -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -DBoost_ROOT_DIR=`pwd`/boost_1_${{ matrix.plat.boost_version }}_0 \ - -DCMAKE_CXX_STANDARD=${{ matrix.plat.cxx_standard || '17' }} \ - -DCMAKE_CXX_FLAGS="${{ matrix.plat.cxx_stdlib }} -fpermissive -DBOOST_NO_AUTO_PTR -DBOOST_FILESYSTEM_NO_CXX20_ATOMIC_REF -DBOOST_TIMER_ENABLE_DEPRECATED" \ - -DPython_ROOT_DIR=`$(brew --prefix)/bin/python${{ matrix.plat.py_version }} -c "import sys, os; print(os.path.dirname(os.path.dirname(str(sys.executable))));"` \ - -DCMAKE_Fortran_COMPILER=gfortran - cmake --build build -j $(sysctl -n hw.ncpu) - ccache -s + key: boost-src-${{ runner.os }}-1.${{ matrix.plat.boost_version }}.0-${{ matrix.plat.boost_sha256 }} + + - name: Configure and build + run: bash .github/scripts/build-alps.sh + - name: Run tests - run: ctest --test-dir build --output-on-failure --output-junit junit.xml + run: bash .github/scripts/run-tests.sh + - name: Test summary if: always() - run: python3 .github/scripts/junit-summary.py build/junit.xml >> "$GITHUB_STEP_SUMMARY" - - name: Upload test logs on failure + run: python .github/scripts/junit-summary.py build/junit.xml >> "$GITHUB_STEP_SUMMARY" + + - name: Upload diagnostics on failure if: failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: test-logs-macos-${{ matrix.plat.label }}-${{ github.run_id }} + name: diagnostics-${{ matrix.plat.label }}-${{ github.run_id }} path: | build/Testing/Temporary/** build/junit.xml + build/CMakeFiles/CMakeConfigureLog.yaml + build/CMakeFiles/CMakeError.log + build/CMakeFiles/CMakeOutput.log + if-no-files-found: ignore + retention-days: 14 + + - name: Save compiled objects + if: >- + always() && matrix.plat.remote_cache && + github.ref == 'refs/heads/master' && + steps.ccache.outputs.cache-hit != 'true' + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 + with: + path: ${{ github.workspace }}/.ccache + key: ccache-v2-${{ runner.os }}-${{ matrix.plat.label }}-${{ needs.matrix.outputs.cache_epoch }} diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index e74df34ee..442119c88 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -1,130 +1,129 @@ -name: ALPS Python Packaging CI/CD - -# PRs build a single smoke-test wheel (one platform, one Python) to verify -# packaging isn't broken. The full wheel matrix — all platforms x CPython -# 3.10-3.13 — runs on release tags and manual dispatch, and publishing to -# PyPI stays gated on 'v*' tags. +name: Release on: - push: - tags: - - 'v*' - pull_request: - branches: - - master - paths-ignore: - - '**.md' - - '.github/ISSUE_TEMPLATE/**' + release: + types: [published] workflow_dispatch: permissions: contents: read -# Newest wins (note: an expression here yields a string, which GitHub -# treats as truthy — use literal booleans only). Tag builds are safe: each -# tag is its own group. concurrency: - group: wheels-${{ github.ref }} - cancel-in-progress: true + group: release-${{ github.event.release.tag_name || github.ref }} + cancel-in-progress: false jobs: - matrix: - name: Select wheel matrix + sdist: + name: Build source distribution runs-on: ubuntu-latest - outputs: - plat: ${{ steps.pick.outputs.plat }} + timeout-minutes: 15 steps: - - id: pick + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + ref: ${{ github.event.release.tag_name || github.ref }} + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + with: + python-version: '3.14' + - name: Verify release tag and package version + if: github.event_name == 'release' + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: python .github/scripts/check-release-version.py "$RELEASE_TAG" + - name: Build and verify source distribution run: | - if [[ "${{ github.event_name }}" == "pull_request" ]]; then - # PR smoke test: newest CPython on Linux only. - echo 'plat=[{"os":"ubuntu-latest","target":"","arch":"x86_64","homebrew":"","cibw_build":"cp313-*"}]' >> "$GITHUB_OUTPUT" - else - # Full matrix for tags / manual dispatch. - full='[ - {"os":"ubuntu-latest","target":"","arch":"x86_64","homebrew":"","cibw_build":"cp310-* cp311-* cp312-* cp313-*"}, - {"os":"ubuntu-24.04-arm","target":"","arch":"aarch64","homebrew":"","cibw_build":"cp310-* cp311-* cp312-* cp313-*"}, - {"os":"macos-15","target":"15.0","arch":"arm64","homebrew":"/opt/homebrew","cibw_build":"cp310-* cp311-* cp312-* cp313-*"}, - {"os":"macos-26","target":"26.0","arch":"arm64","homebrew":"/opt/homebrew","cibw_build":"cp310-* cp311-* cp312-* cp313-*"} - ]' - echo "plat=$(jq -c . <<< "$full")" >> "$GITHUB_OUTPUT" - fi - - build_wheels: - name: Build wheels on ${{ matrix.plat.os }} - needs: matrix - runs-on: ${{ matrix.plat.os }} + python -m pip install "build==1.5.0" "twine==7.0.0" + python -m build --sdist + python -m twine check dist/*.tar.gz + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: release-sdist + path: dist/*.tar.gz + if-no-files-found: error + retention-days: 14 + + wheels: + name: Wheels ${{ matrix.platform.os }} ${{ matrix.platform.arch }} + needs: sdist + runs-on: ${{ matrix.platform.os }} + timeout-minutes: 180 strategy: fail-fast: false + max-parallel: 3 matrix: - plat: ${{ fromJSON(needs.matrix.outputs.plat) }} + platform: + - { os: ubuntu-latest, arch: x86_64, target: '' } + - { os: ubuntu-24.04-arm, arch: aarch64, target: '' } + - { os: macos-15, arch: arm64, target: '15.0' } steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - # Install Fortran compiler based on OS - - name: Install dependencies + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + name: release-sdist + path: dist + - id: sdist + name: Locate source distribution + shell: bash run: | - case "${{ matrix.plat.os }}" in - ubuntu-*) - sudo apt-get update - sudo apt-get install -y gfortran - ;; - *) - brew update - brew install gfortran - ;; - esac - - - name: Build wheels - uses: pypa/cibuildwheel@ee63bf16da6cddfb925f542f2c7b59ad50e93969 # v2.22.0 + path="$(find dist -maxdepth 1 -name '*.tar.gz' -print -quit)" + test -n "$path" + echo "path=$path" >> "$GITHUB_OUTPUT" + - name: Build and test wheels + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + package-dir: ${{ steps.sdist.outputs.path }} env: - CIBW_BUILD: ${{ matrix.plat.cibw_build }} - CIBW_ARCHS: ${{ matrix.plat.arch }} - CIBW_ARCHS_MACOS: ${{ matrix.plat.arch }} - - # Set Fortran compiler for all platforms + CIBW_ARCHS: ${{ matrix.platform.arch }} + CIBW_ARCHS_LINUX: ${{ matrix.platform.arch }} + CIBW_ARCHS_MACOS: ${{ matrix.platform.arch }} CIBW_ENVIRONMENT: "FC=gfortran" - - # macOS-specific settings - CIBW_ENVIRONMENT_MACOS: > - Boost_ROOT_DIR=/Users/runner/work/ALPS/ALPS/boost_1_87_0 - MACOSX_DEPLOYMENT_TARGET=${{ matrix.plat.target }} + CIBW_ENVIRONMENT_MACOS: >- + FC=gfortran + MACOSX_DEPLOYMENT_TARGET=${{ matrix.platform.target }} CXXFLAGS="-stdlib=libc++" - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: cibw-wheels-${{ matrix.plat.os }}-${{ strategy.job-index }} - path: ./wheelhouse/*.whl - - - build_sdist: - name: Build source distribution + name: release-wheels-${{ matrix.platform.os }}-${{ matrix.platform.arch }} + path: wheelhouse/*.whl + if-no-files-found: error + retention-days: 14 + + verify: + name: Verify release artifacts + needs: [sdist, wheels] runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - - name: Build sdist - run: pipx run build --sdist - - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: - name: cibw-sdist - path: dist/*.tar.gz - - - upload_pypi: - needs: [build_wheels, build_sdist] + pattern: release-* + path: dist + merge-multiple: true + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + with: + python-version: '3.14' + - name: Check metadata and list hashes + run: | + python -m pip install "twine==7.0.0" + python -m twine check dist/* + sha256sum dist/* + + publish: + name: Publish to PyPI + if: github.event_name == 'release' + needs: [sdist, wheels, verify] runs-on: ubuntu-latest + timeout-minutes: 15 environment: pypi permissions: - id-token: write - if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'release' + id-token: write # Required only here for PyPI Trusted Publishing via OIDC. steps: - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: - # unpacks all CIBW artifacts into dist/ - pattern: cibw-* + pattern: release-* path: dist merge-multiple: true - - - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1 + - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 + with: + packages-dir: dist + verify-metadata: true + attestations: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 326ebe82a..a796c85ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,52 +1,85 @@ name: CI -# PR gate: the "quick" tier of the build matrix (~9 jobs covering the -# boundaries of every axis). The exhaustive sweep runs weekly and on demand -# via full-matrix.yml. See .github/ci-matrix/README.md. -# -# NOTE for branch protection: if the "Linux …" / "macOS …" checks are made -# required, a paths-ignore skip leaves them in "Expected" state and blocks -# merging. Keep the ignore list to files that can never affect the build. - on: push: - branches: - - master - paths-ignore: - - '**.md' - - '.github/ISSUE_TEMPLATE/**' + branches: [master] pull_request: - paths-ignore: - - '**.md' - - '.github/ISSUE_TEMPLATE/**' merge_group: permissions: contents: read -# Newest wins: a superseded run tests a stale commit, so cancel it — on PR -# branches and master alike. (An expression here is a trap: it yields the -# string "false", which GitHub treats as truthy.) concurrency: - group: ci-${{ github.ref }} + group: ci-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: lint-workflows: - name: Lint workflows + name: Lint workflows and support files runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false - name: actionlint - uses: docker://rhysd/actionlint:1.7.7 + uses: docker://rhysd/actionlint@sha256:b1934ee5f1c509618f2508e6eb47ee0d3520686341fec936f3b79331f9315667 # v1.7.12 with: - # shellcheck style findings in the inherited build scripts are - # tracked separately; lint the workflow structure strictly. - args: -color -ignore shellcheck + args: -color + - name: Validate scripts and matrices + run: | + shellcheck .github/scripts/*.sh + jq empty .github/ci-matrix/*.json + python3 -m py_compile .github/scripts/*.py build: - name: Build + name: Source builds uses: ./.github/workflows/build-alps.yml with: tier: quick + max_parallel: 4 + + package-smoke: + name: Package smoke test + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 + with: + python-version: '3.14' + - name: Build and check source distribution + run: | + python -m pip install "build==1.5.0" "twine==7.0.0" + python -m build --sdist + python -m twine check dist/*.tar.gz + - id: sdist + name: Locate source distribution + run: | + path="$(find dist -maxdepth 1 -name '*.tar.gz' -print -quit)" + test -n "$path" + echo "path=$path" >> "$GITHUB_OUTPUT" + - name: Build and test CPython 3.14 wheel + uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0 + with: + package-dir: ${{ steps.sdist.outputs.path }} + only: cp314-manylinux_x86_64 + + gate: + name: Gate + if: always() + needs: [lint-workflows, build, package-smoke] + runs-on: ubuntu-latest + timeout-minutes: 2 + env: + LINT_RESULT: ${{ needs.lint-workflows.result }} + BUILD_RESULT: ${{ needs.build.result }} + PACKAGE_RESULT: ${{ needs.package-smoke.result }} + steps: + - name: Require every CI component + run: | + test "$LINT_RESULT" = success + test "$BUILD_RESULT" = success + test "$PACKAGE_RESULT" = success diff --git a/.github/workflows/full-matrix.yml b/.github/workflows/full-matrix.yml index bab4bae83..f99ba381a 100644 --- a/.github/workflows/full-matrix.yml +++ b/.github/workflows/full-matrix.yml @@ -1,27 +1,21 @@ -name: Full build matrix - -# The exhaustive sweep: every compiler, Boost, Python, and C++ standard in -# .github/ci-matrix/*.json (~39 jobs). Runs weekly to catch ecosystem drift -# (new compiler/Boost/Python releases) and on demand for risky changes — -# trigger it on any branch with: -# gh workflow run full-matrix.yml --ref -# PRs are gated by the small "quick" tier in ci.yml instead. +name: Compatibility on: schedule: - - cron: '30 5 * * 1' # 05:30 UTC every Monday + - cron: '30 5 * * 1' workflow_dispatch: permissions: contents: read concurrency: - group: full-matrix-${{ github.ref }} - cancel-in-progress: false + group: compatibility-${{ github.ref }} + cancel-in-progress: true jobs: build: - name: Build + name: Compatibility builds uses: ./.github/workflows/build-alps.yml with: tier: full + max_parallel: 6 diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml index b1287ed12..432482f10 100644 --- a/.github/workflows/issue-triage.yml +++ b/.github/workflows/issue-triage.yml @@ -4,37 +4,70 @@ on: issues: types: [opened] -permissions: - issues: write +permissions: {} + +concurrency: + group: issue-triage-${{ github.event.issue.number }} + cancel-in-progress: true jobs: - route_by_method: + route: + name: Label and assign simulation question runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + issues: write # Required to create labels and route the opened issue. steps: - - name: Parse issue body - id: parse - uses: actions-cool/issues-helper@v1.11 + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: - actions: 'parse-form' - issue-number: ${{ github.event.issue.number }} - body: ${{ github.event.issue.body }} + script: | + const body = context.payload.issue.body || ''; + const match = body.match(/(?:^|\n)### Method\s*\n+([^\n]+)/i); + const method = match ? match[1].trim() : 'Other (please specify in additional context)'; + + const routes = { + 'Exact Diagonalization (ED)': { + assignee: 'vws100', label: 'method:ed', color: '1d76db' + }, + 'Quantum Monte Carlo (SSE)': { + assignee: 'wistaria', label: 'method:sse', color: '0e8a16' + }, + 'Quantum Monte Carlo (Worm)': { + assignee: 'LodePollet', label: 'method:worm', color: '0e8a16' + }, + 'Density Matrix Renormalization Group (DMRG)': { + assignee: 'afeiguin', label: 'method:dmrg', color: '5319e7' + }, + 'Dynamical Mean Field Theory (DMFT)': { + assignee: 'egull', label: 'method:dmft', color: 'b60205' + }, + 'Other (please specify in additional context)': { + assignee: 'Ooolab', label: 'method:other', color: '6a737d' + } + }; + const route = routes[method] || routes['Other (please specify in additional context)']; + const {owner, repo} = context.repo; + const issue_number = context.issue.number; - - name: Assign maintainer based on 'Method' choice - env: - METHOD_FROM_FORM: ${{ steps.parse.outputs.method }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ISSUE_NUMBER: ${{ github.event.issue.number }} - run: | - assignee="" - case "$METHOD_FROM_FORM" in - *Exact*Diagonalization* | *ED*) assignee="vws100" ;; - *Quantum*Monte*Carlo* | *SSE*) assignee="wistaria" ;; - *Quantum*Monte*Carlo* | *Worm*) assignee="LodePollet" ;; - *Density*Matrix*Renormalization*Group* | *DMRG*) assignee="afeiguin" ;; - *Dynamical*Mean*Field*Theory* | *DMFT*) assignee="egull" ;; - *) assignee="Ooolab" ;; - esac + try { + await github.rest.issues.getLabel({owner, repo, name: route.label}); + } catch (error) { + if (error.status !== 404) throw error; + await github.rest.issues.createLabel({ + owner, repo, name: route.label, color: route.color, + description: `Simulation help using ${method}` + }); + } + await github.rest.issues.addLabels({owner, repo, issue_number, labels: [route.label]}); - if [ -n "$assignee" ]; then - gh issue edit $ISSUE_NUMBER --add-assignee "$assignee" - fi + try { + await github.request('GET /repos/{owner}/{repo}/assignees/{assignee}', { + owner, repo, assignee: route.assignee + }); + await github.rest.issues.addAssignees({ + owner, repo, issue_number, assignees: [route.assignee] + }); + } catch (error) { + if (error.status !== 404) throw error; + core.notice(`${route.assignee} is not assignable in ${owner}/${repo}; applied ${route.label} instead.`); + } diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b0f021c6..3b2858429 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,7 @@ else (NOT_ALPS_BUILD_PYTHON) endif (NOT_ALPS_BUILD_PYTHON) option(ALPS_BUILD_DEVELOPER_TOOLS "Build tools used by developers to maintain ALPS" OFF) +option(ALPS_ENABLE_LEGACY_PERMISSIVE "Allow legacy C++ constructs rejected by supported compilers" ON) option(ALPS_ENABLE_OPENMP "Enable OpenMP parallelization" OFF) option(ALPS_ENABLE_OPENMP_WORKER "Enable OpenMP worker support" OFF) option(ALPS_BUILD_ON_CRAY "Build ALPS for cray" OFF) @@ -104,7 +105,15 @@ set(CMAKE_CXX_EXTENSIONS OFF) execute_process(COMMAND ${CMAKE_CXX_COMPILER} --version COMMAND head -n1 OUTPUT_VARIABLE ALPS_COMPILER_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) message(STATUS "Compiler version: ${ALPS_COMPILER_VERSION}") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpermissive -DBOOST_NO_AUTO_PTR -DBOOST_FILESYSTEM_NO_CXX20_ATOMIC_REF -DBOOST_TIMER_ENABLE_DEPRECATED") +if(ALPS_ENABLE_LEGACY_PERMISSIVE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpermissive") +endif() + +add_compile_definitions( + BOOST_NO_AUTO_PTR + BOOST_FILESYSTEM_NO_CXX20_ATOMIC_REF + BOOST_TIMER_ENABLE_DEPRECATED +) # set convenient warning flags for clang diff --git a/README-py.md b/README-py.md index 9ea719744..22b3f67ec 100644 --- a/README-py.md +++ b/README-py.md @@ -1,4 +1,5 @@ -[![ALPS CI/CD](https://github.com/ALPSim/legacy/actions/workflows/build.yml/badge.svg)](https://github.com/ALPSim/legacy/actions/workflows/build.yml) +[![CI](https://github.com/skilledwolf/ALPS/actions/workflows/ci.yml/badge.svg)](https://github.com/skilledwolf/ALPS/actions/workflows/ci.yml) +[![Release](https://github.com/skilledwolf/ALPS/actions/workflows/build_wheels.yml/badge.svg)](https://github.com/skilledwolf/ALPS/actions/workflows/build_wheels.yml) ## Python Algorithms and Libraries for Physics Simulations @@ -22,7 +23,8 @@ pip install pyalps - HDF5 - MPI - Python >= 3.10 - - Python 3.13 requires Boost version 1.87 or later + - Python 3.13 and newer require Boost version 1.87 or later + - Boost versions before 1.87 require NumPy version 1.x - Earlier versions maybe also work but unsupported - C++ compiler with C++17 support (CI covers GCC 11 through 15, Clang 14 through 22, and AppleClang) - GNU Make or Ninja build system diff --git a/README.md b/README.md index b49b867bf..b18f3814e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -[![ALPS CI/CD](https://github.com/ALPSim/legacy/actions/workflows/build.yml/badge.svg)](https://github.com/ALPSim/legacy/actions/workflows/build.yml) +[![CI](https://github.com/skilledwolf/ALPS/actions/workflows/ci.yml/badge.svg)](https://github.com/skilledwolf/ALPS/actions/workflows/ci.yml) +[![Compatibility](https://github.com/skilledwolf/ALPS/actions/workflows/full-matrix.yml/badge.svg)](https://github.com/skilledwolf/ALPS/actions/workflows/full-matrix.yml) ## Algorithms and Libraries for Physics Simulations (ALPS) diff --git a/config/FindPythonMod.cmake b/config/FindPythonMod.cmake index 37077624e..4b65267f7 100644 --- a/config/FindPythonMod.cmake +++ b/config/FindPythonMod.cmake @@ -196,13 +196,24 @@ IF (PYTHON_FOUND) # libraries which must be linked in when embedding # if(NOT DEFINED PYTHON_EXTRA_LIBS) - if(PYTHON_VERSION VERSION_LESS "3.11") - EXEC_PYTHON_SCRIPT ("${PYFUNC_NORMALIZE_FLAGS}from distutils.sysconfig import * ;print( normalize_flags( str(get_config_var('LOCALMODLIBS')) + ' ' + str(get_config_var('LIBS')) + ' ' + str(get_config_var('LDFLAGS')) ))" - PYTHON_EXTRA_LIBS) - else() - EXEC_PYTHON_SCRIPT ("${PYFUNC_NORMALIZE_FLAGS}from sysconfig import * ;print( normalize_flags( str(get_config_var('LOCALMODLIBS')) + ' ' + str(get_config_var('LIBS')) + ' ' + str(get_config_var('LDFLAGS')) ))" - PYTHON_EXTRA_LIBS) - endif() + set(_alps_python_link_flags + "str(get_config_var('LOCALMODLIBS')) + ' ' + str(get_config_var('LIBS'))") + # setup-python uses a universal2 Python on arm64 macOS and advertises + # both architectures in LDFLAGS. CMake owns architecture selection; + # forwarding those build-time flags breaks links to arm64-only + # Homebrew libraries. + if(NOT APPLE) + string(APPEND _alps_python_link_flags + " + ' ' + str(get_config_var('LDFLAGS'))") + endif() + if(PYTHON_VERSION VERSION_LESS "3.11") + EXEC_PYTHON_SCRIPT ("${PYFUNC_NORMALIZE_FLAGS}from distutils.sysconfig import * ;print( normalize_flags( ${_alps_python_link_flags} ))" + PYTHON_EXTRA_LIBS) + else() + EXEC_PYTHON_SCRIPT ("${PYFUNC_NORMALIZE_FLAGS}from sysconfig import * ;print( normalize_flags( ${_alps_python_link_flags} ))" + PYTHON_EXTRA_LIBS) + endif() + unset(_alps_python_link_flags) endif() MESSAGE(STATUS "PYTHON_EXTRA_LIBS =${PYTHON_EXTRA_LIBS}" ) mark_as_advanced(PYTHON_EXTRA_LIBS) diff --git a/pyproject.toml b/pyproject.toml index 5f735a7fd..c5f0b3a43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,16 +11,18 @@ logging.level = "DEBUG" [tool.scikit-build.cmake.define] Boost_SRC_DIR = {env="Boost_SRC_DIR"} -CMAKE_CXX_FLAGS = "-fPIC -fpermissive -DBOOST_NO_AUTO_PTR -DBOOST_FILESYSTEM_NO_CXX20_ATOMIC_REF -DBOOST_TIMER_ENABLE_DEPRECATED" +CMAKE_CXX_FLAGS = "-fPIC" CMAKE_C_FLAGS = "-fPIC" ALPS_PYTHON_WHEEL = "ON" ALPS_BUILD_FORTRAN = "ON" +ALPS_ENABLE_LEGACY_PERMISSIVE = "ON" [[tool.scikit-build.overrides]] if.platform-system = "^darwin" -cmake.define.CMAKE_CXX_FLAGS = "-fPIC -stdlib=libc++ -fpermissive -DBOOST_NO_AUTO_PTR -DBOOST_FILESYSTEM_NO_CXX20_ATOMIC_REF -DBOOST_TIMER_ENABLE_DEPRECATED" +cmake.define.CMAKE_CXX_FLAGS = "-fPIC -stdlib=libc++" cmake.define.Boost_SRC_DIR = {env="Boost_SRC_DIR"} cmake.define.ALPS_BUILD_FORTRAN = "ON" +cmake.define.ALPS_ENABLE_LEGACY_PERMISSIVE = "ON" cmake.define.CMAKE_C_FLAGS = "-fPIC" cmake.define.ALPS_PYTHON_WHEEL = "ON" @@ -49,6 +51,7 @@ classifiers = [ 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: Implementation :: CPython', "Operating System :: POSIX", @@ -69,17 +72,16 @@ tests = [ ] [tool.cibuildwheel] +build = ["cp310-*", "cp311-*", "cp312-*", "cp313-*", "cp314-*"] skip = ["*-musllinux*"] test-requires = "pytest" -test-command = "pytest {project}/test" manylinux-x86_64-image = "manylinux_2_28" manylinux-aarch64-image = "manylinux_2_28" [tool.cibuildwheel.linux] -before-all = "dnf install -y epel-release; dnf config-manager --set-enabled powertools; dnf install -y fftw-devel hdf5-devel openblas-devel wget; pipx install patchelf==0.14.5.0 --force" -#before-build="" +before-all = "dnf install -y epel-release; dnf config-manager --set-enabled powertools; dnf install -y gcc-gfortran fftw-devel hdf5-devel openblas-devel; pipx install patchelf==0.14.5.0 --force" test-command = "pytest {project}/test/pyalps" [tool.cibuildwheel.macos] -before-all = "brew reinstall hdf5 fftw gfortran" +before-all = "brew install hdf5 fftw gfortran" test-command = "pytest {project}/test/pyalps" diff --git a/src/alps/alea/simpleobsdata.h b/src/alps/alea/simpleobsdata.h index 8f99fbd85..353e5619b 100644 --- a/src/alps/alea/simpleobsdata.h +++ b/src/alps/alea/simpleobsdata.h @@ -229,6 +229,7 @@ SimpleObservableData::SimpleObservableData() : count_(0), has_variance_(false), has_tau_(false), + can_set_thermal_(false), binsize_(0), max_bin_number_(0), discardedmeas_(0), diff --git a/src/alps/hdf5/python.cpp b/src/alps/hdf5/python.cpp index ef1339da6..f56204bbc 100644 --- a/src/alps/hdf5/python.cpp +++ b/src/alps/hdf5/python.cpp @@ -229,7 +229,7 @@ namespace alps { std::size_t len = std::accumulate(extent.begin(), extent.end(), std::size_t(1), std::multiplies()); value = alps::python::numpy::from_pyobject(boost::python::object(boost::python::handle<>(PyArray_SimpleNew(npextent.size(), &npextent.front(), type)))); if (len) { - boost::scoped_ptr raw(new T[len]); + boost::scoped_array raw(new T[len]); std::pair > data(raw.get(), extent); load(ar, path, data, chunk, offset); PyArrayObject * ptr = (PyArrayObject *)value.ptr(); diff --git a/src/alps/hdf5/python.hpp b/src/alps/hdf5/python.hpp index 7321ed703..6238cf2a4 100644 --- a/src/alps/hdf5/python.hpp +++ b/src/alps/hdf5/python.hpp @@ -39,7 +39,7 @@ #include #include -#include +#include #include #include diff --git a/src/alps/osiris/xdrdump.C b/src/alps/osiris/xdrdump.C index ed7f04466..80b863f54 100644 --- a/src/alps/osiris/xdrdump.C +++ b/src/alps/osiris/xdrdump.C @@ -34,7 +34,9 @@ #include #include +#include #include +#include #include #include @@ -96,13 +98,19 @@ bool xdr_hyper(XDR *xdrs, long long *llp) unsigned long t2; #endif if (xdrs->x_op == XDR_ENCODE) { - t1 = (long)((*llp) >> 32); - t2 = (unsigned long)(*llp - (((long long) t1) << 32)); + std::uint64_t bits; + std::memcpy(&bits, llp, sizeof(bits)); + t1 = static_cast( + static_cast(bits >> 32)); + t2 = static_cast( + static_cast(bits)); return (::xdr_long(xdrs, &t1) && ::xdr_u_long(xdrs, &t2)); } else if (xdrs->x_op == XDR_DECODE) { if (!::xdr_long(xdrs, &t1) || !::xdr_u_long(xdrs, &t2)) return false; - *llp = ((long long) t1) << 32; - *llp |= t2; + std::uint64_t bits = + (static_cast(static_cast(t1)) << 32) | + static_cast(t2); + std::memcpy(llp, &bits, sizeof(bits)); return true; } else if (xdrs->x_op == XDR_FREE) { return true; diff --git a/src/alps/parapack/integer_range.h b/src/alps/parapack/integer_range.h index 68ea1a888..e62c170e8 100644 --- a/src/alps/parapack/integer_range.h +++ b/src/alps/parapack/integer_range.h @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -123,9 +124,14 @@ class integer_range { value_type min BOOST_PREVENT_MACRO_SUBSTITUTION () const { return mi_; } value_type max BOOST_PREVENT_MACRO_SUBSTITUTION () const { return ma_; } - value_type size() const { return 1 + ma_ - mi_; } - bool empty() const { return size() == 0; } - bool valid() const { return size() != 0; } + std::uintmax_t size() const { + if (empty()) return 0; + std::uintmax_t span = static_cast(ma_) - + static_cast(mi_); + return span == (std::numeric_limits::max)() ? span : span + 1; + } + bool empty() const { return mi_ > ma_; } + bool valid() const { return !empty(); } bool is_included(param_type v) const { return (v >= min BOOST_PREVENT_MACRO_SUBSTITUTION ()) && (v <= max BOOST_PREVENT_MACRO_SUBSTITUTION ()); } integer_range overlap(integer_range const& r) const {