name: "[ruff] Release" on: workflow_dispatch: inputs: tag: description: "The version to tag, without the leading 'v'. If omitted, will initiate a dry run (no uploads)." type: string sha: description: "The full sha of the commit to be released. If omitted, the latest commit on the default branch will be used." default: "" type: string pull_request: paths: # When we change pyproject.toml, we want to ensure that the maturin builds still work - pyproject.toml # And when we change this workflow itself... - .github/workflows/release.yaml concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: PACKAGE_NAME: ruff PYTHON_VERSION: "3.11" CARGO_INCREMENTAL: 0 CARGO_NET_RETRY: 10 CARGO_TERM_COLOR: always RUSTUP_MAX_RETRIES: 10 jobs: sdist: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: ref: ${{ inputs.sha }} - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} - name: "Prep README.md" run: python scripts/transform_readme.py --target pypi - name: "Build sdist" uses: PyO3/maturin-action@v1 with: command: sdist args: --out dist - name: "Test sdist" run: | pip install dist/${{ env.PACKAGE_NAME }}-*.tar.gz --force-reinstall ruff --help python -m ruff --help - name: "Upload sdist" uses: actions/upload-artifact@v3 with: name: wheels path: dist macos-x86_64: runs-on: macos-latest steps: - uses: actions/checkout@v4 with: ref: ${{ inputs.sha }} - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} architecture: x64 - name: "Prep README.md" run: python scripts/transform_readme.py --target pypi - name: "Build wheels - x86_64" uses: PyO3/maturin-action@v1 with: target: x86_64 args: --release --locked --out dist - name: "Test wheel - x86_64" run: | pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall ruff --help python -m ruff --help - name: "Upload wheels" uses: actions/upload-artifact@v3 with: name: wheels path: dist - name: "Archive binary" run: | ARCHIVE_FILE=ruff-${{ inputs.tag }}-x86_64-apple-darwin.tar.gz tar czvf $ARCHIVE_FILE -C target/x86_64-apple-darwin/release ruff shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 - name: "Upload binary" uses: actions/upload-artifact@v3 with: name: binaries path: | *.tar.gz *.sha256 macos-universal: runs-on: macos-latest steps: - uses: actions/checkout@v4 with: ref: ${{ inputs.sha }} - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} architecture: x64 - name: "Prep README.md" run: python scripts/transform_readme.py --target pypi - name: "Build wheels - universal2" uses: PyO3/maturin-action@v1 with: args: --release --locked --target universal2-apple-darwin --out dist - name: "Test wheel - universal2" run: | pip install dist/${{ env.PACKAGE_NAME }}-*universal2.whl --force-reinstall ruff --help python -m ruff --help - name: "Upload wheels" uses: actions/upload-artifact@v3 with: name: wheels path: dist - name: "Archive binary" run: | ARCHIVE_FILE=ruff-${{ inputs.tag }}-aarch64-apple-darwin.tar.gz tar czvf $ARCHIVE_FILE -C target/aarch64-apple-darwin/release ruff shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 - name: "Upload binary" uses: actions/upload-artifact@v3 with: name: binaries path: | *.tar.gz *.sha256 windows: runs-on: windows-latest strategy: matrix: platform: - target: x86_64-pc-windows-msvc arch: x64 - target: i686-pc-windows-msvc arch: x86 - target: aarch64-pc-windows-msvc arch: x64 steps: - uses: actions/checkout@v4 with: ref: ${{ inputs.sha }} - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} architecture: ${{ matrix.platform.arch }} - name: "Prep README.md" run: python scripts/transform_readme.py --target pypi - name: "Build wheels" uses: PyO3/maturin-action@v1 with: target: ${{ matrix.platform.target }} args: --release --locked --out dist - name: "Test wheel" if: ${{ !startsWith(matrix.platform.target, 'aarch64') }} shell: bash run: | python -m pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall ruff --help python -m ruff --help - name: "Upload wheels" uses: actions/upload-artifact@v3 with: name: wheels path: dist - name: "Archive binary" shell: bash run: | ARCHIVE_FILE=ruff-${{ inputs.tag }}-${{ matrix.platform.target }}.zip 7z a $ARCHIVE_FILE ./target/${{ matrix.platform.target }}/release/ruff.exe sha256sum $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 - name: "Upload binary" uses: actions/upload-artifact@v3 with: name: binaries path: | *.zip *.sha256 linux: runs-on: ubuntu-latest strategy: matrix: target: - x86_64-unknown-linux-gnu - i686-unknown-linux-gnu steps: - uses: actions/checkout@v4 with: ref: ${{ inputs.sha }} - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} architecture: x64 - name: "Prep README.md" run: python scripts/transform_readme.py --target pypi - name: "Build wheels" uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} manylinux: auto args: --release --locked --out dist - name: "Test wheel" if: ${{ startsWith(matrix.target, 'x86_64') }} run: | pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall ruff --help python -m ruff --help - name: "Upload wheels" uses: actions/upload-artifact@v3 with: name: wheels path: dist - name: "Archive binary" run: | ARCHIVE_FILE=ruff-${{ inputs.tag }}-${{ matrix.target }}.tar.gz tar czvf $ARCHIVE_FILE -C target/${{ matrix.target }}/release ruff shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 - name: "Upload binary" uses: actions/upload-artifact@v3 with: name: binaries path: | *.tar.gz *.sha256 linux-cross: runs-on: ubuntu-latest strategy: matrix: platform: - target: aarch64-unknown-linux-gnu arch: aarch64 # see https://github.com/astral-sh/ruff/issues/3791 # and https://github.com/gnzlbg/jemallocator/issues/170#issuecomment-1503228963 maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16 - target: armv7-unknown-linux-gnueabihf arch: armv7 - target: s390x-unknown-linux-gnu arch: s390x - target: powerpc64le-unknown-linux-gnu arch: ppc64le - target: powerpc64-unknown-linux-gnu arch: ppc64 steps: - uses: actions/checkout@v4 with: ref: ${{ inputs.sha }} - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} - name: "Prep README.md" run: python scripts/transform_readme.py --target pypi - name: "Build wheels" uses: PyO3/maturin-action@v1 with: target: ${{ matrix.platform.target }} manylinux: auto docker-options: ${{ matrix.platform.maturin_docker_options }} args: --release --locked --out dist - uses: uraimo/run-on-arch-action@v2 if: matrix.platform.arch != 'ppc64' name: Test wheel with: arch: ${{ matrix.platform.arch }} distro: ubuntu20.04 githubToken: ${{ github.token }} install: | apt-get update apt-get install -y --no-install-recommends python3 python3-pip pip3 install -U pip run: | pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall ruff --help - name: "Upload wheels" uses: actions/upload-artifact@v3 with: name: wheels path: dist - name: "Archive binary" run: | ARCHIVE_FILE=ruff-${{ inputs.tag }}-${{ matrix.platform.target }}.tar.gz tar czvf $ARCHIVE_FILE -C target/${{ matrix.platform.target }}/release ruff shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 - name: "Upload binary" uses: actions/upload-artifact@v3 with: name: binaries path: | *.tar.gz *.sha256 musllinux: runs-on: ubuntu-latest strategy: matrix: target: - x86_64-unknown-linux-musl - i686-unknown-linux-musl steps: - uses: actions/checkout@v4 with: ref: ${{ inputs.sha }} - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} architecture: x64 - name: "Prep README.md" run: python scripts/transform_readme.py --target pypi - name: "Build wheels" uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} manylinux: musllinux_1_2 args: --release --locked --out dist - name: "Test wheel" if: matrix.target == 'x86_64-unknown-linux-musl' uses: addnab/docker-run-action@v3 with: image: alpine:latest options: -v ${{ github.workspace }}:/io -w /io run: | apk add python3 python -m venv .venv .venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall .venv/bin/ruff check --help - name: "Upload wheels" uses: actions/upload-artifact@v3 with: name: wheels path: dist - name: "Archive binary" run: | ARCHIVE_FILE=ruff-${{ inputs.tag }}-${{ matrix.target }}.tar.gz tar czvf $ARCHIVE_FILE -C target/${{ matrix.target }}/release ruff shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 - name: "Upload binary" uses: actions/upload-artifact@v3 with: name: binaries path: | *.tar.gz *.sha256 musllinux-cross: runs-on: ubuntu-latest strategy: matrix: platform: - target: aarch64-unknown-linux-musl arch: aarch64 maturin_docker_options: -e JEMALLOC_SYS_WITH_LG_PAGE=16 - target: armv7-unknown-linux-musleabihf arch: armv7 steps: - uses: actions/checkout@v4 with: ref: ${{ inputs.sha }} - uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} - name: "Prep README.md" run: python scripts/transform_readme.py --target pypi - name: "Build wheels" uses: PyO3/maturin-action@v1 with: target: ${{ matrix.platform.target }} manylinux: musllinux_1_2 args: --release --locked --out dist docker-options: ${{ matrix.platform.maturin_docker_options }} - uses: uraimo/run-on-arch-action@v2 name: Test wheel with: arch: ${{ matrix.platform.arch }} distro: alpine_latest githubToken: ${{ github.token }} install: | apk add python3 run: | python -m venv .venv .venv/bin/pip3 install ${{ env.PACKAGE_NAME }} --no-index --find-links dist/ --force-reinstall .venv/bin/ruff check --help - name: "Upload wheels" uses: actions/upload-artifact@v3 with: name: wheels path: dist - name: "Archive binary" run: | ARCHIVE_FILE=ruff-${{ inputs.tag }}-${{ matrix.platform.target }}.tar.gz tar czvf $ARCHIVE_FILE -C target/${{ matrix.platform.target }}/release ruff shasum -a 256 $ARCHIVE_FILE > $ARCHIVE_FILE.sha256 - name: "Upload binary" uses: actions/upload-artifact@v3 with: name: binaries path: | *.tar.gz *.sha256 validate-tag: name: Validate tag runs-on: ubuntu-latest # If you don't set an input tag, it's a dry run (no uploads). if: ${{ inputs.tag }} steps: - uses: actions/checkout@v4 with: ref: main # We checkout the main branch to check for the commit - name: Check main branch if: ${{ inputs.sha }} run: | # Fetch the main branch since a shallow checkout is used by default git fetch origin main --unshallow if ! git branch --contains ${{ inputs.sha }} | grep -E '(^|\s)main$'; then echo "The specified sha is not on the main branch" >&2 exit 1 fi - name: Check tag consistency run: | # Switch to the commit we want to release git checkout ${{ inputs.sha }} version=$(grep "version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g') if [ "${{ inputs.tag }}" != "${version}" ]; then echo "The input tag does not match the version from pyproject.toml:" >&2 echo "${{ inputs.tag }}" >&2 echo "${version}" >&2 exit 1 else echo "Releasing ${version}" fi upload-release: name: Upload to PyPI runs-on: ubuntu-latest needs: - macos-universal - macos-x86_64 - windows - linux - linux-cross - musllinux - musllinux-cross - validate-tag # If you don't set an input tag, it's a dry run (no uploads). if: ${{ inputs.tag }} environment: name: release permissions: # For pypi trusted publishing id-token: write steps: - uses: actions/download-artifact@v3 with: name: wheels path: wheels - name: Publish to PyPi uses: pypa/gh-action-pypi-publish@release/v1 with: skip-existing: true packages-dir: wheels verbose: true tag-release: name: Tag release runs-on: ubuntu-latest needs: upload-release # If you don't set an input tag, it's a dry run (no uploads). if: ${{ inputs.tag }} permissions: # For git tag contents: write steps: - uses: actions/checkout@v4 with: ref: ${{ inputs.sha }} - name: git tag run: | git config user.email "hey@astral.sh" git config user.name "Ruff Release CI" git tag -m "v${{ inputs.tag }}" "v${{ inputs.tag }}" # If there is duplicate tag, this will fail. The publish to pypi action will have been a noop (due to skip # existing), so we make a non-destructive exit here git push --tags publish-release: name: Publish to GitHub runs-on: ubuntu-latest needs: tag-release # If you don't set an input tag, it's a dry run (no uploads). if: ${{ inputs.tag }} permissions: # For GitHub release publishing contents: write steps: - uses: actions/download-artifact@v3 with: name: binaries path: binaries - name: "Publish to GitHub" uses: softprops/action-gh-release@v1 with: draft: true files: binaries/* tag_name: v${{ inputs.tag }} docker-publish: # This action doesn't need to wait on any other task, it's easy to re-tag if something failed and we're validating # the tag here also name: Push Docker image ghcr.io/astral-sh/ruff runs-on: ubuntu-latest environment: name: release permissions: # For the docker push packages: write steps: - uses: actions/checkout@v4 with: ref: ${{ inputs.sha }} - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 with: images: ghcr.io/astral-sh/ruff - name: Check tag consistency # Unlike validate-tag we don't check if the commit is on the main branch, but it seems good enough since we can # change docker tags if: ${{ inputs.tag }} run: | version=$(grep "version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g') if [ "${{ inputs.tag }}" != "${version}" ]; then echo "The input tag does not match the version from pyproject.toml:" >&2 echo "${{ inputs.tag }}" >&2 echo "${version}" >&2 exit 1 else echo "Releasing ${version}" fi - name: "Build and push Docker image" uses: docker/build-push-action@v5 with: context: . platforms: linux/amd64,linux/arm64 # Reuse the builder cache-from: type=gha cache-to: type=gha,mode=max push: ${{ inputs.tag != '' }} tags: ghcr.io/astral-sh/ruff:latest,ghcr.io/astral-sh/ruff:${{ inputs.tag || 'dry-run' }} labels: ${{ steps.meta.outputs.labels }} # After the release has been published, we update downstream repositories # This is separate because if this fails the release is still fine, we just need to do some manual workflow triggers update-dependents: name: Update dependents runs-on: ubuntu-latest needs: publish-release steps: - name: "Update pre-commit mirror" uses: actions/github-script@v7 with: github-token: ${{ secrets.RUFF_PRE_COMMIT_PAT }} script: | github.rest.actions.createWorkflowDispatch({ owner: 'astral-sh', repo: 'ruff-pre-commit', workflow_id: 'main.yml', ref: 'main', })