Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions .github/PUBLISH_CRATES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Release Process

Publishing DiskANN crates to [crates.io](https://crates.io).

## Overview

All workspace crates are published together with synchronized version numbers using `cargo publish --workspace`, which automatically resolves dependency order and waits for each crate to be indexed before publishing its dependents. The release is triggered by pushing a version tag. The Rust toolchain version is read from [`rust-toolchain.toml`](../../rust-toolchain.toml).
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation states that cargo publish --workspace "automatically resolves dependency order and waits for each crate to be indexed before publishing its dependents," but this is not accurate. The cargo publish command does not have a --workspace flag and does not automatically handle dependency ordering or index polling. This documentation needs to be updated to reflect the actual implementation approach.

Copilot uses AI. Check for mistakes.

## Prerequisites

1. **CRATES_IO_TOKEN Secret**: A crates.io API token configured as a GitHub repository secret named `CRATES_IO_TOKEN` with publish permissions for all DiskANN crates.
- Create a token: [crates.io/settings/tokens](https://crates.io/settings/tokens)
- Add the secret: Repository Settings → Secrets and variables → Actions → New repository secret

2. **Maintainer Access**: Write access to the repository and owner/maintainer of all crates on crates.io.

## Dry-Run Testing

**Always test before publishing a real release.**

### Option 1: GitHub Actions (Recommended)

1. Navigate to: `https://github.com/microsoft/DiskANN/actions/workflows/publish.yml`
2. Click **Run workflow**, select your branch, keep **dry-run = true**
3. Watch the workflow — look for successful `cargo publish --workspace --dry-run`

### Option 2: Local

```bash
cargo publish --locked --workspace --dry-run
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The command example shows cargo publish --locked --workspace --dry-run, but as noted, cargo publish does not support --workspace. This example needs to be updated to reflect the actual implementation.

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +30
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation mentions "look for successful cargo publish --workspace --dry-run" but this command format is invalid. Update this to reflect the actual command that will be used.

Suggested change
3. Watch the workflow — look for successful `cargo publish --workspace --dry-run`
### Option 2: Local
```bash
cargo publish --locked --workspace --dry-run
3. Watch the workflow — look for successful `cargo publish --locked --dry-run`
### Option 2: Local
```bash
cargo publish --locked --dry-run

Copilot uses AI. Check for mistakes.
```

### What Dry-Run Tests

- Crate metadata and packaging validation
- Dependency resolution and publish ordering
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The claim that dry-run tests "Dependency resolution and publish ordering" is misleading. Since workspace-wide publishing isn't supported by cargo publish, the actual implementation will need to determine publish order separately. A dry-run of a single crate publish won't validate that all crates will be published in the correct dependency order.

Copilot uses AI. Check for mistakes.
- Build verification

### What It Does NOT Test

- Actual publishing, registry token auth, upload reliability
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may want to mention what to do if the publish step fails. Basically, we either need to manually fix and publish the remaining crates, or fix the publish issue, bump the version number, and bump again (or if the fix doesn't change the versions that succeeded, then the remaining ones can be updated and pushed potentially in the same release).

If the publishing fails part way through for networking issues, then we'll need to handle the remaining ones manually.


## Release Steps

1. **Update version** in root `Cargo.toml`:

```toml
[workspace.package]
version = "0.46.0"
```

All workspace crates inherit this via `version.workspace = true`.

2. **Update CHANGELOG** (if applicable).

3. **Run dry-run** on a branch to validate (see above).

4. **Tag and push**:

```bash
git tag v0.46.0
git push origin v0.46.0
```

Tag format: `v{major}.{minor}.{patch}`

5. **Monitor** the workflow in the Actions tab.

6. **Verify**:

```bash
cargo search diskann --limit 20
```

### Example Pre-Release Flow
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This recommends pushing directly to main, which we can't do due to branch protection rules.

Instead, we should use this workflow:

  1. Checkout main, bump the version and push.
  2. Make a pull-request out of the version bumped commit.
  3. Use the dry-run as a pre-merge check.
  4. Merge the PR and use the github UI to tag the release along with change notes.


```bash
# Update version
vim Cargo.toml # Change to 0.46.0

# Commit to a branch (don't tag yet)
git checkout -b release-0.46.0
git commit -am "Bump version to 0.46.0"
git push origin release-0.46.0

# Run dry-run via GitHub Actions UI on release-0.46.0

# If successful, merge and tag
git checkout main
git merge release-0.46.0
git tag v0.46.0
git push origin main --tags # Triggers the real publish
```

## Pre-release Checklist

- [ ] All CI checks pass on the main branch
- [ ] Version number is updated in `Cargo.toml`
- [ ] CHANGELOG is updated (if applicable)
- [ ] Documentation is up to date
- [ ] Breaking changes are clearly documented
- [ ] All tests pass locally: `cargo test --workspace`
- [ ] Code builds without warnings: `cargo build --workspace --release`
- [ ] **Dry-run workflow test passes successfully**
81 changes: 81 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT license.

# Publishes all workspace crates to crates.io.
# Triggered by pushing a version tag (v{major}.{minor}.{patch}) or manually via workflow_dispatch.
# Requires CRATES_IO_TOKEN secret. Rust toolchain version is read from rust-toolchain.toml.

name: Publish to crates.io

on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
workflow_dispatch:
inputs:
dry_run:
description: 'Run in dry-run mode (test without actually publishing)'
required: false
default: 'true'
type: choice
options:
- 'true'
- 'false'

env:
RUST_BACKTRACE: 1

defaults:
run:
shell: bash

permissions:
contents: read

jobs:
publish:
name: ${{ github.event.inputs.dry_run == 'true' && 'Dry-run publish test' || 'Publish crates to crates.io' }}
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
lfs: true

- name: Read Rust version from rust-toolchain.toml
id: rust-version
run: |
RUST_VERSION=$(sed -n 's/^channel = "\(.*\)"/\1/p' rust-toolchain.toml)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading from rust-toolchain.toml is a nice trick - we should update ci.yml to do the same thing (though probably in a different PR).

echo "channel=$RUST_VERSION" >> "$GITHUB_OUTPUT"

- name: Install Rust ${{ steps.rust-version.outputs.channel }}
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ steps.rust-version.outputs.channel }}

- uses: Swatinem/rust-cache@v2

- name: Verify version matches tag
if: github.event_name == 'push'
run: |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I noticed while adding another comment is that since manual running takes dry-run as a parameter - it's currently possible to publish without a dry run from branches that aren't main.

Copilot suggest putting something like this early in the workflow

   - name: Prevent publish from non-main branch
     if: >-
       github.event_name == 'workflow_dispatch'
       && github.event.inputs.dry_run != 'true'
       && github.ref != 'refs/heads/main'
     run: |
       echo "Live publishing is only allowed from main. Use dry-run for other branches."
       exit 1

which I think is reasonable. It will fail the triggered manually on a branch that is not main and dry-run is not provided.

TAG_VERSION="${GITHUB_REF#refs/tags/v}"
CARGO_VERSION=$(grep '^version = ' Cargo.toml | head -n1 | sed 's/.*"\(.*\)".*/\1/')
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version extraction uses grep '^version = ' Cargo.toml | head -n1 which assumes the workspace version is the first version field in the file. However, looking at the root Cargo.toml structure, the version is under [workspace.package] section. A more robust approach would be to use grep -A 5 '^\[workspace\.package\]' Cargo.toml | grep 'version = ' or use a proper TOML parser to avoid potential mismatches if the file structure changes.

Suggested change
CARGO_VERSION=$(grep '^version = ' Cargo.toml | head -n1 | sed 's/.*"\(.*\)".*/\1/')
CARGO_VERSION=$(grep -A 5 '^\[workspace\.package\]' Cargo.toml | grep 'version = ' | head -n1 | sed 's/.*"\(.*\)".*/\1/')

Copilot uses AI. Check for mistakes.
if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then
echo "::error::Tag version ($TAG_VERSION) does not match Cargo.toml version ($CARGO_VERSION)"
exit 1
fi
Comment on lines +59 to +67
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a validation step to ensure that all workspace crates use version.workspace = true and don't have hardcoded versions. This would catch configuration errors before attempting to publish. A simple check like grep -r '^version = "' */Cargo.toml | grep -v workspace could identify crates with hardcoded versions.

Copilot uses AI. Check for mistakes.

- name: Run tests
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running the full test suite on every publish attempt (including dry-run) adds significant time to the workflow. Consider making the test step conditional with if: github.event.inputs.dry_run != 'true' || github.event_name == 'push' to skip tests during dry-run testing, since tests are already validated in PR CI checks before merging. However, keeping tests for actual releases (tag pushes) is valuable as a final safety check.

Suggested change
- name: Run tests
- name: Run tests
if: github.event.inputs.dry_run != 'true' || github.event_name == 'push'

Copilot uses AI. Check for mistakes.
run: cargo test --locked --workspace --profile ci

- name: Publish workspace crates
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vermy minor - this can be slightly safer with

   env:
      DRY_RUN: ${{ github.event.inputs.dry_run || 'false ' }}

And then using DRY_RUN to set the DRY_RUN_FLAG internally rather than expanding the entire github.event directly in the shell script. Evaluating it in env should force it to be a boolean as an extra safety measure.

run: |
DRY_RUN_FLAG=""
if [ "${{ github.event.inputs.dry_run || 'false' }}" = "true" ]; then
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dry-run default expression github.event.inputs.dry_run || 'false' will evaluate to 'false' when the workflow is triggered by a tag push (since github.event.inputs.dry_run will be empty/null). This is correct behavior for tag-triggered publishes. However, consider making this more explicit with a comment or using a more readable approach like setting a workflow-level environment variable that checks both the trigger type and input.

Suggested change
if [ "${{ github.event.inputs.dry_run || 'false' }}" = "true" ]; then
# For tag-push events, github.event.inputs.dry_run is empty, so this evaluates to a real (non-dry-run) publish.
if [ "${{ github.event.inputs.dry_run }}" = "true" ]; then

Copilot uses AI. Check for mistakes.
DRY_RUN_FLAG="--dry-run"
echo "🧪 DRY-RUN MODE"
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When not in dry-run mode (live publishing), there's no visual indicator in the logs. Consider adding an else clause to echo a message like "📦 LIVE MODE - Publishing to crates.io" to make it clear when actual publishing is happening. This helps prevent accidental publishes and makes logs easier to understand.

Suggested change
echo "🧪 DRY-RUN MODE"
echo "🧪 DRY-RUN MODE"
else
echo "📦 LIVE MODE - Publishing to crates.io"

Copilot uses AI. Check for mistakes.
fi
cargo publish --locked --workspace $DRY_RUN_FLAG
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cargo publish command does not support a --workspace flag. As of Rust 1.92 (and as of January 2025), cargo publish only publishes a single crate at a time. To publish multiple workspace crates, you need to either:

  1. Use a loop to publish each crate individually in dependency order
  2. Use a tool like cargo-workspaces or cargo-release
  3. Manually specify the publish order

The current command will fail with an error. You'll need to implement a solution that publishes crates one at a time, ensuring dependencies are published and indexed before their dependents.

Suggested change
cargo publish --locked --workspace $DRY_RUN_FLAG
echo "Resolving workspace crates to publish..."
PKGS=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.source == null) | .name')
if [ -z "$PKGS" ]; then
echo "::error::No workspace crates found to publish"
exit 1
fi
for pkg in $PKGS; do
echo "📦 Publishing crate: $pkg (DRY_RUN_FLAG='$DRY_RUN_FLAG')"
cargo publish --locked -p "$pkg" $DRY_RUN_FLAG
# When actually publishing, wait a bit for crates.io index to update
if [ -z "$DRY_RUN_FLAG" ]; then
echo "Waiting for crates.io index to update before publishing the next crate..."
sleep 30
fi
done

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not accurate. There is a cargo publish --workspace and it does work. It doesn't necessarily handle partial failures gracefully, but hopefully those are relatively rare.

Loading