1+ #! /usr/bin/env bash
2+ #
3+ # Checks semver compatibility between the current and target branches.
4+ # Under the hood uses cargo semver-checks to check for breaking changes.
5+ # We cannot use it directly since it only supports checking against published
6+ # crates.
7+ # That's the intended use case for cargo semver-checks:
8+ # you run before publishing a new version of a crate to check semver breaks.
9+ # Here we are hacking it by first generating JSON files from cargo doc
10+ # and then using those files to check for breaking changes with
11+ # cargo semver-checks.
12+
13+ set -euo pipefail
14+
15+ # Our nightly version.
16+ NIGHTLY=$( cat nightly-version)
17+
18+ # These are the hardcoded flags that cargo semver-checks uses
19+ # under the hood to invoke rustdoc.
20+ RUSTDOCFLAGS=" -Z unstable-options --document-private-items --document-hidden-items --output-format=json --cap-lints=allow"
21+
22+ # These will be set to the commit SHA from the PR's target branch
23+ # GitHub Actions CI.
24+ # NOTE: if running locally this will be set to master.
25+ if [ -n " ${GITHUB_BASE_REF+x} " ]; then
26+ TARGET_COMMIT=$GITHUB_BASE_REF # running on CI
27+ else
28+ TARGET_COMMIT=$( git rev-parse master) # running locally
29+ fi
30+
31+ main () {
32+ # we need cargo nightly to generate the JSON files from cargo doc.
33+ need_nightly
34+
35+ # On current commit:
36+ # 1. bitcoin: all-features and no-default-features.
37+ generate_json_files_all_features " bitcoin" " current"
38+ generate_json_files_no_default_features " bitcoin" " current"
39+
40+ # 2. base58ck: all-features and no-default-features.
41+ generate_json_files_all_features " base58ck" " current"
42+ generate_json_files_no_default_features " base58ck" " current"
43+
44+ # 3. bitcoin_hashes: no-default-features and alloc feature.
45+ generate_json_files_no_default_features " bitcoin_hashes" " current"
46+ generate_json_files_features_alloc " bitcoin_hashes" " current"
47+
48+ # 4. bitcoin-units: no-default-features and alloc feature.
49+ generate_json_files_no_default_features " bitcoin-units" " current"
50+ generate_json_files_features_alloc " bitcoin-units" " current"
51+
52+ # 5. bitcoin-io: no-default-features and alloc feature.
53+ generate_json_files_no_default_features " bitcoin-io" " current"
54+ generate_json_files_features_alloc " bitcoin-io" " current"
55+
56+
57+ # Switch to target commit.
58+ echo " Checking out target commit at $TARGET_COMMIT "
59+ git checkout " $TARGET_COMMIT "
60+
61+ # On target commit:
62+ # 1. bitcoin: all-features and no-default-features.
63+ generate_json_files_all_features " bitcoin" " master"
64+ generate_json_files_no_default_features " bitcoin" " master"
65+
66+ # 2. base58ck: all-features and no-default-features.
67+ generate_json_files_all_features " base58ck" " master"
68+ generate_json_files_no_default_features " base58ck" " master"
69+
70+ # 3. bitcoin_hashes: no-default-features and alloc feature.
71+ generate_json_files_no_default_features " bitcoin_hashes" " master"
72+ generate_json_files_features_alloc " bitcoin_hashes" " master"
73+
74+ # 4. bitcoin-units: no-default-features and alloc feature.
75+ generate_json_files_no_default_features " bitcoin-units" " master"
76+ generate_json_files_features_alloc " bitcoin-units" " master"
77+
78+ # 5. bitcoin-io: no-default-features and alloc feature.
79+ generate_json_files_no_default_features " bitcoin-io" " master"
80+ generate_json_files_features_alloc " bitcoin-io" " master"
81+
82+ # Check for API semver breaks on all the generated JSON files above.
83+ run_cargo_semver_check " bitcoin" " all-features"
84+ run_cargo_semver_check " bitcoin" " no-default-features"
85+ run_cargo_semver_check " base58ck" " all-features"
86+ run_cargo_semver_check " base58ck" " no-default-features"
87+ run_cargo_semver_check " bitcoin_hashes" " no-default-features"
88+ run_cargo_semver_check " bitcoin_hashes" " alloc"
89+ run_cargo_semver_check " bitcoin-units" " no-default-features"
90+ run_cargo_semver_check " bitcoin-units" " alloc"
91+ run_cargo_semver_check " bitcoin-io" " no-default-features"
92+ run_cargo_semver_check " bitcoin-io" " alloc"
93+
94+ # Invoke cargo semver-checks to check for breaking changes
95+ # in all generated files.
96+ check_for_breaking_changes
97+ }
98+
99+ # Run cargo doc with the cargo semver-checks rustdoc flags.
100+ # We don't care about dependencies.
101+ run_cargo_doc () {
102+ RUSTDOCFLAGS=" $RUSTDOCFLAGS " cargo +" $NIGHTLY " doc --no-deps " $@ "
103+ }
104+
105+ # Run cargo semver-check
106+ run_cargo_semver_check () {
107+ local crate=" $1 "
108+ local variant=" $2 "
109+
110+ echo " Running cargo semver-checks for $crate $variant "
111+ # Hack to not fail on errors.
112+ # This is necessary since cargo semver-checks will fail if the
113+ # semver check fails.
114+ # We check that manually later.
115+ set +e
116+ cargo +" $NIGHTLY " semver-checks -v --baseline-rustdoc " $crate -master-$variant .json" --current-rustdoc " $crate -current-$variant .json" > " $crate -$variant -semver.txt" 2>&1
117+ set -e
118+ }
119+
120+ # The following function uses cargo doc to generate JSON files that
121+ # cargo semver-checks can use.
122+ # - no-default-features: generate JSON doc files with no default features.
123+ generate_json_files_no_default_features () {
124+ local crate=" $1 "
125+ local version=" $2 "
126+
127+ echo " Running cargo doc no-default-features for $crate $version "
128+ run_cargo_doc --no-default-features -p " $crate "
129+
130+ # replace _ for - in crate name.
131+ # This is necessary since some crates have - in their name
132+ # which will be converted to _ in the output file by cargo doc.
133+ mv " target/doc/${crate// -/ _} .json" " $crate -$version -no-default-features.json"
134+ }
135+ # - all-features: generate JSON doc files with all features.
136+ generate_json_files_all_features () {
137+ local crate=" $1 "
138+ local version=" $2 "
139+
140+ echo " Running cargo doc all-features for $crate $version "
141+ run_cargo_doc --all-features -p " $crate "
142+
143+ # replace _ for - in crate name.
144+ # This is necessary since some crates have - in their name
145+ # which will be converted to _ in the output file by cargo doc.
146+ mv -v " target/doc/${crate// -/ _} .json" " $crate -$version -all-features.json"
147+ }
148+ # - alloc: generate JSON doc files with the alloc feature.
149+ generate_json_files_features_alloc () {
150+ local crate=" $1 "
151+ local version=" $2 "
152+
153+ echo " Running cargo doc --features alloc for $crate $version "
154+ run_cargo_doc --no-default-features --features alloc -p " $crate "
155+
156+ # replace _ for - in crate name.
157+ # This is necessary since some crates have - in their name
158+ # which will be converted to _ in the output file by cargo doc.
159+ mv -v " target/doc/${crate// -/ _} .json" " $crate -$version -alloc.json"
160+ }
161+
162+ # Check if there are breaking changes.
163+ # We loop through all the generated files and check if there is a FAIL
164+ # in the cargo semver-checks output.
165+ # If we detect a fail, we create an empty file semver-break.
166+ # If the following CI step finds this file, it will add:
167+ # 1. a comment on the PR.
168+ # 2. a label to the PR.
169+ check_for_breaking_changes () {
170+ for file in * semver.txt; do
171+ echo " Checking $file "
172+ if grep -q " FAIL" " $file " ; then
173+ echo " You have introduced changes to the public API"
174+ echo " FAIL found in $file "
175+ # flag it as a breaking change
176+ # Handle the case where FAIL is found
177+ touch semver-break
178+ fi
179+ done
180+ if ! [ -f semver-break ]; then
181+ echo " No breaking changes found"
182+ fi
183+ }
184+
185+ # Safekeeping: check if we have a nightly compiler.
186+ need_nightly () {
187+ cargo_ver=$( cargo +" $NIGHTLY " --version)
188+ if echo " $cargo_ver " | grep -q -v nightly; then
189+ err " Need a nightly compiler; have $cargo_ver "
190+ fi
191+ }
192+
193+ err () {
194+ echo " $1 " >&2
195+ exit 1
196+ }
197+
198+ #
199+ # Main script
200+ #
201+ main " $@ "
202+ exit 0
0 commit comments