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/Cargo.lock b/Cargo.lock index 1764532984..bd03fdaf9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2730,7 +2730,7 @@ dependencies = [ [[package]] name = "libdd-common" -version = "1.0.0" +version = "1.1.0" dependencies = [ "anyhow", "cc", @@ -2781,7 +2781,7 @@ dependencies = [ [[package]] name = "libdd-crashtracker" -version = "1.0.0" +version = "2.0.0" dependencies = [ "anyhow", "backtrace", @@ -2887,7 +2887,7 @@ dependencies = [ [[package]] name = "libdd-ddsketch" -version = "1.0.0" +version = "1.0.1" dependencies = [ "prost", "prost-build", @@ -3044,7 +3044,7 @@ dependencies = [ [[package]] name = "libdd-telemetry" -version = "1.0.0" +version = "2.0.0" dependencies = [ "anyhow", "base64 0.22.1", 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/libdd-common-ffi/Cargo.toml b/libdd-common-ffi/Cargo.toml index 02d08f1ed5..68a6bd775e 100644 --- a/libdd-common-ffi/Cargo.toml +++ b/libdd-common-ffi/Cargo.toml @@ -23,7 +23,7 @@ build_common = { path = "../build-common" } anyhow = "1.0" chrono = { version = "0.4.38", features = ["std"] } crossbeam-queue = "0.3.11" -libdd-common = { version = "1.0.0", path = "../libdd-common" } +libdd-common = { version = "1.1.0", path = "../libdd-common" } hyper = { workspace = true} serde = "1.0" diff --git a/libdd-common/CHANGELOG.md b/libdd-common/CHANGELOG.md index 2dbdac7631..2082964e0a 100644 --- a/libdd-common/CHANGELOG.md +++ b/libdd-common/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog + + +## [1.1.0](https://github.com/datadog/libdatadog/compare/libdd-common-v1.0.0..libdd-common-v1.1.0) - 2026-01-22 + +### Added + +- *(profiling)* Simpler API for profile exporter ([#1423](https://github.com/datadog/libdatadog/issues/1423)) - ([0d4ebbe](https://github.com/datadog/libdatadog/commit/0d4ebbe55ab841c2af8db41da74597c007375f0e)) + +### Changed + +- *(profiling)* [**breaking**] Use reqwest instead of hyper for exporter ([#1444](https://github.com/datadog/libdatadog/issues/1444)) - ([39c7829](https://github.com/datadog/libdatadog/commit/39c7829592142d8fc8e8988b3631208e2d9ad1cc)) +- Add changelog for every published crate ([#1396](https://github.com/datadog/libdatadog/issues/1396)) - ([5c4a024](https://github.com/datadog/libdatadog/commit/5c4a024598d6fe6cbd93a3e3dc9882848912064f)) +- Don't panic if CryptoProvider already installed ([#1391](https://github.com/datadog/libdatadog/issues/1391)) - ([2f641ea](https://github.com/datadog/libdatadog/commit/2f641eae3708c34e4adfe62c9d477e665da4f12e)) + ## 1.0.0 - 2025-11-14 Initial release. diff --git a/libdd-common/Cargo.toml b/libdd-common/Cargo.toml index 4bb6114c3d..dfdd0f3aef 100644 --- a/libdd-common/Cargo.toml +++ b/libdd-common/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "libdd-common" -version = "1.0.0" +version = "1.1.0" description = "Shared utilities for Datadog libraries including HTTP/HTTPS connectors, container entity detection, tag validation, rate limiting, and Unix/Windows platform helpers" homepage = "https://github.com/DataDog/libdatadog/tree/main/datadog-common" repository = "https://github.com/DataDog/libdatadog/tree/main/datadog-common" diff --git a/libdd-crashtracker/CHANGELOG.md b/libdd-crashtracker/CHANGELOG.md index 302ce95f86..4653b1d1ca 100644 --- a/libdd-crashtracker/CHANGELOG.md +++ b/libdd-crashtracker/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog + + +## [2.0.0](https://github.com/datadog/libdatadog/compare/libdd-crashtracker-v1.0.0..libdd-crashtracker-v2.0.0) - 2026-01-22 + +### 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)) +- [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)) +- Fix CI ([#1389](https://github.com/datadog/libdatadog/issues/1389)) - ([4219fa9](https://github.com/datadog/libdatadog/commit/4219fa9adf2080321e58a0c1239edf003ec7529f)) +- [crashtracker] Set OS info in the crash info builder when receiving report ([#1388](https://github.com/datadog/libdatadog/issues/1388)) - ([e6671fc](https://github.com/datadog/libdatadog/commit/e6671fc694068d3f4500a02bdd4b33fff241da82)) +- Support cxx bindings for crashinfo ([#1379](https://github.com/datadog/libdatadog/issues/1379)) - ([6b26318](https://github.com/datadog/libdatadog/commit/6b263189044f48cec6a67745036bd027b44f6daa)) + +### 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 d447a7bcaa..227b7bfe8b 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 @@ -46,8 +46,8 @@ anyhow = "1.0" 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.0.0", path = "../libdd-common" } -libdd-telemetry = { version = "1.0.0", path = "../libdd-telemetry" } +libdd-common = { version = "1.1.0", path = "../libdd-common" } +libdd-telemetry = { version = "2.0.0", path = "../libdd-telemetry" } http = "1.0" libc = "0.2" nix = { version = "0.29", features = ["poll", "signal", "socket"] } @@ -78,4 +78,4 @@ tempfile = { version = "3.13" } # If we use a newer version of cc, CI fails on alpine. cc = "1.1.31" cxx-build = { version = "1.0", optional = true } -libdd-common = { version = "1.0.0", path = "../libdd-common" } +libdd-common = { version = "1.1.0", path = "../libdd-common" } diff --git a/libdd-data-pipeline/Cargo.toml b/libdd-data-pipeline/Cargo.toml index 3bc935c0ff..7517cd401b 100644 --- a/libdd-data-pipeline/Cargo.toml +++ b/libdd-data-pipeline/Cargo.toml @@ -33,12 +33,12 @@ tokio = { version = "1.23", features = [ uuid = { version = "1.10.0", features = ["v4"] } tokio-util = "0.7.11" -libdd-common = { version = "1.0.0", path = "../libdd-common", default-features = false } -libdd-telemetry = { version = "1.0.0", path = "../libdd-telemetry", default-features = false } +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-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..f893ba73ec 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-22 + +### 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-dogstatsd-client/Cargo.toml b/libdd-dogstatsd-client/Cargo.toml index ec3ee80fe0..1903d7b346 100644 --- a/libdd-dogstatsd-client/Cargo.toml +++ b/libdd-dogstatsd-client/Cargo.toml @@ -12,7 +12,7 @@ license.workspace = true bench = false [dependencies] -libdd-common = { version = "1.0.0", path = "../libdd-common", default-features = false } +libdd-common = { version = "1.1.0", path = "../libdd-common", default-features = false } cadence = "1.3.0" serde = { version = "1.0", features = ["derive", "rc"] } tracing = { version = "0.1", default-features = false } diff --git a/libdd-profiling/Cargo.toml b/libdd-profiling/Cargo.toml index 811419458a..317412e05a 100644 --- a/libdd-profiling/Cargo.toml +++ b/libdd-profiling/Cargo.toml @@ -41,7 +41,7 @@ http-body-util = "0.1" httparse = "1.9" indexmap = "2.11" libdd-alloc = { version = "1.0.0", path = "../libdd-alloc" } -libdd-common = { version = "1.0.0", path = "../libdd-common", default-features = false } +libdd-common = { version = "1.1.0", path = "../libdd-common", default-features = false } libdd-profiling-protobuf = { version = "1.0.0", path = "../libdd-profiling-protobuf", features = ["prost_impls"] } mime = "0.3.16" multipart = { version = "0.18", optional = true } diff --git a/libdd-telemetry/CHANGELOG.md b/libdd-telemetry/CHANGELOG.md index 1e77eea5a4..67f73a6487 100644 --- a/libdd-telemetry/CHANGELOG.md +++ b/libdd-telemetry/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog + + +## [2.0.0](https://github.com/datadog/libdatadog/compare/libdd-telemetry-v1.0.0..libdd-telemetry-v2.0.0) - 2026-01-22 + +### Added + +- *(config_visibility)* [APMAPI-1693] Telemetry for enhanced config reporting ([#1385](https://github.com/datadog/libdatadog/issues/1385)) - ([435107c](https://github.com/datadog/libdatadog/commit/435107c245112397914935c0f7148a18b91cafc6)) + +### Changed + +- *(telemetry)* Flush metrics with heartbeats if the interval is small ([#1418](https://github.com/datadog/libdatadog/issues/1418)) - ([40a1ad6](https://github.com/datadog/libdatadog/commit/40a1ad6bc8fe903b67af0c95ce530fd7efe28329)) +- Add changelog for every published crate ([#1396](https://github.com/datadog/libdatadog/issues/1396)) - ([5c4a024](https://github.com/datadog/libdatadog/commit/5c4a024598d6fe6cbd93a3e3dc9882848912064f)) + ## 1.0.0 - 2025-11-17 Initial release. diff --git a/libdd-telemetry/Cargo.toml b/libdd-telemetry/Cargo.toml index 6ef38626f0..c503d93e32 100644 --- a/libdd-telemetry/Cargo.toml +++ b/libdd-telemetry/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libdd-telemetry" -version= "1.0.0" +version= "2.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" @@ -33,8 +33,8 @@ tracing = { version = "0.1", default-features = false } uuid = { version = "1.3", features = ["v4"] } hashbrown = "0.15" -libdd-common = { version = "1.0.0", path = "../libdd-common", default-features = false } -libdd-ddsketch = { version = "1.0.0", path = "../libdd-ddsketch" } +libdd-common = { version = "1.1.0", path = "../libdd-common", default-features = false } +libdd-ddsketch = { version = "1.0.1", path = "../libdd-ddsketch" } [target."cfg(unix)".dependencies] libc = "0.2.176" diff --git a/libdd-trace-obfuscation/Cargo.toml b/libdd-trace-obfuscation/Cargo.toml index 22381331e1..757b8c3a9f 100644 --- a/libdd-trace-obfuscation/Cargo.toml +++ b/libdd-trace-obfuscation/Cargo.toml @@ -19,7 +19,7 @@ percent-encoding = "2.1" log = "0.4" libdd-trace-protobuf = { version = "1.0.0", path = "../libdd-trace-protobuf" } libdd-trace-utils = { version = "1.0.0", path = "../libdd-trace-utils" } -libdd-common = { version = "1.0.0", path = "../libdd-common" } +libdd-common = { version = "1.1.0", path = "../libdd-common" } [dev-dependencies] duplicate = "0.4.1" 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" } diff --git a/libdd-trace-utils/Cargo.toml b/libdd-trace-utils/Cargo.toml index e5f8ecdb55..4b76ffb24e 100644 --- a/libdd-trace-utils/Cargo.toml +++ b/libdd-trace-utils/Cargo.toml @@ -34,7 +34,7 @@ bytes = "1.6.0" rmpv = { version = "1.3.0", default-features = false } rmp = { version = "0.8.14", default-features = false } -libdd-common = { version = "1.0.0", path = "../libdd-common", default-features = false } +libdd-common = { version = "1.1.0", path = "../libdd-common", default-features = false } libdd-trace-protobuf = { version = "1.0.0", path = "../libdd-trace-protobuf" } libdd-trace-normalization = { version = "1.0.0", path = "../libdd-trace-normalization" } libdd-tinybytes = { version = "1.0.0", path = "../libdd-tinybytes", features = [ diff --git a/scripts/check-public-api.sh b/scripts/check-public-api.sh new file mode 100755 index 0000000000..eff2e1eb83 --- /dev/null +++ b/scripts/check-public-api.sh @@ -0,0 +1,202 @@ +#!/bin/bash +# Check Public API Changes Script +# Executes cargo-public-api diff and determines the semver level of changes +# +# Usage: ./check-public-api.sh [OPTIONS] -p PACKAGE RANGE +# +# Output: JSON with change level (major, minor, patch) and details + +set -euo pipefail + +# Parse arguments +PACKAGE="" +RANGE="" +ALL_FEATURES=false +FORMAT="json" +VERBOSE=false + +print_usage() { + echo "Usage: $0 [OPTIONS] -p PACKAGE RANGE" + echo "" + echo "Executes cargo-public-api diff and determines the semver level of changes." + echo "" + echo "Arguments:" + echo " -p PACKAGE Package name to check" + echo " RANGE Git range to compare (e.g., v1.0.0..HEAD)" + echo "" + echo "Options:" + echo " --all-features Enable all features" + echo " --format=FORMAT Output format: json (default), summary" + echo " --verbose, -v Show verbose output" + echo " --help, -h Show this help message" + echo "" + echo "Examples:" + echo " ./check-public-api.sh -p libdd-common v1.0.0..HEAD" + echo " ./check-public-api.sh -p libdd-common --all-features libdd-common-v1.0.0..HEAD" + echo "" + echo "Output JSON format:" + echo ' {"package":"name","range":"tag..HEAD","level":"minor","removed":0,"changed":0,"added":5}' + echo "" + echo "Semver level rules:" + echo " major - Any removed or changed items (breaking changes)" + echo " minor - Only added items (backwards compatible additions)" + echo " patch - No public API changes" +} + +while [[ $# -gt 0 ]]; do + case "$1" in + -p) + PACKAGE="$2" + shift 2 + ;; + --all-features) + ALL_FEATURES=true + shift + ;; + --format=*) + FORMAT="${1#--format=}" + shift + ;; + --verbose|-v) + VERBOSE=true + shift + ;; + --help|-h) + print_usage + exit 0 + ;; + -*) + echo "Unknown option: $1" >&2 + echo "Use --help for usage information" >&2 + exit 1 + ;; + *) + # Positional argument - treat as range + RANGE="$1" + shift + ;; + esac +done + +# Validate required arguments +if [ -z "$PACKAGE" ]; then + echo "ERROR: Package name is required (-p PACKAGE)" >&2 + exit 1 +fi + +if [ -z "$RANGE" ]; then + echo "ERROR: Git range is required (e.g., v1.0.0..HEAD)" >&2 + exit 1 +fi + +log_verbose() { + if [ "$VERBOSE" = true ]; then + echo "$@" >&2 + fi +} + +# Build cargo public-api command +CMD="cargo public-api -p $PACKAGE --color=never" +if [ "$ALL_FEATURES" = true ]; then + CMD="$CMD --all-features" +fi +CMD="$CMD diff --force $RANGE" + +log_verbose "Executing: $CMD" + +# Run cargo public-api diff and capture output +DIFF_OUTPUT=$(eval "$CMD" 2>&1) || true + +log_verbose "Raw output:" +log_verbose "$DIFF_OUTPUT" + +# Parse the output to count removed, changed, and added items +# cargo-public-api diff output format: +# Removed: +# - pub fn some_function() +# Changed: +# - pub fn changed_function() -> OldType +# + pub fn changed_function() -> NewType +# Added: +# + pub fn new_function() + +REMOVED_COUNT=0 +CHANGED_COUNT=0 +ADDED_COUNT=0 + +CURRENT_SECTION="" + +while IFS= read -r line; do + case "$line" in + "Removed items from the public API"*) + CURRENT_SECTION="removed" + ;; + "Changed items in the public API"*) + CURRENT_SECTION="changed" + ;; + "Added items to the public API"*) + CURRENT_SECTION="added" + ;; + "-"*) + if [ "$CURRENT_SECTION" = "removed" ]; then + REMOVED_COUNT=$((REMOVED_COUNT + 1)) + fi + # Don't count "-" lines in "Changed" section, they're paired with "+" + ;; + "+"*) + if [ "$CURRENT_SECTION" = "changed" ]; then + CHANGED_COUNT=$((CHANGED_COUNT + 1)) + elif [ "$CURRENT_SECTION" = "added" ]; then + ADDED_COUNT=$((ADDED_COUNT + 1)) + fi + ;; + esac +done <<< "$DIFF_OUTPUT" + +log_verbose "Counts: removed=$REMOVED_COUNT, changed=$CHANGED_COUNT, added=$ADDED_COUNT" + +# Determine semver level based on changes +# Major: Any removed or changed items (breaking changes) +# Minor: Only added items (backwards compatible) +# Patch: No changes +if [ "$REMOVED_COUNT" -gt 0 ] || [ "$CHANGED_COUNT" -gt 0 ]; then + LEVEL="major" +elif [ "$ADDED_COUNT" -gt 0 ]; then + LEVEL="minor" +else + LEVEL="patch" +fi + +# Output results +case "$FORMAT" in + json) + jq -n \ + --arg package "$PACKAGE" \ + --arg range "$RANGE" \ + --arg level "$LEVEL" \ + --argjson removed "$REMOVED_COUNT" \ + --argjson changed "$CHANGED_COUNT" \ + --argjson added "$ADDED_COUNT" \ + '{package: $package, range: $range, level: $level, removed: $removed, changed: $changed, added: $added}' + ;; + summary) + echo "Package: $PACKAGE" + echo "Range: $RANGE" + echo "Level: $LEVEL" + echo "" + echo "Changes:" + echo " Removed: $REMOVED_COUNT" + echo " Changed: $CHANGED_COUNT" + echo " Added: $ADDED_COUNT" + if [ -n "$DIFF_OUTPUT" ] && [ "$DIFF_OUTPUT" != "No changes detected" ]; then + echo "" + echo "Details:" + echo "$DIFF_OUTPUT" + fi + ;; + *) + echo "Unknown format: $FORMAT" >&2 + echo "Available formats: json, summary" >&2 + exit 1 + ;; +esac 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