name: 'cargo-semver-checks'
description: 'Ensure the public API in your Rust crate follows semantic versioning'
branding:
  icon: 'check-circle'
  color: 'green'
inputs:
  crate-name:
    description: 'The crate whose API to check for semver'
    required: false
    default: ''
  crate-target:
    description: 'By default, check the library target of the crate. To check a different target (e.g. a binary target), set this to `--bin <NAME>`'
    required: false
    default: '--lib'
  version-tag-prefix:
    description: 'The prefix to use for the git tag for a version; the default "v" creates tags like "v1.0.0"'
    required: false
    default: 'v'
runs:
  using: "composite"
  steps:
    - name: Install rust
      uses: actions-rs/toolchain@v1
      with:
        toolchain: stable
        profile: minimal
    - name: Build rustdoc and check it
      shell: bash
      run: |
        set -euxo pipefail

        # Colorize output, since GitHub Actions terminals support color.
        export CARGO_TERM_COLOR=always

        # Record the current git sha, so we can come back to it after generating the baseline.
        export CURRENT_GIT_SHA="$(git rev-parse HEAD)"

        # We add `--all-features` to semver-check every part of the crate.
        # In principle, semver can be broken by moving public API code into a feature.
        # Checking this requires rebuilding rustdoc multiple times with different sets of features,
        # which can get expensive and is therefore left to individual maintainers' discretion.
        #
        # We add `--document-private-items` because for some reason, rustdoc seems to not always
        # generate all implemented trait information without it:
        # https://github.com/obi1kenobi/cargo-semver-check/issues/32
        export RUSTDOC_EARLY_FLAGS="${{ inputs.crate-target }} --all-features"
        export RUSTDOC_LATE_FLAGS="--document-private-items -Zunstable-options --output-format json"

        export PACKAGE_NAME="${{ inputs.crate-name }}"
        if [[ "$PACKAGE_NAME" == '' ]]; then
          export PACKAGE_NAME="$("$GITHUB_ACTION_PATH/find_workspace_crates.sh")"
        else
          # cargo rustdoc uses the exact package name, not the "underscores" version.
          export RUSTDOC_EARLY_FLAGS="--package $PACKAGE_NAME $RUSTDOC_EARLY_FLAGS"
        fi
        export PACKAGE_NAME_WITH_UNDERSCORES="$(echo "$PACKAGE_NAME" | tr '-' '_')"

        # Switch to the tag for the correct baseline version,
        # then build rustdoc JSON.
        #
        # We *do not* want to record and reuse the target directory path
        # across different git commits, since it may be at a different location
        # in different commits.
        export COMPARISON_TAG="${{ inputs.version-tag-prefix }}$("$GITHUB_ACTION_PATH/find_comparison_version.sh" "$PACKAGE_NAME")"
        git fetch --depth=1 origin "+refs/tags/$COMPARISON_TAG:refs/tags/$COMPARISON_TAG"
        git checkout "$COMPARISON_TAG"
        RUSTC_BOOTSTRAP=1 cargo rustdoc $RUSTDOC_EARLY_FLAGS -- $RUSTDOC_LATE_FLAGS
        mv "$(cargo metadata --format-version 1 | jq -r .target_directory)/doc/$PACKAGE_NAME_WITH_UNDERSCORES.json" /tmp/baseline.json

        # Return to the original git sha.
        git checkout "$CURRENT_GIT_SHA"

        # Build rustdoc JSON for the current version, and move it to /tmp/
        # so it doesn't get overwritten by the baseline build.
        RUSTC_BOOTSTRAP=1 cargo rustdoc $RUSTDOC_EARLY_FLAGS -- $RUSTDOC_LATE_FLAGS
        mv "$(cargo metadata --format-version 1 | jq -r .target_directory)/doc/$PACKAGE_NAME_WITH_UNDERSCORES.json" /tmp/current.json

        # Check for semver violations.
        cargo install cargo-semver-checks --locked
        cargo semver-checks check-release --current /tmp/current.json --baseline /tmp/baseline.json