feat!: use C++20 for extension build #3012
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build | |
| on: | |
| push: | |
| branches: | |
| - main # allow to trigger the workflow with tag push event | |
| pull_request: | |
| types: | |
| - labeled | |
| - unlabeled | |
| - opened | |
| - synchronize | |
| - reopened | |
| - ready_for_review | |
| paths: | |
| - setup.py | |
| - setup.cfg | |
| - pyproject.toml | |
| - MANIFEST.in | |
| - CMakeLists.txt | |
| - include/** | |
| - src/** | |
| - optree/version.py | |
| - .github/workflows/build.yml | |
| release: | |
| types: | |
| - published | |
| # Allow to trigger the workflow manually | |
| workflow_dispatch: | |
| inputs: | |
| task: | |
| description: "Task type" | |
| type: choice | |
| options: | |
| - build-only | |
| - build-and-publish | |
| required: true | |
| nightly-pybind11: | |
| description: "Use nightly pybind11" | |
| type: boolean | |
| required: false | |
| default: false | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: "${{ github.workflow }}-${{ github.ref }}" | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| OPTREE_CXX_WERROR: "OFF" | |
| _GLIBCXX_USE_CXX11_ABI: "1" | |
| _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR: "1" | |
| # Apple's libc++ gates `std::format` behind macOS 13.3 / iOS 16.3. iOS wheel tags carry the | |
| # minor, so 16.3 is taggable exactly. macOS tags do not: PEP 425 floors the minor to zero above | |
| # macOS 11, so a 13.3 binary would ship as `macosx_13_0_*`, install on 13.0-13.2 and fail in | |
| # `dyld`. 14.0 is the lowest target whose tag does not lie. | |
| MACOSX_DEPLOYMENT_TARGET: "14.0" | |
| IPHONEOS_DEPLOYMENT_TARGET: "16.3" | |
| PYTHONUNBUFFERED: "1" | |
| PYTHON_TAG: "py3" # to be updated | |
| PYTHON_VERSION: "3" # to be updated | |
| COLUMNS: "100" | |
| FORCE_COLOR: "1" | |
| CLICOLOR_FORCE: "1" | |
| XDG_CACHE_HOME: "${{ github.workspace }}/.cache" | |
| PIP_CACHE_DIR: "${{ github.workspace }}/.cache/pip" | |
| PIP_RETRIES: "10" | |
| PIP_TIMEOUT: "120" | |
| ALLOW_PRERELEASES: "false" | |
| jobs: | |
| build-sdist: | |
| name: Build sdist | |
| if: | | |
| github.repository_owner == 'metaopt' && | |
| (github.event_name != 'push' || startsWith(github.ref, 'refs/tags/')) && | |
| (github.event_name != 'pull_request' || !github.event.pull_request.draft) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.9 - 3.15" | |
| update-environment: true | |
| - name: Upgrade pip | |
| run: python -m pip install --upgrade pip setuptools | |
| - name: Set __release__ | |
| if: | | |
| startsWith(github.ref, 'refs/tags/') || | |
| (github.event_name == 'workflow_dispatch' && github.event.inputs.task == 'build-and-publish') | |
| run: python .github/workflows/set_release.py | |
| - name: Print version | |
| run: python setup.py --version | |
| - name: Use nightly pybind11 | |
| shell: bash | |
| if: | | |
| (github.event_name == 'workflow_dispatch' && github.event.inputs.nightly-pybind11 == 'true') || | |
| ( | |
| github.event_name == 'pull_request' && | |
| contains(github.event.pull_request.labels.*.name, 'test-with-nightly-pybind11') | |
| ) | |
| run: | | |
| python -m pip install --force-reinstall "$(python .github/workflows/set_setup_requires.py)" | |
| echo "::group::pyproject.toml" | |
| cat pyproject.toml | |
| echo "::endgroup::" | |
| - name: Install dependencies | |
| run: python -m pip install --upgrade pip setuptools wheel build | |
| - name: Build sdist | |
| run: python -m build --sdist | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: sdist | |
| path: dist/*.tar.gz | |
| if-no-files-found: error | |
| build-wheels: | |
| name: >- | |
| Build wheels for | |
| ${{ startsWith(matrix.python-version, 'pypy') && 'PyPy' || 'CPython' }} ${{ matrix.python-version }} | |
| on ${{ matrix.target.runner }} (${{ matrix.target.platform }} / ${{ matrix.target.archs }}) | |
| if: | | |
| github.repository_owner == 'metaopt' && | |
| (github.event_name != 'push' || startsWith(github.ref, 'refs/tags/')) && | |
| (github.event_name != 'pull_request' || !github.event.pull_request.draft) | |
| # https://docs.github.com/en/actions/reference/runners/github-hosted-runners | |
| runs-on: ${{ matrix.target.runner }} | |
| strategy: | |
| matrix: | |
| # Reference: https://cibuildwheel.pypa.io | |
| target: | |
| - { runner: ubuntu-latest, platform: linux, archs: "auto64" } | |
| - { runner: ubuntu-latest, platform: linux, archs: "auto32" } | |
| - { runner: ubuntu-24.04-arm, platform: linux, archs: "aarch64" } | |
| - { runner: ubuntu-latest, platform: linux, archs: "ppc64le" } | |
| - { runner: ubuntu-latest, platform: linux, archs: "s390x" } | |
| - { runner: ubuntu-latest, platform: linux, archs: "riscv64" } | |
| - { runner: macos-latest, platform: macos, archs: "auto64" } | |
| - { runner: macos-latest, platform: macos, archs: "x86_64" } | |
| - { runner: windows-latest, platform: windows, archs: "auto64" } | |
| - { runner: windows-latest, platform: windows, archs: "auto32" } | |
| - { runner: windows-11-arm, platform: windows, archs: "ARM64" } | |
| python-version: | |
| - "3.9" | |
| - "3.10" | |
| - "3.11" | |
| - "3.12" | |
| - "3.13" | |
| - "3.13t" | |
| - "3.14" | |
| - "3.14t" | |
| - "3.15" | |
| - "3.15t" | |
| - "pypy-3.11" | |
| exclude: | |
| # Exclude unsupported Python versions | |
| - python-version: "3.9" | |
| target: { archs: "ARM64" } | |
| - python-version: "3.10" | |
| target: { archs: "ARM64" } | |
| - python-version: "pypy-3.11" | |
| target: { archs: "auto32" } | |
| - python-version: "pypy-3.11" | |
| target: { archs: "ppc64le" } | |
| - python-version: "pypy-3.11" | |
| target: { archs: "s390x" } | |
| - python-version: "pypy-3.11" | |
| target: { archs: "riscv64" } | |
| - python-version: "pypy-3.11" | |
| target: { archs: "ARM64" } | |
| include: | |
| # iOS | |
| - python-version: "3.13" | |
| target: | |
| runner: macos-latest | |
| platform: ios | |
| archs: "arm64_iphoneos" | |
| - python-version: "3.14" | |
| target: | |
| runner: macos-latest | |
| platform: ios | |
| archs: "arm64_iphoneos" | |
| - python-version: "3.15" | |
| target: | |
| runner: macos-latest | |
| platform: ios | |
| archs: "arm64_iphoneos" | |
| # iOS Simulator | |
| - python-version: "3.13" | |
| target: | |
| runner: macos-latest | |
| platform: ios | |
| archs: "arm64_iphonesimulator" | |
| - python-version: "3.14" | |
| target: | |
| runner: macos-latest | |
| platform: ios | |
| archs: "arm64_iphonesimulator" | |
| - python-version: "3.15" | |
| target: | |
| runner: macos-latest | |
| platform: ios | |
| archs: "arm64_iphonesimulator" | |
| # Android | |
| - python-version: "3.13" | |
| target: | |
| runner: ubuntu-latest | |
| platform: android | |
| archs: "arm64_v8a" | |
| - python-version: "3.14" | |
| target: | |
| runner: ubuntu-latest | |
| platform: android | |
| archs: "arm64_v8a" | |
| - python-version: "3.15" | |
| target: | |
| runner: ubuntu-latest | |
| platform: android | |
| archs: "arm64_v8a" | |
| # Pyodide | |
| - python-version: "3.13" | |
| target: | |
| runner: ubuntu-latest | |
| platform: pyodide | |
| archs: "wasm32" | |
| - python-version: "3.14" | |
| target: | |
| runner: ubuntu-latest | |
| platform: pyodide | |
| archs: "wasm32" | |
| - python-version: "3.15" | |
| target: | |
| runner: ubuntu-latest | |
| platform: pyodide | |
| archs: "wasm32" | |
| fail-fast: false | |
| timeout-minutes: 180 | |
| steps: | |
| - name: Sanity check for variables | |
| shell: bash | |
| run: | | |
| if [[ '${{ env.ALLOW_PRERELEASES }}' != 'true' && | |
| '${{ env.ALLOW_PRERELEASES }}' != 'false' ]]; then | |
| echo '::error::Expect ALLOW_PRERELEASES ("${{ env.ALLOW_PRERELEASES }}") to be "true" or "false"' >&2 | |
| exit 1 | |
| fi | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Determine Python version | |
| shell: bash | |
| run: | | |
| PYTHON_VERSION="${{ matrix.python-version }}" | |
| PYTHON_VERSION="${PYTHON_VERSION%t}" | |
| echo "Using Python version: ${PYTHON_VERSION}" | |
| echo "PYTHON_VERSION=${PYTHON_VERSION}" | tee -a "${GITHUB_ENV}" | |
| - name: Set up Python | |
| uses: actions/setup-python@v7 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| update-environment: true | |
| allow-prereleases: true | |
| - name: Set up Environment | |
| shell: bash | |
| run: | | |
| python -m pip install --upgrade pip setuptools | |
| if [[ "${{ runner.os }}" == 'macOS' ]]; then | |
| brew update --quiet --force | |
| brew list --formula cmake >/dev/null && brew uninstall --formula cmake || true | |
| brew install --formula cmake | |
| fi | |
| - name: Set __release__ | |
| if: | | |
| startsWith(github.ref, 'refs/tags/') || | |
| (github.event_name == 'workflow_dispatch' && github.event.inputs.task == 'build-and-publish') | |
| run: python .github/workflows/set_release.py | |
| - name: Print version | |
| run: python setup.py --version | |
| - name: Use nightly pybind11 | |
| shell: bash | |
| if: | | |
| (github.event_name == 'workflow_dispatch' && github.event.inputs.nightly-pybind11 == 'true') || | |
| ( | |
| github.event_name == 'pull_request' && | |
| contains(github.event.pull_request.labels.*.name, 'test-with-nightly-pybind11') | |
| ) | |
| run: | | |
| python -m pip install --force-reinstall "$(python .github/workflows/set_setup_requires.py)" | |
| echo "::group::pyproject.toml" | |
| cat pyproject.toml | |
| echo "::endgroup::" | |
| - name: Set CIBW_BUILD | |
| shell: bash | |
| run: | | |
| set -x | |
| PYTHON_TAG="$( | |
| echo 'import sys; print( | |
| "{0.name[0]}p{1.major}{1.minor}".format( | |
| sys.implementation, | |
| sys.version_info, | |
| ).lower(), | |
| )' | python - | |
| )" | |
| if [[ "${{ matrix.python-version }}" == *"t" ]]; then | |
| PYTHON_TAG="${PYTHON_TAG}t" | |
| fi | |
| echo "PYTHON_TAG=${PYTHON_TAG}" | tee -a "${GITHUB_ENV}" | |
| echo "CIBW_BUILD=${PYTHON_TAG}-*" | tee -a "${GITHUB_ENV}" | |
| if [[ "${{ matrix.target.platform }}" == 'ios' && | |
| "${{ matrix.target.archs }}" == *simulator* ]]; then | |
| xcrun simctl shutdown all || true | |
| xcrun simctl delete unavailable || true | |
| xcrun simctl list devices >/dev/null || true | |
| xcodebuild -downloadPlatform iOS || true | |
| xcrun simctl list devices available || true | |
| fi | |
| - name: Set up QEMU | |
| if: runner.os == 'Linux' | |
| uses: docker/setup-qemu-action@v4 | |
| with: | |
| platforms: all | |
| - name: Build wheels | |
| if: matrix.python-version != '3.13t' | |
| uses: pypa/cibuildwheel@v4.2 | |
| env: | |
| CIBW_BUILD: ${{ env.CIBW_BUILD }} | |
| CIBW_PLATFORM: ${{ matrix.target.platform }} | |
| CIBW_ARCHS: ${{ matrix.target.archs }} | |
| CIBW_ENABLE: pypy${{ env.ALLOW_PRERELEASES == 'true' && ' cpython-prerelease' || '' }} | |
| CIBW_ALLOW_EMPTY: ${{ env.ALLOW_PRERELEASES == 'true' }} | |
| with: | |
| package-dir: . | |
| output-dir: wheelhouse | |
| config-file: "{package}/pyproject.toml" | |
| # cibuildwheel 4.0 dropped the experimental CPython 3.13 free-threading (`cp313t`) builds and | |
| # the `cpython-freethreading` enable group. Install the 3.x series via `pip` (rather than the | |
| # `uses: pypa/cibuildwheel` action reference) so this target keeps building while Dependabot's | |
| # `github-actions` ecosystem cannot bump it back to 4.x. | |
| - name: Build wheels (with Python 3.13t) | |
| if: matrix.python-version == '3.13t' | |
| shell: bash | |
| run: | | |
| python -m pip install --ignore-installed --upgrade 'cibuildwheel~=3.4' | |
| python -m cibuildwheel . --output-dir wheelhouse --config-file '{package}/pyproject.toml' | |
| env: | |
| CIBW_BUILD: ${{ env.CIBW_BUILD }} | |
| CIBW_PLATFORM: ${{ matrix.target.platform }} | |
| CIBW_ARCHS: ${{ matrix.target.archs }} | |
| CIBW_ENABLE: pypy cpython-freethreading${{ env.ALLOW_PRERELEASES == 'true' && ' cpython-prerelease' || '' }} | |
| CIBW_ALLOW_EMPTY: ${{ env.ALLOW_PRERELEASES == 'true' }} | |
| - name: Upload wheels | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: wheels-${{ env.PYTHON_TAG }}-${{ matrix.target.platform }}-${{ matrix.target.archs }} | |
| path: wheelhouse/*.whl | |
| if-no-files-found: ${{ env.ALLOW_PRERELEASES == 'true' && 'warn' || 'error' }} | |
| list-artifacts: | |
| name: List artifacts | |
| runs-on: ubuntu-latest | |
| needs: [build-sdist, build-wheels] | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Download built sdist | |
| uses: actions/download-artifact@v8 | |
| with: | |
| # unpacks default artifact into dist/ | |
| # if `name: artifact` is omitted, the action will create extra parent dir | |
| name: sdist | |
| path: dist | |
| - name: Download built wheels | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: wheels-* | |
| path: dist | |
| merge-multiple: true | |
| - name: List distributions | |
| run: ls -lh dist/* | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: artifacts | |
| path: dist/* | |
| if-no-files-found: error | |
| publish: | |
| name: Publish to PyPI | |
| runs-on: ubuntu-latest | |
| needs: [list-artifacts] | |
| if: | | |
| github.repository_owner == 'metaopt' && | |
| github.event_name != 'pull_request' && | |
| (github.event_name != 'workflow_dispatch' || github.event.inputs.task == 'build-and-publish') && | |
| (github.event_name != 'push' || startsWith(github.ref, 'refs/tags/')) | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python | |
| if: startsWith(github.ref, 'refs/tags/') | |
| uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.9 - 3.15" | |
| update-environment: true | |
| - name: Upgrade pip | |
| run: python -m pip install --upgrade pip setuptools | |
| - name: Set __release__ | |
| if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' | |
| run: python .github/workflows/set_release.py | |
| - name: Print version | |
| run: python setup.py --version | |
| - name: Check consistency between the package version and release tag | |
| if: startsWith(github.ref, 'refs/tags/') | |
| run: | | |
| PACKAGE_VER="v$(python setup.py --version)" | |
| RELEASE_TAG="${GITHUB_REF#refs/*/}" | |
| if [[ "${PACKAGE_VER}" != "${RELEASE_TAG}" ]]; then | |
| echo "::error::package ver. (${PACKAGE_VER}) != release tag. (${RELEASE_TAG})" >&2 | |
| exit 1 | |
| fi | |
| - name: Download built artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| # unpacks default artifact into dist/ | |
| # if `name: artifact` is omitted, the action will create extra parent dir | |
| name: artifacts | |
| path: dist | |
| - name: List distributions | |
| run: ls -lh dist/* | |
| - name: Publish to TestPyPI | |
| if: | | |
| (startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch') && | |
| env.ALLOW_PRERELEASES == 'false' | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| user: __token__ | |
| password: ${{ secrets.TESTPYPI_UPLOAD_TOKEN }} | |
| repository-url: https://test.pypi.org/legacy/ | |
| verbose: true | |
| print-hash: true | |
| skip-existing: true | |
| - name: Publish to PyPI | |
| if: | | |
| (startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch') && | |
| env.ALLOW_PRERELEASES == 'false' | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| user: __token__ | |
| password: ${{ secrets.PYPI_UPLOAD_TOKEN }} | |
| verbose: true | |
| print-hash: true | |
| skip-existing: true |