Add a script for checking bitcoin re-exports primitives stuff
What changed, and why it matters
This commit adds a new development script and a corresponding CI step that automatically checks whether the `bitcoin` crate correctly re-exports public items from the `bitcoin_primitives` crate. It is purely a testing/quality-assurance change and does not modify any library code, user-facing behavior, or security-sensitive logic.
No security action required. This is a routine CI/test infrastructure improvement. Reviewers may verify the script correctly maps `bitcoin_primitives::` to `bitcoin::` and that the CI step runs as expected.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change introduces contrib/generate-bitcoin-re-export-test.sh, which parses api/primitives/all-features.txt and generates bitcoin/tests/check-re-exports.rs. The generated Rust test contains use statements for every public enum, struct, and module from bitcoin_primitives, mapped to the bitcoin crate path. If any re-export is missing, the test fails to compile. The GitHub Actions workflow is updated to run this script and then execute cargo test --all-features inside the bitcoin crate. The script is a duplicate of the existing primitives re-export checker, as noted in the commit message.
Changed components
.github/workflows/rust.ymlcontrib/generate-bitcoin-re-export-test.shInspect captured patch +173 / −1
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 5016ba16..2bb64cc1 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -434,4 +434,6 @@ jobs:
- name: "Select toolchain"
uses: dtolnay/rust-toolchain@5d458579430fc14a04a08a1e7d3694f545e91ce6 # stable
- name: "Run primitives API checker script"
- run: contrib/generate-primitives-re-export-test.sh && cd ./primitives && cargo test --all-features
+ run: |
+ contrib/generate-primitives-re-export-test.sh && (cd ./primitives && cargo test --all-features)
+ contrib/generate-bitcoin-re-export-test.sh && (cd ./bitcoin && cargo test --all-features)
diff --git a/contrib/generate-bitcoin-re-export-test.sh b/contrib/generate-bitcoin-re-export-test.sh
new file mode 100755
index 00000000..1314d93d
--- /dev/null
+++ b/contrib/generate-bitcoin-re-export-test.sh
@@ -0,0 +1,170 @@
+#!/usr/bin/env bash
+#
+# Script for generating a Rust test file that verifies all bitcoin_primitives
+# items are re-exported in the `bitcoin` crate.
+#
+# The script parses api/primitives/all-features.txt and generates use statements
+# that will fail to compile if any re-exports are missing.
+
+set -euo pipefail
+
+api_file="./api/primitives/all-features.txt"
+output_file="./bitcoin/tests/check-re-exports.rs"
+
+usage() {
+ cat <<EOF
+Usage:
+
+ ./generate-bitcoin-re-export-test.sh
+
+DESCRIPTION
+ Generates a Rust test file that verifies all public types and modules from
+ bitcoin_primitives are re-exported in the bitcoin crate;
+
+ The script parses api/primitives/all-features.txt and creates use statements for
+ every 'pub enum', 'pub struct', and 'pub mod' declaration.
+
+ Output file: bitcoin/tests/check-re-exports.rs
+EOF
+}
+
+main() {
+ while [[ $# -gt 0 ]]; do
+ case $1 in
+ -h|--help)
+ usage
+ exit 0
+ ;;
+ *)
+ say_err "unknown option: $1"
+ usage
+ exit 1
+ ;;
+ esac
+ done
+
+ check_required_commands
+ check_required_files
+
+ say "Parsing $api_file and generating Rust test..."
+
+ generate_test_file
+
+ say "Generated $output_file"
+ say "Run 'cd bitcoin && cargo test --all-features check_all_bitcoin_primitives_items_are_reexported' to test"
+}
+
+generate_test_file() {
+ local temp_file;
+ temp_file=$(mktemp)
+
+ # Generate the file header
+ cat > "$temp_file" <<'EOF'
+// SPDX-License-Identifier: CC0-1.0
+
+//! Test that all public types and modules from bitcoin_primitives are re-exported in the bitcoin crate.
+//!
+//! This test is automatically generated by contrib/generate-bitcoin-re-export-test.sh
+//! Any compilation error indicates a missing re-export.
+
+#![allow(dead_code)]
+#![allow(unused_imports)]
+// No benefit in running this test without features enabled.
+#[cfg(not(feature = "arbitrary"))]
+compile_error!("arbitrary feature needs to be enabled");
+
+#[test]
+fn check_all_bitcoin_primitives_items_are_reexported() {
+ // This test will fail to compile if any bitcoin_primitives item is not re-exported in the bitcoin crate.
+
+EOF
+
+ # Extract and convert all pub items
+ local use_statements=()
+ local seen_items=()
+
+ while IFS= read -r line; do
+ local path=""
+
+ # Extract pub enum
+ if [[ "$line" =~ pub\ enum\ (bitcoin_primitives::[^[:space:]]+) ]]; then
+ path="${BASH_REMATCH[1]}"
+ # Extract pub struct
+ elif [[ "$line" =~ pub\ struct\ (bitcoin_primitives::[^[:space:]\(]+) ]]; then
+ path="${BASH_REMATCH[1]}"
+ # Extract pub mod
+ elif [[ "$line" =~ ^pub\ mod\ (bitcoin_primitives::[^[:space:]]+)$ ]]; then
+ path="${BASH_REMATCH[1]}"
+ fi
+
+ if [[ -n "$path" ]]; then
+ # Remove generic type parameters (e.g., <T>)
+ path="${path%%<*}"
+
+ if [[ "$path" == *Encoder* ]] || [[ $path == *Decoder* ]]; then
+ continue
+ fi
+
+ # Convert bitcoin_primitives:: to bitcoin::
+ local bitcoin_path="${path//bitcoin_primitives::/bitcoin::}"
+
+ # Skip if we've already seen this item
+ if [[ " ${seen_items[*]} " != *" $bitcoin_path "* ]]; then
+ seen_items+=("$bitcoin_path")
+ use_statements+=(" use $bitcoin_path as _;")
+ fi
+ fi
+ done < "$api_file"
+
+ # Sort use statements and add to file
+ printf '%s\n' "${use_statements[@]}" | sort >> "$temp_file"
+
+ # Add closing brace
+ echo "}" >> "$temp_file"
+
+ # Move temp file to final location
+ mv "$temp_file" "$output_file"
+}
+
+check_required_files() {
+ if [[ ! -f "$api_file" ]]; then
+ err "Required file not found: $api_file"
+ fi
+
+ local output_dir
+ output_dir=$(dirname "$output_file")
+ if [[ ! -d "$output_dir" ]]; then
+ err "Output directory not found: $output_dir"
+ fi
+}
+
+check_required_commands() {
+ need_cmd grep
+ need_cmd sort
+ need_cmd mktemp
+}
+
+say() {
+ echo "generate-bitcoin-re-export-test: $1"
+}
+
+say_err() {
+ say "$1" >&2
+}
+
+err() {
+ echo "$1" >&2
+ exit 1
+}
+
+need_cmd() {
+ if ! command -v "$1" > /dev/null 2>&1
+ then err "need '$1' (command not found)"
+ fi
+}
+
+#
+# Main script
+#
+main "$@"
+exit 0
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.