From f86c2f4af1e5d7d0a645ce19dde93be77cb29e9e Mon Sep 17 00:00:00 2001 From: Igor Unanua Date: Tue, 13 Jan 2026 10:00:04 +0100 Subject: [PATCH 1/7] Disable workflows and enable release-dispatch --- .../{coverage.yml => coverage.yml.off} | 0 .github/workflows/{fuzz.yml => fuzz.yml.off} | 0 .github/workflows/{lint.yml => lint.yml.off} | 0 .github/workflows/{miri.yml => miri.yml.off} | 0 .github/workflows/release-dispatch.yml | 221 +++++++++++++ .github/workflows/{test.yml => test.yml.off} | 0 ...o-files.yml => verify-proto-files.yml.off} | 0 ....yml => weekly-verify-proto-files.yml.off} | 0 cliff.toml | 106 ++++++ datadog-ipc/README.md | 1 + scripts/commits-since-release.sh | 202 ++++++++++++ scripts/publication-order.sh | 304 ++++++++++++++++++ scripts/semver-level.sh | 150 +++++++++ 13 files changed, 984 insertions(+) rename .github/workflows/{coverage.yml => coverage.yml.off} (100%) rename .github/workflows/{fuzz.yml => fuzz.yml.off} (100%) rename .github/workflows/{lint.yml => lint.yml.off} (100%) rename .github/workflows/{miri.yml => miri.yml.off} (100%) create mode 100644 .github/workflows/release-dispatch.yml rename .github/workflows/{test.yml => test.yml.off} (100%) rename .github/workflows/{verify-proto-files.yml => verify-proto-files.yml.off} (100%) rename .github/workflows/{weekly-verify-proto-files.yml => weekly-verify-proto-files.yml.off} (100%) create mode 100644 cliff.toml create mode 100644 datadog-ipc/README.md create mode 100755 scripts/commits-since-release.sh create mode 100755 scripts/publication-order.sh create mode 100755 scripts/semver-level.sh diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml.off similarity index 100% rename from .github/workflows/coverage.yml rename to .github/workflows/coverage.yml.off diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml.off similarity index 100% rename from .github/workflows/fuzz.yml rename to .github/workflows/fuzz.yml.off diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml.off similarity index 100% rename from .github/workflows/lint.yml rename to .github/workflows/lint.yml.off diff --git a/.github/workflows/miri.yml b/.github/workflows/miri.yml.off similarity index 100% rename from .github/workflows/miri.yml rename to .github/workflows/miri.yml.off diff --git a/.github/workflows/release-dispatch.yml b/.github/workflows/release-dispatch.yml new file mode 100644 index 0000000000..0ea4b9b71e --- /dev/null +++ b/.github/workflows/release-dispatch.yml @@ -0,0 +1,221 @@ +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: + make-release-pr: + 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 + - uses: taiki-e/cache-cargo-install-action@7447f04c51f2ba27ca35e7f1e28fab848c5b3ba7 # 2.3.1 + with: + tool: cargo-public-api # @0.50.2 + - 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: cargo-semver-checks + + - 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') + + 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" + + # TODO: Should we skip the release if the public API is not changed? (removed: 0, changed: 0, added: 0) + + 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') + + 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" '. += [{"name": $name, "level": $level, "tag": $tag, "version": $version}]' \ + /tmp/api-changes.json > /tmp/api-changes.tmp && mv /tmp/api-changes.tmp /tmp/api-changes.json + done + + # Output the results + echo "API changes summary:" + cat /tmp/api-changes.json | jq . + + - name: Create a PR + env: + GH_TOKEN: ${{ github.token }} + run: | + BRANCH_NAME="${{ steps.proposal-branch.outputs.branch_name }}" + + # Check if there are commits to push + if git diff --quiet origin/main; then + echo "No changes to push" + exit 0 + fi + + # Generate the PR body by merging commits and API changes + PR_BODY=$(jq -r --slurpfile api /tmp/api-changes.json ' + .[] | + . as $crate | + ($api[0] | map(select(.name == $crate.name)) | first // {level: "unknown"}) as $api_info | + "## \($crate.name)\n\n" + + "**Next version:** `\($api_info.version)`\n\n" + + "**Semver bump:** `\($api_info.level)`\n\n" + + "**Tag:** `\($api_info.tag)`\n\n" + + ($crate.commits | map("- \(.subject)") | join("\n")) + ' /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" + + git push origin "$BRANCH_NAME" + gh pr create \ + --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 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml.off similarity index 100% rename from .github/workflows/test.yml rename to .github/workflows/test.yml.off diff --git a/.github/workflows/verify-proto-files.yml b/.github/workflows/verify-proto-files.yml.off similarity index 100% rename from .github/workflows/verify-proto-files.yml rename to .github/workflows/verify-proto-files.yml.off diff --git a/.github/workflows/weekly-verify-proto-files.yml b/.github/workflows/weekly-verify-proto-files.yml.off similarity index 100% rename from .github/workflows/weekly-verify-proto-files.yml rename to .github/workflows/weekly-verify-proto-files.yml.off diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 0000000000..987523a31d --- /dev/null +++ b/cliff.toml @@ -0,0 +1,106 @@ +# git-cliff ~ configuration file +# https://git-cliff.org/docs/configuration + +[changelog] +# A Tera template to be rendered as the changelog's header. +# See https://keats.github.io/tera/docs/#introduction +header = """ +# Changelog +""" +# A Tera template to be rendered for each release in the changelog. +# See https://keats.github.io/tera/docs/#introduction +body = """ +{%- macro remote_url() -%} + https://github.com/datadog/libdatadog +{%- endmacro -%} + +{% macro print_commit(commit, strip_body=false) -%} + - {% if commit.scope %}*({{ commit.scope }})* {% endif %}\ + {% if commit.breaking %}[**breaking**] {% endif %}\ + {% if strip_body %}{{ commit.message | split(pat="\n") | first | upper_first }}{% else %}{{ commit.message | upper_first }}{% endif %} - \ + ([{{ commit.id | truncate(length=7, end="") }}]({{ self::remote_url() }}/commit/{{ commit.id }}))\ +{% endmacro -%} + +{% if version %}\ + {% if version is containing("-v") %} + {% set version_crate = version | split(pat="-v") | last %} + {% else %} + {% set version_crate = version | trim_start_matches(pat="v") %} + {% endif %} + {% if previous.version %}\ + ## [{{ version_crate }}]\ + ({{ self::remote_url() }}/compare/{{ previous.version }}..{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }} + {% else %}\ + ## [{{ version_crate }}] - {{ timestamp | date(format="%Y-%m-%d") }} + {% endif %}\ +{% else %}\ + ## [unreleased] +{% endif %}\ + +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | striptags | trim | upper_first }} + {%- set is_others = group is containing("Changed") %} + {% for commit in commits + | filter(attribute="scope") + | sort(attribute="scope") %} + {{ self::print_commit(commit=commit, strip_body=is_others) }} + {%- endfor %} + {% for commit in commits %} + {%- if not commit.scope -%} + {{ self::print_commit(commit=commit, strip_body=is_others) }} + {% endif -%} + {% endfor -%} +{% endfor -%} + + +""" +# A Tera template to be rendered as the changelog's footer. +# See https://keats.github.io/tera/docs/#introduction +footer = """ + +""" +# Remove leading and trailing whitespaces from the changelog's body. +trim = true +# An array of regex based postprocessors to modify the changelog. +postprocessors = [ + # Replace the placeholder `` with a URL. + { pattern = '', replace = "https://github.com/datadog/libdatadog" }, # replace repository URL +] + +[git] +# Parse commits according to the conventional commits specification. +# See https://www.conventionalcommits.org +conventional_commits = true +# Exclude commits that do not match the conventional commits specification. +filter_unconventional = false +# Split commits on newlines, treating each line as an individual commit. +split_commits = false +# An array of regex based parsers to modify commit messages prior to further processing. +commit_preprocessors = [ + # Replace issue numbers with link templates to be updated in `changelog.postprocessors`. + { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))" }, +] +# An array of regex based parsers for extracting data from the commit message. +# Assigns commits to groups. +# Optionally sets the commit's scope and can decide to exclude commits from further processing. +commit_parsers = [ + { message = "^feat", group = "Added" }, + { message = "^fix", group = "Fixed" }, + { message = "^refactor\\(clippy\\)", skip = true }, + { message = "^chore\\(release\\): prepare for", skip = true }, + { message = "^chore: Release", skip = true }, + { message = "^chore\\(deps.*\\)", skip = true }, + { message = "^chore\\(pr\\)", skip = true }, + { message = "^chore\\(pull\\)", skip = true }, + { message = "^chore\\(npm\\).*yarn\\.lock", skip = true }, + { message = ".*", group = "Changed" }, +] +# Prevent commits that are breaking from being excluded by commit parsers. +protect_breaking_commits = false +# Exclude commits that are not matched by any commit parser. +filter_commits = false +# Order releases topologically instead of chronologically. +topo_order = false +# Order of commits in each group/release within the changelog. +# Allowed values: newest, oldest +sort_commits = "newest" diff --git a/datadog-ipc/README.md b/datadog-ipc/README.md new file mode 100644 index 0000000000..3d6d81df04 --- /dev/null +++ b/datadog-ipc/README.md @@ -0,0 +1 @@ +# datadog-ipc \ No newline at end of file diff --git a/scripts/commits-since-release.sh b/scripts/commits-since-release.sh new file mode 100755 index 0000000000..fc7e749073 --- /dev/null +++ b/scripts/commits-since-release.sh @@ -0,0 +1,202 @@ +#!/bin/bash +# Commits Since Release Script +# Takes JSON from publication-order.sh and finds commits since the last release tag for each crate +# +# Usage: ./commits-since-release.sh [OPTIONS] [JSON] +# +# Input: JSON from argument or stdin (output of publication-order.sh --format=json) +# Output: JSON with commits grouped by crate + +set -euo pipefail + +# Parse arguments +FORMAT="json" +VERBOSE=false +INPUT_JSON="" + +for arg in "$@"; do + case "$arg" in + --format=*) + FORMAT="${arg#--format=}" + ;; + --verbose|-v) + VERBOSE=true + ;; + --help|-h) + echo "Usage: $0 [OPTIONS] [JSON]" + echo "" + echo "Takes JSON from publication-order.sh and finds commits since the last release tag for each crate." + echo "" + echo "Arguments:" + echo " JSON JSON array of crates (if not provided, reads from stdin)" + echo "" + echo "Options:" + echo " --format=FORMAT Output format: json (default), summary" + echo " --verbose, -v Show verbose output to stderr" + echo " --help, -h Show this help message" + echo "" + echo "Examples:" + echo " ./publication-order.sh --format=json libdd-common | ./commits-since-release.sh" + echo " ./commits-since-release.sh '[{\"name\":\"libdd-common\",\"version\":\"1.0.0\"}]'" + echo " ./commits-since-release.sh --format=summary \"\$(./publication-order.sh --format=json)\"" + echo "" + echo "Output JSON format:" + echo ' [{"name":"crate-name","version":"1.0.0","tag":"crate-name-v1.0.0","tag_exists":true,"commits":[...]}]' + exit 0 + ;; + -*) + echo "Unknown option: $arg" >&2 + echo "Use --help for usage information" >&2 + exit 1 + ;; + *) + # Positional argument - treat as JSON input + INPUT_JSON="$arg" + ;; + esac +done + +# Read input JSON from stdin if not provided as argument +if [ -z "$INPUT_JSON" ]; then + INPUT_JSON=$(cat) +fi + +# Validate JSON +if ! echo "$INPUT_JSON" | jq empty 2>/dev/null; then + echo "ERROR: Invalid JSON input" >&2 + exit 1 +fi + +# Get workspace root (for determining crate paths) +WORKSPACE_ROOT=$(cargo metadata --format-version=1 --no-deps 2>/dev/null | jq -r '.workspace_root' || pwd) + +log_verbose() { + if [ "$VERBOSE" = true ]; then + echo "$@" >&2 + fi +} + +# Build output JSON +OUTPUT_JSON="[" +FIRST=true + +while read -r crate; do + NAME=$(echo "$crate" | jq -r '.name') + VERSION=$(echo "$crate" | jq -r '.version') + TAG="${NAME}-v${VERSION}" + + log_verbose "Processing $NAME v$VERSION (tag: $TAG)..." + + # Find crate path from cargo metadata + CRATE_PATH=$(cargo metadata --format-version=1 --no-deps 2>/dev/null | \ + jq -r --arg name "$NAME" '.packages[] | select(.name == $name) | .manifest_path' | \ + sed 's|/Cargo.toml$||' | \ + sed "s|^$WORKSPACE_ROOT/||") + + if [ -z "$CRATE_PATH" ]; then + log_verbose " WARNING: Could not find path for crate $NAME, using name as path" + CRATE_PATH="$NAME" + fi + + log_verbose " Crate path: $CRATE_PATH" + + # Check if tag exists + TAG_EXISTS=false + COMMITS_JSON="[]" + + if git rev-parse "refs/tags/$TAG" >/dev/null 2>&1; then + TAG_EXISTS=true + log_verbose " Tag exists, finding commits since $TAG..." + + # Check if tag is an ancestor of HEAD (i.e., release was merged back to main) + # If not, use merge-base to find the common ancestor + if git merge-base --is-ancestor "$TAG" HEAD 2>/dev/null; then + COMMIT_RANGE="$TAG..HEAD" + log_verbose " Tag is ancestor of HEAD, using $COMMIT_RANGE" + else + MERGE_BASE=$(git merge-base "$TAG" HEAD 2>/dev/null || echo "") + if [ -n "$MERGE_BASE" ]; then + COMMIT_RANGE="$MERGE_BASE..HEAD" + log_verbose " Tag is NOT ancestor of HEAD, using merge-base: $COMMIT_RANGE" + else + log_verbose " WARNING: Could not find merge-base, using $TAG..HEAD" + COMMIT_RANGE="$TAG..HEAD" + fi + fi + + # Get commits since tag that affect this crate's directory + # Format: hash|subject|author|date + COMMITS_RAW=$(git log "$COMMIT_RANGE" --format="%H|%s|%an|%aI" -- "$CRATE_PATH" 2>/dev/null || true) + + if [ -n "$COMMITS_RAW" ]; then + COMMITS_JSON="[" + COMMIT_FIRST=true + + while IFS='|' read -r hash subject author date; do + if [ -n "$hash" ]; then + if [ "$COMMIT_FIRST" = true ]; then + COMMIT_FIRST=false + else + COMMITS_JSON+="," + fi + + # Escape special characters in subject for JSON + subject_escaped=$(echo "$subject" | jq -R .) + author_escaped=$(echo "$author" | jq -R .) + + COMMITS_JSON+="{\"hash\":\"$hash\",\"subject\":$subject_escaped,\"author\":$author_escaped,\"date\":\"$date\"}" + fi + done <<< "$COMMITS_RAW" + + COMMITS_JSON+="]" + fi + + COMMIT_COUNT=$(echo "$COMMITS_JSON" | jq 'length') + log_verbose " Found $COMMIT_COUNT commits since $TAG" + else + log_verbose " Tag does NOT exist - no previous release found" + fi + + # Add to output + if [ "$FIRST" = true ]; then + FIRST=false + else + OUTPUT_JSON+="," + fi + + OUTPUT_JSON+="{\"name\":\"$NAME\",\"version\":\"$VERSION\",\"path\":\"$CRATE_PATH\",\"tag\":\"$TAG\",\"tag_exists\":$TAG_EXISTS,\"commits\":$COMMITS_JSON}" + +done < <(echo "$INPUT_JSON" | jq -c '.[]') + +OUTPUT_JSON+="]" + +# Ensure valid JSON output +OUTPUT_JSON=$(echo "$OUTPUT_JSON" | jq -c .) + +# Output in requested format +case "$FORMAT" in + json) + echo "$OUTPUT_JSON" + ;; + + summary) + echo "Commits since last release by crate:" + echo "========================================" + echo "$OUTPUT_JSON" | jq -r '.[] | + "\(.name) v\(.version)" + + (if .tag_exists then + " (tag: \(.tag))\n Commits: \(.commits | length)" + + (if (.commits | length) > 0 then + "\n" + (.commits | map(" - \(.hash[0:8]) \(.subject)") | join("\n")) + else "" end) + else + "\n No previous release tag found" + end) + "\n"' + ;; + + *) + echo "Unknown format: $FORMAT" >&2 + echo "Available formats: json, summary" >&2 + exit 1 + ;; +esac diff --git a/scripts/publication-order.sh b/scripts/publication-order.sh new file mode 100755 index 0000000000..11c6ad8c62 --- /dev/null +++ b/scripts/publication-order.sh @@ -0,0 +1,304 @@ +#!/bin/bash +# Publication Order Script +# Determines the correct order to publish workspace crates based on their dependencies +# Usage: ./publication-order.sh [OPTIONS] [CRATE...] +# +# If crate names are provided, shows only those crates and their dependencies in publication order. + +set -euo pipefail + +# Parse arguments +FORMAT="json" +INCLUDE_UNPUBLISHABLE=false +declare -a TARGET_CRATES=() + +for arg in "$@"; do + case "$arg" in + --format=*) + FORMAT="${arg#--format=}" + ;; + --include-unpublishable|--all) + INCLUDE_UNPUBLISHABLE=true + ;; + --help|-h) + echo "Usage: $0 [OPTIONS] [CRATE...]" + echo "" + echo "Options:" + echo " --format=FORMAT Output format: list, json, simple (default: json)" + echo " --include-unpublishable Include crates marked with publish=false" + echo " --all Alias for --include-unpublishable" + echo " --help, -h Show this help message" + echo "" + echo "Arguments:" + echo " CRATE... Optional list of crate names to filter by." + echo " If provided, shows only these crates and their" + echo " dependencies in publication order." + echo "" + echo "Examples:" + echo " $0 --format=list" + echo " $0 --format=simple --include-unpublishable" + echo " $0 libdd-common libdd-telemetry" + echo " $0 --format=json datadog-sidecar" + exit 0 + ;; + -*) + echo "Unknown option: $arg" >&2 + echo "Use --help for usage information" >&2 + exit 1 + ;; + *) + # Positional argument - treat as crate name + TARGET_CRATES+=("$arg") + ;; + esac +done + +# Get workspace metadata as JSON +METADATA=$(cargo metadata --format-version=1 --no-deps) + +# Extract workspace members (package IDs) +if [ "$INCLUDE_UNPUBLISHABLE" = true ]; then + # Include all workspace members + WORKSPACE_MEMBERS=$(echo "$METADATA" | jq -r '.workspace_members[]') +else + # Exclude packages with publish = false + WORKSPACE_MEMBERS=$(echo "$METADATA" | jq -r ' + .workspace_members[] as $id | + .packages[] | + select(.id == $id) | + select(.publish == null or (.publish | type == "array" and length > 0)) | + .id + ') +fi + +# Create associative arrays for package info +declare -A PKG_NAME_TO_ID +declare -A PKG_ID_TO_NAME +declare -A PKG_DEPS + +# Build package mapping +while IFS= read -r pkg_id; do + pkg_name=$(echo "$METADATA" | jq -r --arg id "$pkg_id" \ + '.packages[] | select(.id == $id) | .name') + PKG_NAME_TO_ID["$pkg_name"]="$pkg_id" + PKG_ID_TO_NAME["$pkg_id"]="$pkg_name" +done <<< "$WORKSPACE_MEMBERS" + +# Extract dependencies for each workspace member +while IFS= read -r pkg_id; do + pkg_name="${PKG_ID_TO_NAME[$pkg_id]}" + + # Get all dependencies that are workspace members (excluding dev-dependencies and self-references) + # Also filter to only include dependencies that are publishable + deps=$(echo "$METADATA" | jq -r --arg id "$pkg_id" --arg pkg_name "$pkg_name" \ + --argjson publishable_ids "$(echo "$WORKSPACE_MEMBERS" | jq -R . | jq -s .)" \ + '.packages[] | select(.id == $id) | + .dependencies[] | + select(.path != null and .kind != "dev" and .name != $pkg_name) | + .name' | sort | uniq | while IFS= read -r dep; do + # Only include if dependency is in our publishable list + if [ -n "${PKG_NAME_TO_ID[$dep]+x}" ]; then + echo "$dep" + fi + done) + + # Store dependencies + PKG_DEPS["$pkg_name"]="$deps" +done <<< "$WORKSPACE_MEMBERS" + +# If target crates were specified, filter to only include them and their dependencies +if [ ${#TARGET_CRATES[@]} -gt 0 ]; then + # Validate that all target crates exist + for crate in "${TARGET_CRATES[@]}"; do + if [ -z "${PKG_NAME_TO_ID[$crate]+x}" ]; then + echo "ERROR: Unknown crate '$crate'" >&2 + echo "Available crates:" >&2 + for name in "${!PKG_NAME_TO_ID[@]}"; do + echo " - $name" >&2 + done | sort >&2 + exit 1 + fi + done + + # Recursively collect all dependencies of target crates + declare -A INCLUDED_CRATES + declare -a TO_PROCESS=("${TARGET_CRATES[@]}") + + while [ ${#TO_PROCESS[@]} -gt 0 ]; do + current="${TO_PROCESS[0]}" + TO_PROCESS=("${TO_PROCESS[@]:1}") + + # Skip if already processed + if [ -n "${INCLUDED_CRATES[$current]+x}" ]; then + continue + fi + + INCLUDED_CRATES["$current"]=1 + + # Add dependencies to process queue + deps="${PKG_DEPS[$current]}" + if [ -n "$deps" ]; then + while IFS= read -r dep; do + if [ -n "$dep" ] && [ -z "${INCLUDED_CRATES[$dep]+x}" ]; then + TO_PROCESS+=("$dep") + fi + done <<< "$deps" + fi + done + + # Filter PKG_DEPS to only include the selected crates + declare -A FILTERED_PKG_DEPS + for crate in "${!INCLUDED_CRATES[@]}"; do + # Filter dependencies to only include other selected crates + deps="${PKG_DEPS[$crate]}" + filtered_deps="" + if [ -n "$deps" ]; then + while IFS= read -r dep; do + if [ -n "$dep" ] && [ -n "${INCLUDED_CRATES[$dep]+x}" ]; then + if [ -n "$filtered_deps" ]; then + filtered_deps="$filtered_deps"$'\n'"$dep" + else + filtered_deps="$dep" + fi + fi + done <<< "$deps" + fi + FILTERED_PKG_DEPS["$crate"]="$filtered_deps" + done + + # Replace PKG_DEPS with filtered version + unset PKG_DEPS + declare -A PKG_DEPS + for crate in "${!FILTERED_PKG_DEPS[@]}"; do + PKG_DEPS["$crate"]="${FILTERED_PKG_DEPS[$crate]}" + done +fi + +# Topological sort using Kahn's algorithm +declare -A IN_DEGREE +declare -a SORTED_ORDER +declare -a QUEUE + +# Calculate in-degrees (number of workspace dependencies each package has) +for pkg_name in "${!PKG_DEPS[@]}"; do + deps="${PKG_DEPS[$pkg_name]}" + count=0 + if [ -n "$deps" ]; then + while IFS= read -r dep; do + if [ -n "$dep" ]; then + ((count++)) || true + fi + done <<< "$deps" + fi + IN_DEGREE["$pkg_name"]=$count +done + +# Find all packages with no dependencies (in-degree = 0) +for pkg_name in "${!IN_DEGREE[@]}"; do + if [ "${IN_DEGREE[$pkg_name]}" -eq 0 ]; then + QUEUE+=("$pkg_name") + fi +done + +# Process queue +while [ ${#QUEUE[@]} -gt 0 ]; do + # Pop from queue + current="${QUEUE[0]}" + QUEUE=("${QUEUE[@]:1}") + + SORTED_ORDER+=("$current") + + # For each package that depends on current, reduce its in-degree + for pkg_name in "${!PKG_DEPS[@]}"; do + deps="${PKG_DEPS[$pkg_name]}" + if [ -n "$deps" ] && echo "$deps" | grep -qx "$current"; then + ((IN_DEGREE["$pkg_name"]--)) || true + if [ "${IN_DEGREE[$pkg_name]}" -eq 0 ]; then + QUEUE+=("$pkg_name") + fi + fi + done +done + +# Check for cycles +if [ ${#SORTED_ORDER[@]} -ne ${#PKG_DEPS[@]} ]; then + echo "ERROR: Circular dependency detected!" >&2 + echo "Processed: ${#SORTED_ORDER[@]} packages" >&2 + echo "Total: ${#PKG_DEPS[@]} packages" >&2 + exit 1 +fi + +# Output in requested format +case "$FORMAT" in + list) + if [ ${#TARGET_CRATES[@]} -gt 0 ]; then + echo "Publication order for: ${TARGET_CRATES[*]}" + echo "(including dependencies)" + else + echo "Publication order (dependencies first):" + fi + echo "========================================" + for i in "${!SORTED_ORDER[@]}"; do + pkg_name="${SORTED_ORDER[$i]}" + deps="${PKG_DEPS[$pkg_name]}" + pkg_id="${PKG_NAME_TO_ID[$pkg_name]}" + + # Get package version and publishable status + pkg_info=$(echo "$METADATA" | jq -r --arg id "$pkg_id" \ + '.packages[] | select(.id == $id) | + {version: .version, publishable: (if .publish == null or (.publish | type == "array" and length > 0) then "true" else "false" end)} | + "\(.version)|\(.publishable)"') + + version=$(echo "$pkg_info" | cut -d'|' -f1) + is_publishable=$(echo "$pkg_info" | cut -d'|' -f2) + + if [ "$is_publishable" = "false" ]; then + echo "$((i+1)). $pkg_name ($version) [unpublishable]" + else + echo "$((i+1)). $pkg_name ($version)" + fi + + if [ -n "$deps" ]; then + # Add versions to dependencies + deps_with_versions="" + while IFS= read -r dep; do + if [ -n "$dep" ]; then + dep_id="${PKG_NAME_TO_ID[$dep]}" + dep_version=$(echo "$METADATA" | jq -r --arg id "$dep_id" \ + '.packages[] | select(.id == $id) | .version') + deps_with_versions="$deps_with_versions $dep ($dep_version)" + fi + done <<< "$deps" + echo " Dependencies:$deps_with_versions" + fi + done + ;; + + json) + # Output as JSON array with name and version + printf '[' + for i in "${!SORTED_ORDER[@]}"; do + [ $i -gt 0 ] && printf ',' + pkg_name="${SORTED_ORDER[$i]}" + pkg_id="${PKG_NAME_TO_ID[$pkg_name]}" + version=$(echo "$METADATA" | jq -r --arg id "$pkg_id" \ + '.packages[] | select(.id == $id) | .version') + printf '{"name":"%s","version":"%s"}' "$pkg_name" "$version" + done + printf ']\n' + ;; + + simple) + # Just the names, one per line + for pkg_name in "${SORTED_ORDER[@]}"; do + echo "$pkg_name" + done + ;; + + *) + echo "Unknown format: $FORMAT" >&2 + echo "Available formats: list, json, simple" >&2 + exit 1 + ;; +esac + diff --git a/scripts/semver-level.sh b/scripts/semver-level.sh new file mode 100755 index 0000000000..eb81592e9b --- /dev/null +++ b/scripts/semver-level.sh @@ -0,0 +1,150 @@ +#!/bin/bash + +VERBOSE=false + +# Use GITHUB_OUTPUT from environment or default to /dev/stdout for local testing +if [ -z "$GITHUB_OUTPUT" ]; then + GITHUB_OUTPUT=/dev/stdout +fi + +while [[ $# -gt 0 ]]; do + case "$1" in + -v|--verbose) + VERBOSE=true + shift + ;; + -h|--help) + echo "Usage: $0 [-v] [-h] CRATE BASE_REF CURRENT_REF" + exit 0 + ;; + -*) + echo "Unknown option: $1" >&2 + exit 1 + ;; + *) + # Stop parsing flags, rest are positional + break + ;; + esac +done + +CRATE="${1:?ERROR: CRATE is required}" +BASE_REF="${2:-main}" +CURRENT_REF="${3:-HEAD}" + +log_verbose() { + if [ "$VERBOSE" = true ]; then + echo "$@" >&2 + fi +} + +compute_semver_results() { + local crate=$1 + local baseline=$2 + local current=$3 + + # If current is not provided set it to the tip of the branch + if [ -z "$current" ]; then + current="HEAD" + fi + + # Fetch base commit + git fetch origin "$baseline" --depth=50 --quiet + + log_verbose "========================================" >&2 + log_verbose "Checking semver for: $crate" >&2 + log_verbose "========================================" >&2 + + LEVEL="none" + + SEMVER_OUTPUT=$(cargo semver-checks -p "$crate" --color=never --all-features --baseline-rev "$baseline" 2>&1) + SEMVER_EXIT_CODE=$? + + if [[ $SEMVER_EXIT_CODE -eq 0 ]]; then + log_verbose "No semver violations detected" >&2 + LEVEL="none" + elif [[ $SEMVER_EXIT_CODE -eq 1 ]]; then + # Check if it's an error or actual semver violation + if echo "$SEMVER_OUTPUT" | grep -qE "Summary semver requires new major version"; then + # It's a semver violation - this is a major change + LEVEL="major" + log_verbose "Detected semver violations (major change)" >&2 + log_verbose "$SEMVER_OUTPUT" >&2 + else + echo "Error running cargo-semver-checks: $SEMVER_OUTPUT" >&2 + exit $SEMVER_EXIT_CODE + fi + else + echo "Unexpected exit code from cargo-semver-checks: $SEMVER_EXIT_CODE" >&2 + echo "$SEMVER_OUTPUT" >&2 + exit $SEMVER_EXIT_CODE + fi + + + if [[ "$LEVEL" == "none" ]]; then + # Try to run cargo-public-api diff against base branch + PUBLIC_API_OUTPUT=$(cargo public-api --package "$crate" --color=never diff "$baseline..$current" 2>&1) + EXIT_CODE=$? + + if [[ $EXIT_CODE -ne 0 ]]; then + echo "Unexpected error for $crate (exit code: $EXIT_CODE)" >&2 + echo "$PUBLIC_API_OUTPUT" >&2 + exit $EXIT_CODE + fi + + log_verbose "$PUBLIC_API_OUTPUT" + + # Check for removed items (major change) + if echo "$PUBLIC_API_OUTPUT" | grep -q "Removed items from the public API$"; then + if ! echo "$PUBLIC_API_OUTPUT" | grep -A 2 "^Removed items from the public API$" | grep -q "^(none)$"; then + LEVEL="major" + log_verbose "Detected removed items (major change)" >&2 + fi + fi + + # Check for changed items (major change) + if echo "$PUBLIC_API_OUTPUT" | grep -q "^Changed items in the public API$"; then + if ! echo "$PUBLIC_API_OUTPUT" | grep -A 2 "^Changed items in the public API$" | grep -q "^(none)$"; then + LEVEL="major" + log_verbose "Detected changed items (major change)" >&2 + fi + fi + + # Check for added items (minor change) - only if not already major + if [[ "$LEVEL" != "major" ]]; then + if echo "$PUBLIC_API_OUTPUT" | grep -q "Added items to the public API$"; then + if ! echo "$PUBLIC_API_OUTPUT" | grep -A 2 "^Added items to the public API$" | grep -q "^(none)"; then + LEVEL="minor" + log_verbose "Detected added items (minor change)" >&2 + fi + fi + fi + fi + + if [[ "$LEVEL" == "none" ]]; then + # No API changes detected, assume patch level + LEVEL="patch" + fi + + echo "$(jq -n \ + --arg name "$crate" \ + --arg level "$LEVEL" \ + '{"name": $name, "level": $level}')" +} + +# Run the computation and capture JSON output +RESULT_JSON=$(compute_semver_results "$CRATE" "$BASE_REF" "$CURRENT_REF") + +# Output JSON to stdout (captured by workflow) +echo "$RESULT_JSON" + +# Extract values from JSON for backwards compatibility / local testing +NAME=$(echo "$RESULT_JSON" | jq -r '.name') +LEVEL=$(echo "$RESULT_JSON" | jq -r '.level') + +# For local testing, also output individual values +if [[ "$GITHUB_OUTPUT" == "/dev/stdout" ]]; then + echo "---" >&2 + echo "crate=$NAME" >&2 + echo "semver_level=$LEVEL" >&2 +fi From 73650d05a1bb674c2a047ca207fb5f5727ea16e3 Mon Sep 17 00:00:00 2001 From: Igor Unanua Date: Thu, 22 Jan 2026 15:48:28 +0100 Subject: [PATCH 2/7] Exit on not initial version and on missing tag --- .github/workflows/release-dispatch.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-dispatch.yml b/.github/workflows/release-dispatch.yml index 0ea4b9b71e..7d01de1513 100644 --- a/.github/workflows/release-dispatch.yml +++ b/.github/workflows/release-dispatch.yml @@ -97,7 +97,7 @@ jobs: # 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') @@ -113,8 +113,10 @@ jobs: # 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" - - # TODO: Should we skip the release if the public API is not changed? (removed: 0, changed: 0, added: 0) + 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) @@ -131,6 +133,12 @@ jobs: # 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 + echo "Executing cargo release for $NAME with level $LEVEL..." cargo release version -p "$NAME" -x $LEVEL --no-confirm From e36283c31f3b691b0e59ead3dec67a6c9cc00958 Mon Sep 17 00:00:00 2001 From: Igor Unanua Date: Thu, 22 Jan 2026 16:10:21 +0100 Subject: [PATCH 3/7] print versions --- .github/workflows/release-dispatch.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/release-dispatch.yml b/.github/workflows/release-dispatch.yml index 7d01de1513..80684ee26b 100644 --- a/.github/workflows/release-dispatch.yml +++ b/.github/workflows/release-dispatch.yml @@ -59,6 +59,7 @@ jobs: - uses: taiki-e/cache-cargo-install-action@7447f04c51f2ba27ca35e7f1e28fab848c5b3ba7 # 2.3.1 with: tool: cargo-semver-checks + locked: false - name: Get publication order for crate and dependencies run: | @@ -118,6 +119,10 @@ jobs: exit 1 fi + echo "Checking cargo semver-checks and cargo public-api versions..." + cargo semver-checks --version + cargo public-api --version + echo "Executing semver-level.sh for $NAME since $RANGE..." SEMVER_LEVEL=$(./scripts/semver-level.sh "$NAME" "$TAG" 2>&1) echo "Semver level: $SEMVER_LEVEL" From c6a45bc99cd76db6a7c5744279d0a3c7e417b1cd Mon Sep 17 00:00:00 2001 From: Igor Unanua Date: Thu, 22 Jan 2026 16:29:59 +0100 Subject: [PATCH 4/7] try with nightly-2025-01-09 --- .github/workflows/release-dispatch.yml | 120 ++++++++++++++++++------- 1 file changed, 89 insertions(+), 31 deletions(-) diff --git a/.github/workflows/release-dispatch.yml b/.github/workflows/release-dispatch.yml index 80684ee26b..8ccc70e798 100644 --- a/.github/workflows/release-dispatch.yml +++ b/.github/workflows/release-dispatch.yml @@ -28,7 +28,7 @@ env: RELEASE_BRANCH: release jobs: - make-release-pr: + cargo-release: permissions: id-token: write # Enable OIDC pull-requests: write @@ -47,9 +47,11 @@ jobs: 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: cargo-public-api # @0.50.2 + tool: cargo-public-api@0.50.2 - uses: taiki-e/cache-cargo-install-action@7447f04c51f2ba27ca35e7f1e28fab848c5b3ba7 # 2.3.1 with: tool: cargo-release @@ -58,8 +60,7 @@ jobs: tool: git-cliff - uses: taiki-e/cache-cargo-install-action@7447f04c51f2ba27ca35e7f1e28fab848c5b3ba7 # 2.3.1 with: - tool: cargo-semver-checks - locked: false + tool: cargo-semver-checks@0.45.0 - name: Get publication order for crate and dependencies run: | @@ -106,7 +107,16 @@ jobs: 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" @@ -119,10 +129,6 @@ jobs: exit 1 fi - echo "Checking cargo semver-checks and cargo public-api versions..." - cargo semver-checks --version - cargo public-api --version - echo "Executing semver-level.sh for $NAME since $RANGE..." SEMVER_LEVEL=$(./scripts/semver-level.sh "$NAME" "$TAG" 2>&1) echo "Semver level: $SEMVER_LEVEL" @@ -144,6 +150,8 @@ jobs: exit 1 fi + INITIAL_RELEASE=true + echo "Executing cargo release for $NAME with level $LEVEL..." cargo release version -p "$NAME" -x $LEVEL --no-confirm @@ -172,37 +180,87 @@ jobs: 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" '. += [{"name": $name, "level": $level, "tag": $tag, "version": $version}]' \ + 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="${{ steps.proposal-branch.outputs.branch_name }}" - - # Check if there are commits to push - if git diff --quiet origin/main; then - echo "No changes to push" - exit 0 - fi - + run: | + BRANCH_NAME="${{ needs.cargo-release.outputs.branch_name }}" + # Generate the PR body by merging commits and API changes - PR_BODY=$(jq -r --slurpfile api /tmp/api-changes.json ' - .[] | - . as $crate | - ($api[0] | map(select(.name == $crate.name)) | first // {level: "unknown"}) as $api_info | - "## \($crate.name)\n\n" + - "**Next version:** `\($api_info.version)`\n\n" + - "**Semver bump:** `\($api_info.level)`\n\n" + - "**Tag:** `\($api_info.tag)`\n\n" + - ($crate.commits | map("- \(.subject)") | join("\n")) - ' /tmp/commits-by-crate.json) + # 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), + (if ($crate.commits | length) > 0 then "### Commits\n\n" + ($crate.commits | map("- \(.subject)") | join("\n")) else null end) + ] | map(select(. != null and . != "")) | 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 @@ -212,8 +270,8 @@ jobs: echo "PR_BODY: $PR_BODY" - git push origin "$BRANCH_NAME" gh pr create \ + --head "$BRANCH_NAME" \ --title "chore(release): proposal for ${{ inputs.crate }}" \ --body "$PR_BODY" \ --base "${{ env.RELEASE_BRANCH }}" From 9e6261d53758d51b9f1695501ce17d562d50d29a Mon Sep 17 00:00:00 2001 From: Release - Open a release proposal PR <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:57:03 +0000 Subject: [PATCH 5/7] chore: Release --- Cargo.lock | 2 +- libdd-data-pipeline/Cargo.toml | 2 +- libdd-ddsketch/CHANGELOG.md | 9 +++++++++ libdd-ddsketch/Cargo.toml | 2 +- libdd-telemetry/Cargo.toml | 2 +- libdd-trace-stats/Cargo.toml | 2 +- 6 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eac3ed9658..f629dbe8fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3121,7 +3121,7 @@ dependencies = [ [[package]] name = "libdd-ddsketch" -version = "1.0.0" +version = "1.0.1" dependencies = [ "prost", "prost-build", diff --git a/libdd-data-pipeline/Cargo.toml b/libdd-data-pipeline/Cargo.toml index 97b2d6449e..a83748fe05 100644 --- a/libdd-data-pipeline/Cargo.toml +++ b/libdd-data-pipeline/Cargo.toml @@ -37,7 +37,7 @@ libdd-telemetry = { version = "2.0.0", path = "../libdd-telemetry", default-feat libdd-trace-protobuf = { version = "1.0.0", path = "../libdd-trace-protobuf" } libdd-trace-stats = { version = "1.0.0", path = "../libdd-trace-stats" } libdd-trace-utils = { version = "1.0.0", path = "../libdd-trace-utils", default-features = false } -libdd-ddsketch = { version = "1.0.0", path = "../libdd-ddsketch" } +libdd-ddsketch = { version = "1.0.1", path = "../libdd-ddsketch" } libdd-dogstatsd-client = { version = "1.0.0", path = "../libdd-dogstatsd-client", default-features = false } libdd-tinybytes = { version = "1.0.0", path = "../libdd-tinybytes", features = [ "bytes_string", diff --git a/libdd-ddsketch/CHANGELOG.md b/libdd-ddsketch/CHANGELOG.md index 874d1fbd62..a8186c1889 100644 --- a/libdd-ddsketch/CHANGELOG.md +++ b/libdd-ddsketch/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog + + +## [1.0.1](https://github.com/datadog/libdatadog/compare/libdd-ddsketch-v1.0.0..libdd-ddsketch-v1.0.1) - 2026-01-23 + +### Changed + +- Update `prost` crates ([#1426](https://github.com/datadog/libdatadog/issues/1426)) - ([14bab86](https://github.com/datadog/libdatadog/commit/14bab865cfab5151fd399c594ab8f67e8bc7dcf1)) +- Add changelog for every published crate ([#1396](https://github.com/datadog/libdatadog/issues/1396)) - ([5c4a024](https://github.com/datadog/libdatadog/commit/5c4a024598d6fe6cbd93a3e3dc9882848912064f)) + ## Unreleased ### Changed diff --git a/libdd-ddsketch/Cargo.toml b/libdd-ddsketch/Cargo.toml index 73a08894a9..f40095b730 100644 --- a/libdd-ddsketch/Cargo.toml +++ b/libdd-ddsketch/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libdd-ddsketch" -version = "1.0.0" +version = "1.0.1" description = "Minimal implementation of Datadog's DDSketch" homepage = "https://github.com/DataDog/libdatadog/tree/main/libdd-ddsketch" repository = "https://github.com/DataDog/libdatadog/tree/main/libdd-ddsketch" diff --git a/libdd-telemetry/Cargo.toml b/libdd-telemetry/Cargo.toml index cfb3d32eca..c503d93e32 100644 --- a/libdd-telemetry/Cargo.toml +++ b/libdd-telemetry/Cargo.toml @@ -34,7 +34,7 @@ uuid = { version = "1.3", features = ["v4"] } hashbrown = "0.15" libdd-common = { version = "1.1.0", path = "../libdd-common", default-features = false } -libdd-ddsketch = { version = "1.0.0", path = "../libdd-ddsketch" } +libdd-ddsketch = { version = "1.0.1", path = "../libdd-ddsketch" } [target."cfg(unix)".dependencies] libc = "0.2.176" diff --git a/libdd-trace-stats/Cargo.toml b/libdd-trace-stats/Cargo.toml index eea90d5a48..deb5bedcf7 100644 --- a/libdd-trace-stats/Cargo.toml +++ b/libdd-trace-stats/Cargo.toml @@ -10,7 +10,7 @@ license.workspace = true autobenches = false [dependencies] -libdd-ddsketch = { version = "1.0.0", path = "../libdd-ddsketch" } +libdd-ddsketch = { version = "1.0.1", path = "../libdd-ddsketch" } libdd-trace-protobuf = { version = "1.0.0", path = "../libdd-trace-protobuf" } libdd-trace-utils = { version = "1.0.0", path = "../libdd-trace-utils", default-features = false } hashbrown = { version = "0.15" } From c4af170c4184c7b5d9f28573303a512f9b3deeec Mon Sep 17 00:00:00 2001 From: Release - Open a release proposal PR <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:58:23 +0000 Subject: [PATCH 6/7] chore: Release --- Cargo.lock | 2 +- libdd-crashtracker/Cargo.toml | 2 +- libdd-data-pipeline/Cargo.toml | 2 +- libdd-telemetry/CHANGELOG.md | 8 ++++++++ libdd-telemetry/Cargo.toml | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f629dbe8fe..6c09c27ce2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3278,7 +3278,7 @@ dependencies = [ [[package]] name = "libdd-telemetry" -version = "2.0.0" +version = "3.0.0" dependencies = [ "anyhow", "base64 0.22.1", diff --git a/libdd-crashtracker/Cargo.toml b/libdd-crashtracker/Cargo.toml index 89883415c2..380f4701f1 100644 --- a/libdd-crashtracker/Cargo.toml +++ b/libdd-crashtracker/Cargo.toml @@ -47,7 +47,7 @@ backtrace = "=0.3.74" chrono = {version = "0.4", default-features = false, features = ["std", "clock", "serde"]} cxx = { version = "1.0", optional = true } libdd-common = { version = "1.1.0", path = "../libdd-common" } -libdd-telemetry = { version = "2.0.0", path = "../libdd-telemetry" } +libdd-telemetry = { version = "3.0.0", path = "../libdd-telemetry" } http = "1.0" libc = "0.2" nix = { version = "0.29", features = ["poll", "signal", "socket"] } diff --git a/libdd-data-pipeline/Cargo.toml b/libdd-data-pipeline/Cargo.toml index a83748fe05..093566e0f7 100644 --- a/libdd-data-pipeline/Cargo.toml +++ b/libdd-data-pipeline/Cargo.toml @@ -33,7 +33,7 @@ tokio = { version = "1.23", features = [ uuid = { version = "1.10.0", features = ["v4"] } tokio-util = "0.7.11" libdd-common = { version = "1.1.0", path = "../libdd-common", default-features = false } -libdd-telemetry = { version = "2.0.0", path = "../libdd-telemetry", default-features = false } +libdd-telemetry = { version = "3.0.0", path = "../libdd-telemetry", default-features = false } libdd-trace-protobuf = { version = "1.0.0", path = "../libdd-trace-protobuf" } libdd-trace-stats = { version = "1.0.0", path = "../libdd-trace-stats" } libdd-trace-utils = { version = "1.0.0", path = "../libdd-trace-utils", default-features = false } diff --git a/libdd-telemetry/CHANGELOG.md b/libdd-telemetry/CHANGELOG.md index 5350ad3bf4..c87b8cc957 100644 --- a/libdd-telemetry/CHANGELOG.md +++ b/libdd-telemetry/CHANGELOG.md @@ -1,6 +1,14 @@ # Changelog + +## [3.0.0](https://github.com/datadog/libdatadog/compare/libdd-telemetry-v2.0.0..libdd-telemetry-v3.0.0) - 2026-01-23 + +### Added + +- *(appsec)* Add endpoints collection ([#1182](https://github.com/datadog/libdatadog/issues/1182)) - ([44cabf1](https://github.com/datadog/libdatadog/commit/44cabf193fd0bde789b53be2a91bcce7ebce3fe7)) + + ## [2.0.0](https://github.com/datadog/libdatadog/compare/libdd-telemetry-v1.0.0..libdd-telemetry-v2.0.0) - 2026-01-20 ### Added diff --git a/libdd-telemetry/Cargo.toml b/libdd-telemetry/Cargo.toml index c503d93e32..36ad617ed3 100644 --- a/libdd-telemetry/Cargo.toml +++ b/libdd-telemetry/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libdd-telemetry" -version= "2.0.0" +version= "3.0.0" description = "Telemetry client allowing to send data as described in https://docs.datadoghq.com/tracing/configure_data_security/?tab=net#telemetry-collection" homepage = "https://github.com/DataDog/libdatadog/tree/main/libdd-telemetry" repository = "https://github.com/DataDog/libdatadog/tree/main/libdd-telemetry" From 5b2f5b79f3d60dd3203ba2d4ecde7f434d506bba Mon Sep 17 00:00:00 2001 From: Release - Open a release proposal PR <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 14:00:18 +0000 Subject: [PATCH 7/7] chore: Release --- Cargo.lock | 2 +- libdd-crashtracker/CHANGELOG.md | 17 +++++++++++++++++ libdd-crashtracker/Cargo.toml | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6c09c27ce2..4876a1c3eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3015,7 +3015,7 @@ dependencies = [ [[package]] name = "libdd-crashtracker" -version = "1.0.0" +version = "2.0.0" dependencies = [ "anyhow", "backtrace", diff --git a/libdd-crashtracker/CHANGELOG.md b/libdd-crashtracker/CHANGELOG.md index 302ce95f86..3d7248e622 100644 --- a/libdd-crashtracker/CHANGELOG.md +++ b/libdd-crashtracker/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog + + +## [2.0.0](https://github.com/datadog/libdatadog/compare/libdd-crashtracker-v1.0.0..libdd-crashtracker-v2.0.0) - 2026-01-23 + +### Changed + +- *(crashtracking)* Add `is_crash_debug` tag to crashtracker receiver debug logs ([#1445](https://github.com/datadog/libdatadog/issues/1445)) - ([efe99d5](https://github.com/datadog/libdatadog/commit/efe99d5e2992ab029e6ad58c3a77b0f615447b95)) +- Release libddcommon-v1.1.0 ([#1456](https://github.com/datadog/libdatadog/issues/1456)) - ([94cc701](https://github.com/datadog/libdatadog/commit/94cc701e24bbaacfdcc4b034419e72dea1816cc9)) +- Prepare libdd-telemetry-v2.0.0 ([#1457](https://github.com/datadog/libdatadog/issues/1457)) - ([753df4f](https://github.com/datadog/libdatadog/commit/753df4f235074cd3420a7e3cd8d2ff9bc964db0d)) +- [crashtracker] Retrieve panic message when crashing ([#1361](https://github.com/datadog/libdatadog/issues/1361)) - ([65a5d9a](https://github.com/datadog/libdatadog/commit/65a5d9af8c9931f8ecbf2db8729fabbc3881fb07)) +- [crashtracker] Log errors in crashtracker receiver ([#1395](https://github.com/datadog/libdatadog/issues/1395)) - ([73c675b](https://github.com/datadog/libdatadog/commit/73c675b79f81978ee1190be6af0c5abec997e3b0)) +- Add changelog for every published crate ([#1396](https://github.com/datadog/libdatadog/issues/1396)) - ([5c4a024](https://github.com/datadog/libdatadog/commit/5c4a024598d6fe6cbd93a3e3dc9882848912064f)) + +### Fixed + +- *(sidecar)* AWS lambda also can return EACCESS for shm_open ([#1446](https://github.com/datadog/libdatadog/issues/1446)) - ([c65d768](https://github.com/datadog/libdatadog/commit/c65d7680109c92f49195b9a9314c9c301fc29f32)) + ## 1.0.0 - 2025-11-28 Initial release. diff --git a/libdd-crashtracker/Cargo.toml b/libdd-crashtracker/Cargo.toml index 380f4701f1..1d4226da7b 100644 --- a/libdd-crashtracker/Cargo.toml +++ b/libdd-crashtracker/Cargo.toml @@ -4,7 +4,7 @@ description = "Detects program crashes and reports them to datadog backend." homepage = "https://github.com/DataDog/libdatadog/tree/main/libdd-crashtracker" repository = "https://github.com/DataDog/libdatadog/tree/main/libdd-crashtracker" edition.workspace = true -version = "1.0.0" +version = "2.0.0" rust-version.workspace = true license.workspace = true autobenches = false