77 required : true
88 type : choice
99 options :
10+ - libdd-alloc
11+ - libdd-capabilities
12+ - libdd-capabilities-impl
1013 - libdd-common
1114 - libdd-crashtracker
1215 - libdd-data-pipeline
1316 - libdd-ddsketch
1417 - libdd-dogstatsd-client
18+ - libdd-http-client
1519 - libdd-library-config
1620 - libdd-libunwind-sys
1721 - libdd-log
22+ - libdd-otel-thread-ctx
1823 - libdd-profiling
1924 - libdd-profiling-protobuf
25+ - libdd-shared-runtime
2026 - libdd-telemetry
2127 - libdd-tinybytes
2228 - libdd-trace-normalization
@@ -388,55 +394,151 @@ jobs:
388394 echo "No previous release tag for $NAME, preparing initial release..."
389395
390396 # Use the version from the crate metadata
391- LEVEL=$(echo "$crate" | jq -r '.version')
392-
397+ VERSION=$(echo "$crate" | jq -r '.version')
398+ LEVEL="major"
399+ TAG=""
400+ RANGE=""
401+
393402 # fail when the version is not an initial release
394- if [ "$LEVEL" != "1.0.0" ] && [ "$LEVEL " != "0.1.0" ]; then
395- echo "Error: $NAME is not a 1.0.0 or 0.1.0 release" >&2
403+ if [ "$VERSION " != "0.1.0" ]; then
404+ echo "Error: $NAME is not a 0.1.0 release" >&2
396405 exit 1
397406 fi
398407
399408 INITIAL_RELEASE=true
400409
401410 echo "Executing cargo release for $NAME with level $LEVEL..."
402- cargo release version -p "$NAME" -x $LEVEL --no-confirm
411+ cargo release version -p "$NAME" --allow-branch "$BRANCH_NAME" -x $LEVEL --no-confirm
412+ fi
413+
414+ # Commit the changes
415+ cargo release commit --no-confirm -x
416+
417+ NEXT_VERSION=$(cargo metadata --format-version=1 --no-deps | jq -r --arg name "$NAME" '.packages[] | select(.name == $name) | .version')
418+ NEXT_TAG="$TAG_PREFIX$NEXT_VERSION"
419+
420+ # Add to results array
421+ jq --arg name "$NAME" \
422+ --arg level "$LEVEL" \
423+ --arg tag "$NEXT_TAG" \
424+ --arg prev_tag "$TAG" \
425+ --arg version "$NEXT_VERSION" \
426+ --arg range "$RANGE" \
427+ --argjson commits "$COMMITS" \
428+ --arg path "$CRATE_PATH" \
429+ --arg initial_release "$INITIAL_RELEASE" \
430+ '. += [{"name": $name, "level": $level, "tag": $tag, "prev_tag": $prev_tag, "version": $version, "range": $range, "commits": $commits, "path": $path, "initial_release": $initial_release}]' \
431+ /tmp/api-changes.json > /tmp/api-changes.tmp && mv /tmp/api-changes.tmp /tmp/api-changes.json
432+ done
433+
434+ # Check if there are commits to push
435+ if git diff --quiet "${{ steps.ephemeral-branch.outputs.ephemeral_branch }}"; then
436+ echo "No changes to push. Cancelling the workflow."
437+ exit 1
438+ fi
439+
440+ # Output the results
441+ echo "API changes summary:"
442+ jq . /tmp/api-changes.json
443+
444+ - name : Update version for crates with libdd-* direct dependency major bumps since last release
445+ run : |
446+ set -euo pipefail
447+ BRANCH_NAME="${{ steps.proposal-branch.outputs.branch_name }}"
448+
449+ # Run the audit in a throwaway worktree so extra worktrees / cargo metadata do not touch the job checkout.
450+ MAJOR_BUMPS_WT=$(mktemp -d "${RUNNER_TEMP:-/tmp}/major-bumps-wt.XXXXXX")
451+ WF_SHA="${{ github.sha }}"
452+
453+ git worktree add --detach "$MAJOR_BUMPS_WT" "$WF_SHA"
454+ set +e
455+ ( cd "$MAJOR_BUMPS_WT" && ./scripts/major-bumps-level.sh /tmp/api-changes.json ) \
456+ > /tmp/api-changes-with-major-bumps-pre-commit.json
457+ MB_RC=$?
458+ git worktree remove --force "$MAJOR_BUMPS_WT" || true
459+ set -e
460+ if [[ "$MB_RC" -ne 0 ]]; then
461+ echo "Major bumps level script failed with code $MB_RC"
462+ echo "Major bumps level script output:"
463+ cat /tmp/api-changes-with-major-bumps-pre-commit.json
464+ exit "$MB_RC"
465+ fi
466+
467+ # Full same crate list as api-changes.json; rows updated in place when a major bump is applied.
468+ cp /tmp/api-changes-with-major-bumps-pre-commit.json /tmp/api-changes-with-major-bumps.json
469+
470+ # iterate over the major bumps and check the major bumps and update the version
471+ jq -c '.[]' /tmp/api-changes-with-major-bumps-pre-commit.json | while read -r bump; do
472+ NAME=$(echo "$bump" | jq -r '.name')
473+ PREV_TAG=$(echo "$bump" | jq -r '.prev_tag')
474+ TAG=$(echo "$bump" | jq -r '.tag')
475+ VERSION=$(echo "$bump" | jq -r '.version')
476+ MAJOR_BUMPS=$(echo "$bump" | jq -r '.major_bumps')
477+
478+ if [ "$MAJOR_BUMPS" != "[]" ]; then
479+ echo "Updating version for $NAME with major bumps: $MAJOR_BUMPS"
480+ cargo release version -p "$NAME" --prev-tag-name "$PREV_TAG" --allow-branch "$BRANCH_NAME" -x major --no-confirm
481+
482+ git commit -am "chore(release): update version for $NAME with major bumps"
483+
484+ NEXT_VERSION=$(cargo metadata --format-version=1 --no-deps | jq -r --arg name "$NAME" '.packages[] | select(.name == $name) | .version')
485+ NEXT_TAG="$NAME-v$NEXT_VERSION"
486+
487+ echo "Updating tag $TAG to $NEXT_TAG and version $VERSION to $NEXT_VERSION for $NAME"
488+
489+ jq --arg name "$NAME" \
490+ --arg nl "major" \
491+ --arg version "$NEXT_VERSION" \
492+ --arg tag "$NEXT_TAG" \
493+ 'map(if .name == $name then . + {level: $nl, version: $version, tag: $tag} else . end)' \
494+ /tmp/api-changes-with-major-bumps.json > /tmp/api-changes-with-major-bumps.tmp \
495+ && mv /tmp/api-changes-with-major-bumps.tmp /tmp/api-changes-with-major-bumps.json
496+ fi
497+ done
498+
499+ # Output the results
500+ echo "API changes with major bumps summary:"
501+ jq . /tmp/api-changes-with-major-bumps.json
502+
503+ - name : Generate CHANGELOGS
504+ id : generate-changelogs
505+ run : |
506+ set -euo pipefail
507+ ORIGINAL_HEAD=$(cat /tmp/release_head_sha)
508+
509+ echo "Generating CHANGELOGS"
510+
511+ jq -c '.[]' /tmp/api-changes-with-major-bumps.json | while read -r bump; do
512+ COMMITS=$(echo "$bump" | jq -r '.commits')
513+ RANGE=$(echo "$bump" | jq -r '.range')
514+ NAME=$(echo "$bump" | jq -r '.name')
515+ TAG=$(echo "$bump" | jq -r '.prev_tag')
516+ NEXT_TAG=$(echo "$bump" | jq -r '.tag')
517+ CRATE_PATH=$(echo "$bump" | jq -r '.path')
518+ INITIAL_RELEASE=$(echo "$bump" | jq -r '.initial_release')
519+
520+ if [ "$INITIAL_RELEASE" = "true" ]; then
521+ echo "Initial release for $NAME"
403522
404523 # Use the existing CHANGELOG.md if present, otherwise create a minimal one
405524 if [ ! -f "$CRATE_PATH/CHANGELOG.md" ]; then
406525 echo "Creating CHANGELOG.md for $NAME..."
407526 RELEASE_DATE=$(date +%Y-%m-%d)
408527 printf '# Changelog\n\n\n## %s - %s\n\nInitial release.\n' "$LEVEL" "$RELEASE_DATE" > "$CRATE_PATH/CHANGELOG.md"
528+
529+ git add "$CRATE_PATH/CHANGELOG.md"
530+ git commit -m "chore(release): update CHANGELOG.md for $NAME"
409531 else
410532 echo "Using existing CHANGELOG.md for $NAME..."
411533 fi
534+ continue
535+ fi
412536
413- git add "$CRATE_PATH/CHANGELOG.md"
414- if git diff --cached --quiet; then
415- # Nothing changed (version already correct, CHANGELOG already present).
416- # An empty commit is required so the proposal branch is ahead of the
417- # ephemeral branch — GitHub rejects PRs between branches at the same commit.
418- echo "Nothing to commit for $NAME (version and CHANGELOG already up to date), creating empty release marker commit..."
419- git commit --allow-empty -m "chore: Release $NAME $LEVEL"
420- else
421- cargo release commit --no-confirm -x
422- fi
423-
424- # Add to results array
425- jq --arg name "$NAME" \
426- --arg level "$LEVEL" \
427- --arg tag "$TAG_PREFIX$LEVEL" \
428- --arg version "$LEVEL" \
429- --arg initial_release "$INITIAL_RELEASE" \
430- '. += [{"name": $name, "level": $level, "tag": $tag, "version": $version, "initial_release": $initial_release}]' \
431- /tmp/api-changes.json > /tmp/api-changes.tmp && mv /tmp/api-changes.tmp /tmp/api-changes.json
432-
433- # Skip the git-cliff changelog update below — CHANGELOG is already handled above
537+ # FIXME: $COMMITS could be empty if there are no commits since last release
538+ if [ "$COMMITS" = "[]" ]; then
539+ echo "No commits since last release for $NAME, skipping CHANGELOG generation"
434540 continue
435541 fi
436-
437- # Update the CHANGELOG.md
438- NEXT_VERSION=$(cargo metadata --format-version=1 --no-deps | jq -r --arg name "$NAME" '.packages[] | select(.name == $name) | .version')
439- NEXT_TAG="$TAG_PREFIX$NEXT_VERSION"
440542
441543 # Build a tight range from commits already found by commits-since-release.sh.
442544 # This will save some time analising unnecessary commits and prevent unrelated commits
@@ -471,44 +573,29 @@ jobs:
471573 "$CLIFF_CONTEXT_FILE" > "$CLIFF_FILTERED_FILE"
472574 git cliff --from-context "$CLIFF_FILTERED_FILE" -u -v --prepend "$CRATE_PATH/CHANGELOG.md"
473575 rm -f "$CLIFF_CONTEXT_FILE" "$CLIFF_HASHES_FILE" "$CLIFF_FILTERED_FILE"
474- git add "$CRATE_PATH/CHANGELOG.md"
475-
476- # Commit the changes
477- cargo release commit --no-confirm -x
478576
479- # Add to results array
480- jq --arg name "$NAME" \
481- --arg level "$LEVEL" \
482- --arg tag "$NEXT_TAG" \
483- --arg version "$NEXT_VERSION" \
484- --arg initial_release "$INITIAL_RELEASE" \
485- '. += [{"name": $name, "level": $level, "tag": $tag, "version": $version, "initial_release": $initial_release}]' \
486- /tmp/api-changes.json > /tmp/api-changes.tmp && mv /tmp/api-changes.tmp /tmp/api-changes.json
577+ git add "$CRATE_PATH/CHANGELOG.md"
578+ git commit -m "chore(release): update CHANGELOG.md for $NAME"
487579 done
488580
489- # Check if any crates are being released
490- if [ "$(jq 'length' /tmp/api-changes.json)" -eq 0 ] ; then
491- echo "No crates to release . Cancelling the workflow."
581+ # Check if there are commits to push
582+ if git diff --quiet "${{ steps.ephemeral-branch.outputs.ephemeral_branch }}" ; then
583+ echo "No changes to push . Cancelling the workflow."
492584 exit 1
493585 fi
494586
495587 # Oldest → newest (chronological). Plain `git log` is newest-first; commit-headless should receive
496- # parent→ child order so replays/signing match git history. Space-separated SHAs for the action.
588+ # parent → child order so replays/signing match git history. Space-separated SHAs for the action.
497589 COMMITS=$(git log --reverse "$ORIGINAL_HEAD".. --format='%H' | tr '\n' ' ' | xargs)
498590 echo "commits=$COMMITS" >> $GITHUB_OUTPUT
499591
500- # Output the results
501- echo "API changes summary:"
502- jq . /tmp/api-changes.json
503-
504592 - name : Push commits
505- if : steps.release-version-bumps.outputs.commits != ''
506593 uses : DataDog/commit-headless@action/v2.0.3
507594 with :
508595 branch : ${{ steps.proposal-branch.outputs.branch_name }}
509596 head-sha : ${{ steps.commits-since-release.outputs.release_head_sha }}
510597 command : push
511- commits : " ${{ steps.release-version-bumps .outputs.commits }}"
598+ commits : " ${{ steps.generate-changelogs .outputs.commits }}"
512599
513600 - name : Upload release data
514601 uses : actions/upload-artifact@v4
@@ -517,6 +604,7 @@ jobs:
517604 path : |
518605 /tmp/commits-by-crate.json
519606 /tmp/api-changes.json
607+ /tmp/api-changes-with-major-bumps.json
520608 retention-days : 1
521609
522610 - name : Cleanup on failure
@@ -536,7 +624,7 @@ jobs:
536624 outputs :
537625 branch_name : ${{ steps.proposal-branch.outputs.branch_name }}
538626 ephemeral_branch : ${{ steps.ephemeral-branch.outputs.ephemeral_branch }}
539-
627+
540628 create-pr :
541629 needs : cargo-release
542630 runs-on : ubuntu-latest
@@ -574,37 +662,42 @@ jobs:
574662
575663 NON_DEFAULT=""
576664 if [ -n "$MAIN_START_REF" ]; then
577- NON_DEFAULT="${NON_DEFAULT}"$'\n### Cut from non-default ref\n\n'"This proposal was generated from \`$MAIN_START_REF\` instead of the default latest \`origin/$MAIN_BRANCH\`."$'\n'
665+ NON_DEFAULT="${NON_DEFAULT}"$'\n### :exclamation: Cut from non-default ref\n\n'"This proposal was generated from \`$MAIN_START_REF\` instead of the default latest \`origin/$MAIN_BRANCH\`."$'\n'
578666 fi
579667 if [ "$BYPASS_STANDARD_CHECKS" = "true" ]; then
580- NON_DEFAULT="${NON_DEFAULT}"$'\n### Non-default workflow options\n\n'"**bypass_standard_checks** was enabled: the ongoing-proposal branch guard was skipped; branches use proposal prefix \`$PROPOSAL_BRANCH_PREFIX\` and release prefix \`$RELEASE_BRANCH_PREFIX\`. Crates whose resolved git tag is not the latest SemVer tag for that crate are still included (normally skipped)."$'\n'
668+ NON_DEFAULT="${NON_DEFAULT}"$'\n### :test_tube: Non-default workflow options\n\n'"**bypass_standard_checks** was enabled: the ongoing-proposal branch guard was skipped; branches use proposal prefix \`$PROPOSAL_BRANCH_PREFIX\` and release prefix \`$RELEASE_BRANCH_PREFIX\`. Crates whose resolved git tag is not the latest SemVer tag for that crate are still included (normally skipped)."$'\n'
581669 fi
582670 if [ -n "$NON_DEFAULT" ]; then
583671 NON_DEFAULT="${NON_DEFAULT}"$'\n\n'
584672 fi
585673
586- # Generate the PR body by merging commits and API changes
674+ # PR body from api-changes-with-major-bumps.json (same crates as api- changes.json; tags/versions updated after libdd major bumps).
587675 # Note: read returns 1 when it reaches EOF, which is expected for heredocs
588676 read -r -d '' JQ_FILTER << 'EOF' || true
589- .[] | select((.commits | length) > 0) |
590- . as $crate |
591- ($api[0] | map(select(.name == $crate.name)) | first // {
592- level: "skipped because there were no changes to the public API"
593- }) as $api_info |
594- [
595- "## \($crate.name)",
596- "",
597- (if $api_info.version then "**Next version:** `\($api_info.version)`" else null end),
598- "**Semver bump:** `\($api_info.level)`",
599- (if $api_info.tag then "**Tag:** `\($api_info.tag)`\n" else null end),
600- (if $api_info.initial_release == "true" then
601- "**Warning:** this is an initial release. Please verify that the version and commits included are correct.\n"
602- else null end),
603- (if ($crate.commits | length) > 0 then "### Commits\n\n" + ($crate.commits | map("- \(.subject)") | join("\n")) else null end)
604- ] | map(select(. != null and . != "")) | join("\n")
677+ [ $api[0][]
678+ | [
679+ "## \(.name)",
680+ "",
681+ (if .version then "**Next version:** `\(.version)`" else null end),
682+ "**Semver bump:** `\(.level)`",
683+ (if .tag then "**Tag:** `\(.tag)`\n" else null end),
684+ (if (.major_bumps // [] | length) > 0 then
685+ "### :warning: major bump forced due to:\n\n"
686+ + ((.major_bumps // []) | map("- `\(.dependency)`: \(.previous_req) → \(.current_req)") | join("\n"))
687+ + "\n"
688+ else null end),
689+ (if .initial_release == "true" then
690+ "**Warning:** this is an initial release. Please verify that the version and commits included are correct.\n"
691+ else null end),
692+ (if (.commits | length) > 0 then "### Commits\n\n" + (.commits | map("- \(.subject)") | join("\n")) else null end)
693+ ]
694+ | map(select(. != null and . != ""))
695+ | join("\n")
696+ ]
697+ | join("\n\n")
605698 EOF
606699
607- COMMITS_AND_API_BODY=$(jq -r --slurpfile api /tmp/api-changes.json "$JQ_FILTER" /tmp/commits-by-crate.json )
700+ COMMITS_AND_API_BODY=$(jq -nr --slurpfile api /tmp/api-changes-with-major-bumps .json "$JQ_FILTER")
608701
609702 PR_BODY="# Release proposal for ${{ inputs.crate }} and its dependencies
610703
@@ -623,6 +716,7 @@ jobs:
623716 --label "release-proposal" \
624717 --label "skip-metadata-check" \
625718 --label "skip-changelog-check" \
719+ --label "skip-pr-title-semver-check" \
626720 --base "${{ needs.cargo-release.outputs.ephemeral_branch }}" \
627721 --draft
628722
0 commit comments