Release #1090
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| permissions: | |
| contents: read | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| target: | |
| description: Package to be released | |
| required: true | |
| type: choice | |
| options: | |
| - install-action | |
| - install-action-manifest-schema | |
| version: | |
| description: Version to be increased | |
| required: true | |
| type: choice | |
| options: | |
| - patch | |
| - minor | |
| - major | |
| defaults: | |
| run: | |
| shell: bash --noprofile --norc -CeEuxo pipefail {0} | |
| concurrency: | |
| group: ${{ github.workflow }} | |
| cancel-in-progress: false | |
| jobs: | |
| prepare: | |
| if: github.repository_owner == 'taiki-e' && inputs.target == 'install-action' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| steps: | |
| - uses: taiki-e/checkout-action@v1 | |
| - uses: taiki-e/install-action@v2 | |
| with: | |
| tool: parse-changelog | |
| fallback: none | |
| - id: check | |
| run: | | |
| set +x | |
| IFS=$'\n\t' | |
| trap -- 's=$?; printf >&2 "%s\n" "${0##*/}:${LINENO}: \`${BASH_COMMAND}\` exit with ${s}"; exit ${s}' ERR | |
| retry() { | |
| for i in {1..10}; do | |
| if "$@"; then | |
| return 0 | |
| else | |
| sleep "${i}" | |
| fi | |
| done | |
| "$@" | |
| } | |
| bail() { | |
| printf '::error::%s\n' "$*" | |
| exit 1 | |
| } | |
| normalize_comma_or_space_separated() { | |
| # Normalize whitespace characters into space because it's hard to handle single input contains lines with POSIX sed alone. | |
| local list="${1//[$'\r\n\t']/ }" | |
| if [[ "${list}" == *","* ]]; then | |
| # If a comma is contained, consider it is a comma-separated list. | |
| # Drop leading and trailing whitespaces in each element. | |
| sed -E 's/ *, */,/g; s/^.//' <<<",${list}," | |
| else | |
| # Otherwise, consider it is a whitespace-separated list. | |
| # Convert whitespace characters into comma. | |
| sed -E 's/ +/,/g; s/^.//' <<<" ${list} " | |
| fi | |
| } | |
| if { sed --help 2>&1 || true; } | grep -Eq -e '-i extension'; then | |
| in_place=(-i '') | |
| else | |
| in_place=(-i) | |
| fi | |
| # shellcheck disable=SC2153 | |
| version="${VERSION}" | |
| printf '%s\n' "version(input): ${version}" | |
| # shellcheck disable=SC2153 | |
| tag_prefix="${TAG_PREFIX}" | |
| printf '%s\n' "tag_prefix: ${tag_prefix}" | |
| # shellcheck disable=SC2153 | |
| changelog="${CHANGELOG}" | |
| printf '%s\n' "changelog: ${changelog}" | |
| # Get the current date. | |
| release_date=$(date -u '+%Y-%m-%d') | |
| printf '%s\n' "release-date: ${release_date}" | |
| printf '%s\n' "release-date=${release_date}" >>"${GITHUB_OUTPUT}" | |
| # Get the current revision. | |
| rev=$(git rev-parse HEAD) | |
| printf '%s\n' "rev: ${rev}" | |
| printf '%s\n' "rev=${rev}" >>"${GITHUB_OUTPUT}" | |
| prev_version=$(parse-changelog --title-no-link "${changelog}" | cut -d' ' -f1) | |
| # Determine the new version number and tag name. | |
| case "${version}" in | |
| major | minor | patch) | |
| if [[ ! "${prev_version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| bail "pre-release/build-metadata" | |
| fi | |
| major="${prev_version%%.*}" | |
| minor_patch="${prev_version#*.}" | |
| minor="${minor_patch%%.*}" | |
| patch="${minor_patch#*.}" | |
| case "${version}" in | |
| major) version="$((major+1)).0.0" ;; | |
| minor) version="${major}.$((minor+1)).0" ;; | |
| patch) version="${major}.${minor}.$((patch+1))" ;; | |
| esac | |
| ;; | |
| *) version="${version#v}" ;; | |
| esac | |
| if [[ ! "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z\.-]+)?(\+[0-9A-Za-z\.-]+)?$ ]]; then | |
| bail "invalid version format '${version}'" | |
| fi | |
| printf '%s\n' "version: ${version}" | |
| printf '%s\n' "version=${version}" >>"${GITHUB_OUTPUT}" | |
| tag="${tag_prefix}${version}" | |
| printf '%s\n' "tag: ${tag}" | |
| printf '%s\n' "tag=${tag}" >>"${GITHUB_OUTPUT}" | |
| # Make sure the same release has not been created in the past. | |
| if gh release view "${tag}" &>/dev/null; then | |
| bail "tag '${tag}' has already been created and pushed" | |
| fi | |
| # Make sure that the release was created from an allowed branch. | |
| if ! git branch | grep -Eq '\* '"${BRANCH}"'$'; then | |
| bail "current branch is not '${BRANCH}'" | |
| fi | |
| changed_paths=() | |
| retry git fetch origin --tags &>/dev/null | |
| tags=$(git --no-pager tag | { grep -E "^${tag_prefix}[0-9]+" || true; }) | |
| if [[ -n "${tags}" ]]; then | |
| printf 'has-tags=true\n' >>"${GITHUB_OUTPUT}" | |
| # Make sure the same release does not exist in changelog. | |
| if grep -Eq "^## \\[${version//./\\.}\\]" "${changelog}"; then | |
| bail "release ${version} already exist in ${changelog}" | |
| fi | |
| if grep -Eq "^\\[${version//./\\.}\\]: " "${changelog}"; then | |
| bail "link to ${version} already exist in ${changelog}" | |
| fi | |
| # Update changelog. | |
| changed_paths+=("${changelog}") | |
| remote_url=$(grep -E '^\[Unreleased\]: https://' "${changelog}" | sed -E 's/^\[Unreleased\]: //; s/\.\.\.HEAD$//') | |
| prev_tag="${remote_url#*/compare/}" | |
| remote_url="${remote_url%/compare/*}" | |
| sed -E "${in_place[@]}" \ | |
| -e "s/^## \\[Unreleased\\]/## [Unreleased]\\n\\n## [${version}] - ${release_date}/" \ | |
| -e "s#^\[Unreleased\]: https://.*#[Unreleased]: ${remote_url}/compare/${tag}...HEAD\\n[${version}]: ${remote_url}/compare/${prev_tag}...${tag}#" "${changelog}" | |
| if ! grep -Eq "^## \\[${version//./\\.}\\] - ${release_date}$" "${changelog}"; then | |
| bail "failed to update ${changelog}" | |
| fi | |
| if ! grep -Eq "^\\[${version//./\\.}\\]: " "${changelog}"; then | |
| bail "failed to update ${changelog}" | |
| fi | |
| else | |
| # Make sure the release exists in changelog. | |
| if ! grep -Eq "^## \\[${version//./\\.}\\] - ${release_date}$" "${changelog}"; then | |
| bail "release ${version} does not exist in ${changelog} or has wrong release date" | |
| fi | |
| if ! grep -Eq "^\\[${version//./\\.}\\]: " "${changelog}"; then | |
| bail "link to ${version} does not exist in ${changelog}" | |
| fi | |
| fi | |
| # Make sure that a valid release note for this version exists. | |
| # https://github.com/taiki-e/parse-changelog | |
| changes=$(parse-changelog "${changelog}" "${version}") | |
| if [[ -z "${changes}" ]]; then | |
| bail "changelog for ${version} has no body" | |
| fi | |
| printf '============== CHANGELOG ==============\n' | |
| printf '%s\n' "${changes}" | |
| printf '=======================================\n' | |
| if [[ -n "${tags}" ]]; then | |
| git -c color.ui=always diff "${changed_paths[@]}" | |
| git add "${changed_paths[@]}" | |
| fi | |
| # Make sure that there is no unintended change. | |
| git add -N . | |
| git -c color.ui=always diff --exit-code | |
| ( | |
| set -x | |
| git show HEAD --shortstat | |
| ) | |
| env: | |
| VERSION: ${{ inputs.version }} | |
| TAG_PREFIX: v | |
| CHANGELOG: CHANGELOG.md | |
| BRANCH: main | |
| outputs: | |
| has-tags: ${{ steps.check.outputs.has-tags }} | |
| release-date: ${{ steps.check.outputs.release-date }} | |
| rev: ${{ steps.check.outputs.rev }} | |
| tag: ${{ steps.check.outputs.tag }} | |
| version: ${{ steps.check.outputs.version }} | |
| release: | |
| if: github.repository_owner == 'taiki-e' && inputs.target == 'install-action' | |
| needs: prepare | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| environment: | |
| name: release | |
| deployment: false | |
| permissions: | |
| contents: write # for taiki-e/create-gh-release-action | |
| steps: | |
| - uses: taiki-e/checkout-action@v1 | |
| - uses: taiki-e/install-action@v2 | |
| with: | |
| tool: parse-changelog | |
| fallback: none | |
| - name: Create and push release commit and tag | |
| id: push | |
| run: | | |
| set +x | |
| IFS=$'\n\t' | |
| trap -- 's=$?; printf >&2 "%s\n" "${0##*/}:${LINENO}: \`${BASH_COMMAND}\` exit with ${s}"; exit ${s}' ERR | |
| retry() { | |
| for i in {1..10}; do | |
| if "$@"; then | |
| return 0 | |
| else | |
| sleep "${i}" | |
| fi | |
| done | |
| "$@" | |
| } | |
| bail() { | |
| printf '::error::%s\n' "$*" | |
| exit 1 | |
| } | |
| if { sed --help 2>&1 || true; } | grep -Eq -e '-i extension'; then | |
| in_place=(-i '') | |
| else | |
| in_place=(-i) | |
| fi | |
| git config user.name 'Taiki Endo' | |
| git config user.email 'te316e89@gmail.com' | |
| # shellcheck disable=SC2153 | |
| version="${VERSION}" | |
| # shellcheck disable=SC2153 | |
| tag="${TAG}" | |
| # shellcheck disable=SC2153 | |
| changelog="${CHANGELOG}" | |
| # shellcheck disable=SC2153 | |
| release_date="${RELEASE_DATE}" | |
| # Make sure the current revision is same as prepare step. | |
| # --unshallow is necessary to successfully push the | |
| # "releases/${major_version_tag}" branch in the subsequent step. | |
| retry git fetch origin --unshallow &>/dev/null | |
| rev=$(git rev-parse HEAD) | |
| if [[ "${rev}" != "${PREPARE_REV}" ]]; then | |
| bail "revision difference between prepare step" | |
| fi | |
| # Make sure the same release has not been created in the past. | |
| if gh release view "${tag}" &>/dev/null; then | |
| bail "tag '${tag}' has already been created and pushed" | |
| fi | |
| # Make sure that the release was created from an allowed branch. | |
| if ! git branch | grep -Eq '\* '"${BRANCH}"'$'; then | |
| bail "current branch is not '${BRANCH}'" | |
| fi | |
| changed_paths=() | |
| if [[ "${HAS_TAGS}" == "true" ]]; then | |
| # Update changelog. | |
| changed_paths+=("${changelog}") | |
| remote_url=$(grep -E '^\[Unreleased\]: https://' "${changelog}" | sed -E 's/^\[Unreleased\]: //; s/\.\.\.HEAD$//') | |
| prev_tag="${remote_url#*/compare/}" | |
| remote_url="${remote_url%/compare/*}" | |
| sed -E "${in_place[@]}" \ | |
| -e "s/^## \\[Unreleased\\]/## [Unreleased]\\n\\n## [${version}] - ${release_date}/" \ | |
| -e "s#^\[Unreleased\]: https://.*#[Unreleased]: ${remote_url}/compare/${tag}...HEAD\\n[${version}]: ${remote_url}/compare/${prev_tag}...${tag}#" "${changelog}" | |
| if ! grep -Eq "^## \\[${version//./\\.}\\] - ${release_date}$" "${changelog}"; then | |
| bail "failed to update ${changelog}" | |
| fi | |
| if ! grep -Eq "^\\[${version//./\\.}\\]: " "${changelog}"; then | |
| bail "failed to update ${changelog}" | |
| fi | |
| fi | |
| changes=$(parse-changelog "${changelog}" "${version}") | |
| printf '============== CHANGELOG ==============\n' | |
| printf '%s\n' "${changes}" | |
| printf '=======================================\n' | |
| if [[ "${HAS_TAGS}" == "true" ]]; then | |
| # Create a release commit. | |
| ( | |
| set -x | |
| git add "${changed_paths[@]}" | |
| git commit -m "Release ${version}" | |
| ) | |
| fi | |
| prev_credential_helper=$(git config get --local credential.helper || true) | |
| if [[ -n "${prev_credential_helper}" ]]; then | |
| printf 'credential helper is already set (%s)\n' "${prev_credential_helper}" | |
| else | |
| protocol="${GITHUB_SERVER_URL%%://*}" | |
| hostname="${GITHUB_SERVER_URL#*://}" | |
| ( | |
| set -x | |
| git config --local credential.helper cache | |
| ) | |
| git credential approve <<EOF | |
| protocol=${protocol} | |
| host=${hostname} | |
| username=${GITHUB_ACTOR} | |
| password=${PUSH_TOKEN} | |
| EOF | |
| # Remove credential helper config on exit. | |
| trap -- '(set -x; git credential-cache exit; git config --local --unset credential.helper || true)' EXIT | |
| fi | |
| ( | |
| set -x | |
| git tag "${tag}" | |
| retry git push origin HEAD | |
| major_version_tag="v${version%%.*}" | |
| git branch "releases/${major_version_tag}" | |
| git tag -f "${major_version_tag}" | |
| refs=("refs/tags/${tag}" "refs/heads/releases/${major_version_tag}" "+refs/tags/${major_version_tag}") | |
| tools=() | |
| for tool in tools/codegen/base/*.json; do | |
| tool="${tool##*/}" | |
| tools+=("${tool%.*}") | |
| done | |
| # Aliases. | |
| # NB: Update case for aliases in main.sh, tool input option in test-alias job | |
| # in .github/workflows/ci.yml, and match for alias for tools/codegen/src/tools-markdown.rs. | |
| tools+=( | |
| nextest | |
| taplo-cli | |
| typos-cli | |
| wasm-bindgen-cli | |
| wasmtime-cli | |
| ) | |
| # Non-manifest-based tools. | |
| tools+=(valgrind) | |
| branches=() | |
| for tool in "${tools[@]}"; do | |
| git checkout -b "releases/${tool}" | |
| sed -E "${in_place[@]}" action.yml \ | |
| -e "s/required: true/required: false/g" \ | |
| -e "s/# default: #publish:tool/default: ${tool}/g" | |
| git add action.yml | |
| git commit -m "${tool}" | |
| git tag -f "${tool}" | |
| git checkout refs/tags/"${tag}" | |
| refs+=("+refs/heads/releases/${tool}" "+refs/tags/${tool}") | |
| branches+=("releases/${tool}") | |
| done | |
| retry git push origin --atomic "${refs[@]}" | |
| git branch -d "releases/${major_version_tag}" | |
| git branch -D "${branches[@]}" | |
| schema_workspace=/tmp/workspace | |
| rm -rf -- "${schema_workspace}" | |
| # Checkout manifest-schema branch | |
| schema_version="$(cargo metadata --format-version=1 --no-deps | jq -r '.packages[] | select(.name == "install-action-manifest-schema") | .version')" | |
| if [[ "${schema_version}" == "0."* ]]; then | |
| schema_version="0.$(cut -d. -f2 <<<"${schema_version}")" | |
| else | |
| schema_version="$(cut -d. -f1 <<<"${schema_version}")" | |
| fi | |
| schema_branch="manifest-schema-${schema_version}" | |
| git worktree add --force "${schema_workspace}" | |
| ( | |
| cd -- "${schema_workspace}" | |
| if git fetch origin "${schema_branch}"; then | |
| git checkout "origin/${schema_branch}" -B "${schema_branch}" | |
| elif ! git checkout "${schema_branch}"; then | |
| # New branch with no history. Credit: https://stackoverflow.com/a/13969482 | |
| git checkout --orphan "${schema_branch}" | |
| git rm -rf -- . || true | |
| git commit -m 'Initial commit' --allow-empty | |
| fi | |
| ) | |
| # Copy over schema | |
| cp -- ./manifests/* "${schema_workspace}" | |
| ( | |
| cd -- "${schema_workspace}" | |
| # Stage changes | |
| git add . | |
| # Detect changes, then commit and push if changes exist | |
| if [[ "$(git status --porcelain=v1 | LC_ALL=C wc -l)" != "0" ]]; then | |
| git commit -m 'Update manifest schema' | |
| retry git push origin HEAD | |
| fi | |
| ) | |
| rm -rf -- "${schema_workspace}" | |
| git worktree prune | |
| # TODO: get branch in schema_workspace dir instead | |
| git branch -D "${schema_branch}" "${schema_workspace##*/}" | |
| ) | |
| env: | |
| VERSION: ${{ needs.prepare.outputs.version }} | |
| RELEASE_DATE: ${{ needs.prepare.outputs.release-date }} | |
| HAS_TAGS: ${{ needs.prepare.outputs.has-tags }} | |
| TAG: ${{ needs.prepare.outputs.tag }} | |
| CHANGELOG: CHANGELOG.md | |
| BRANCH: main | |
| PREPARE_REV: ${{ needs.prepare.outputs.rev }} | |
| # Note that if we use secrets.GITHUB_TOKEN, the pushed commit/tag cannot trigger other workflows. | |
| PUSH_TOKEN: ${{ secrets.PUSH_TOKEN }} | |
| - uses: taiki-e/create-gh-release-action@v1 | |
| with: | |
| changelog: CHANGELOG.md | |
| title: $version | |
| branch: main | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| ref: refs/tags/${{ needs.prepare.outputs.tag }} | |
| release-manifest-schema: | |
| if: github.repository_owner == 'taiki-e' && inputs.target == 'install-action-manifest-schema' | |
| uses: taiki-e/github-actions/.github/workflows/rust-release.yml@main | |
| permissions: | |
| contents: write # for taiki-e/create-gh-release-action | |
| id-token: write # for rust-lang/crates-io-auth-action | |
| attestations: write # unused (used when options for uploading binaries are set) | |
| secrets: | |
| PUSH_TOKEN: ${{ secrets.PUSH_TOKEN }} | |
| with: | |
| version: ${{ inputs.version }} | |
| tag-prefix: install-action-manifest-schema- | |
| crates: tools/manifest-schema | |
| changelog: tools/manifest-schema/CHANGELOG.md | |
| title: $prefix $version | |
| prefix: install-action-manifest-schema |