feat: publish docs with releases #2
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
| # Reusable workflow that can be referenced by repositories in their `.github/workflows/release.yaml`. | ||
| # See example usage in https://github.com/bazel-contrib/rules-template/blob/main/.github/workflows/release.yaml | ||
| # | ||
| # This workflow calls `.github/workflows/release_prep.sh` as the command to prepare the release. | ||
| # Release notes are expected to be outputted to stdout from the release prep command. | ||
| # | ||
| # This workflow uses https://github.com/bazel-contrib/setup-bazel to prepare the cache folders. | ||
| # Caching may be disabled by setting `mount_bazel_caches` to false. | ||
| # | ||
| # The workflow requires the following permissions to be set on the invoking job: | ||
| # | ||
| # permissions: | ||
| # id-token: write # Needed to attest provenance | ||
| # attestations: write # Needed to attest provenance | ||
| # contents: write # Needed to upload release files | ||
| permissions: {} | ||
| on: | ||
| # Make this workflow reusable, see | ||
| # https://github.blog/2022-02-10-using-reusable-workflows-github-actions | ||
| workflow_call: | ||
| inputs: | ||
| release_files: | ||
| required: true | ||
| description: | | ||
| Newline-delimited globs of paths to assets to upload for release. | ||
| relative to the module repository. The paths should include any files | ||
| such as a release archive created by the release_prep script`. | ||
| See https://github.com/softprops/action-gh-release#inputs. | ||
| type: string | ||
| # TODO: there's a security design problem here: | ||
| # Users of a workflow_dispatch trigger could fill in something via the GH Web UI | ||
| # that would cause the release to use an arbitrary script. | ||
| # That change wouldn't be reflected in the sources in the repo, and therefore | ||
| # would not be verifiable by the attestation. | ||
| # For now, we force this path to be hard-coded. | ||
| # | ||
| # release_prep_command: | ||
| # default: .github/workflows/release_prep.sh | ||
| # description: | | ||
| # Command to run to prepare the release and generate release notes. | ||
| # Release notes are expected to be outputted to stdout. | ||
| # type: string | ||
| bazel_test_command: | ||
| default: "bazel test //..." | ||
| description: | | ||
| Bazel test command that may be overridden to set custom flags and targets. | ||
| The --disk_cache=~/.cache/bazel-disk-cache --repository_cache=~/.cache/bazel-repository-cache flags are | ||
| automatically appended to the command. | ||
| type: string | ||
| mount_bazel_caches: | ||
| default: true | ||
| description: | | ||
| Whether to enable caching in the bazel-contrib/setup-bazel action. | ||
| type: boolean | ||
| prerelease: | ||
| default: true | ||
| description: Indicator of whether or not this is a prerelease. | ||
| type: boolean | ||
| docs: | ||
| default: false | ||
| description: | | ||
| Indicator of whether or not to generate starlark documentation from bzl_library targets. | ||
| When true, an additional release artifact will be produced, which is an archive file of the binary protobuf files | ||
| output by the [starlark_doc_extract](https://bazel.build/reference/be/general#starlark_doc_extract) rule. | ||
| type: boolean | ||
| draft: | ||
| default: false | ||
| description: | | ||
| Whether the release should be created as a draft or published immediately. | ||
| type: boolean | ||
| tag_name: | ||
| description: | | ||
| The tag which is being released. | ||
| By default, https://github.com/softprops/action-gh-release will use `github.ref_name`. | ||
| type: string | ||
| jobs: | ||
| build: | ||
| outputs: | ||
| release-files-artifact-id: ${{ steps.upload-release-files.outputs.artifact-id }} | ||
| release-notes-artifact-id: ${{ steps.upload-release-notes.outputs.artifact-id }} | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ inputs.tag_name }} | ||
| - uses: bazel-contrib/setup-bazel@0.14.0 | ||
| with: | ||
| disk-cache: ${{ inputs.mount_bazel_caches }} | ||
| external-cache: ${{ inputs.mount_bazel_caches }} | ||
| repository-cache: ${{ inputs.mount_bazel_caches }} | ||
| - name: Generate starlark documentation | ||
| id: generate-docs | ||
| if: inputs.docs | ||
| env: | ||
| - USE_BAZEL_VERSION: 7.x | ||
| run: | | ||
| cd $(mktemp -d) | ||
| cat >MODULE.bazel <<EOF | ||
| bazel_dep(name = "rules_oci" version = "0.0.0") | ||
| local_path_override( | ||
| module_name = "rules_oci", | ||
| path = "${{github.workspace}}", | ||
| ) | ||
| EOF | ||
| BES_JSON=$(mktemp) | ||
| bazel query 'kind("starlark_doc_extract rule", @rules_oci//oci/...)' | \ | ||
| xargs bazel build \ | ||
| --remote_download_regex='.*doc_extract\.binaryproto' \ | ||
| --build_event_json_file=$BES_JSON | ||
| cat $BES_JSON | jq -r '.namedSetOfFiles | values | .files[] | select(.name | endswith(".doc_extract.binaryproto")) | ((.pathPrefix | join("/")) + "/" + .name)' > $GITHUB_OUTPUT | ||
| - name: Test | ||
| run: ${{ inputs.bazel_test_command }} --disk_cache=~/.cache/bazel-disk-cache --repository_cache=~/.cache/bazel-repository-cache | ||
| # Fetch built artifacts (if any) from earlier jobs, which the release script may want to read. | ||
| # Extract into ${GITHUB_WORKSPACE}/artifacts/* | ||
| - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1 | ||
| - name: Build release artifacts and prepare release notes | ||
| run: | | ||
| if [ ! -f ".github/workflows/release_prep.sh" ]; then | ||
| echo "ERROR: create a .github/workflows/release_prep.sh script" | ||
| exit 1 | ||
| fi | ||
| .github/workflows/release_prep.sh ${{ inputs.tag_name || github.ref_name }} > release_notes.txt | ||
| - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 #v4.6.0 | ||
| id: upload-release-files | ||
| with: | ||
| name: release_files | ||
| path: ${{ inputs.release_files }} | ||
| - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 #v4.6.0 | ||
| id: upload-release-notes | ||
| with: | ||
| name: release_notes | ||
| path: release_notes.txt | ||
| - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 #v4.6.0 | ||
| id: upload-generated-docs | ||
| if: inputs.docs | ||
| with: | ||
| name: docs | ||
| path: ${{ steps.generate-docs.outputs.files }} | ||
| attest: | ||
| needs: build | ||
| outputs: | ||
| attestations-artifact-id: ${{ steps.upload-attestations.outputs.artifact-id }} | ||
| permissions: | ||
| id-token: write | ||
| attestations: write | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| # actions/download-artifact@v4 does not yet support downloading via the immutable artifact-id, | ||
| # but the Javascript library does. See: https://github.com/actions/download-artifact/issues/349 | ||
| - run: npm install @actions/artifact@2.1.9 | ||
| - name: download-release-files | ||
| uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | ||
| env: | ||
| ARTIFACT_ID: ${{ needs.build.outputs.release-files-artifact-id }} | ||
| with: | ||
| script: | | ||
| const {default: artifactClient} = require('@actions/artifact') | ||
| const { ARTIFACT_ID } = process.env | ||
| await artifactClient.downloadArtifact(ARTIFACT_ID, { path: 'release_files/'}) | ||
| # https://github.com/actions/attest-build-provenance | ||
| - name: Attest release files | ||
| id: attest_release | ||
| uses: actions/attest-build-provenance@v2 | ||
| with: | ||
| subject-path: release_files/**/* | ||
| # The Bazel Central Registry requires an attestation per release archive, but the | ||
| # actions/attest-build-provenance action only produces a single attestation for a | ||
| # list of subjects. Copy the combined attestations into individually named | ||
| # .intoto.jsonl files. | ||
| - name: Write release archive attestations into intoto.jsonl | ||
| id: write_release_archive_attestation | ||
| run: | | ||
| # https://bazel.build/rules/lib/repo/http#http_archive | ||
| RELEASE_ARCHIVE_REGEX="(\.zip|\.jar|\.war|\.aar|\.tar|\.tar\.gz|\.tgz|\.tar\.xz|\.txz|\.tar\.xzt|\.tzst|\.tar\.bz2|\.ar|\.deb)$" | ||
| ATTESTATIONS_DIR=$(mktemp --directory) | ||
| for filename in $(find release_files/ -type f -printf "%f\n"); do | ||
| if [[ "${filename}" =~ $RELEASE_ARCHIVE_REGEX ]]; then | ||
| ATTESTATION_FILE="$(basename "${filename}").intoto.jsonl" | ||
| echo "Writing attestation to ${ATTESTATION_FILE}" | ||
| cat ${{ steps.attest_release.outputs.bundle-path }} | jq --compact-output > "${ATTESTATIONS_DIR}/${ATTESTATION_FILE}" | ||
| fi | ||
| done | ||
| echo "release_archive_attestations_dir=${ATTESTATIONS_DIR}" >> $GITHUB_OUTPUT | ||
| - uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 #v4.6.0 | ||
| id: upload-attestations | ||
| with: | ||
| name: attestations | ||
| path: ${{ steps.write_release_archive_attestation.outputs.release_archive_attestations_dir }}/* | ||
| release: | ||
| needs: [build, attest] | ||
| permissions: | ||
| contents: write | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| # actions/download-artifact@v4 does not yet support downloading via the immutable artifact-id, | ||
| # but the Javascript library does. See: https://github.com/actions/download-artifact/issues/349 | ||
| - run: npm install @actions/artifact@2.1.9 | ||
| - name: download-artifacts | ||
| uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | ||
| env: | ||
| RELEASE_FILES_ARTIFACT_ID: ${{ needs.build.outputs.release-files-artifact-id }} | ||
| RELEASE_NOTES_ARTIFACT_ID: ${{ needs.build.outputs.release-notes-artifact-id }} | ||
| ATTESTATIONS_ARTIFACT_ID: ${{ needs.attest.outputs.attestations-artifact-id }} | ||
| with: | ||
| script: | | ||
| const {default: artifactClient} = require('@actions/artifact') | ||
| const { RELEASE_FILES_ARTIFACT_ID, RELEASE_NOTES_ARTIFACT_ID, ATTESTATIONS_ARTIFACT_ID } = process.env | ||
| await Promise.all([ | ||
| artifactClient.downloadArtifact(RELEASE_FILES_ARTIFACT_ID, { path: 'release_files/'}), | ||
| artifactClient.downloadArtifact(RELEASE_NOTES_ARTIFACT_ID, { path: 'release_notes/'}), | ||
| artifactClient.downloadArtifact(ATTESTATIONS_ARTIFACT_ID, { path: 'attestations/'}) | ||
| ]) | ||
| - name: Release | ||
| uses: softprops/action-gh-release@v2 | ||
| with: | ||
| prerelease: ${{ inputs.prerelease }} | ||
| draft: ${{ inputs.draft }} | ||
| # Use GH feature to populate the changelog automatically | ||
| generate_release_notes: true | ||
| body_path: release_notes/release_notes.txt | ||
| fail_on_unmatched_files: true | ||
| tag_name: ${{ inputs.tag_name }} | ||
| files: | | ||
| release_files/**/* | ||
| attestations/* | ||