# Much of this workflow is adapted from the ripgrep release workflow. # https://github.com/BurntSushi/ripgrep/blob/master/.github/workflows/release.yml name: release on: push: # Enable when testing release infrastructure on a branch. # branches: # - fix-releases tags: # For now, real releases always use `workflow_dispatch`, and running the workflow on tag pushes # is only done in testing. This is because we usually push too many tags at once for the `push` # event to be triggered, since there are usually more than 3 crates tagged together. So the # `push` trigger doesn't usually work. If we allow it, we risk running the workflow twice if # it is also manually triggered based on the assumption that it would not run. See #1970 for # details. See also the `run-release-workflow` and `roll-release` recipes in the `justfile`. # - 'v*' - 'v*-DO-NOT-USE' # Pattern for tags used to test the workflow (usually done in a fork). workflow_dispatch: permissions: contents: read # This is set more permissively in jobs that need `write`. defaults: run: shell: bash # Use `bash` even in the Windows jobs. jobs: # Create a draft release, initially with no binary assets attached. create-release: runs-on: ubuntu-slim permissions: contents: write # Allows the use of `gh release create`. # env: # # Set to force version number, e.g., when no tag exists. # VERSION: TEST-0.0.0 steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Get the release version from the tag if: env.VERSION == '' run: echo "VERSION=$REF_NAME" >> "$GITHUB_ENV" env: REF_NAME: ${{ github.ref_name }} - name: Validate version against Cargo.toml run: | manifest_version="$(yq -r .package.version Cargo.toml)" echo "version to name the release: $VERSION" echo "version Cargo.toml suggests: v$manifest_version" case "$VERSION" in "v$manifest_version" ) echo 'OK: Release name/version agrees with Cargo.toml version.' ;; TEST-* | *-DO-NOT-USE ) # NOTE: If changed, change it in `announce-release` below, too. echo 'OK: Release name/version is strange but marked as such.' ;; "$manifest_version" ) echo 'STOPPING: Release name/version is missing the leading "v".' exit 1 ;; * ) echo 'STOPPING: Release name/version and Cargo.toml version do not match.' echo 'STOPPING: Usually this means either a wrong tag name or wrong version in Cargo.toml.' echo 'STOPPING: If intended, prepend `TEST-` or append `-DO-NOT-USE` to the release name.' exit 1 ;; esac - name: Create GitHub release run: gh release create "$VERSION" --title="$VERSION" --draft env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} outputs: version: ${{ env.VERSION }} # Build for a particular feature and target, and attach an archive for it. build-release: needs: [ create-release ] strategy: matrix: target: - x86_64-unknown-linux-musl - x86_64-unknown-linux-gnu - i686-unknown-linux-musl - i686-unknown-linux-gnu - aarch64-unknown-linux-musl - aarch64-unknown-linux-gnu - arm-unknown-linux-musleabihf - arm-unknown-linux-gnueabihf - powerpc64le-unknown-linux-gnu - riscv64gc-unknown-linux-gnu - s390x-unknown-linux-gnu - x86_64-apple-darwin - aarch64-apple-darwin - x86_64-pc-windows-msvc - x86_64-pc-windows-gnu - i686-pc-windows-msvc - aarch64-pc-windows-msvc # When changing these features, make the same change in build-macos-universal2-release. feature: [ small, lean, max, max-pure ] include: - rust: stable - target: x86_64-unknown-linux-musl os: ubuntu-latest - target: x86_64-unknown-linux-gnu os: ubuntu-latest - target: i686-unknown-linux-musl os: ubuntu-latest - target: i686-unknown-linux-gnu os: ubuntu-latest - target: aarch64-unknown-linux-musl os: ubuntu-latest - target: aarch64-unknown-linux-gnu os: ubuntu-latest - target: arm-unknown-linux-musleabihf os: ubuntu-latest - target: arm-unknown-linux-gnueabihf os: ubuntu-latest - target: powerpc64le-unknown-linux-gnu os: ubuntu-latest - target: riscv64gc-unknown-linux-gnu os: ubuntu-latest - target: s390x-unknown-linux-gnu os: ubuntu-latest - target: x86_64-apple-darwin os: macos-15-intel - target: aarch64-apple-darwin os: macos-latest - target: x86_64-pc-windows-msvc os: windows-latest - target: x86_64-pc-windows-gnu os: windows-latest rust: stable-x86_64-gnu - target: i686-pc-windows-msvc os: windows-latest - target: aarch64-pc-windows-msvc os: windows-latest # On linux we build with musl which causes trouble with open-ssl. For now, just build max-pure there. # It's a TODO. See https://github.com/GitoxideLabs/gitoxide/issues/1242. exclude: - target: x86_64-unknown-linux-musl feature: small - target: x86_64-unknown-linux-musl feature: lean - target: x86_64-unknown-linux-musl feature: max - target: x86_64-unknown-linux-gnu feature: small - target: x86_64-unknown-linux-gnu feature: lean - target: x86_64-unknown-linux-gnu feature: max - target: i686-unknown-linux-musl feature: small - target: i686-unknown-linux-musl feature: lean - target: i686-unknown-linux-musl feature: max - target: i686-unknown-linux-gnu feature: small - target: i686-unknown-linux-gnu feature: lean - target: i686-unknown-linux-gnu feature: max - target: aarch64-unknown-linux-musl feature: small - target: aarch64-unknown-linux-musl feature: lean - target: aarch64-unknown-linux-musl feature: max - target: aarch64-unknown-linux-gnu feature: small - target: aarch64-unknown-linux-gnu feature: lean - target: aarch64-unknown-linux-gnu feature: max - target: arm-unknown-linux-musleabihf feature: small - target: arm-unknown-linux-musleabihf feature: lean - target: arm-unknown-linux-musleabihf feature: max - target: arm-unknown-linux-gnueabihf feature: small - target: arm-unknown-linux-gnueabihf feature: lean - target: arm-unknown-linux-gnueabihf feature: max - target: powerpc64le-unknown-linux-gnu feature: small - target: powerpc64le-unknown-linux-gnu feature: lean - target: powerpc64le-unknown-linux-gnu feature: max - target: riscv64gc-unknown-linux-gnu feature: small - target: riscv64gc-unknown-linux-gnu feature: lean - target: riscv64gc-unknown-linux-gnu feature: max - target: s390x-unknown-linux-gnu feature: small - target: s390x-unknown-linux-gnu feature: lean - target: s390x-unknown-linux-gnu feature: max runs-on: ${{ matrix.os }} permissions: contents: write # Allows the use of `gh release upload`. env: RUST_BACKTRACE: '1' # Emit backtraces on panics. CARGO_TERM_COLOR: always CLICOLOR: '1' CARGO: cargo # On Linux, this will be changed to `cross` in a later step. FEATURE: ${{ matrix.feature }} VERSION: ${{ needs.create-release.outputs.version }} TARGET: ${{ matrix.target }} TARGET_FLAGS: --target=${{ matrix.target }} TARGET_DIR: target/${{ matrix.target }} PROFILE: release-github steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Install packages (Ubuntu) # Because openssl doesn't work on musl by default, we resort to max-pure. # And that won't need any dependency, so we can skip this or use `continue-on-error`. if: matrix.os == 'ubuntu-latest-disabled' run: | sudo apt-get update sudo apt-get install -y --no-install-recommends xz-utils liblz4-tool musl-tools - name: Install Rust env: RUST: ${{ matrix.rust }} run: | rustup update "$RUST" rustup default "$RUST" rustup target add "$TARGET" - name: Use Cross if: matrix.os == 'ubuntu-latest' run: | cargo install cross echo 'CARGO=cross' >> "$GITHUB_ENV" - name: Show command used for Cargo run: | echo "cargo command is: $CARGO" echo "target flag is: $TARGET_FLAGS" echo "target dir is: $TARGET_DIR" - name: Build release binary (with extra optimizations) run: | "$CARGO" build --verbose --profile="$PROFILE" "$TARGET_FLAGS" --no-default-features --features="$FEATURE" - name: Determine archive basename run: echo "ARCHIVE=gitoxide-$FEATURE-$VERSION-$TARGET" >> "$GITHUB_ENV" - name: Pre-populate directory for archive run: | mkdir -- "$ARCHIVE" cp -- {README.md,LICENSE-*,CHANGELOG.md} "$ARCHIVE/" - name: Build archive (Windows) if: matrix.os == 'windows-latest' run: | file -- "$TARGET_DIR/$PROFILE"/{ein,src}.exe cp -- "$TARGET_DIR/$PROFILE"/{ein,src}.exe "$ARCHIVE/" 7z a "$ARCHIVE.zip" "$ARCHIVE" /usr/bin/core_perl/shasum --algorithm=256 --binary -- "$ARCHIVE.zip" > "$ARCHIVE.zip.sha256" echo "ASSET=$ARCHIVE.zip" >> "$GITHUB_ENV" echo "ASSET_SUM=$ARCHIVE.zip.sha256" >> "$GITHUB_ENV" - name: Build archive (Unix) if: matrix.os != 'windows-latest' run: | file -- "$TARGET_DIR/$PROFILE"/{ein,src} cp -- "$TARGET_DIR/$PROFILE"/{ein,src} "$ARCHIVE/" tar czf "$ARCHIVE.tar.gz" -- "$ARCHIVE" shasum --algorithm=256 --binary -- "$ARCHIVE.tar.gz" > "$ARCHIVE.tar.gz.sha256" echo "ASSET=$ARCHIVE.tar.gz" >> "$GITHUB_ENV" echo "ASSET_SUM=$ARCHIVE.tar.gz.sha256" >> "$GITHUB_ENV" - name: Upload release archive run: gh release upload "$VERSION" "$ASSET" "$ASSET_SUM" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Add a macOS universal binary archive for a feature using its built aarch64 and x86_64 assets. build-macos-universal2-release: runs-on: macos-latest needs: [ create-release, build-release ] strategy: matrix: # These features need to be exactly the same as the features in build-release. feature: [ small, lean, max, max-pure ] permissions: contents: write # Allows the use of `gh release upload`. env: BASH_ENV: ./helpers.sh REPOSITORY: ${{ github.repository }} FEATURE: ${{ matrix.feature }} VERSION: ${{ needs.create-release.outputs.version }} steps: - name: Define helper function run: | name() { echo "gitoxide-$FEATURE-$VERSION-$1-apple-darwin"; } declare -f name >> "$BASH_ENV" - name: Obtain single-architecture releases run: | gh release --repo="$REPOSITORY" download "$VERSION" \ --pattern="$(name aarch64).tar.gz" --pattern="$(name aarch64).tar.gz.sha256" \ --pattern="$(name x86_64).tar.gz" --pattern="$(name x86_64).tar.gz.sha256" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Unpack single-architecture releases run: | shasum --check -- "$(name aarch64).tar.gz.sha256" "$(name x86_64).tar.gz.sha256" tar xf "$(name aarch64).tar.gz" tar xf "$(name x86_64).tar.gz" - name: Determine archive basename run: echo "ARCHIVE=$(name universal)" >> "$GITHUB_ENV" - name: Pre-populate directory for archive run: | cp -R -- "$(name aarch64)" "$ARCHIVE" rm -- "$ARCHIVE"/{ein,src} - name: Create Universal 2 binaries run: | for bin in ein src; do lipo -create "$(name aarch64)/$bin" "$(name x86_64)/$bin" -output "$ARCHIVE/$bin" file -- "$ARCHIVE/$bin" done - name: Build archive run: | tar czf "$ARCHIVE.tar.gz" -- "$ARCHIVE" shasum --algorithm=256 --binary -- "$ARCHIVE.tar.gz" > "$ARCHIVE.tar.gz.sha256" echo "ASSET=$ARCHIVE.tar.gz" >> "$GITHUB_ENV" echo "ASSET_SUM=$ARCHIVE.tar.gz.sha256" >> "$GITHUB_ENV" - name: Upload release archive run: gh release --repo="$REPOSITORY" upload "$VERSION" "$ASSET" "$ASSET_SUM" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Check for some problems, consolidate checksum files into one, and mark the release non-draft. publish-release: runs-on: ubuntu-slim needs: [ create-release, build-release, build-macos-universal2-release ] permissions: contents: write # Allows use of `gh release` for `upload`, `delete-asset`, and `edit`. env: REPOSITORY: ${{ github.repository }} VERSION: ${{ needs.create-release.outputs.version }} steps: - name: Discover assets run: | gh release --repo="$REPOSITORY" view "$VERSION" --json assets --jq '.assets.[].name' > assets.txt env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Show all individual asset names run: cat assets.txt # The `features` array is repeated because GHA doesn't support YAML anchors. # We will check that the macOS `universal` features match the others exactly. # In the future this and the next step may be removed, or expanded to do more validation. - name: Extract macOS asset names by architecture run: | for arch in aarch64 x86_64 universal; do grep -Fwe "$arch-apple-darwin" assets.txt | sort | tee -- "$arch.txt" done - name: Check macOS archive features run: | mask() { sed -E 's/\w+-apple-darwin/-apple-darwin/' -- "$1.txt"; } diff -- <(mask aarch64) <(mask universal) diff -- <(mask x86_64) <(mask universal) - name: Clean up local temporary macOS asset list files run: rm {assets,aarch64,x86_64,universal}.txt - name: Retrieve all individual checksums run: gh release --repo="$REPOSITORY" download "$VERSION" --pattern='gitoxide-*.sha256' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Concatenate checksums into one file run: cat gitoxide-*.sha256 > hashes.sha256 - name: Upload the combined checksum file run: gh release --repo="$REPOSITORY" upload "$VERSION" hashes.sha256 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # If any step of any job fails before this, the draft still has the individual checksum files. - name: Remove the individual checksum file assets run: | for sumfile in gitoxide-*.sha256; do gh release --repo="$REPOSITORY" delete-asset "$VERSION" "$sumfile" --yes done env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Publish the release if: vars.DRY_RUN_RELEASE != 'true' && vars.DRY_RUN_RELEASE != 'yes' && vars.DRY_RUN_RELEASE != '1' run: gh release --repo="$REPOSITORY" edit "$VERSION" --draft=false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Comment in a locked discussion that notifies about only `gitoxide` (e.g. not `src-*`) releases. announce-release: runs-on: ubuntu-slim needs: [ create-release, publish-release ] permissions: contents: write # Needed to distinguish unpublished (still draft) from missing releases. discussions: write # For adding a comment in the announcement discussion. env: REPOSITORY: ${{ github.repository }} VERSION: ${{ needs.create-release.outputs.version }} DISCUSSION_URL: ${{ vars.RELEASE_ANNOUNCEMENTS_URL }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Find the discussion ID run: | [[ "$DISCUSSION_URL" =~ ^https://github\.com/([^/:@]+)/([^/:@]+)/discussions/([0-9]+)$ ]] owner="${BASH_REMATCH[1]}" name="${BASH_REMATCH[2]}" number="${BASH_REMATCH[3]}" id="$(gh api graphql -f query=' query GetDiscussionId($owner: String!, $name: String!, $number: Int!) { repository(owner: $owner, name: $name) { discussion(number: $number) { id } } }' -F owner="$owner" -F name="$name" -F number="$number" --jq .data.repository.discussion.id)" echo "DISCUSSION_ID=$id" >> "$GITHUB_ENV" - name: Avoid announcing a test in a non-test thread run: | case "$VERSION" in TEST-* | *-DO-NOT-USE ) # NOTE: Should be the same pattern as in `create-release` above. echo "The release name indicates testing, so we'll only post if the thread is for that." ;; * ) is_draft="$(gh release --repo="$REPOSITORY" view "$VERSION" --json isDraft --jq .isDraft)" if [ "$is_draft" = false ]; then exit 0 # OK to post in a non-test announcement thread. fi echo "The release is not published, so we'll only post if the thread is for testing." ;; esac title="$(gh api graphql -f query=' query($id: ID!) { node(id: $id) { ... on Discussion { title } } }' -F id="$DISCUSSION_ID" --jq .data.node.title)" grep -Eiqz '^[[(]?test\b' <<<"$title" - name: Post the comment run: | grep -Eqx '[[:alnum:]._+-]+' <<<"$VERSION" # Ensure the version needs no sanitization. release_url="https://github.com/$REPOSITORY/releases/tag/$VERSION" comment_body="\`gitoxide\` [$VERSION]($release_url) has been released." gh api graphql -f query=' mutation PostComment($discussionId: ID!, $body: String!) { addDiscussionComment(input: {discussionId: $discussionId, body: $body}) { comment { id body } } }' -F discussionId="$DISCUSSION_ID" -F body="$comment_body" installation: strategy: matrix: build: [ win-msvc, win-gnu, win32-msvc, win32-gnu ] include: - build: win-msvc os: windows-latest rust: stable target: x86_64-pc-windows-msvc - build: win-gnu os: windows-latest rust: stable-x86_64-gnu target: x86_64-pc-windows-gnu - build: win32-msvc os: windows-latest rust: stable target: i686-pc-windows-msvc - build: win32-gnu os: windows-latest rust: stable target: i686-pc-windows-gnu runs-on: ${{ matrix.os }} env: RUST: ${{ matrix.rust }} TARGET: ${{ matrix.target }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Install Rust run: | rustup update "$RUST" rustup default "$RUST" rustup target add "$TARGET" - uses: msys2/setup-msys2@4f806de0a5a7294ffabaff804b38a9b435a73bda # v2.30.0 with: msystem: MINGW${{ startsWith(matrix.target, 'i686-') && '32' || '64' }} pacboy: cc:p path-type: inherit - name: 'Installation from crates.io: gitoxide' run: | cargo +"$RUST" install --target "$TARGET" --no-default-features \ --features max-pure --target-dir install-artifacts --debug --force gitoxide shell: msys2 {0}