ci: merge check-semver scripts into one
What changed, and why it matters
This commit is a cleanup of the project's automated version-compatibility checking scripts. It merges two separate scripts into one and updates the GitHub Actions workflow that runs them. There is no change to the actual Bitcoin library code that users install, so this does not introduce or fix a security vulnerability in the software itself.
No security action required. Treat as a normal CI maintenance change.
Security signals we found
No changes to library source code
CI-only refactor
Workflow adds a comment citing GitHub security best practices for pull_request workflows and labeler follow-up jobs
Evidence from the diff
The commit refactors CI-only tooling for rust-bitcoin. It deletes contrib/check-semver-feature.sh and contrib/check-semver-pr.sh and replaces them with a single contrib/check-semver.sh that uses newer cargo-semver-checks CLI options (e.g., –baseline-root, –only-explicit-features, –current-features) to detect non-additive features and semver breaks. The workflow file .github/workflows/semver-checks.yml is updated to run this single script, upload an artifact, and fail only on breaks in stable (>=1.0.0) packages. No Rust source code in the published crates is modified.
Changed components
.github/workflows/semver-checks.ymlcontrib/check-semver.shcontrib/check-semver-feature.sh (deleted)contrib/check-semver-pr.sh (deleted)Inspect captured patch +241 / −412
diff --git a/.github/workflows/semver-checks.yml b/.github/workflows/semver-checks.yml
index 8f0c99a2..187484a5 100644
--- a/.github/workflows/semver-checks.yml
+++ b/.github/workflows/semver-checks.yml
@@ -1,18 +1,21 @@
+# The workflow detects semver breaks and uploads an artifact
+# for another job to use to add labels to the relevant PR. This
+# is following security best practices: https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/
+
on: # yamllint disable-line rule:truthy
pull_request:
+# This key is what connects the job output to the labeller followup.
name: Check semver breaks
permissions: {}
jobs:
- PR:
- name: PR Semver - stable toolchain
+ Semver:
runs-on: ubuntu-24.04
strategy:
fail-fast: false
permissions:
contents: read
- pull-requests: write
steps:
- name: "Checkout repo"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -28,48 +31,22 @@ jobs:
uses: cargo-bins/cargo-binstall@aaa84a43aec4955a42c5ffc65d258961e39f276e # v1.19.1
- name: "Binstall pinned cargo-semver-checks"
run: cargo binstall cargo-semver-checks@$(cat ./.github/workflows/cargo-semver-checks-version) --no-confirm
- - name: "Run semver checker script"
- run: ./contrib/check-semver-pr.sh
- - name: Save PR number
- if: ${{ hashFiles('semver-break') != '' }}
+ - name: "Run semver checks"
+ id: semver
+ continue-on-error: true
+ run: ./contrib/check-semver.sh ${{ github.event.pull_request.base.sha || github.event.before }}
+ - name: "Save breaking state"
env:
- PR_NUMBER: ${{ github.event.number }}
+ PR_NUMBER: ${{ github.event.pull_request.number }}
+ EXIT_CODE: ${{ steps.semver.exit_code }}
run: |
- # check if PR_NUMBER is a number
- if ! [[ "$PR_NUMBER" =~ ^-?[0-9]+$ ]]; then
- echo "$PR_NUMBER is not a number."
- exit 1
- fi
- echo "$PR_NUMBER" > ./semver-break
- - name: "Save breaking state"
- if: ${{ hashFiles('semver-break') != '' }}
+ echo "$PR_NUMBER" > semver-break
+ echo "$EXIT_CODE" >> semver-break
+ - name: "Upload breaking state"
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: semver-break
path: semver-break
-
- Feature:
- name: Non additive cargo features - stable toolchain
- runs-on: ubuntu-24.04
- strategy:
- fail-fast: false
- permissions:
- contents: read
- pull-requests: write
- steps:
- - name: "Checkout repo"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: false
- - name: "Install Rustup"
- uses: dtolnay/rust-toolchain@5d458579430fc14a04a08a1e7d3694f545e91ce6 # stable
- - name: "Select stable-version"
- run: |
- rustup default $(cargo metadata --format-version 1 | jq -r '.metadata.rbmt.toolchains.stable')
- - name: "Install cargo-binstall"
- uses: cargo-bins/cargo-binstall@aaa84a43aec4955a42c5ffc65d258961e39f276e # v1.19.1
- - name: "Binstall pinned cargo-semver-checks"
- run: cargo binstall cargo-semver-checks@$(cat ./.github/workflows/cargo-semver-checks-version) --no-confirm
- - name: "Run semver checker script"
- run: ./contrib/check-semver-feature.sh
-
+ - name: "Fail on breaks to stable packages"
+ if: ${{ steps.semver.exit_code == 1 }}
+ run: exit 1
diff --git a/contrib/check-semver-feature.sh b/contrib/check-semver-feature.sh
deleted file mode 100755
index 46839d67..00000000
--- a/contrib/check-semver-feature.sh
+++ /dev/null
@@ -1,137 +0,0 @@
-#!/usr/bin/env bash
-#
-# Checks semver compatibility between the `--no-features` and `all-features`.
-# This is important since it tests for the presence of non-additive cargo features.
-#
-# Under the hood uses cargo semver-checks to check for breaking changes.
-# We cannot use it directly since it only supports checking against published
-# crates.
-# That's the intended use case for cargo semver-checks:
-# you run before publishing a new version of a crate to check semver breaks.
-# Here we are hacking it by first generating JSON files from cargo doc
-# and then using those files to check for breaking changes with
-# cargo semver-checks.
-
-set -euo pipefail
-
-# These are the hardcoded flags that cargo semver-checks uses
-# under the hood to invoke rustdoc.
-RUSTDOCFLAGS="-Z unstable-options --document-private-items --document-hidden-items --output-format=json --cap-lints=allow"
-
-main() {
- # Generate JSON files for no-features and all-features
- # 1. bitcoin
- generate_json_files_all_features "bitcoin"
- generate_json_files_no_default_features "bitcoin"
-
- # 2. base58ck
- generate_json_files_all_features "base58ck"
- generate_json_files_no_default_features "base58ck"
-
- # 3. bitcoin_hashes
- generate_json_files_all_features "bitcoin_hashes"
- generate_json_files_no_default_features "bitcoin_hashes"
-
- # 4. bitcoin-units
- generate_json_files_all_features "bitcoin-units"
- generate_json_files_no_default_features "bitcoin-units"
-
- # 5. bitcoin-io
- generate_json_files_all_features "bitcoin-io"
- generate_json_files_no_default_features "bitcoin-io"
-
- # Check for API semver non-additive cargo features on all the generated JSON files above.
- run_cargo_semver_check "bitcoin"
- run_cargo_semver_check "base58ck"
- run_cargo_semver_check "bitcoin_hashes"
- run_cargo_semver_check "bitcoin-units"
- run_cargo_semver_check "bitcoin-io"
-
- # Invoke cargo semver-checks to check for non-additive cargo features
- # in all generated files.
- check_for_non_additive_cargo_features
-}
-
-# Run cargo doc with the cargo semver-checks rustdoc flags.
-# We don't care about dependencies.
-run_cargo_doc() {
- RUSTDOCFLAGS="$RUSTDOCFLAGS" RUSTC_BOOTSTRAP=1 cargo doc --no-deps "$@"
-}
-
-# Run cargo semver-check
-run_cargo_semver_check() {
- local crate="$1"
-
- echo "Running cargo semver-checks for $crate"
- # Hack to not fail on errors.
- # This is necessary since cargo semver-checks will fail if the
- # semver check fails.
- # We check that manually later.
- set +e
- cargo semver-checks -v --baseline-rustdoc "$crate-no-default-features.json" --current-rustdoc "$crate-all-features.json" > "$crate--additive-features.txt" 2>&1
- set -e
-}
-
-# The following function uses cargo doc to generate JSON files that
-# cargo semver-checks can use.
-# - no-default-features: generate JSON doc files with no default features.
-generate_json_files_no_default_features() {
- local crate="$1"
-
- echo "Running cargo doc no-default-features for $crate"
- run_cargo_doc --no-default-features -p "$crate"
-
- # replace _ for - in crate name.
- # This is necessary since some crates have - in their name
- # which will be converted to _ in the output file by cargo doc.
- mv "target/doc/${crate//-/_}.json" "$crate-no-default-features.json"
-}
-# - all-features: generate JSON doc files with all features.
-generate_json_files_all_features() {
- local crate="$1"
-
- echo "Running cargo doc all-features for $crate"
- run_cargo_doc --all-features -p "$crate"
-
- # replace _ for - in crate name.
- # This is necessary since some crates have - in their name
- # which will be converted to _ in the output file by cargo doc.
- mv -v "target/doc/${crate//-/_}.json" "$crate-all-features.json"
-}
-
-# Check if there are non-additive cargo features.
-# We loop through all the generated files and check if there is a FAIL
-# in the cargo semver-checks output.
-# If we detect a fail, we create an empty file non-additive-cargo.
-# If the following CI step finds this file, it will add:
-# 1. a comment on the PR.
-# 2. a label to the PR.
-check_for_non_additive_cargo_features() {
- for file in *additive-features.txt; do
- echo "Checking $file"
- if grep -q "FAIL" "$file"; then
- echo "You have introduced non-additive cargo features"
- echo "FAIL found in $file"
- cat "$file"
- # flag it as a breaking change
- # Handle the case where FAIL is found
- touch non-additive-cargo
- fi
- done
- if ! [ -f non-additive-cargo ]; then
- echo "No non-additive cargo features found"
- else
- err "Non-additive cargo features found"
- fi
-}
-
-err() {
- echo "$1" >&2
- exit 1
-}
-
-#
-# Main script
-#
-main "$@"
-exit 0
diff --git a/contrib/check-semver-pr.sh b/contrib/check-semver-pr.sh
deleted file mode 100755
index 7bc97ebc..00000000
--- a/contrib/check-semver-pr.sh
+++ /dev/null
@@ -1,233 +0,0 @@
-#!/usr/bin/env bash
-#
-# Checks semver compatibility between the current and target branches.
-# Under the hood uses cargo semver-checks to check for breaking changes.
-# We cannot use it directly since it only supports checking against published
-# crates.
-# That's the intended use case for cargo semver-checks:
-# you run before publishing a new version of a crate to check semver breaks.
-# Here we are hacking it by first generating JSON files from cargo doc
-# and then using those files to check for breaking changes with
-# cargo semver-checks.
-
-set -euo pipefail
-
-# These are the hardcoded flags that cargo semver-checks uses
-# under the hood to invoke rustdoc.
-RUSTDOCFLAGS="-Z unstable-options --document-private-items --document-hidden-items --output-format=json --cap-lints=allow"
-
-# Crates that have reached 1.0 must not introduce semver-breaking API changes.
-SEMVER_HARD_FAIL_CRATES=("bitcoin-consensus-encoding" "bitcoin_hashes" "bitcoin-network-kind")
-
-# These will be set to the commit SHA from the PR's target branch
-# GitHub Actions CI.
-# NOTE: if running locally this will be set to master.
-if [ -n "${GITHUB_BASE_REF+x}" ]; then
- TARGET_COMMIT=$GITHUB_BASE_REF # running on CI
-else
- TARGET_COMMIT=$(git rev-parse master) # running locally
-fi
-
-main() {
- # On current commit:
- # 1. bitcoin: all-features and no-default-features.
- generate_json_files_all_features "bitcoin" "current"
- generate_json_files_no_default_features "bitcoin" "current"
-
- # 2. base58ck: all-features and no-default-features.
- generate_json_files_all_features "base58ck" "current"
- generate_json_files_no_default_features "base58ck" "current"
-
- # 3. bitcoin_hashes: all-features, no-default-features and alloc feature.
- generate_json_files_all_features "bitcoin_hashes" "current"
- generate_json_files_no_default_features "bitcoin_hashes" "current"
- generate_json_files_features_alloc "bitcoin_hashes" "current"
-
- # 4. bitcoin-units: all-features, no-default-features and alloc feature.
- generate_json_files_all_features "bitcoin-units" "current"
- generate_json_files_no_default_features "bitcoin-units" "current"
- generate_json_files_features_alloc "bitcoin-units" "current"
-
- # 5. bitcoin-io: all-features, no-default-features and alloc feature.
- generate_json_files_all_features "bitcoin-io" "current"
- generate_json_files_no_default_features "bitcoin-io" "current"
- generate_json_files_features_alloc "bitcoin-io" "current"
-
- # 6. bitcoin-consensus-encoding: all-features, no-default-features and alloc feature.
- generate_json_files_all_features "bitcoin-consensus-encoding" "current"
- generate_json_files_no_default_features "bitcoin-consensus-encoding" "current"
- generate_json_files_features_alloc "bitcoin-consensus-encoding" "current"
-
- # 7. bitcoin-network-kind: all-features, no-default-features and alloc feature.
- generate_json_files_all_features "bitcoin-network-kind" "current"
- generate_json_files_no_default_features "bitcoin-network-kind" "current"
- generate_json_files_features_alloc "bitcoin-network-kind" "current"
-
-
- # Switch to target commit.
- echo "Checking out target commit at $TARGET_COMMIT"
- git checkout "$TARGET_COMMIT"
-
- # On target commit:
- # 1. bitcoin: all-features and no-default-features.
- generate_json_files_all_features "bitcoin" "master"
- generate_json_files_no_default_features "bitcoin" "master"
-
- # 2. base58ck: all-features and no-default-features.
- generate_json_files_all_features "base58ck" "master"
- generate_json_files_no_default_features "base58ck" "master"
-
- # 3. bitcoin_hashes: all-features, no-default-features and alloc feature.
- generate_json_files_all_features "bitcoin_hashes" "master"
- generate_json_files_no_default_features "bitcoin_hashes" "master"
- generate_json_files_features_alloc "bitcoin_hashes" "master"
-
- # 4. bitcoin-units: all-features, no-default-features and alloc feature.
- generate_json_files_all_features "bitcoin-units" "master"
- generate_json_files_no_default_features "bitcoin-units" "master"
- generate_json_files_features_alloc "bitcoin-units" "master"
-
- # 5. bitcoin-io: all-features, no-default-features and alloc feature.
- generate_json_files_all_features "bitcoin-io" "master"
- generate_json_files_no_default_features "bitcoin-io" "master"
- generate_json_files_features_alloc "bitcoin-io" "master"
-
- # 6. bitcoin-consensus-encoding: all-features, no-default-features and alloc feature.
- generate_json_files_all_features "bitcoin-consensus-encoding" "master"
- generate_json_files_no_default_features "bitcoin-consensus-encoding" "master"
- generate_json_files_features_alloc "bitcoin-consensus-encoding" "master"
-
- # 7. bitcoin-network-kind: all-features, no-default-features and alloc feature.
- generate_json_files_all_features "bitcoin-network-kind" "master"
- generate_json_files_no_default_features "bitcoin-network-kind" "master"
- generate_json_files_features_alloc "bitcoin-network-kind" "master"
-
- # Check for API semver breaks on all the generated JSON files above.
- run_cargo_semver_check "bitcoin" "all-features"
- run_cargo_semver_check "bitcoin" "no-default-features"
- run_cargo_semver_check "base58ck" "all-features"
- run_cargo_semver_check "base58ck" "no-default-features"
- run_cargo_semver_check "bitcoin_hashes" "all-features"
- run_cargo_semver_check "bitcoin_hashes" "no-default-features"
- run_cargo_semver_check "bitcoin_hashes" "alloc"
- run_cargo_semver_check "bitcoin-units" "all-features"
- run_cargo_semver_check "bitcoin-units" "no-default-features"
- run_cargo_semver_check "bitcoin-units" "alloc"
- run_cargo_semver_check "bitcoin-io" "all-features"
- run_cargo_semver_check "bitcoin-io" "no-default-features"
- run_cargo_semver_check "bitcoin-io" "alloc"
- run_cargo_semver_check "bitcoin-consensus-encoding" "all-features"
- run_cargo_semver_check "bitcoin-consensus-encoding" "no-default-features"
- run_cargo_semver_check "bitcoin-consensus-encoding" "alloc"
- run_cargo_semver_check "bitcoin-network-kind" "all-features"
- run_cargo_semver_check "bitcoin-network-kind" "no-default-features"
- run_cargo_semver_check "bitcoin-network-kind" "alloc"
-
- # Invoke cargo semver-checks to check for breaking changes
- # in all generated files.
- check_for_breaking_changes
-}
-
-# Run cargo doc with the cargo semver-checks rustdoc flags.
-# We don't care about dependencies.
-run_cargo_doc() {
- RUSTDOCFLAGS="$RUSTDOCFLAGS" RUSTC_BOOTSTRAP=1 cargo doc --no-deps "$@"
-}
-
-# Run cargo semver-check
-run_cargo_semver_check() {
- local crate="$1"
- local variant="$2"
-
- echo "Running cargo semver-checks for $crate $variant"
- # Hack to not fail on errors.
- # This is necessary since cargo semver-checks will fail if the
- # semver check fails.
- # We check that manually later.
- set +e
- cargo semver-checks -v --baseline-rustdoc "$crate-master-$variant.json" --current-rustdoc "$crate-current-$variant.json" > "$crate-$variant-semver.txt" 2>&1
- set -e
-}
-
-# The following function uses cargo doc to generate JSON files that
-# cargo semver-checks can use.
-# - no-default-features: generate JSON doc files with no default features.
-generate_json_files_no_default_features() {
- local crate="$1"
- local version="$2"
-
- echo "Running cargo doc no-default-features for $crate $version"
- run_cargo_doc --no-default-features -p "$crate"
-
- # replace _ for - in crate name.
- # This is necessary since some crates have - in their name
- # which will be converted to _ in the output file by cargo doc.
- mv "target/doc/${crate//-/_}.json" "$crate-$version-no-default-features.json"
-}
-# - all-features: generate JSON doc files with all features.
-generate_json_files_all_features() {
- local crate="$1"
- local version="$2"
-
- echo "Running cargo doc all-features for $crate $version"
- run_cargo_doc --all-features -p "$crate"
-
- # replace _ for - in crate name.
- # This is necessary since some crates have - in their name
- # which will be converted to _ in the output file by cargo doc.
- mv -v "target/doc/${crate//-/_}.json" "$crate-$version-all-features.json"
-}
-# - alloc: generate JSON doc files with the alloc feature.
-generate_json_files_features_alloc() {
- local crate="$1"
- local version="$2"
-
- echo "Running cargo doc --features alloc for $crate $version"
- run_cargo_doc --no-default-features --features alloc -p "$crate"
-
- # replace _ for - in crate name.
- # This is necessary since some crates have - in their name
- # which will be converted to _ in the output file by cargo doc.
- mv -v "target/doc/${crate//-/_}.json" "$crate-$version-alloc.json"
-}
-
-# Check if there are breaking changes.
-# We loop through all the generated files and check whether cargo semver-checks
-# reported a major (breaking) change.
-# Minor changes are not breaking and are ignored.
-# If we detect a break, we create an empty file semver-break.
-# If the following CI step finds this file, it will add:
-# 1. a comment on the PR.
-# 2. a label to the PR.
-check_for_breaking_changes() {
- for file in *semver.txt; do
- echo "Checking $file"
- # Only flag major failures. minor changes are ignored.
- if grep -qE '[1-9][0-9]* major .* checks failed' "$file"; then
- echo "You have introduced breaking changes to the public API"
- echo "Major semver break found in $file"
- cat "$file"
- # flag it as a breaking change
- touch semver-break
-
- for crate in "${SEMVER_HARD_FAIL_CRATES[@]}"; do
- if [[ "$file" == "$crate"-* ]]; then
- touch semver-hard-fail
- fi
- done
- fi
- done
- if ! [ -f semver-break ]; then
- echo "No breaking changes found"
- fi
- if [ -f semver-hard-fail ]; then
- echo "Semver break detected in a 1.0 crate; failing CI"
- exit 1
- fi
-}
-
-#
-# Main script
-#
-main "$@"
-exit 0
diff --git a/contrib/check-semver.sh b/contrib/check-semver.sh
new file mode 100755
index 00000000..86b28a1b
--- /dev/null
+++ b/contrib/check-semver.sh
@@ -0,0 +1,222 @@
+#!/usr/bin/env bash
+#
+# Checks semver compatibility and non-additive cargo features.
+#
+# Usage: check-semver.sh [BASELINE_COMMIT]
+# BASELINE_COMMIT: Git commit hash to compare against (defaults to master)
+#
+# Exit Codes:
+# 0 - Success, no semver breaks detected.
+# 1 - Hard failure, non-additive features or semver breaks detected in stable packages (>= 1.0.0).
+# 2 - Soft failure, semver breaks detected in unstable packages (< 1.0.0).
+
+set -euo pipefail
+
+BASELINE_COMMIT="${1:-$(git rev-parse master)}"
+WORKSPACE_ROOT="$(git rev-parse --show-toplevel)"
+
+# Check for required tools.
+if ! command -v jq &> /dev/null; then
+ echo "ERROR: jq is required but not installed"
+ exit 127
+fi
+
+# Get all workspace package IDs from cargo metadata, excluding unpublished packages.
+#
+# Example output:
+# path+file:///home/user/rust-bitcoin/bitcoin#bitcoin@0.33.0-beta
+# path+file:///home/user/rust-bitcoin/hashes#bitcoin_hashes@0.21.0
+# path+file:///home/user/rust-bitcoin/consensus_encoding#bitcoin-consensus-encoding@1.0.0
+get_workspace_packages() {
+ cargo metadata --format-version 1 | jq -r '.workspace_members[] as $member |
+ .packages[] |
+ select(.id == $member and .publish == null) |
+ .id'
+}
+
+# Get package name from package ID.
+#
+# Example input: path+file:///home/user/rust-bitcoin/hashes#bitcoin_hashes@0.21.0
+# Example output: bitcoin_hashes
+get_package_name() {
+ local pkg_id="$1"
+ cargo metadata --format-version 1 | \
+ jq -r ".packages[] | select(.id == \"$pkg_id\") | .name"
+}
+
+# Get all feature names for a package except `default`.
+#
+# Example input: path+file:///home/user/rust-bitcoin/consensus_encoding#bitcoin-consensus-encoding@1.0.0
+# Example output:
+# alloc
+# std
+get_package_features() {
+ local pkg_id="$1"
+ cargo metadata --format-version 1 | \
+ jq -r ".packages[] | select(.id == \"$pkg_id\") | .features | keys[] | select(. != \"default\")" | sort | uniq
+}
+
+# Get test variants for a package.
+#
+# All-features and no-default-features are always tested, and alloc
+# variant is tested if the package has an alloc feature.
+#
+# Example input (with alloc): path+file:///home/user/rust-bitcoin/consensus_encoding#bitcoin-consensus-encoding@1.0.0
+# Example output:
+# all-features
+# no-default-features
+# alloc
+get_package_variants() {
+ local pkg_id="$1"
+ local variants=("all-features" "no-default-features")
+
+ # Check if package has "alloc" feature
+ if cargo metadata --format-version 1 | jq -e "
+ .packages[] |
+ select(.id == \"$pkg_id\") |
+ .features | has(\"alloc\")
+ " &>/dev/null; then
+ variants+=("alloc")
+ fi
+
+ printf '%s\n' "${variants[@]}"
+}
+
+# Check if a package has reached 1.0.0 or higher (semver hard-fail).
+#
+# Example input: path+file:///home/user/rust-bitcoin/consensus_encoding#bitcoin-consensus-encoding@1.0.0
+# Returns: 0 (success) because version is 1.0.0
+is_stabilized_package() {
+ local pkg_id="$1"
+ cargo metadata --format-version 1 | jq -e "
+ .packages[] |
+ select(.id == \"$pkg_id\") |
+ .version | split(\".\")[0] | tonumber >= 1
+ " &>/dev/null
+}
+
+# Check that all cargo features are additive (enabling features doesn't break the API).
+#
+# Exit Codes:
+# 0 - Success, all features are additive.
+# 1 - Failure, non-additive features detected in one or more packages.
+check_non_additive_features() {
+ echo "Checking for non-additive features..."
+
+ local has_non_additive=false
+
+ for pkg_id in $(get_workspace_packages); do
+ local pkg_name
+ pkg_name=$(get_package_name "$pkg_id")
+
+ echo "Checking $pkg_name for non-additive features..."
+ local -a current_features=()
+ for feature in $(get_package_features "$pkg_id"); do
+ current_features+=("--current-features" "$feature")
+ done
+
+ # Compare no-features to all-features. Due to cargo-semver-checks
+ # interface, all-features need to be explicitly listed.
+ if ! cargo semver-checks --quiet \
+ -p "$pkg_name" \
+ --release-type minor \
+ --only-explicit-features \
+ --baseline-root "$WORKSPACE_ROOT" \
+ --baseline-features "" \
+ "${current_features[@]}"; then
+ echo "Non-additive cargo features found in $pkg_name"
+ has_non_additive=true
+ fi
+ done
+
+ if [ "$has_non_additive" = true ]; then
+ echo "ERROR: Non-additive cargo features detected"
+ return 1
+ fi
+
+ echo "No non-additive cargo features found"
+ return 0
+}
+
+# Check for semver compatibility breaks against a baseline commit.
+#
+# Exit Codes:
+# 0 - Success, no semver breaks detected.
+# 1 - Failure, breaking changes in stable packages (version >= 1.0.0).
+# 2 - Failure, breaking changes in unstable packages (version < 1.0.0).
+check_semver_breaks() {
+ echo "Checking semver against baseline: $BASELINE_COMMIT"
+
+ # Create a worktree for the baseline commit.
+ # cargo-semver-checks has git built in with the --baseline-rev
+ # flag, but it doesn't work with the symlinks of the include
+ # system in rust-bitcoin's repo. Using a worktree ensures symlinks
+ # work correctly while keeping the baseline repo lightweight.
+ local baseline_dir
+ baseline_dir=$(mktemp -d)
+ trap 'git -C "$WORKSPACE_ROOT" worktree remove "$baseline_dir"' RETURN
+ git -C "$WORKSPACE_ROOT" worktree add "$baseline_dir" "$BASELINE_COMMIT"
+
+ local has_breaks=false
+ local has_hard_fails=false
+
+ for pkg_id in $(get_workspace_packages); do
+ local pkg_name
+ pkg_name=$(get_package_name "$pkg_id")
+
+ for variant in $(get_package_variants "$pkg_id"); do
+ echo "Checking $pkg_name ($variant)..."
+
+ # Convert cargo args to cargo-semver-checks equivalents.
+ local -a features_args=()
+ case "$variant" in
+ all-features)
+ features_args=("--all-features")
+ ;;
+ no-default-features)
+ features_args=("--only-explicit-features")
+ ;;
+ alloc)
+ features_args=("--only-explicit-features" "--current-features" "alloc")
+ ;;
+ esac
+
+ if ! cargo semver-checks --quiet \
+ -p "$pkg_name" \
+ --release-type minor \
+ --baseline-root "$baseline_dir" \
+ "${features_args[@]}"; then
+ echo "Breaking changes found in $pkg_name ($variant)"
+ has_breaks=true
+
+ if is_stabilized_package "$pkg_id"; then
+ has_hard_fails=true
+ fi
+ fi
+ done
+ done
+
+ if [ "$has_hard_fails" = true ]; then
+ echo "ERROR: Semver break detected in stable package(s)"
+ return 1
+ fi
+
+ if [ "$has_breaks" = true ]; then
+ echo "ERROR: Semver breaks detected in unstable package(s)"
+ return 2
+ fi
+
+ return 0
+}
+
+# Main entry point.
+#
+# Exit Codes:
+# 0 - Success, all checks passed.
+# 1 - Failure, non-additive features or stable semver breaks detected.
+# 2 - Failure, unstable semver breaks detected.
+main() {
+ check_non_additive_features && check_semver_breaks
+}
+
+main
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.