| --- |
| name: Release Binary |
| |
| # DISCUSS: adding a whitelist for binaries to publish |
| on: |
| workflow_call: |
| inputs: |
| submodules: |
| description: Whether to checkout submodules. Use `true` to checkout submodules or `recursive` to recursively checkout submodules -- see https://github.com/actions/checkout |
| required: false |
| type: string |
| default: false |
| targets: |
| description: Whitelist of compilation targets to upload GitHub release binaries for. Must be a subset of supported targets. |
| type: string |
| default: | |
| aarch64-apple-darwin |
| x86_64-apple-darwin |
| x86_64-pc-windows-gnu |
| x86_64-unknown-linux-gnu |
| toolchain: |
| description: Rust toolchain specification |
| type: string |
| default: stable |
| disable-semantic-release-cargo: |
| description: Disable semantic-release-cargo in your release flow. Only takes effect if the action semantic-release config is used. |
| type: boolean |
| default: false |
| disable-semantic-release-git: |
| description: Disable @semantic-release/git in your release flow. Only takes effect if the action semantic-release config is used. |
| type: boolean |
| default: false |
| secrets: |
| cargo-registry-token: |
| description: API token for writing to your cargo registry |
| |
| # Only allow one release workflow to execute at a time, since each release |
| # workflow uses shared resources (git tags, package registries) |
| concurrency: |
| group: ${{ github.workflow }} |
| |
| env: |
| RUST_BACKTRACE: 1 |
| |
| jobs: |
| get-next-version: |
| uses: semantic-release-action/next-release-version/.github/workflows/next-release-version.yml@v4 |
| |
| configure-job-matrix: |
| name: Configure compilation targets |
| runs-on: ubuntu-latest |
| outputs: |
| compilation-matrix: ${{ steps.configure-job-matrix.outputs.compilation-matrix }} |
| test-matrix: ${{ steps.configure-job-matrix.outputs.test-matrix }} |
| |
| steps: |
| - name: Checkout |
| uses: actions/checkout@v4 |
| |
| - id: sanitize-input-targets |
| env: |
| raw_targets: ${{ inputs.targets }} |
| run: | |
| : sanitize targets input |
| |
| targets="$( |
| jq \ |
| --null-input \ |
| --raw-input \ |
| '[inputs] | map(splits("\\s+") | select(length > 0))' \ |
| <<< "$raw_targets" |
| )" |
| |
| # TODO: enforce that every target is in the whitelist of targets |
| |
| # Array<string> |
| { |
| echo "targets<<EOF" |
| echo "$targets" |
| echo "EOF" |
| } >> "$GITHUB_OUTPUT" |
| |
| - id: get-binary-names |
| run: | |
| : detect binary names |
| |
| # Array<{ binary_name: string, src_path: string }> |
| binary_manifest="$( |
| cargo metadata --no-deps --format-version 1 \ |
| | jq '.packages | map(.targets[] | select(.kind[] | IN("bin")) | { binary_name: .name, src_path })' |
| )" |
| |
| # Validate there are no binary-name collisions |
| # https://github.com/rust-lang/cargo/issues/6313 |
| |
| # Array<string> |
| name_collisions="$( |
| jq \ |
| 'group_by(.binary_name) | map(select(length > 1)[] | .binary_name) | unique' \ |
| <<< "$binary_manifest" |
| )" |
| |
| has_name_collisions="$(jq 'length > 0' <<< "$name_collisions")" |
| |
| if [[ $has_name_collisions == true ]]; then |
| targets="$(jq --raw-output 'join(", ")' <<< "$name_collisions")" |
| echo "::error title={Binary name collision detected}::Refusing to process binary targets \"$targets\" due to name collisions" |
| exit 1 |
| fi |
| |
| binary_names="$(jq 'map({ binary_name })' <<< "$binary_manifest")" |
| |
| # Array<{ binary_name: string }> |
| { |
| echo "binary-names<<EOF" |
| echo "$binary_names" |
| echo "EOF" |
| } >> "$GITHUB_OUTPUT" |
| |
| - id: configure-job-matrix |
| run: | |
| : configure compilation matrix |
| |
| # Perform a cartesian product of target_triples and binary_names |
| compilation_matrix="$( |
| jq \ |
| --null-input \ |
| --argjson targets_whitelist "$targets_whitelist" \ |
| --argjson target_triples "$target_triples" \ |
| --argjson binary_names "$binary_names" \ |
| '[$target_triples[] | select(.target | IN($targets_whitelist[])) + $binary_names[]]' |
| )" |
| |
| # TODO: support running tests on aarch64-apple-darwin and x86_64-pc-windows-gnu. Cross was complaining |
| test_matrix="$( |
| jq \ |
| --null-input \ |
| --argjson targets_whitelist "$targets_whitelist" \ |
| --argjson target_triples "$target_triples" \ |
| '[$target_triples[] | select((.target | IN($targets_whitelist[])) and (.target != "aarch64-apple-darwin") and (.target != "x86_64-pc-windows-gnu"))]' |
| )" |
| |
| { |
| echo "compilation-matrix<<EOF" |
| echo "$compilation_matrix" |
| echo "EOF" |
| |
| echo "test-matrix<<EOF" |
| echo "$test_matrix" |
| echo "EOF" |
| } >> "$GITHUB_OUTPUT" |
| env: |
| targets_whitelist: ${{ steps.sanitize-input-targets.outputs.targets }} |
| binary_names: ${{ steps.get-binary-names.outputs.binary-names }} |
| target_triples: | |
| [ |
| { |
| "target": "aarch64-apple-darwin", |
| "host": "macOS-latest", |
| "cross": true |
| }, |
| { |
| "target": "aarch64-unknown-linux-gnu", |
| "host": "ubuntu-latest", |
| "cross": true |
| }, |
| { |
| "target": "aarch64-unknown-linux-musl", |
| "host": "ubuntu-latest", |
| "cross": true |
| }, |
| { |
| "target": "i686-unknown-linux-gnu", |
| "host": "ubuntu-latest", |
| "cross": true |
| }, |
| { |
| "target": "i686-unknown-linux-musl", |
| "host": "ubuntu-latest", |
| "cross": true |
| }, |
| { |
| "target": "x86_64-apple-darwin", |
| "host": "macOS-latest", |
| "cross": false |
| }, |
| { |
| "target": "x86_64-pc-windows-gnu", |
| "host": "ubuntu-latest", |
| "cross": true |
| }, |
| { |
| "target": "x86_64-unknown-linux-gnu", |
| "host": "ubuntu-latest", |
| "cross": false |
| }, |
| { |
| "target": "x86_64-unknown-linux-musl", |
| "host": "ubuntu-latest", |
| "cross": false |
| } |
| ] |
| |
| test: |
| name: Test ${{ matrix.test.target }} |
| runs-on: ${{ matrix.test.host }} |
| needs: |
| - configure-job-matrix |
| |
| strategy: |
| fail-fast: false |
| matrix: |
| test: ${{ fromJson(needs.configure-job-matrix.outputs.test-matrix) }} |
| |
| steps: |
| ### |
| # Phase 1: set up |
| |
| - name: Checkout |
| uses: actions/checkout@v4 |
| with: |
| submodules: ${{ inputs.submodules }} |
| |
| - name: Install build inputs |
| if: runner.os == 'Linux' && !matrix.test.cross && endsWith(matrix.test.target, '-musl') |
| run: sudo apt install musl-tools |
| |
| - name: Install Rust toolchain |
| uses: dtolnay/rust-toolchain@master |
| with: |
| toolchain: ${{ inputs.toolchain }} |
| target: ${{ matrix.test.target }} |
| |
| - name: Install cross |
| uses: taiki-e/install-action@v2 |
| if: matrix.test.cross |
| with: |
| tool: cross |
| |
| - name: Cache Rust dependencies |
| uses: Swatinem/rust-cache@v2 |
| |
| ### |
| # Phase 2: test |
| |
| # Separate compilation from testing to accurately report the runtime of each |
| |
| - env: |
| CARGO: ${{ matrix.test.cross && 'cross' || 'cargo' }} |
| run: | |
| : build tests |
| $CARGO test \ |
| --target ${{ matrix.test.target }} \ |
| --no-run \ |
| --locked |
| |
| - env: |
| CARGO: ${{ matrix.test.cross && 'cross' || 'cargo' }} |
| run: | |
| : tests |
| $CARGO test \ |
| --target ${{ matrix.test.target }} \ |
| --locked \ |
| -- \ |
| --nocapture |
| |
| # Compile release artifacts |
| build-release: |
| name: (${{ matrix.build.binary_name }}, ${{ matrix.build.target }}) |
| if: needs.get-next-version.outputs.new-release-published == 'true' |
| runs-on: ${{ matrix.build.host }} |
| env: |
| VERSION: ${{ needs.get-next-version.outputs.new-release-version }} |
| needs: |
| - get-next-version |
| - configure-job-matrix |
| |
| strategy: |
| fail-fast: false |
| matrix: |
| build: ${{ fromJson(needs.configure-job-matrix.outputs.compilation-matrix) }} |
| |
| steps: |
| ### |
| # Phase 1: set up |
| |
| - name: Checkout |
| uses: actions/checkout@v4 |
| with: |
| submodules: ${{ inputs.submodules }} |
| |
| - name: Install build inputs |
| if: runner.os == 'Linux' && !matrix.build.cross && endsWith(matrix.build.target, '-musl') |
| run: sudo apt install musl-tools |
| |
| - name: Install Rust toolchain |
| uses: dtolnay/rust-toolchain@master |
| with: |
| toolchain: ${{ inputs.toolchain }} |
| target: ${{ matrix.build.target }} |
| |
| - name: Install cross |
| uses: taiki-e/install-action@v2 |
| if: matrix.build.cross |
| with: |
| tool: cross |
| |
| - name: Cache Rust dependencies |
| uses: Swatinem/rust-cache@v2 |
| |
| ### |
| # Phase 2: compile release binary |
| |
| - name: Compile release binary |
| env: |
| CARGO: ${{ matrix.build.cross && 'cross' || 'cargo' }} |
| run: | |
| ${{ env.CARGO }} build \ |
| --verbose \ |
| --release \ |
| --bin ${{ matrix.build.binary_name }} \ |
| --target ${{ matrix.build.target }} |
| |
| ### |
| # Phase 3: configure release artifacts |
| |
| - name: Install semantic-release-cargo |
| uses: EricCrosson/install-github-release-binary@v2 |
| with: |
| targets: semantic-release-cargo/semantic-release-cargo@v2 |
| |
| # NOTE: semantic-release-cargo currently implements the "lock-step" |
| # versioning strategy, where all crates in a workspace are bumped to the |
| # next version simultaneously. |
| - name: Prepare semantic-release for Rust |
| run: | |
| semantic-release-cargo \ |
| --verbose --verbose --verbose \ |
| prepare ${{ needs.get-next-version.outputs.new-release-version }} |
| |
| - name: Create release artifacts ( non-Windows ) |
| if: matrix.build.target != 'x86_64-pc-windows-gnu' |
| run: | |
| mkdir dist |
| cp \ |
| target/${{ matrix.build.target }}/release/${{ matrix.build.binary_name }} \ |
| dist/${{ matrix.build.binary_name }}-${{ matrix.build.target }} |
| |
| - name: Create release artifacts ( Windows ) |
| if: matrix.build.target == 'x86_64-pc-windows-gnu' |
| run: | |
| mkdir dist |
| cp \ |
| target/${{ matrix.build.target }}/release/${{ matrix.build.binary_name }}.exe \ |
| dist/${{ matrix.build.binary_name }}-${{ matrix.build.target }} |
| |
| - name: Create binary checksum |
| run: | |
| shasum \ |
| --algorithm 256 \ |
| --binary ${{ matrix.build.binary_name }}-${{ matrix.build.target }} \ |
| | tee ${{ matrix.build.binary_name }}-${{ matrix.build.target }}-SHA256SUM.txt |
| working-directory: ./dist |
| |
| - name: Upload release artifacts |
| uses: actions/upload-artifact@v4 |
| with: |
| name: ${{ matrix.build.binary_name }}-${{ matrix.build.target }} |
| path: | |
| dist/${{ matrix.build.binary_name }}-${{ matrix.build.target }} |
| dist/${{ matrix.build.binary_name }}-${{ matrix.build.target }}-SHA256SUM.txt |
| if-no-files-found: error |
| retention-days: 1 |
| |
| release: |
| name: Release |
| if: needs.get-next-version.outputs.new-release-published == 'true' |
| runs-on: ubuntu-latest |
| needs: |
| - get-next-version |
| - configure-job-matrix |
| - test |
| - build-release |
| |
| steps: |
| - name: Release |
| uses: casbin-rs/semantic-release-action-rust/semantic-release-binary@master |
| with: |
| toolchain: ${{ inputs.toolchain }} |
| compiled-targets: ${{ needs.configure-job-matrix.outputs.compilation-matrix }} |
| cargo-registry-token: ${{ secrets.cargo-registry-token }} |
| disable-semantic-release-cargo: ${{ inputs.disable-semantic-release-cargo }} |
| disable-semantic-release-git: ${{ inputs.disable-semantic-release-git }} |
| next-version: ${{ needs.get-next-version.outputs.new-release-version }} |
| submodules: ${{ inputs.submodules }} |