diff --git a/.github/workflows/cd-pypi-binwheel.yml b/.github/workflows/cd-pypi-binwheel.yml new file mode 100644 index 0000000..8577837 --- /dev/null +++ b/.github/workflows/cd-pypi-binwheel.yml @@ -0,0 +1,145 @@ +--- +name: cd-pypi-binwheel + +on: + workflow_call: + inputs: + testpypi: + description: Whether to upload to testpypi instead of pypi. + Requires secrets.PYPI_TEST_API_TOKEN to be defined. + type: boolean + required: false + default: false + working-directory: + description: Working directory to build Python package (for monorepos). + Defaults to root directory. + type: string + required: false + default: "./" + platforms: + description: For not pure python project, the platforms to build for e.g. "['ubuntu-latest','macos-latest','windows-latest']" + required: false + type: string + default: '' + pyversions: + description: For not pure python project, the Python versions to build for e.g. "['38','39','310','311','312','313']" + required: false + type: string + default: '' + env_vars: + description: A way to set env variables + type: string + required: false + default: '' + + +jobs: + deploy: + if: ${{ github.ref_type == 'tag' || github.event_name == 'release' || inputs.testpypi || (inputs.platforms != '' && inputs.pyversions != '') }} + strategy: + matrix: + platform: ${{ fromJson(inputs.platforms) }} + python: ${{ fromJson(inputs.pyversions) }} + runs-on: ${{ matrix.platform }} + defaults: + run: + working-directory: ${{ inputs.working-directory }} + steps: + - name: Set env variables from input + if: inputs.env_vars != '' + run: | + for key in $(echo '${{ inputs.env_vars }}' | jq -r 'keys[]'); do + value=$(echo '${{ inputs.env_vars }}' | jq -r ".\"$key\"") + echo "$key=$value" >> $GITHUB_ENV + done + shell: bash + + - uses: actions/checkout@v4 + with: + fetch-depth: "0" + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + + - name: Test tag name pattern + if: ${{ !inputs.testpypi }} + shell: python + run: | + import re + import sys + + tag_pattern = r"${{ vars.TAG_REGEX_FOR_DEPLOYMENT }}" + ref = "${{ github.ref_name }}" + + if not tag_pattern: + sys.exit(0) + + if not re.match(tag_pattern, ref): + print(f"::error::{ref} does not match {tag_pattern}") + sys.exit(1) + + - name: Test branch name pattern + if: ${{ vars.BRANCH_REGEX_FOR_DEPLOYMENT != '' && !inputs.testpypi }} + run: | + echo "Checking that this release is being made from a main/master branch ( ${{ vars.BRANCH_REGEX_FOR_DEPLOYMENT }} ) - will fail if not" + git branch --all --list --format "%(refname:lstrip=-1)" --contains "${{ github.ref_name }}" | grep -E "${{ vars.BRANCH_REGEX_FOR_DEPLOYMENT }}" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install twine setuptools wheel cibuildwheel + + - name: Check version + shell: python + env: + INPUT_TESTPYPI: ${{ inputs.testpypi }} + run: | + import os + import subprocess + import tomllib + from pathlib import Path + + if Path("setup.py").is_file(): + if Path("pyproject.toml").is_file(): + with open("pyproject.toml", "rb") as f: + data = tomllib.load(f) + requires = data["build-system"]["requires"] + subprocess.run(["pip", "install", "--upgrade", *requires], check=True) + + result = subprocess.run(["python", "setup.py", "--version"], capture_output=True, text=True, check=True) + version = result.stdout.strip() + release = os.environ.get("GITHUB_REF_NAME") + testpypi = os.environ.get("INPUT_TESTPYPI").lower() == "true" + + if not testpypi and release != version: + raise SystemExit(f"Version mismatch: release '{release}' != setup.py version '{version}'") + + - name: Build wheels + run: | + cibuildwheel --output-dir dist + env: + CIBW_BUILD: "cp${{ matrix.python }}-*" + CIBW_BUILD_VERBOSITY: 1 + + - name: Upload to PyPI + if: ${{ !inputs.testpypi }} + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: | + twine check dist/*.whl + twine upload dist/*.whl + + - name: Upload to test-PyPI + if: ${{ inputs.testpypi }} + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_TEST_API_TOKEN }} + run: | + twine check dist/*.whl + # HINT: if your upload fails here due to "unsupported local version", + # put `local_scheme = "no-local-version"` pyproject.toml's + # [tools.setuptools_scm] + twine upload --repository testpypi --verbose dist/*.whl diff --git a/.github/workflows/cd-pypi.yml b/.github/workflows/cd-pypi.yml index 2754ffb..8913b51 100644 --- a/.github/workflows/cd-pypi.yml +++ b/.github/workflows/cd-pypi.yml @@ -16,6 +16,18 @@ on: type: string required: false default: "./" + buildargs: + description: Args to pass in to build as `python -m build {buildargs}` + e.g. '--sdist' for just source distribution + type: string + required: false + default: '' + env_vars: + description: A way to set env variables + type: string + required: false + default: '' + jobs: deploy: @@ -25,6 +37,15 @@ jobs: run: working-directory: ${{ inputs.working-directory }} steps: + - name: Set env variables from input + if: inputs.env_vars != '' + run: | + for key in $(echo '${{ inputs.env_vars }}' | jq -r 'keys[]'); do + value=$(echo '${{ inputs.env_vars }}' | jq -r ".\"$key\"") + echo "$key=$value" >> $GITHUB_ENV + done + shell: bash + - uses: actions/checkout@v4 with: fetch-depth: "0" @@ -81,7 +102,7 @@ jobs: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} run: | - python -m build + python -m build ${{ inputs.buildargs }} twine check dist/* twine upload dist/* @@ -91,7 +112,7 @@ jobs: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_TEST_API_TOKEN }} run: | - python -m build + python -m build ${{ inputs.buildargs }} twine check dist/* # HINT: if your upload fails here due to "unsupported local version", # put `local_scheme = "no-local-version"` pyproject.toml's