Testing - build and publish wheels for Windows and Linux #25
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: Testing - build and publish wheels for Windows and Linux | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| branch: | |
| description: "Branch to build (manual runs)" | |
| required: true | |
| default: "3.0.0b1" | |
| target: | |
| description: "Publish target" | |
| required: false | |
| default: "" | |
| type: choice | |
| options: ["", "testpypi", "pypi"] | |
| python_version: | |
| description: "Python version(s) to build" | |
| required: false | |
| default: "cp311-*" | |
| type: choice | |
| options: | |
| - "cp38-*" | |
| - "cp39-*" | |
| - "cp310-*" | |
| - "cp311-*" | |
| - "cp312-*" | |
| - "cp313-*" | |
| - "cp314-*" | |
| - "cp315-*" | |
| - "cp38-* cp39-* cp310-*" | |
| - "cp310-* cp311-* cp312-*" | |
| - "cp313-* cp314-* cp315-*" | |
| - "cp38-* cp39-* cp310-* cp311-* cp312-* cp313-* cp314-* cp315-*" | |
| os: | |
| description: "OS to build" | |
| required: false | |
| default: "both" | |
| type: choice | |
| options: | |
| - "both" | |
| - "windows" | |
| - "linux" | |
| run_tests: | |
| description: "Run pytest after build" | |
| required: false | |
| default: true | |
| type: boolean | |
| jobs: | |
| build-wheels: | |
| continue-on-error: true | |
| name: Build wheels (${{ matrix.os }}, ${{ github.event.inputs.python_version || 'cp311-*' }}) — ${{ github.event.inputs.branch || github.ref_name }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: ${{ fromJSON( | |
| github.event.inputs.os == 'windows' && '["windows-2022"]' || | |
| github.event.inputs.os == 'linux' && '["ubuntu-22.04"]' || | |
| '["windows-2022", "ubuntu-22.04"]' | |
| ) }} | |
| env: | |
| CIBW_BUILD: ${{ github.event.inputs.python_version || 'cp311-*' }} | |
| CIBW_SKIP: "pp* *-win32 *-musllinux_*" | |
| CIBW_ARCHS_WINDOWS: "AMD64" | |
| CIBW_ARCHS_LINUX: "x86_64" | |
| CIBW_TEST_COMMAND: > | |
| python -c "import pydsstools, sys, numpy; print('OK', sys.version, 'pydsstools:', getattr(pydsstools,'__version__','?'), 'numpy:', numpy.__version__)" | |
| CIBW_MANYLINUX_X86_64_IMAGE: "manylinux2014" | |
| CIBW_BEFORE_ALL_LINUX: > | |
| yum -y install gcc gcc-c++ gcc-gfortran zlib-devel libstdc++-devel && | |
| echo "Installed gcc/gfortran and headers in manylinux" | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| fetch-depth: 0 | |
| # build the selected branch even though the workflow file lives on master | |
| ref: ${{ github.event.inputs.branch }} | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| # Windows: install Intel Fortran (ifx) | |
| - name: Setup Intel Fortran (Windows) | |
| if: startsWith(matrix.os, 'windows-') | |
| uses: fortran-lang/setup-fortran@v1 | |
| with: | |
| compiler: intel # ifx; use 'intel-classic' for ifort | |
| - name: Show toolchain (Windows) | |
| if: startsWith(matrix.os, 'windows-') | |
| shell: cmd | |
| run: | | |
| where ifx || ver | |
| where cl || ver | |
| - name: Upgrade build tooling | |
| run: | | |
| python -m pip install -U pip wheel setuptools build | |
| python -m pip install -U cibuildwheel==2.* | |
| - name: Build wheels (Windows) | |
| if: startsWith(matrix.os, 'windows-') | |
| shell: cmd | |
| env: | |
| CIBW_ENVIRONMENT_WINDOWS: "FC=ifx" | |
| run: | | |
| python -m cibuildwheel --output-dir wheelhouse | |
| - name: Build wheels (Linux) | |
| if: startsWith(matrix.os, 'ubuntu-') | |
| env: | |
| CIBW_BUILD_VERBOSITY: "3" # more logs from pip/cibuildwheel | |
| CIBW_TEST_SKIP: "cp311-manylinux_x86_64" # TEMP: let others pass & publish | |
| run: | | |
| python -m cibuildwheel --output-dir wheelhouse | |
| #- name: Build sdist | |
| # run: | | |
| # python -m build --sdist | |
| - name: Collect artifacts | |
| shell: bash | |
| run: | | |
| mkdir -p dist | |
| cp -v wheelhouse/*.whl dist/ | |
| #cp -v dist/*.tar.gz dist/ 2>/dev/null || true | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: wheels-${{ matrix.os }} | |
| path: dist/* | |
| test-wheels: | |
| name: Test wheels (${{ matrix.os }}) | |
| needs: [build-wheels] | |
| if: ${{ github.event.inputs.run_tests == 'true' }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: ${{ fromJSON( | |
| github.event.inputs.os == 'windows' && '["windows-2022"]' || | |
| github.event.inputs.os == 'linux' && '["ubuntu-22.04"]' || | |
| '["windows-2022", "ubuntu-22.04"]' | |
| ) }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| fetch-depth: 0 | |
| ref: ${{ github.event.inputs.branch }} | |
| # Extract Python version from input (e.g., "cp311-*" → "3.11", "cp310-* cp311-*" → "3.10") | |
| - name: Determine Python version | |
| id: pyver | |
| shell: bash | |
| run: | | |
| input="${{ github.event.inputs.python_version || 'cp311-*' }}" | |
| # Extract first cpXYZ pattern and convert to X.YZ format | |
| ver=$(echo "$input" | grep -oE 'cp[0-9]+' | head -1 | sed 's/cp\([0-9]\)\([0-9]*\)/\1.\2/') | |
| echo "version=$ver" | |
| echo "version=$ver" >> "$GITHUB_OUTPUT" | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ steps.pyver.outputs.version }} | |
| - name: Download wheel artifacts for this OS | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: wheels-${{ matrix.os }} | |
| path: dist | |
| - name: Show downloaded files | |
| run: | | |
| echo "Listing files in dist/" | |
| ls -l dist || true | |
| - name: Install test dependencies | |
| shell: bash | |
| run: | | |
| python -m pip install -U pip | |
| python -m pip install pytest pytest-cov pytest-order | |
| # Install pydsstools dependencies with version constraints matching pyproject.toml | |
| python -m pip install \ | |
| "numpy>=1.21.5,<2; python_version>='3.8' and python_version<'3.10'" \ | |
| "numpy>=1.23.2,<2; python_version>='3.10' and python_version<'3.11'" \ | |
| "numpy>=1.26.0,<2; python_version>='3.11' and python_version<'3.13'" \ | |
| "numpy>=2.1.0; python_version>='3.13'" \ | |
| pandas affine pyproj "pydantic>=2,<3" | |
| python -c "import numpy; print('Test env NumPy:', numpy.__version__)" | |
| - name: Install built wheel | |
| run: | | |
| python -m pip install --no-index --find-links=dist pydsstools | |
| python -c "import pydsstools, sys; print('Imported wheel OK:', getattr(pydsstools,'__version__','?'), sys.version)" | |
| - name: Run pytest | |
| env: | |
| # add any runtime env your tests need here | |
| PYTHONWARNINGS: default | |
| run: | | |
| pytest -v | |
| publish: | |
| name: Publish to (Test)PyPI | |
| needs: [build-wheels, test-wheels] | |
| if: | | |
| always() && | |
| github.event_name == 'workflow_dispatch' && | |
| github.event.inputs.target != '' && | |
| needs.build-wheels.result == 'success' && | |
| (github.event.inputs.run_tests != 'true' || needs.test-wheels.result == 'success') | |
| runs-on: ubuntu-22.04 | |
| steps: | |
| # The next couple of steps build the sdist here (once) in the publish job. | |
| # Previously, building the sdist in both Windows and Linux wheel jobs caused the merged artifact to be corrupted. | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| fetch-depth: 0 | |
| ref: ${{ github.event.inputs.branch }} | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Build sdist (fresh) | |
| run: | | |
| python -m pip install -U build | |
| python -m build --sdist | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| pattern: wheels-* | |
| merge-multiple: true | |
| path: dist | |
| - name: Show packages found | |
| run: | | |
| echo "Listing files in dist/" | |
| ls -l dist || true | |
| - name: Choose repository URL | |
| id: repo | |
| shell: bash | |
| run: | | |
| t="${{ github.event.inputs.target }}" | |
| echo "Raw target input: '$t'" | |
| t="${t,,}" | |
| url="" | |
| token_name="" | |
| if [[ "$t" == "testpypi" || "$t" == "test" ]]; then | |
| url="https://test.pypi.org/legacy/" | |
| token_name="TEST_PYPI_API_TOKEN" | |
| elif [[ "$t" == "pypi" || "$t" == "prod" || "$t" == "release" ]]; then | |
| url="https://upload.pypi.org/legacy/" | |
| token_name="PYPI_API_TOKEN" | |
| fi | |
| echo "url=$url" >> "$GITHUB_OUTPUT" | |
| echo "token_name=$token_name" >> "$GITHUB_OUTPUT" | |
| echo "Resolved (local) url='$url', token_name='$token_name'" | |
| - name: Publish to TestPyPI | |
| if: steps.repo.outputs.token_name == 'TEST_PYPI_API_TOKEN' | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| repository-url: ${{ steps.repo.outputs.url }} | |
| password: ${{ secrets.TEST_PYPI_API_TOKEN }} | |
| packages-dir: dist | |
| skip-existing: true | |
| - name: Publish to PyPI | |
| if: steps.repo.outputs.token_name == 'PYPI_API_TOKEN' | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| repository-url: ${{ steps.repo.outputs.url }} | |
| password: ${{ secrets.PYPI_API_TOKEN }} | |
| packages-dir: dist | |
| skip-existing: true |