diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml deleted file mode 100644 index 0d922ef..0000000 --- a/.github/workflows/publish-release.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Publish release to Maven Central - -on: - push: - tags: - - 'v[0-9]+.[0-9]+.[0-9]+' - -permissions: - contents: read - -concurrency: - group: maven-central-release - cancel-in-progress: false - -jobs: - publish-release: - if: github.repository == 'uber/ussi' - runs-on: ubuntu-latest - steps: - - name: Check out USSI sources - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4.2.2 - with: - persist-credentials: false - - - name: Verify release tag and version - env: - RELEASE_TAG: ${{ github.ref_name }} - run: | - if [[ ! "$RELEASE_TAG" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then - echo "Release tag must have the form vX.Y.Z: $RELEASE_TAG" >&2 - exit 1 - fi - release_version="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}" - configured_version="$(sed -n 's/^VERSION_NAME=//p' gradle.properties)" - if [[ "$configured_version" != "$release_version" ]]; then - echo "VERSION_NAME ($configured_version) must match the release tag ($RELEASE_TAG)" >&2 - exit 1 - fi - - - name: Set up JDK - uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v4.7.0 - with: - java-version: '21' - distribution: 'temurin' - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v4.4.0 - with: - cache-disabled: true - - - name: Test with Gradle - run: ./gradlew test --no-daemon - - - name: Upload signed release - env: - ORG_GRADLE_PROJECT_RELEASE_SIGNING_ENABLED: 'true' - ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USER_TOKEN_USERNAME }} - ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_USER_TOKEN_PASSWORD }} - ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.PGP_PRIVATE_KEY_ID }} - ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.PGP_SIGNING_KEY_PRIVATE_KEY }} - ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.PGP_SIGNING_KEY_PASSWORD }} - run: ./gradlew publishToMavenCentral --no-daemon diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..8bfd6fd --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,82 @@ +name: Release to Maven Central + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + +permissions: + contents: write # Create the GitHub Release. + +concurrency: + group: maven-central-release + cancel-in-progress: false + +jobs: + release: + if: github.repository == 'uber/ussi' + runs-on: ubuntu-latest + steps: + - name: Check out release tag + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Verify release tag and version + env: + RELEASE_TAG: ${{ github.ref_name }} + run: | + if [[ ! "$RELEASE_TAG" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then + echo 'Expected a release tag such as v0.1.0.' >&2 + exit 1 + fi + version="$(sed -n 's/^VERSION_NAME=//p' gradle.properties)" + if [[ "$version" != "${RELEASE_TAG#v}" || "$version" =~ -[Ss][Nn][Aa][Pp][Ss][Hh][Oo][Tt]$ ]]; then + echo 'Release tag must match a non-SNAPSHOT VERSION_NAME.' >&2 + exit 1 + fi + coordinates="$(sed -n 's/.*maven_coordinates = "com.uber.ussi:ussi:\([^"]*\)".*/\1/p' BUILD.bazel)" + if [[ "$coordinates" != "$version" ]]; then + echo 'BUILD.bazel maven_coordinates must match VERSION_NAME.' >&2 + exit 1 + fi + printf 'RELEASE_VERSION=%s\n' "$version" >> "$GITHUB_ENV" + + - name: Extract release notes + run: | + awk -v ver="$RELEASE_VERSION" ' + /^# / { if (found) exit; if ($0 == "# " ver) { found=1; next } } + found + ' CHANGELOG.md > release_notes.md + if ! grep -q '[^[:space:]]' release_notes.md; then + echo "Add a # $RELEASE_VERSION section with release notes to CHANGELOG.md" >&2 + exit 1 + fi + + - name: Set up JDK + uses: actions/setup-java@v6 + with: + distribution: temurin + java-version: '21' + + - name: Set up Gradle and validate wrapper + uses: gradle/actions/setup-gradle@v6 + with: + cache-disabled: true + + - name: Publish + env: + ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USER_TOKEN_USERNAME }} + ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_USER_TOKEN_PASSWORD }} + ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.PGP_PRIVATE_KEY_ID }} + ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.PGP_SIGNING_KEY_PRIVATE_KEY }} + ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.PGP_SIGNING_KEY_PASSWORD }} + run: ./gradlew publishToMavenCentral --no-daemon + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ github.ref_name }} + run: | + gh release create "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --verify-tag \ + --title "$RELEASE_VERSION" --notes-file release_notes.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6826a04 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +Release notes for each version use a top-level `# X.Y.Z` heading (matching the +release workflow and [RELEASING.md](RELEASING.md)). + +# 0.1.0 + +Initial open-source release of Uber Similarity Search Index (USSI). + +Published Maven coordinates: `com.uber.ussi:ussi:0.1.0`. diff --git a/RELEASING.md b/RELEASING.md index f641951..144c27d 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -7,21 +7,67 @@ Published artifacts use **`com.uber.ussi:ussi`**. ## CI and local builds - **Pull requests and main:** Bazel tests (`.github/workflows/tests.yml`). -- **Releases:** Gradle publishes to Maven Central (`.github/workflows/publish-release.yml`). +- **Releases:** [Release to Maven Central](.github/workflows/release.yml) on version tags. Both build systems share the same `src/main/java` and `src/test/java` trees. Bazel remains supported for day-to-day development; Gradle is the supported release path for Uber open-source JVM libraries. Gradle requires JDK 21 or later. On Uber-managed machines that inject an internal Artifactory `init.gradle`, use a clean `GRADLE_USER_HOME` (for example `GRADLE_USER_HOME=$PWD/.gradle-home ./gradlew test`) so dependencies resolve from Maven Central only. -## Cut a release +## Release process -1. Set `VERSION_NAME` in `gradle.properties` to the release version (no `-SNAPSHOT` suffix). -2. Update `maven_coordinates` in `BUILD.bazel` to the same version. -3. Merge to `main`. -4. Create and push an annotated tag `vX.Y.Z` matching `VERSION_NAME`. -5. Confirm the **Publish release to Maven Central** workflow succeeds on GitHub Actions. -6. Confirm the artifact on [Maven Central](https://central.sonatype.com/artifact/com.uber.ussi/ussi). +Creating and pushing a release tag to `uber/ussi` automatically triggers the +[Release to Maven Central workflow](.github/workflows/release.yml). Merging a PR +or pushing a commit to `main` does not trigger a release. -If tag `vX.Y.Z` already exists on an older commit, move the tag after the publish workflow is on `main`, or cut the next patch version instead. +The workflow signs, uploads, and releases `com.uber.ussi:ussi` to Maven Central. +The Gradle Maven Publish plugin's `publishToMavenCentral` task handles both upload +and release because `mavenCentralAutomaticPublishing=true` is set in +`gradle.properties`. After publishing, the workflow creates a GitHub Release for +the tag using the matching `CHANGELOG.md` section as its release notes. The tagged +commit should already have passed CI on `main`. -After the release is published, bump `VERSION_NAME` in `gradle.properties` and `maven_coordinates` in `BUILD.bazel` to the next `-SNAPSHOT` development version. +## Prepare and publish a release + +1. Set `VERSION_NAME` in `gradle.properties` to the release version without + `-SNAPSHOT`, for example `0.1.0`. +2. Set `maven_coordinates` on `//:src_main` in `BUILD.bazel` to the same version. +3. Add a matching `# 0.1.0` section with release notes to `CHANGELOG.md`. +4. Commit with `Prepare for release 0.1.0.` and merge the PR into `main`. +5. Tag the release commit and push the tag: + + ```sh + git switch main + git pull --ff-only origin main + git tag -a v0.1.0 -m "Version 0.1.0" + git push origin v0.1.0 + ``` + +6. Follow **Release to Maven Central** in GitHub Actions. The workflow validates + the tag, version, Bazel coordinates, and changelog before publishing, then + creates the GitHub Release. Maven Central may take additional time to make a + released version available. +7. Prepare the next development version, such as `0.2.0-SNAPSHOT`, in + `gradle.properties` and `BUILD.bazel`, and submit it in a separate PR. + +Release tags use the form `vX.Y.Z`. The tag version (without the `v` prefix) must +exactly match `VERSION_NAME`, and snapshot versions are rejected. The workflow +does not change versions, create commits, or push tags. + +## Retry a release + +Open the failed tag-push run of **Release to Maven Central** in GitHub Actions +and select **Re-run failed jobs**. Versions already released to Maven Central +cannot be overwritten; use a new version for changed artifacts. + +## Repository secrets + +These GitHub Actions secrets are configured at the Uber organization level for +`uber/ussi`: + +| Secret | Purpose | +| --- | --- | +| `MAVEN_CENTRAL_USER_TOKEN_USERNAME` | Central Portal user token username for `com.uber.ussi`. | +| `MAVEN_CENTRAL_USER_TOKEN_PASSWORD` | Central Portal user token password. | +| `PGP_SIGNING_KEY_PRIVATE_KEY` | ASCII-armored private signing key. | +| `PGP_PRIVATE_KEY_ID` | Signing subkey ID, if required. Optional. | +| `PGP_SIGNING_KEY_PASSWORD` | Private key passphrase. Optional for an unencrypted key. | diff --git a/gradle.properties b/gradle.properties index 4402b68..3b89b06 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,4 +21,7 @@ POM_DEVELOPER_NAME=Uber Technologies POM_DEVELOPER_URL=https://uber.com mavenCentralPublishing=true +mavenCentralAutomaticPublishing=true +mavenCentralDeploymentValidation=PUBLISHED +signAllPublications=true RELEASE_SIGNING_ENABLED=true