7.1 KiB · text History 65cab78
name: publish

# A tag publishes; master only checks that a publish *would* work.
#
# Cargo refuses to overwrite a published version, and rightly — so a workflow
# that published on every push would fail on its second run and stay failed
# until someone bumped the version. The dry run on master is the useful half of
# that anyway: it catches a manifest that cannot be packaged at the commit that
# broke it, rather than at the tag three weeks later.
on:
  push:
    branches: [master]
    tags: ['v*']

# A newer push supersedes this run: on a branch or PR the run in
# progress is cancelled; on master it finishes, and only a run still
# waiting is replaced.
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}

jobs:
  publish:
    runs-on: self-hosted

    steps:
      - uses: actions/checkout@v4

      # Through $GITHUB_ENV rather than a job-level `env:` block: every value
      # in this file travels that way, so there is one mechanism to trust
      # instead of two.
      #
      # `cargo publish` needs a host toolchain and nothing else — no ESP
      # anything, since esp-idf-svc is target-scoped in Cargo.toml and a host
      # build compiles only the pure modules. It is installed with rustup
      # rather than taken from a `container:` image, because the runner
      # executes JavaScript actions with node inside the job container and
      # `rust:1.92-bookworm` has no node: `actions/checkout@v4` would die
      # before this workflow started. (lambent-flex-c6 hits the same wall from
      # the other side; see its firmware.yml.)
      - name: Settings
        run: |
          set -euo pipefail
          echo "RUST_VERSION=1.92.0" >> "$GITHUB_ENV"
          echo "REGISTRY_NAME=neiam-portal" >> "$GITHUB_ENV"

      - name: Resolve the forge URL
        env:
          CI_FORGE_URL: ${{ vars.CI_FORGE_URL }}
        run: |
          set -euo pipefail
          forge_url=${CI_FORGE_URL:-${GITHUB_SERVER_URL:-}}

          if [ -z "$forge_url" ]; then
            echo "No forge URL available." >&2
            echo "Set a CI_FORGE_URL variable on this repository or its org." >&2
            exit 1
          fi

          echo "FORGE_URL=$forge_url" >> "$GITHUB_ENV"

      - name: Cache the toolchain and the registry
        uses: actions/cache@v4
        with:
          path: |
            .home
            target
          key: rust-${{ env.RUST_VERSION }}-${{ hashFiles('Cargo.toml') }}
          restore-keys: rust-${{ env.RUST_VERSION }}-

      - name: Rust
        run: |
          set -euo pipefail
          export RUSTUP_HOME="$PWD/.home/.rustup" CARGO_HOME="$PWD/.home/.cargo"
          echo "RUSTUP_HOME=$RUSTUP_HOME" >> "$GITHUB_ENV"
          echo "CARGO_HOME=$CARGO_HOME" >> "$GITHUB_ENV"
          echo "$CARGO_HOME/bin" >> "$GITHUB_PATH"

          if [ ! -x "$CARGO_HOME/bin/cargo" ]; then
            curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
              | sh -s -- -y --profile minimal --default-toolchain "$RUST_VERSION"
          fi

          "$CARGO_HOME/bin/cargo" --version

      # Every warning clippy or rustc raises fails the run, before anything is
      # packaged. The forge-wide command minus `--locked`: this is a library,
      # its Cargo.lock is ignored rather than committed, so there is no
      # lockfile to hold the resolve to. The component is added on its own
      # line because the toolchain may come back from the cache without it.
      - name: Clippy
        run: |
          set -euo pipefail
          rustup component add clippy
          cargo clippy --workspace --all-targets -- -D warnings

      # The registry is per repository: its index lives under this repository's
      # own path, and the name a consumer registers it under is the repository
      # name. Cargo takes an index from configuration only, never a flag — and
      # this goes in CARGO_HOME rather than ./.cargo/config.toml on purpose,
      # because `cargo publish` refuses a dirty working tree and writing into
      # the checkout would make it one.
      #
      # Written whole (`>`), not appended (`>>`). CARGO_HOME is restored from
      # the cache, so appending accumulated a second copy of the stanza on
      # every run after the first, and cargo rejects a duplicate key outright:
      #
      #   could not parse TOML configuration in …/.home/.cargo/config.toml
      #   TOML parse error at line 5, column 13: duplicate key
      #
      # That failed in one second, before the registry was ever contacted,
      # which is why it looked like an auth problem. The file is entirely
      # ours — rustup writes `env` and `bin/`, never this — so owning it
      # outright is both safe and idempotent.
      - name: Point cargo at the forge
        run: |
          set -euo pipefail
          mkdir -p "$CARGO_HOME"
          cat > "$CARGO_HOME/config.toml" <<TOML
          [registries.$REGISTRY_NAME]
          index = "sparse+$FORGE_URL/pkg/cargo/${{ github.repository }}/"
          TOML
          cat "$CARGO_HOME/config.toml"

      # `${{ github.token }}` is minted for this job and may write this
      # repository's packages and nothing else, so there is no secret to create
      # first. Cargo reads it from CARGO_REGISTRIES_<NAME>_TOKEN, uppercased
      # with every non-alphanumeric run folded to `_`.
      - name: Publish
        env:
          CARGO_REGISTRIES_NEIAM_PORTAL_TOKEN: ${{ github.token }}
        run: |
          set -euo pipefail

          # Run #2 failed here in one second and said nothing about why. The
          # two candidates — an empty registry name and a missing token —
          # both fail that fast, so name them rather than guess again. The
          # token is reported by length, never by value.
          : "${REGISTRY_NAME:?registry name is empty}"
          : "${CARGO_HOME:?cargo home is unset}"
          echo "registry=$REGISTRY_NAME ref=${GITHUB_REF:-<unset>}"
          token=${CARGO_REGISTRIES_NEIAM_PORTAL_TOKEN:-}
          echo "token: ${#token} chars"
          echo "config: $CARGO_HOME/config.toml"
          grep -A 1 "registries" "$CARGO_HOME/config.toml" || echo "  (no registry stanza!)"

          # Off GITHUB_REF, not GITHUB_REF_TYPE: this forge hands the runner
          # `ref`, `sha` and `repository` and no ref_type at all
          # (runner_controller.ex), so `${GITHUB_REF_TYPE:-branch}` read
          # "branch" on a tag push and run #7 quietly took the dry run —
          # green, and publishing nothing. A ref that starts with refs/tags/
          # is a tag; there is nothing to be unset about.
          case "${GITHUB_REF:-}" in
            refs/tags/*)
              cargo publish --registry "$REGISTRY_NAME"
              echo
              echo "Published. Consumers add:"
              echo "  [registries.$REGISTRY_NAME]"
              echo "  index = \"sparse+$FORGE_URL/pkg/cargo/${{ github.repository }}/\""
              ;;
            *)
              # --dry-run still packages and verify-builds; it just does not
              # upload. That is the whole value of running this on master.
              cargo publish --registry "$REGISTRY_NAME" --dry-run
              ;;
          esac