Commit 262b2ad426
Verified · cmc ci/pages: success ci/test: failure
Layout: unified · split
.gitbay/ci.yml added +27
| @@ -0,0 +1,27 @@ | ||
| 1 | # gitbay CI. Each job becomes one build per push; the runner clones this | |
| 2 | # repository at the pushed commit and runs every step with `sh -c`, stopping at | |
| 3 | # the first failure. Repository secrets arrive as environment variables. | |
| 4 | # | |
| 5 | # The runner is Linux, and `runner next` claims the oldest pending build with no | |
| 6 | # platform targeting, so a second runner could not be aimed at macOS jobs. The | |
| 7 | # darwin tarballs are therefore built on a Mac by .githooks/pre-push at tag time | |
| 8 | # and uploaded to the same release; see RELEASING.org. | |
| 9 | jobs: | |
| 10 | test: | |
| 11 | steps: | |
| 12 | - cargo test --locked | |
| 13 | - cargo clippy --all-targets --locked -- -D warnings | |
| 14 | - cargo run --locked -- build docs -o "/tmp/orgo-docs-$GITBAY_SHA" --strict | |
| 15 | - rm -rf "/tmp/orgo-docs-$GITBAY_SHA" | |
| 16 | ||
| 17 | # Publishes the documentation site by force-pushing the built output to the | |
| 18 | # `pages` branch, which gitbay serves at https://orgo.krz.sh. A no-op off main. | |
| 19 | pages: | |
| 20 | steps: | |
| 21 | - sh .gitbay/pages.sh | |
| 22 | ||
| 23 | # The tag push is the release: binaries, the gitbay release, and crates.io. | |
| 24 | release: | |
| 25 | tags: "v*" | |
| 26 | steps: | |
| 27 | - sh .gitbay/release.sh | |
.gitbay/pages.sh added +36
| @@ -0,0 +1,36 @@ | ||
| 1 | #!/bin/sh | |
| 2 | # Publish the documentation site to the `pages` branch, which gitbay serves at | |
| 3 | # https://orgo.krz.sh. Queued on every branch push; does nothing off main. | |
| 4 | # | |
| 5 | # The site is fifteen org files built by the tool that documents them, so this is | |
| 6 | # also the most honest smoke test in the repository: if orgo cannot build its own | |
| 7 | # documentation, the job fails and says so. --strict makes a broken internal link | |
| 8 | # a failure, because docs rot by having their links quietly stop resolving. | |
| 9 | set -eu | |
| 10 | ||
| 11 | if [ "${GITBAY_REF:-}" != "main" ]; then | |
| 12 | echo "ref is ${GITBAY_REF:-unset}, not main — nothing to publish" | |
| 13 | exit 0 | |
| 14 | fi | |
| 15 | ||
| 16 | tmp=$(mktemp -d) | |
| 17 | trap 'rm -rf "$tmp"' EXIT | |
| 18 | site="$tmp/site" | |
| 19 | work="$tmp/work" | |
| 20 | ||
| 21 | cargo run --release --locked -- build docs -o "$site" --strict | |
| 22 | rm -f "$site/.orgo-cache.json" | |
| 23 | ||
| 24 | # An orphan history: the pages branch carries the built site and nothing else, so | |
| 25 | # each publish replaces it wholesale rather than accumulating every past build. | |
| 26 | git init -q "$work" | |
| 27 | cp -R "$site/." "$work/" | |
| 28 | printf '.orgo-cache.json\n' >"$work/.gitignore" | |
| 29 | git -C "$work" add -A | |
| 30 | git -C "$work" -c user.name=gitbay-ci -c user.email=ci@orgo.krz.sh commit -q \ | |
| 31 | -m "Publish the documentation site | |
| 32 | ||
| 33 | Built from ${GITBAY_SHA} by \`orgo build docs -o _site --strict\`." | |
| 34 | git -C "$work" push -q --force "ssh://git@gitbay.org/${GITBAY_REPO}.git" HEAD:refs/heads/pages | |
| 35 | ||
| 36 | echo "published the site from ${GITBAY_SHA} to the pages branch" | |
.gitbay/release.sh added +62
| @@ -0,0 +1,62 @@ | ||
| 1 | #!/bin/sh | |
| 2 | # The tag push is the release. Builds the Linux binaries, creates the gitbay | |
| 3 | # release with the tag's own annotation as its notes, attaches the tarballs, and | |
| 4 | # publishes to crates.io. | |
| 5 | # | |
| 6 | # The two darwin tarballs are not built here: this runner is Linux, and gitbay's | |
| 7 | # `runner next` claims the oldest pending build with no platform targeting, so a | |
| 8 | # Mac runner could not be aimed at them. .githooks/pre-push builds them on a Mac | |
| 9 | # before the tag is pushed and uploads them to this release once it exists. | |
| 10 | # | |
| 11 | # crates.io is the one step that cannot be undone — a version can be yanked but | |
| 12 | # never replaced — so it runs last, after the release exists and the binaries are | |
| 13 | # attached. CARGO_REGISTRY_TOKEN is a repository secret (`gitbay repo secret set`). | |
| 14 | set -eu | |
| 15 | ||
| 16 | tag="${GITBAY_REF:?no tag in GITBAY_REF}" | |
| 17 | repo="${GITBAY_REPO:?no repository in GITBAY_REPO}" | |
| 18 | : "${CARGO_REGISTRY_TOKEN:?CARGO_REGISTRY_TOKEN secret is not set}" | |
| 19 | ||
| 20 | # The tag is the source of truth for the version, checked rather than trusted: a | |
| 21 | # release tagged v0.18.0 whose binary reports 0.17.0 is the kind of thing nobody | |
| 22 | # notices for months. | |
| 23 | version=$(cargo metadata --no-deps --format-version 1 | | |
| 24 | python3 -c 'import json,sys; print(json.load(sys.stdin)["packages"][0]["version"])') | |
| 25 | if [ "$tag" != "v$version" ]; then | |
| 26 | echo "tag $tag does not match Cargo.toml version $version" >&2 | |
| 27 | exit 1 | |
| 28 | fi | |
| 29 | ||
| 30 | dist=dist | |
| 31 | mkdir -p "$dist" | |
| 32 | ||
| 33 | # glibc for ordinary distributions, musl for containers and anything older than | |
| 34 | # the runner's glibc — a dynamically linked binary is the usual reason a download | |
| 35 | # does not run. | |
| 36 | for target in x86_64-unknown-linux-gnu x86_64-unknown-linux-musl; do | |
| 37 | cargo build --release --locked --target "$target" | |
| 38 | # A tarball rather than a bare binary: it keeps the executable bit through the | |
| 39 | # download path, and carries the licence with the thing it licenses. | |
| 40 | staging="orgo-$tag-$target" | |
| 41 | rm -rf "$staging" | |
| 42 | mkdir "$staging" | |
| 43 | cp "target/$target/release/orgo" README.md LICENSE "$staging/" | |
| 44 | tar czf "$dist/$staging.tar.gz" "$staging" | |
| 45 | rm -rf "$staging" | |
| 46 | (cd "$dist" && sha256sum "$staging.tar.gz" >"$staging.tar.gz.sha256") | |
| 47 | done | |
| 48 | ||
| 49 | # Notes come from the annotated tag, so the person cutting the release writes | |
| 50 | # them at the moment they decide to cut it (`git tag -a "$tag" -F notes.md`). | |
| 51 | git tag -l --format='%(contents)' "$tag" | | |
| 52 | ssh git@gitbay.org release create "$repo" "$tag" --title "${tag#v}" --file - | |
| 53 | ||
| 54 | for f in "$dist"/*; do | |
| 55 | ssh git@gitbay.org release asset add "$repo" "$tag" "$(basename "$f")" <"$f" | |
| 56 | echo "attached $(basename "$f")" | |
| 57 | done | |
| 58 | ||
| 59 | # --locked publishes exactly the dependency versions the tests ran against, | |
| 60 | # rather than whatever resolves at publish time. | |
| 61 | cargo publish --locked | |
| 62 | echo "published $tag to crates.io" | |
.githooks/pre-push added +54
| @@ -0,0 +1,54 @@ | ||
| 1 | #!/bin/sh | |
| 2 | # Build the macOS binaries when a version tag is pushed. | |
| 3 | # | |
| 4 | # gitbay's runner is Linux and has no platform targeting, so the darwin tarballs | |
| 5 | # cannot be built in CI. They are built here instead, on the Mac doing the push, | |
| 6 | # and this is the better place for the gate: a build failure aborts the push, so | |
| 7 | # a tag whose Mac binaries do not compile is never published. GitHub Actions | |
| 8 | # found that out only after the tag was already public. | |
| 9 | # | |
| 10 | # Uploading has to wait: `release asset add` needs the release, and the release is | |
| 11 | # created by the CI job this push triggers. So the upload is handed to a detached | |
| 12 | # helper that waits for the release to appear. Its log, and the tarballs, stay in | |
| 13 | # dist/macos/<tag>/ — rerun .githooks/upload-macos <tag> if it fails. | |
| 14 | # | |
| 15 | # Wired up with: git config core.hooksPath .githooks | |
| 16 | set -eu | |
| 17 | ||
| 18 | root=$(git rev-parse --show-toplevel) | |
| 19 | targets="aarch64-apple-darwin x86_64-apple-darwin" | |
| 20 | ||
| 21 | # stdin: <local ref> <local sha> <remote ref> <remote sha>, one line per ref. | |
| 22 | while read -r _local_ref _local_sha remote_ref _remote_sha; do | |
| 23 | case "$remote_ref" in | |
| 24 | refs/tags/v*) ;; | |
| 25 | *) continue ;; | |
| 26 | esac | |
| 27 | tag=${remote_ref#refs/tags/} | |
| 28 | ||
| 29 | version=$(cargo metadata --no-deps --format-version 1 --manifest-path "$root/Cargo.toml" | | |
| 30 | python3 -c 'import json,sys; print(json.load(sys.stdin)["packages"][0]["version"])') | |
| 31 | if [ "$tag" != "v$version" ]; then | |
| 32 | echo "pre-push: tag $tag does not match Cargo.toml version $version" >&2 | |
| 33 | exit 1 | |
| 34 | fi | |
| 35 | ||
| 36 | dist="$root/dist/macos/$tag" | |
| 37 | rm -rf "$dist" | |
| 38 | mkdir -p "$dist" | |
| 39 | ||
| 40 | for target in $targets; do | |
| 41 | echo "pre-push: building $target" | |
| 42 | (cd "$root" && cargo build --release --locked --target "$target") | |
| 43 | staging="orgo-$tag-$target" | |
| 44 | rm -rf "$root/$staging" | |
| 45 | mkdir "$root/$staging" | |
| 46 | cp "$root/target/$target/release/orgo" "$root/README.md" "$root/LICENSE" "$root/$staging/" | |
| 47 | (cd "$root" && tar czf "$dist/$staging.tar.gz" "$staging") | |
| 48 | rm -rf "$root/$staging" | |
| 49 | (cd "$dist" && shasum -a 256 "$staging.tar.gz" >"$staging.tar.gz.sha256") | |
| 50 | done | |
| 51 | ||
| 52 | echo "pre-push: built $tag for $targets; upload waits for the release" | |
| 53 | nohup "$root/.githooks/upload-macos" "$tag" >>"$dist/upload.log" 2>&1 & | |
| 54 | done | |
.githooks/upload-macos added +34
| @@ -0,0 +1,34 @@ | ||
| 1 | #!/bin/sh | |
| 2 | # Attach the macOS tarballs built by pre-push to the gitbay release. | |
| 3 | # | |
| 4 | # The release is created by the CI job the tag push triggers, so this waits for it | |
| 5 | # rather than assuming it is there. Run by hand as `.githooks/upload-macos <tag>` | |
| 6 | # if the detached run from the hook failed; the tarballs are already on disk and | |
| 7 | # uploading one twice replaces it rather than duplicating it. | |
| 8 | set -eu | |
| 9 | ||
| 10 | tag=${1:?usage: upload-macos <tag>} | |
| 11 | root=$(git rev-parse --show-toplevel) | |
| 12 | dist="$root/dist/macos/$tag" | |
| 13 | repo=$(git config --get remote.origin.url | sed 's#.*gitbay.org[:/]##; s#\.git$##') | |
| 14 | ||
| 15 | [ -d "$dist" ] || { echo "no build for $tag in $dist" >&2; exit 1; } | |
| 16 | ||
| 17 | # The release job builds two Linux targets before creating the release, so the | |
| 18 | # wait is minutes rather than seconds. Twenty minutes, then give up loudly. | |
| 19 | i=0 | |
| 20 | until ssh git@gitbay.org release show "$repo" "$tag" >/dev/null 2>&1; do | |
| 21 | i=$((i + 1)) | |
| 22 | if [ "$i" -gt 80 ]; then | |
| 23 | echo "$(date -u +%FT%TZ) release $tag never appeared; rerun this script" >&2 | |
| 24 | exit 1 | |
| 25 | fi | |
| 26 | sleep 15 | |
| 27 | done | |
| 28 | ||
| 29 | for f in "$dist"/*.tar.gz "$dist"/*.sha256; do | |
| 30 | [ -e "$f" ] || continue | |
| 31 | ssh git@gitbay.org release asset add "$repo" "$tag" "$(basename "$f")" <"$f" | |
| 32 | echo "$(date -u +%FT%TZ) attached $(basename "$f")" | |
| 33 | done | |
| 34 | echo "$(date -u +%FT%TZ) macOS assets attached to $tag" | |
.github/workflows/docs.yml deleted −75
| @@ -1,75 +0,0 @@ | ||
| 1 | name: Docs | |
| 2 | ||
| 3 | # Publish the documentation site to GitHub Pages. | |
| 4 | # | |
| 5 | # The site is fifteen org files under docs/, built by the tool they document — so this | |
| 6 | # job is also the most honest smoke test in the repository: if orgo cannot build its own | |
| 7 | # documentation, the deploy fails and says so. | |
| 8 | # | |
| 9 | # `--strict` makes a broken internal link a build failure. Docs rot by having their links | |
| 10 | # quietly stop resolving, and that is exactly the failure a static site generator is | |
| 11 | # supposed to be able to catch. | |
| 12 | ||
| 13 | on: | |
| 14 | push: | |
| 15 | branches: [main] | |
| 16 | # Templates and the config shape the pages as much as the prose does, and a change to | |
| 17 | # the renderer changes every page. Anything else — a test, a fixture — cannot. | |
| 18 | paths: | |
| 19 | - "docs/**" | |
| 20 | - "src/**" | |
| 21 | - "Cargo.toml" | |
| 22 | - "Cargo.lock" | |
| 23 | - ".github/workflows/docs.yml" | |
| 24 | workflow_dispatch: | |
| 25 | ||
| 26 | # Read the repository, write a Pages deployment. Nothing else. | |
| 27 | permissions: | |
| 28 | contents: read | |
| 29 | pages: write | |
| 30 | id-token: write | |
| 31 | ||
| 32 | # One deployment at a time, and let a newer commit win: a queue of doc builds racing each | |
| 33 | # other to publish is worse than the last one arriving late. | |
| 34 | concurrency: | |
| 35 | group: pages | |
| 36 | cancel-in-progress: true | |
| 37 | ||
| 38 | jobs: | |
| 39 | build: | |
| 40 | runs-on: ubuntu-latest | |
| 41 | steps: | |
| 42 | - name: Checkout | |
| 43 | uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| 44 | ||
| 45 | - name: Install Rust | |
| 46 | uses: dtolnay/rust-toolchain@1ff72ee08e3cb84d84adba594e0a297990fc1ed3 # stable | |
| 47 | with: | |
| 48 | toolchain: stable | |
| 49 | ||
| 50 | - name: Cache cargo | |
| 51 | uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 | |
| 52 | ||
| 53 | - name: Configure Pages | |
| 54 | uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 | |
| 55 | ||
| 56 | # Built with the release profile: the docs deploy is also the only place a release | |
| 57 | # build runs outside a tag, so a break in it surfaces here rather than at release. | |
| 58 | - name: Build the site | |
| 59 | run: cargo run --release --locked -- build docs -o docs/_site --strict | |
| 60 | ||
| 61 | - name: Upload the artifact | |
| 62 | uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v3.0.1 | |
| 63 | with: | |
| 64 | path: docs/_site | |
| 65 | ||
| 66 | deploy: | |
| 67 | needs: build | |
| 68 | runs-on: ubuntu-latest | |
| 69 | environment: | |
| 70 | name: github-pages | |
| 71 | url: ${{ steps.deployment.outputs.page_url }} | |
| 72 | steps: | |
| 73 | - name: Deploy | |
| 74 | id: deployment | |
| 75 | uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 | |
.github/workflows/release.yml deleted −155
| @@ -1,155 +0,0 @@ | ||
| 1 | name: Release | |
| 2 | ||
| 3 | # Build binaries for a tag and attach them to a GitHub release. | |
| 4 | # | |
| 5 | # WHY BINARIES AT ALL, given `cargo install orgo` exists: installing from source needs | |
| 6 | # a Rust toolchain and about a minute of compiling syntect. Someone evaluating a site | |
| 7 | # generator should be able to download one file and point it at their notes. | |
| 8 | # | |
| 9 | # The tag is the source of truth for the version. The build checks it against Cargo.toml | |
| 10 | # rather than trusting them to match, because a release tagged v0.18.0 containing a binary | |
| 11 | # that reports 0.17.0 is the kind of thing nobody notices for months. | |
| 12 | ||
| 13 | on: | |
| 14 | push: | |
| 15 | tags: ["v*"] | |
| 16 | workflow_dispatch: | |
| 17 | inputs: | |
| 18 | tag: | |
| 19 | description: "Tag to build (for a re-run of a failed release)" | |
| 20 | required: true | |
| 21 | ||
| 22 | env: | |
| 23 | CARGO_TERM_COLOR: always | |
| 24 | ||
| 25 | jobs: | |
| 26 | build: | |
| 27 | name: ${{ matrix.target }} | |
| 28 | runs-on: ${{ matrix.os }} | |
| 29 | strategy: | |
| 30 | fail-fast: false | |
| 31 | matrix: | |
| 32 | include: | |
| 33 | # Apple Silicon and Intel Macs, each built natively on its own runner. The | |
| 34 | # Intel label moves with the image: macos-13 was retired, and a job asking for | |
| 35 | # a retired label does not fail — it sits queued until someone notices, which | |
| 36 | # is how the v0.20.0 release spent fifteen minutes doing nothing. | |
| 37 | - { os: macos-latest, target: aarch64-apple-darwin } | |
| 38 | - { os: macos-15-intel, target: x86_64-apple-darwin } | |
| 39 | # glibc for ordinary distributions, musl for containers and anything older than | |
| 40 | # the runner's glibc — a dynamically linked binary is the usual reason a | |
| 41 | # download does not run. | |
| 42 | - { os: ubuntu-latest, target: x86_64-unknown-linux-gnu } | |
| 43 | - { os: ubuntu-latest, target: x86_64-unknown-linux-musl } | |
| 44 | steps: | |
| 45 | - name: Checkout | |
| 46 | uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| 47 | with: | |
| 48 | ref: ${{ github.event.inputs.tag || github.ref }} | |
| 49 | ||
| 50 | - name: Install Rust | |
| 51 | uses: dtolnay/rust-toolchain@1ff72ee08e3cb84d84adba594e0a297990fc1ed3 # stable | |
| 52 | with: | |
| 53 | toolchain: stable | |
| 54 | targets: ${{ matrix.target }} | |
| 55 | ||
| 56 | - name: Install musl tools | |
| 57 | if: endsWith(matrix.target, '-musl') | |
| 58 | run: sudo apt-get update && sudo apt-get install -y --no-install-recommends musl-tools | |
| 59 | ||
| 60 | - name: Check the tag against Cargo.toml | |
| 61 | shell: bash | |
| 62 | run: | | |
| 63 | tag="${{ github.event.inputs.tag || github.ref_name }}" | |
| 64 | crate=$(cargo metadata --no-deps --format-version 1 \ | |
| 65 | | python3 -c 'import json,sys; print(json.load(sys.stdin)["packages"][0]["version"])') | |
| 66 | if [ "$tag" != "v$crate" ]; then | |
| 67 | echo "tag $tag does not match Cargo.toml version $crate" >&2 | |
| 68 | exit 1 | |
| 69 | fi | |
| 70 | ||
| 71 | - name: Build | |
| 72 | run: cargo build --release --locked --target ${{ matrix.target }} | |
| 73 | ||
| 74 | # A tarball rather than a bare binary: it keeps the executable bit through GitHub's | |
| 75 | # download path, and carries the licence with the thing it licenses. | |
| 76 | - name: Package | |
| 77 | shell: bash | |
| 78 | run: | | |
| 79 | staging="orgo-${{ github.event.inputs.tag || github.ref_name }}-${{ matrix.target }}" | |
| 80 | mkdir "$staging" | |
| 81 | cp "target/${{ matrix.target }}/release/orgo" "$staging/" | |
| 82 | cp README.md LICENSE "$staging/" | |
| 83 | tar czf "$staging.tar.gz" "$staging" | |
| 84 | shasum -a 256 "$staging.tar.gz" > "$staging.tar.gz.sha256" | |
| 85 | ||
| 86 | - name: Upload | |
| 87 | uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| 88 | with: | |
| 89 | name: ${{ matrix.target }} | |
| 90 | path: | | |
| 91 | *.tar.gz | |
| 92 | *.tar.gz.sha256 | |
| 93 | ||
| 94 | release: | |
| 95 | name: publish the release | |
| 96 | needs: build | |
| 97 | runs-on: ubuntu-latest | |
| 98 | permissions: | |
| 99 | contents: write | |
| 100 | steps: | |
| 101 | - name: Download every build | |
| 102 | uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| 103 | with: | |
| 104 | merge-multiple: true | |
| 105 | ||
| 106 | # A draft, deliberately. The release notes are written by a person, and a release | |
| 107 | # that publishes itself before anyone has read it cannot be edited quietly. | |
| 108 | - name: Create the draft release | |
| 109 | uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 | |
| 110 | with: | |
| 111 | draft: true | |
| 112 | tag_name: ${{ github.event.inputs.tag || github.ref_name }} | |
| 113 | files: | | |
| 114 | *.tar.gz | |
| 115 | *.tar.gz.sha256 | |
| 116 | ||
| 117 | publish: | |
| 118 | name: publish to crates.io | |
| 119 | needs: build | |
| 120 | runs-on: ubuntu-latest | |
| 121 | # Named so it can be gated. Adding a required reviewer to this environment in the | |
| 122 | # repository settings turns a tag push into "waiting for a human", which is the right | |
| 123 | # shape for the one step in this workflow that cannot be undone: a published version | |
| 124 | # can be yanked but never replaced. | |
| 125 | environment: crates-io | |
| 126 | permissions: | |
| 127 | # The OIDC token this mints *is* the credential — there is no API token stored in | |
| 128 | # this repository, and nothing to leak from a compromised job beyond a token that | |
| 129 | # crates.io revokes when the job ends. | |
| 130 | id-token: write | |
| 131 | contents: read | |
| 132 | steps: | |
| 133 | - name: Checkout | |
| 134 | uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| 135 | with: | |
| 136 | ref: ${{ github.event.inputs.tag || github.ref }} | |
| 137 | ||
| 138 | - name: Install Rust | |
| 139 | uses: dtolnay/rust-toolchain@1ff72ee08e3cb84d84adba594e0a297990fc1ed3 # stable | |
| 140 | with: | |
| 141 | toolchain: stable | |
| 142 | ||
| 143 | # Exchanges GitHub's OIDC token for a short-lived crates.io token, and revokes it | |
| 144 | # when the job finishes. Configured on crates.io against this repository, this | |
| 145 | # workflow's filename, and the environment above. | |
| 146 | - name: Authenticate with crates.io | |
| 147 | id: auth | |
| 148 | uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5 | |
| 149 | ||
| 150 | # `--locked` publishes exactly the dependency versions the tests ran against, rather | |
| 151 | # than whatever resolves at publish time. | |
| 152 | - name: Publish | |
| 153 | run: cargo publish --locked | |
| 154 | env: | |
| 155 | CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} | |
.gitignore +1
| @@ -1,2 +1,3 @@ | ||
| 1 | 1 | /target |
| 2 | 2 | _site/ |
| 3 | dist/ | |
RELEASING.org +83 −41
| @@ -3,30 +3,65 @@ | ||
| 3 | 3 | Read the content below for the release process. |
| 4 | 4 | |
| 5 | 5 | ** Where the repository lives |
| 6 | =origin= is gitbay (=ssh://git@gitbay.org/krz/orgo.git=), which is where the issues and | |
| 7 | merge requests are. It push-mirrors to =https://github.com/krazywarez/orgo=, tags | |
| 8 | included. | |
| 6 | =origin= is gitbay (=ssh://git@gitbay.org/krz/orgo.git=), which is where the issues, merge | |
| 7 | requests, builds and releases are. It push-mirrors to | |
| 8 | =https://github.com/krazywarez/orgo=, tags included, but nothing in a release depends on | |
| 9 | that mirror any more — it is a copy, not a step. | |
| 10 | ||
| 11 | ** What runs where | |
| 12 | Three things happen automatically, and one of them happens on your Mac. | |
| 13 | ||
| 14 | | Trigger | Where | What | | |
| 15 | |--------------------+---------------------------+---------------------------------------------------| | |
| 16 | | Any branch push | gitbay CI (=test= job) | =cargo test=, clippy, and a =--strict= docs build | | |
| 17 | | A push to =main= | gitbay CI (=pages= job) | Publishes the site to the =pages= branch | | |
| 18 | | A =v*= tag push | Your Mac (=pre-push=) | Builds and packages the two darwin tarballs | | |
| 19 | | A =v*= tag push | gitbay CI (=release= job) | Linux tarballs, the release, then crates.io | | |
| 20 | ||
| 21 | The darwin binaries are the one part CI cannot do. The runner is Linux, and gitbay's | |
| 22 | =runner next= claims the oldest pending build with no platform targeting, so a second | |
| 23 | runner could not be aimed at macOS jobs. Building them in =pre-push= is the better place | |
| 24 | anyway: a build failure aborts the push, so a tag whose Mac binaries do not compile is | |
| 25 | never published. | |
| 26 | ||
| 27 | Uploading them has to wait, because =release asset add= needs a release and the release is | |
| 28 | created by the job the tag push triggers. =pre-push= hands that to a detached | |
| 29 | =.githooks/upload-macos=, which waits for the release and then attaches the tarballs. Its | |
| 30 | log and the tarballs stay in =dist/macos/<tag>/=. | |
| 9 | 31 | |
| 10 | That mirror is what makes a release work. The automation is GitHub Actions and nothing | |
| 11 | triggers it directly: a tag pushed to gitbay reaches GitHub within a minute, and | |
| 12 | =.github/workflows/release.yml= runs there. =docs.yml= deploys the documentation site to | |
| 13 | Pages the same way, on a push to =main= that touches =docs/=, =src/=, =Cargo.toml= or | |
| 14 | =Cargo.lock=. | |
| 32 | ** Before the first publish | |
| 33 | Once per clone: | |
| 15 | 34 | |
| 16 | Neither forge runs the tests. =ci.yml= is gone and there is no =.gitbay/ci.yml=, so the | |
| 17 | checks in step 2 are the only gate a release passes. | |
| 35 | #+begin_src sh | |
| 36 | git config core.hooksPath .githooks | |
| 37 | #+end_src | |
| 18 | 38 | |
| 19 | ** Before the first publish | |
| 20 | Nothing to install and no token to store. The release workflow mints a short-lived | |
| 21 | crates.io token with OIDC, configured on crates.io against the GitHub repository, the | |
| 22 | =release.yml= workflow file and the =crates-io= environment that job runs in. | |
| 39 | Once on the Mac that cuts releases — Homebrew's =rust= cannot add targets, so the | |
| 40 | toolchain has to be rustup: | |
| 41 | ||
| 42 | #+begin_src sh | |
| 43 | brew install rustup && rustup default stable | |
| 44 | rustup target add x86_64-apple-darwin | |
| 45 | #+end_src | |
| 46 | ||
| 47 | Once on the VPS runner: a Rust toolchain with =x86_64-unknown-linux-musl= added and | |
| 48 | =musl-gcc= installed, and an SSH key registered on gitbay that can push to the repository | |
| 49 | (the =pages= job) and run =release create= (the =release= job). | |
| 50 | ||
| 51 | Once on the repository — the crates.io token, which is the one credential this setup | |
| 52 | stores: | |
| 23 | 53 | |
| 24 | =repository= in =Cargo.toml= points at gitbay; =homepage= points at the documentation | |
| 25 | site on Pages. | |
| 54 | #+begin_src sh | |
| 55 | gitbay repo secret set krz/orgo CARGO_REGISTRY_TOKEN | |
| 56 | #+end_src | |
| 57 | ||
| 58 | =repository= in =Cargo.toml= points at gitbay; =homepage= points at | |
| 59 | [[https://orgo.krz.sh]], the documentation site the =pages= job publishes. | |
| 26 | 60 | |
| 27 | 61 | ** Every release |
| 28 | 62 | 1. Bump the version in =Cargo.toml=, and build once so =Cargo.lock= follows. |
| 29 | 2. Test and package the release. | |
| 63 | 2. Test and package the release. CI runs the first two on every push, but a release is | |
| 64 | worth checking before you tag rather than after. | |
| 30 | 65 | |
| 31 | 66 | #+begin_src sh |
| 32 | 67 | cargo test |
| @@ -37,49 +72,56 @@ site on Pages. | ||
| 37 | 72 | |
| 38 | 73 | 3. Build a site you know with =--no-cache= and diff the output against the previous |
| 39 | 74 | version's. |
| 40 | 4. Commit, tag, push. | |
| 75 | 4. Commit and push. The =test= and =pages= jobs run. | |
| 41 | 76 | |
| 42 | 77 | #+begin_src sh |
| 43 | git commit -am "0.18: <what changed>" | |
| 44 | git tag -a v0.18.0 -m "0.18.0" | |
| 45 | git push && git push --tags | |
| 78 | git commit -am "0.24: <what changed>" | |
| 79 | git push | |
| 46 | 80 | #+end_src |
| 47 | 81 | |
| 48 | 5. The tag push is the release, by way of the mirror. It builds binaries for macOS | |
| 49 | (arm64 and x86_64) and Linux (gnu and musl), opens a /draft/ GitHub release with them | |
| 50 | attached, and runs =cargo publish --locked= — there is nothing to publish by hand, and | |
| 51 | running =cargo publish= locally now only fails on a version crates.io already has. The | |
| 52 | build checks the tag against =Cargo.toml= rather than trusting the two to match. | |
| 82 | 5. Write the release notes, then tag with them. The =release= job uses the tag's own | |
| 83 | annotation as the release notes, so the notes are written at the moment you decide to | |
| 84 | cut the release rather than afterwards. | |
| 53 | 85 | |
| 54 | =gh= talks to the mirror, so it is how you watch the run: | |
| 86 | #+begin_src sh | |
| 87 | $EDITOR notes.md | |
| 88 | git tag -a v0.24.0 -F notes.md | |
| 89 | git push --tags | |
| 90 | #+end_src | |
| 91 | ||
| 92 | The push blocks while your Mac builds both darwin targets. If either fails, the push | |
| 93 | is refused and nothing is tagged. | |
| 94 | ||
| 95 | 6. The tag push is the release. It checks the tag against =Cargo.toml= rather than | |
| 96 | trusting the two to match, builds the gnu and musl tarballs, creates the release with | |
| 97 | your notes, attaches the tarballs, and runs =cargo publish --locked=. The macOS | |
| 98 | tarballs arrive a few minutes later from the detached uploader. | |
| 55 | 99 | |
| 56 | 100 | #+begin_src sh |
| 57 | gh run list -R krazywarez/orgo --limit 3 | |
| 101 | gitbay build list krz/orgo | |
| 102 | gitbay release show krz/orgo v0.24.0 | |
| 58 | 103 | #+end_src |
| 59 | 104 | |
| 60 | 105 | Publishing is the one step that cannot be undone: a version can be yanked but never |
| 61 | replaced. The publish job runs in the =crates-io= environment so it can be held — | |
| 62 | add a required reviewer to that environment in the GitHub repository settings and a | |
| 63 | tag push waits for a human before it reaches crates.io. | |
| 106 | replaced. It runs last, after the release exists and the binaries are attached, so a | |
| 107 | failure earlier in the job costs you a tag rather than a version number. | |
| 64 | 108 | |
| 65 | A release that fails halfway is re-run from the Actions tab: the workflow takes the | |
| 66 | tag to build as an input, so it does not need a second tag. | |
| 67 | ||
| 68 | 6. Write the release notes and publish the draft GitHub release. | |
| 69 | 7. Release on gitbay, which has no automation of its own. Same notes, same binaries. | |
| 109 | 7. If the macOS upload did not land — check =dist/macos/v0.24.0/upload.log= — rerun it. | |
| 110 | The tarballs are already built, and uploading one twice replaces it. | |
| 70 | 111 | |
| 71 | 112 | #+begin_src sh |
| 72 | gitbay release create v0.18.0 --title 0.18.0 --file - < notes.md | |
| 73 | gh release download v0.18.0 -R krazywarez/orgo -D dist | |
| 74 | for f in dist/*; do gitbay release asset add v0.18.0 "$(basename "$f")" < "$f"; done | |
| 113 | .githooks/upload-macos v0.24.0 | |
| 75 | 114 | #+end_src |
| 76 | 115 | |
| 77 | 116 | ** If a release goes wrong |
| 78 | 117 | Yank rather than delete, and ship a fix as a new version: |
| 79 | 118 | |
| 80 | 119 | #+begin_src sh |
| 81 | cargo yank --version 0.18.0 | |
| 120 | cargo yank --version 0.24.0 | |
| 82 | 121 | #+end_src |
| 83 | 122 | |
| 84 | 123 | Yanking stops new dependents from selecting it; anyone who already has it keeps working. |
| 85 | Then release =0.18.1= with the fix. | |
| 124 | Then release =0.24.1= with the fix. | |
| 125 | ||
| 126 | A build that failed halfway is re-run with =gitbay build trigger=, which does not need a | |
| 127 | second tag. | |