From 710ca0ed96d7d8489e3208ea8eb07b888e9506c0 Mon Sep 17 00:00:00 2001 From: Ben Breslauer Date: Fri, 4 Sep 2026 16:11:17 -0700 Subject: [PATCH] Publish crates to crates.io via trusted publishing Adds a workflow_dispatch workflow that publishes one selected crate using crates.io trusted publishing, so no long-lived CARGO_REGISTRY_TOKEN secret is needed. rust-lang/crates-io-auth-action exchanges the job's GitHub OIDC identity for a token that is revoked when the run ends. The job runs in the crates-io environment, both because crates.io matches on it and so that protection rules gate who can release and from which ref. Also marks the benchmark and integration-test helper crates publish = false so they can never be selected or published by accident. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/publish-crates.yaml | 92 +++++++++++++++++++ CONTRIBUTING.md | 31 +++++-- crates/bpe/benchmarks/Cargo.toml | 1 + crates/bpe/tests/Cargo.toml | 1 + crates/casefold/benchmarks/Cargo.toml | 1 + .../consistent-choose-k/benchmarks/Cargo.toml | 1 + 6 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/publish-crates.yaml diff --git a/.github/workflows/publish-crates.yaml b/.github/workflows/publish-crates.yaml new file mode 100644 index 00000000..1dcb6703 --- /dev/null +++ b/.github/workflows/publish-crates.yaml @@ -0,0 +1,92 @@ +# Publishes a single crate from this workspace to crates.io using Trusted Publishing. +# +# There is no `CARGO_REGISTRY_TOKEN` secret: `rust-lang/crates-io-auth-action` exchanges this +# workflow's GitHub OIDC identity for a short-lived crates.io token that expires after the run. +# +# One-time setup per crate, on https://crates.io/crates//settings/trusted-publishing: +# Repository owner: github +# Repository name: rust-gems +# Workflow name: publish-crates.yaml +# Environment: crates-io +# The environment name must match the `environment:` value below exactly, or crates.io rejects +# the token exchange. A crate must be published manually once before it can be configured. +name: Publish crates + +on: + workflow_dispatch: + inputs: + crate: + description: Crate to publish, at the version in its Cargo.toml + required: true + type: choice + options: + - bpe + - bpe-openai + - casefold + - commutative_hasher + - consistent-choose-k + - geo_filters + - hash-sorted-map + - sparse-ngrams + - string-offsets + crate_name_override: + description: Crate name to publish instead, for crates missing from the list above + required: false + type: string + dry_run: + description: Package and verify the crate without uploading it + type: boolean + default: false + +permissions: + contents: read + id-token: write # Required to mint the crates.io OIDC token. + +jobs: + publish: + name: Publish ${{ inputs.crate_name_override || inputs.crate }} + runs-on: ubuntu-latest + # Gate releases behind an environment so protection rules apply, and so the OIDC claim + # matches the trusted publisher configured on crates.io. + environment: crates-io + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - uses: rui314/setup-mold@7e4f20ad28a2e8ca6fd0892ccf72e2abb706b9c3 + + # `crate_name_override` wins when set, because a choice input always has one option + # selected and so can't express "none of these". + - name: Resolve crate + id: resolve + env: + CHOICE: ${{ inputs.crate }} + OVERRIDE: ${{ inputs.crate_name_override }} + run: | + crate=$(printf '%s' "${OVERRIDE:-$CHOICE}" | tr -d '[:space:]') + # Check the name against the workspace so a typo fails here with a clear message, + # and so nothing unvetted reaches the cargo commands below. + publishable=$(cargo metadata --no-deps --format-version 1 | + jq -r '.packages[] | select(.publish != []) | .name') + if ! printf '%s\n' "$publishable" | grep -qxF "$crate"; then + echo "::error::'$crate' is not a publishable crate. Available: $(echo $publishable)" + exit 1 + fi + echo "crate=$crate" >> "$GITHUB_OUTPUT" + + - name: Package and verify + run: cargo publish --package "$CRATE" --dry-run + env: + CRATE: ${{ steps.resolve.outputs.crate }} + + - name: Get crates.io token + if: ${{ !inputs.dry_run }} + id: auth + uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5 + + # Verification already happened in the dry run above, on this exact tree. + - name: Publish + if: ${{ !inputs.dry_run }} + run: cargo publish --package "$CRATE" --no-verify + env: + CRATE: ${{ steps.resolve.outputs.crate }} + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 17d7dd3d..aa154f79 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,12 +15,12 @@ These are one time installations required to be able to test your changes locall ## Submitting a pull request 1. [Fork][fork] and clone the repository -1. Make sure the tests pass on your machine: `make test` -1. Make sure linter passes on your machine: `make lint` -1. Create a new branch: `git checkout -b my-branch-name` -1. Make your change, add tests, and make sure the tests and linter still pass -1. Push to your fork and [submit a pull request][pr] -1. Pat yourself on the back and wait for your pull request to be reviewed and merged. +2. Make sure the tests pass on your machine: `make test` +3. Make sure linter passes on your machine: `make lint` +4. Create a new branch: `git checkout -b my-branch-name` +5. Make your change, add tests, and make sure the tests and linter still pass +6. Push to your fork and [submit a pull request][pr] +7. Pat yourself on the back and wait for your pull request to be reviewed and merged. Here are a few things you can do that will increase the likelihood of your pull request being accepted: @@ -29,6 +29,25 @@ Here are a few things you can do that will increase the likelihood of your pull - Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). +## Releasing a crate + +Crates are published to [crates.io](https://crates.io) with +[Trusted Publishing](https://crates.io/docs/trusted-publishing), so there is no long-lived API +token stored in this repository. The `Publish crates` workflow exchanges its GitHub OIDC identity +for a token that is revoked when the run ends. + +1. Bump `version` in the crate's `Cargo.toml` and merge that change to `main`. +2. Run the [`Publish crates`](../../actions/workflows/publish-crates.yaml) workflow via + *Run workflow*, pick the crate, and optionally tick *dry-run* first to package and verify it + without uploading. If a newly added crate isn't in the dropdown yet, type its name into + *crate_name_override* instead — and add it to the dropdown in `publish-crates.yaml` while + you're there. + +A crate has to be published manually once before crates.io will let you configure a trusted +publisher for it. Configure it at `https://crates.io/crates//settings/trusted-publishing` +with repository `github/rust-gems`, workflow `publish-crates.yaml`, and environment `crates-io`. +The environment name must match the workflow's `environment:` exactly or the token exchange fails. + ## Resources - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) diff --git a/crates/bpe/benchmarks/Cargo.toml b/crates/bpe/benchmarks/Cargo.toml index 0c0c9aea..0017b300 100644 --- a/crates/bpe/benchmarks/Cargo.toml +++ b/crates/bpe/benchmarks/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "bpe-benchmarks" edition = "2021" +publish = false [lib] path = "lib.rs" diff --git a/crates/bpe/tests/Cargo.toml b/crates/bpe/tests/Cargo.toml index c6747181..2355c8ce 100644 --- a/crates/bpe/tests/Cargo.toml +++ b/crates/bpe/tests/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "bpe-tests" edition = "2021" +publish = false [dependencies] bpe = { path = "../../bpe", features = ["rand"] } diff --git a/crates/casefold/benchmarks/Cargo.toml b/crates/casefold/benchmarks/Cargo.toml index 7c0944fd..2a4a2627 100644 --- a/crates/casefold/benchmarks/Cargo.toml +++ b/crates/casefold/benchmarks/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "casefold-benchmarks" edition = "2021" +publish = false [lib] path = "lib.rs" diff --git a/crates/consistent-choose-k/benchmarks/Cargo.toml b/crates/consistent-choose-k/benchmarks/Cargo.toml index 7b171026..5f671fb3 100644 --- a/crates/consistent-choose-k/benchmarks/Cargo.toml +++ b/crates/consistent-choose-k/benchmarks/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "consistent-choose-k-benchmarks" edition = "2021" +publish = false [[bench]] name = "performance"