Skip to content
Closed
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
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
294 changes: 294 additions & 0 deletions .github/workflows/release-dispatch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
name: Release - Open a release proposal PR
on:
workflow_dispatch:
inputs:
crate:
description: Crate to release
required: true
type: choice
options:
- libdd-common
- libdd-crashtracker
- libdd-data-pipeline
- libdd-ddsketch
- libdd-dogstatsd-client
- libdd-library-config
- libdd-log
- libdd-profiling
- libdd-profiling-protobuf
- libdd-telemetry
- libdd-tinybytes
- libdd-trace-normalization
- libdd-trace-obfuscation
- libdd-trace-protobuf
- libdd-trace-stats
- libdd-trace-utils

env:
RELEASE_BRANCH: release

jobs:
cargo-release:
permissions:
id-token: write # Enable OIDC
pull-requests: write
contents: write
needs: update-release-branch
runs-on: ubuntu-latest
# TODO: uncomment this when we have a way to test this workflow
# if: ${{ github.repository_owner == 'datadog' }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2
with:
fetch-depth: 0 # Need full history for git tags
- uses: chainguard-dev/actions/setup-gitsign@0cda751b114eb55c388e88f7479292668165602a # v1.0.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
cache-targets: true
- uses: dtolnay/rust-toolchain@nightly
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.92.0
- uses: taiki-e/cache-cargo-install-action@7447f04c51f2ba27ca35e7f1e28fab848c5b3ba7 # 2.3.1
with:
tool: [email protected]
- uses: taiki-e/cache-cargo-install-action@7447f04c51f2ba27ca35e7f1e28fab848c5b3ba7 # 2.3.1
with:
tool: cargo-release
- uses: taiki-e/cache-cargo-install-action@7447f04c51f2ba27ca35e7f1e28fab848c5b3ba7 # 2.3.1
with:
tool: git-cliff
- uses: taiki-e/cache-cargo-install-action@7447f04c51f2ba27ca35e7f1e28fab848c5b3ba7 # 2.3.1
with:
tool: [email protected]

- name: Get publication order for crate and dependencies
run: |
echo "Getting publication order for ${{ inputs.crate }}..."

# Get the publication order as JSON and save to file
./scripts/publication-order.sh --format=json "${{ inputs.crate }}" > /tmp/crates.json
echo "Publication order:"
cat /tmp/crates.json

- name: Get commits since last release for each crate
run: |
# Get commits since release for each crate and save to file
./scripts/commits-since-release.sh "$(cat /tmp/crates.json)" > /tmp/commits-by-crate.json

# Display summary
./scripts/commits-since-release.sh --format=summary "$(cat /tmp/crates.json)"

- name: Create a branch for the release proposal
id: proposal-branch
run: |
git checkout "${{ env.RELEASE_BRANCH }}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BRANCH_NAME="release-proposal/${{ inputs.crate }}/$TIMESTAMP"
git checkout -b "$BRANCH_NAME"
git push origin "$BRANCH_NAME"
echo "Branch created: $BRANCH_NAME from $RELEASE_BRANCH branch"
echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT"

- name: Release version bumps
id: release-version-bumps
env:
ORIGINAL_HEAD: ${{ github.sha }}
run: |
echo "Release version bumps..."

# Initialize results array
echo "[]" > /tmp/api-changes.json

# iterate over the commits and execute cargo release for each crate
jq -c '.[]' /tmp/commits-by-crate.json | while read -r crate; do
NAME=$(echo "$crate" | jq -r '.name')
TAG=$(echo "$crate" | jq -r '.tag')
CRATE_PATH=$(echo "$crate" | jq -r '.path')
TAG_EXISTS=$(echo "$crate" | jq -r '.tag_exists')
COMMITS=$(echo "$crate" | jq -r '.commits')
INITIAL_RELEASE=false

echo "Checking Rust toolchain versions..."

# if there are no commits, skip the release
if [ "$COMMITS" = "[]" ]; then
echo "No commits since last release for $NAME, skipping release"
continue
fi

if [ "$TAG_EXISTS" = "true" ]; then
RANGE="$TAG..$ORIGINAL_HEAD"
echo "Using $RANGE as range"

# TODO: Should we skip the release if the tag is not in the branch?
echo "Is the tag $TAG in the branch:"
git branch --contains "$TAG"
if [ $? -ne 0 ]; then
echo "Error: Tag $TAG is not in the branch" >&2
exit 1
fi

echo "Executing semver-level.sh for $NAME since $RANGE..."
SEMVER_LEVEL=$(./scripts/semver-level.sh "$NAME" "$TAG" 2>&1)
echo "Semver level: $SEMVER_LEVEL"

LEVEL=$(echo "$SEMVER_LEVEL" | jq -r '.level')

echo "Executing cargo release for $NAME since $TAG with level $LEVEL..."
cargo release version -p "$NAME" --prev-tag-name "$TAG" -x $LEVEL --no-confirm
else
RANGE="$ORIGINAL_HEAD"
echo "No previous release tag for $NAME, using $ORIGINAL_HEAD as range"

# Use the version from the crate metadata
LEVEL=$(echo "$crate" | jq -r '.version')

# fail when the version is not an initial release
if [ "$LEVEL" != "1.0.0" ] && [ "$LEVEL" != "0.1.0" ]; then
echo "Error: $NAME is not a 1.0.0 or 0.1.0 release" >&2
exit 1
fi

INITIAL_RELEASE=true

echo "Executing cargo release for $NAME with level $LEVEL..."
cargo release version -p "$NAME" -x $LEVEL --no-confirm

# if CHANGELOG.md does not exist, create it
if [ ! -f "$CRATE_PATH/CHANGELOG.md" ]; then
echo "Creating CHANGELOG.md for $NAME..."
touch "$CRATE_PATH/CHANGELOG.md"
fi
fi

# Update the CHANGELOG.md
NEXT_VERSION=$(cargo metadata --format-version=1 --no-deps | jq -r --arg name "$NAME" '.packages[] | select(.name == $name) | .version')
NEXT_TAG="$NAME-v$NEXT_VERSION"

# Check what commits git sees in the range
echo "Commits in the range $RANGE:"
git log --oneline "$RANGE" -- "$CRATE_PATH/"

echo "Executing git cliff for $NAME since $RANGE, next tag: $NEXT_TAG..."

# It seems that git cliff does not like to use tags that are not on the branch
git cliff --tag "$NEXT_TAG" --include-path "$CRATE_PATH/**/*" --prepend "$CRATE_PATH/CHANGELOG.md" --tag-pattern "$NAME-v.*" -u -v "$RANGE"
git add "$CRATE_PATH/CHANGELOG.md"

# Commit the changes
cargo release commit --no-confirm -x --sign-commit -vv

# Add to results array
jq --arg name "$NAME" \
--arg level "$LEVEL" \
--arg tag "$NEXT_TAG" \
--arg version "$NEXT_VERSION" \
--arg initial_release "$INITIAL_RELEASE" \
'. += [{"name": $name, "level": $level, "tag": $tag, "version": $version, "initial_release": $initial_release}]' \
/tmp/api-changes.json > /tmp/api-changes.tmp && mv /tmp/api-changes.tmp /tmp/api-changes.json
done

# Check if there are commits to push
if git diff --quiet "${{ env.RELEASE_BRANCH }}"; then
echo "No changes to push. Cancelling the workflow."
exit 1
fi

BRANCH_NAME="${{ steps.proposal-branch.outputs.branch_name }}"
echo "Pushing branch $BRANCH_NAME to origin..."
git push origin "$BRANCH_NAME"

# Output the results
echo "API changes summary:"
cat /tmp/api-changes.json | jq .

- name: Upload release data
uses: actions/upload-artifact@v4
with:
name: release-dispatch-data
path: |
/tmp/commits-by-crate.json
/tmp/api-changes.json
retention-days: 1

outputs:
branch_name: ${{ steps.proposal-branch.outputs.branch_name }}

create-pr:
needs: cargo-release
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2
with:
ref: ${{ needs.cargo-release.outputs.branch_name }}

- name: Download release data
uses: actions/download-artifact@v4
with:
name: release-dispatch-data
path: /tmp

- name: Create a PR
env:
GH_TOKEN: ${{ github.token }}
run: |
BRANCH_NAME="${{ needs.cargo-release.outputs.branch_name }}"

# Generate the PR body by merging commits and API changes
# Note: read returns 1 when it reaches EOF, which is expected for heredocs
read -r -d '' JQ_FILTER << 'EOF' || true
.[] |
. as $crate |
($api[0] | map(select(.name == $crate.name)) | first // {
level: "skipped because there were no changes to the public API"
}) as $api_info |
[
"## \($crate.name)",
"",
(if $api_info.version then "**Next version:** `\($api_info.version)`\n" else null end),
"**Semver bump:** `\($api_info.level)`",
"",
(if $api_info.tag then "**Tag:** `\($api_info.tag)`\n" else null end),
(if $api_info.initial_release == "true" then
"**Warning:** this is an initial release. Please verify that the version and commits included are correct.\n"
else null end),
"### Commits",
"",
($crate.commits | map("- \(.subject)") | join("\n"))
] | map(select(. != null)) | join("\n")
EOF

PR_BODY=$(jq -r --slurpfile api /tmp/api-changes.json "$JQ_FILTER" /tmp/commits-by-crate.json)

PR_BODY="# Release proposal for ${{ inputs.crate }} and its dependencies

This PR contains version bumps based on public API changes and commits since last release.

${PR_BODY}"

echo "PR_BODY: $PR_BODY"

gh pr create \
--head "$BRANCH_NAME" \
--title "chore(release): proposal for ${{ inputs.crate }}" \
--body "$PR_BODY" \
--base "${{ env.RELEASE_BRANCH }}"

update-release-branch:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2
with:
fetch-depth: 0
fetch-tags: true
- name: Merge the main branch into the release branch
run: |
git fetch origin "${{ env.RELEASE_BRANCH }}" --tags
git checkout "${{ env.RELEASE_BRANCH }}"
# only fast-forward the merge
git merge --ff-only origin/main
git push origin "${{ env.RELEASE_BRANCH }}" --tags
File renamed without changes.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading