diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98c9bc1..abb4ea6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,17 +11,14 @@ jobs: build: # we want to run ubuntu-latest but we'll pin to a specific version so CI is reproducable runs-on: ubuntu-24.04 - strategy: - matrix: - java: [ '17' ] # Java is steps: - uses: actions/checkout@v4 - - name: Set up JDK ${{ matrix.java }} + - name: Set up JDK 17 uses: actions/setup-java@v4 with: - java-version: ${{ matrix.java }} + java-version: 17 distribution: 'temurin' - name: Grant execute permission for gradlew @@ -34,5 +31,5 @@ jobs: uses: actions/upload-artifact@v4 if: always() with: - name: test-results-java-${{ matrix.java }} + name: test-results path: build/test-results/test/ diff --git a/.github/workflows/create-and-publish-release.yml b/.github/workflows/create-and-publish-release.yml new file mode 100644 index 0000000..9488445 --- /dev/null +++ b/.github/workflows/create-and-publish-release.yml @@ -0,0 +1,66 @@ +# This workflow creates a new release by running CI and then executing the release script. +# It can be run manually with a choice of which semver segment to bump. +name: Create And Publish Release +on: + workflow_dispatch: + inputs: + semver_segment_to_bump: + description: 'Which semver segment to bump' + required: true + default: 'PATCH' + type: choice + options: + - PATCH + - MINOR + - MAJOR + branch: + description: 'Branch to create release from' + required: true + default: 'main' + type: string + +jobs: + ci: + name: Run CI + # we want to run ubuntu-latest but we'll pin to a specific version so CI is reproducable + runs-on: ubuntu-24.04 + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch }} + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + + - name: Run tests + run: ./gradlew check build + + create-release: + name: Create Release + # we want to run ubuntu-latest but we'll pin to a specific version so CI is reproducable + runs-on: ubuntu-24.04 + needs: ci + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch }} + token: ${{ secrets.RELEASE_TOKEN }} + fetch-depth: 0 + + - name: Configure Git + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: Run release script + run: | + chmod +x scripts/create-new-release.sh + scripts/create-new-release.sh --push=true --branch=${{ inputs.branch }} --semver-segment-to-bump=${{ inputs.semver_segment_to_bump }} diff --git a/.github/workflows/publish-existing-release.yml b/.github/workflows/publish-existing-release.yml new file mode 100644 index 0000000..85612dc --- /dev/null +++ b/.github/workflows/publish-existing-release.yml @@ -0,0 +1,136 @@ +# This workflow is triggered when a new tag is pushed to main. +# It can also be run manually to re-publish a release in case it failed for some reason. +name: Publish Existing Release + +on: + push: + tags: + - 'v*' + branches: + - main + workflow_dispatch: + inputs: + tag: + description: 'Tag to publish (e.g., v1.0.0)' + required: true + type: string + +jobs: + validate-and-publish: + name: Validate Tag and Publish Release + runs-on: ubuntu-24.04 + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Determine tag + id: determine-tag + run: | + if [[ "${{ github.event_name }}" == "push" ]]; then + TAG_NAME="${{ github.ref_name }}" + else + TAG_NAME="${{ inputs.tag }}" + fi + echo "tag=$TAG_NAME" >> $GITHUB_OUTPUT + echo "Using tag: $TAG_NAME" + + - name: Validate tag format + run: | + TAG="${{ steps.determine-tag.outputs.tag }}" + + # Check if tag starts with 'v' + if [[ ! "$TAG" =~ ^v ]]; then + echo "Error: Tag '$TAG' must start with 'v'" + exit 1 + fi + + # Extract version without 'v' prefix + VERSION="${TAG#v}" + + # Check if version is valid semver (x.y.z) + if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: Tag '$TAG' is not valid semver format (vx.y.z)" + exit 1 + fi + + # Check that version does not end with -SNAPSHOT + if [[ "$VERSION" =~ -SNAPSHOT$ ]]; then + echo "Error: Tag '$TAG' cannot end with '-SNAPSHOT'" + exit 1 + fi + + echo "Tag '$TAG' is valid" + + - name: Verify tag exists + run: | + TAG="${{ steps.determine-tag.outputs.tag }}" + if ! git tag -l | grep -q "^$TAG$"; then + echo "Error: Tag '$TAG' does not exist" + exit 1 + fi + echo "Tag '$TAG' exists" + + - name: Checkout tag + run: | + git checkout ${{ steps.determine-tag.outputs.tag }} + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + + - name: Run CI + run: ./gradlew check build + + - name: Build release artifacts + run: ./gradlew publishToMavenLocal -Pversion=${{ steps.determine-tag.outputs.tag }} + + - name: Find built artifacts + id: find-artifacts + run: | + VERSION="${{ steps.determine-tag.outputs.tag }}" + VERSION_NO_V="${VERSION#v}" + + # Find the built JAR files + MAIN_JAR=$(find build/libs -name "*-${VERSION_NO_V}.jar" ! -name "*-sources.jar" ! -name "*-javadoc.jar" | head -1) + SOURCES_JAR=$(find build/libs -name "*-${VERSION_NO_V}-sources.jar" | head -1) + JAVADOC_JAR=$(find build/libs -name "*-${VERSION_NO_V}-javadoc.jar" | head -1) + + echo "main-jar=$MAIN_JAR" >> $GITHUB_OUTPUT + echo "sources-jar=$SOURCES_JAR" >> $GITHUB_OUTPUT + echo "javadoc-jar=$JAVADOC_JAR" >> $GITHUB_OUTPUT + + echo "Found artifacts:" + echo " Main JAR: $MAIN_JAR" + echo " Sources JAR: $SOURCES_JAR" + echo " Javadoc JAR: $JAVADOC_JAR" + + - name: Create GitHub Release + run: | + TAG="${{ steps.determine-tag.outputs.tag }}" + + # Create the release + gh release create "$TAG" \ + --generate-notes \ + --title "Release $TAG" + + # Upload artifacts if they exist + if [[ -n "${{ steps.find-artifacts.outputs.main-jar }}" && -f "${{ steps.find-artifacts.outputs.main-jar }}" ]]; then + gh release upload "$TAG" "${{ steps.find-artifacts.outputs.main-jar }}" + fi + + if [[ -n "${{ steps.find-artifacts.outputs.sources-jar }}" && -f "${{ steps.find-artifacts.outputs.sources-jar }}" ]]; then + gh release upload "$TAG" "${{ steps.find-artifacts.outputs.sources-jar }}" + fi + + if [[ -n "${{ steps.find-artifacts.outputs.javadoc-jar }}" && -f "${{ steps.find-artifacts.outputs.javadoc-jar }}" ]]; then + gh release upload "$TAG" "${{ steps.find-artifacts.outputs.javadoc-jar }}" + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index cc6ba68..18d530f 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,13 @@ The remaining sections document developing the SDK itself. Nothing below is requ - These hooks automatically run common checks for you but CI also runs the same checks before merging to the main branch is allowed - NOTE: this will overwrite existing hooks. Take backups before running + + + + + + + ## Running a local OpenTelemetry collector OpenTelemetry provides a local collector with a debug exporter which logs all traces, logs, and metrics to stdout. diff --git a/scripts/create-new-release.sh b/scripts/create-new-release.sh new file mode 100755 index 0000000..8c81029 --- /dev/null +++ b/scripts/create-new-release.sh @@ -0,0 +1,173 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Default values +BRANCH="main" +PUSH="" +SEMVER_SEGMENT_TO_BUMP="" + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + --push=*) + PUSH="${1#*=}" + shift + ;; + --branch=*) + BRANCH="${1#*=}" + shift + ;; + --semver-segment-to-bump=*) + SEMVER_SEGMENT_TO_BUMP="${1#*=}" + shift + ;; + *) + echo "Unknown option $1" + exit 1 + ;; + esac +done + +# Validate required arguments +if [[ -z "$PUSH" ]]; then + echo "Error: --push=true|false is required" + exit 1 +fi + +if [[ -z "$SEMVER_SEGMENT_TO_BUMP" ]]; then + echo "Error: --semver-segment=MAJOR|MINOR|PATCH is required" + exit 1 +fi + +if [[ "$PUSH" != "true" && "$PUSH" != "false" ]]; then + echo "Error: --push must be 'true' or 'false'" + exit 1 +fi + +if [[ "$SEMVER_SEGMENT_TO_BUMP" != "MAJOR" && "$SEMVER_SEGMENT_TO_BUMP" != "MINOR" && "$SEMVER_SEGMENT_TO_BUMP" != "PATCH" ]]; then + echo "Error: --semver-segment must be 'MAJOR', 'MINOR', or 'PATCH'" + exit 1 +fi + +# Find repository root +REPO_ROOT=$(git rev-parse --show-toplevel) +cd "$REPO_ROOT" + +# Checkout and update the specified branch +echo "Checking out branch: $BRANCH" +git checkout "$BRANCH" +git pull origin "$BRANCH" + +# Assert git state is clean +if [[ -n $(git status --porcelain) ]]; then + echo "Error: Git working directory is not clean" + git status + exit 1 +fi + +# Read current version from braintrust.properties +PROPERTIES_FILE="./src/main/resources/braintrust.properties" +if [[ ! -f "$PROPERTIES_FILE" ]]; then + echo "Error: $PROPERTIES_FILE not found" + exit 1 +fi + +CURRENT_SNAPSHOT=$(grep "^sdk.version=" "$PROPERTIES_FILE" | cut -d'=' -f2) +if [[ -z "$CURRENT_SNAPSHOT" ]]; then + echo "Error: sdk.version not found in $PROPERTIES_FILE" + exit 1 +fi + +echo "Current snapshot version: $CURRENT_SNAPSHOT" + +# Validate current version ends with -SNAPSHOT +if [[ ! "$CURRENT_SNAPSHOT" =~ -SNAPSHOT$ ]]; then + echo "Error: Current version '$CURRENT_SNAPSHOT' does not end with '-SNAPSHOT'" + exit 1 +fi + +# Remove -SNAPSHOT to get current release version +CURRENT_RELEASE_VERSION="${CURRENT_SNAPSHOT%-SNAPSHOT}" +echo "Current release version: $CURRENT_RELEASE_VERSION" + +# Parse version components +if [[ ! "$CURRENT_RELEASE_VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then + echo "Error: Version '$CURRENT_RELEASE_VERSION' is not in semver format (x.y.z)" + exit 1 +fi + +MAJOR="${BASH_REMATCH[1]}" +MINOR="${BASH_REMATCH[2]}" +PATCH="${BASH_REMATCH[3]}" + +# Calculate new version based on semver segment to bump +case "$SEMVER_SEGMENT_TO_BUMP" in + "MAJOR") + NEW_MAJOR=$((MAJOR + 1)) + NEW_MINOR=0 + NEW_PATCH=0 + ;; + "MINOR") + NEW_MAJOR=$MAJOR + NEW_MINOR=$((MINOR + 1)) + NEW_PATCH=0 + ;; + "PATCH") + NEW_MAJOR=$MAJOR + NEW_MINOR=$MINOR + NEW_PATCH=$((PATCH + 1)) + ;; +esac + +case "$SEMVER_SEGMENT_TO_BUMP" in + # For MAJOR/MINOR bumps, release the .0 version and prepare the next patch snapshot + "MAJOR"|"MINOR") + # Release version is the bumped version with patch 0 + CURRENT_RELEASE_VERSION="${NEW_MAJOR}.${NEW_MINOR}.0" + # Next snapshot is patch 1 + NEW_SNAPSHOT="${NEW_MAJOR}.${NEW_MINOR}.1-SNAPSHOT" + ;; + "PATCH") + # For patch bumps, use the original logic + NEW_SNAPSHOT="${NEW_MAJOR}.${NEW_MINOR}.${NEW_PATCH}-SNAPSHOT" + ;; +esac + +echo "New snapshot version: $NEW_SNAPSHOT" + +# Update properties file with current release version +echo "Creating release commit for version: $CURRENT_RELEASE_VERSION" +sed -i "s/^sdk.version=.*/sdk.version=$CURRENT_RELEASE_VERSION/" "$PROPERTIES_FILE" + +# Commit release version +git add "$PROPERTIES_FILE" +git commit -m "release $CURRENT_RELEASE_VERSION" + +# Create tag +git tag "v$CURRENT_RELEASE_VERSION" +echo "Created tag: v$CURRENT_RELEASE_VERSION" + +# Update properties file with new snapshot version +echo "Creating snapshot commit for version: $NEW_SNAPSHOT" +sed -i "s/^sdk.version=.*/sdk.version=$NEW_SNAPSHOT/" "$PROPERTIES_FILE" + +# Commit new snapshot version +git add "$PROPERTIES_FILE" +git commit -m "begin $NEW_SNAPSHOT" + +git log --oneline -n 3 # print out what we just did +# Push if requested +if [[ "$PUSH" == "true" ]]; then + echo "Pushing commits and tags to origin..." + git push origin "$BRANCH" + git push origin "v$CURRENT_RELEASE_VERSION" + echo "Successfully pushed release $CURRENT_RELEASE_VERSION and new snapshot $NEW_SNAPSHOT" +else + echo "Skipping push (--push=false)" + echo "To push manually, run:" + echo " git push origin $BRANCH" + echo " git push origin v$CURRENT_RELEASE_VERSION" +fi + +echo "Release process completed successfully!"