From b5180732e6e8bff24c9a49c1c186897b1d718c17 Mon Sep 17 00:00:00 2001 From: Jose Storopoli Date: Wed, 10 Jul 2024 16:10:41 +0000 Subject: [PATCH 1/5] io: add not_unwind_safe as PhantomData --- io/src/error.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/io/src/error.rs b/io/src/error.rs index e88d6f4ff..bdc00288b 100644 --- a/io/src/error.rs +++ b/io/src/error.rs @@ -6,6 +6,10 @@ use core::fmt; #[derive(Debug)] pub struct Error { kind: ErrorKind, + /// Indicates that the `struct` can pretend to own a mutable static reference + /// and an [`UnsafeCell`](core::cell::UnsafeCell), which are not unwind safe. + /// This is so that it does not introduce non-additive cargo features. + _not_unwind_safe: core::marker::PhantomData<(&'static mut (), core::cell::UnsafeCell<()>)>, #[cfg(feature = "std")] error: Option>, @@ -20,13 +24,13 @@ impl Error { where E: Into>, { - Self { kind, error: Some(error.into()) } + Self { kind, _not_unwind_safe: core::marker::PhantomData, error: Some(error.into()) } } /// Creates a new I/O error. #[cfg(all(feature = "alloc", not(feature = "std")))] pub fn new(kind: ErrorKind, error: E) -> Error { - Self { kind, error: Some(error.into()) } + Self { kind, _not_unwind_safe: core::marker::PhantomData, error: Some(error.into()) } } /// Returns the error kind for this error. @@ -49,6 +53,7 @@ impl From for Error { fn from(kind: ErrorKind) -> Error { Self { kind, + _not_unwind_safe: core::marker::PhantomData, #[cfg(any(feature = "std", feature = "alloc"))] error: None, } @@ -89,7 +94,11 @@ impl std::error::Error for Error { #[cfg(feature = "std")] impl From for Error { fn from(o: std::io::Error) -> Error { - Self { kind: ErrorKind::from_std(o.kind()), error: o.into_inner() } + Self { + kind: ErrorKind::from_std(o.kind()), + _not_unwind_safe: core::marker::PhantomData, + error: o.into_inner(), + } } } From 1948995443d9b26f628cf4b228d1c69a82252f2f Mon Sep 17 00:00:00 2001 From: Jose Storopoli Date: Wed, 10 Jul 2024 14:22:36 +0000 Subject: [PATCH 2/5] ci: semver-check for non-additive cargo features Co-authored-by: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com> --- .github/workflows/semver-checks.yml | 24 +++- contrib/check-semver-feature.sh | 136 ++++++++++++++++++ .../{check-semver.sh => check-semver-pr.sh} | 0 3 files changed, 157 insertions(+), 3 deletions(-) create mode 100755 contrib/check-semver-feature.sh rename contrib/{check-semver.sh => check-semver-pr.sh} (100%) diff --git a/.github/workflows/semver-checks.yml b/.github/workflows/semver-checks.yml index eb89381c8..166200585 100644 --- a/.github/workflows/semver-checks.yml +++ b/.github/workflows/semver-checks.yml @@ -4,8 +4,8 @@ on: # yamllint disable-line rule:truthy name: Check semver breaks jobs: - API: - name: API - stable toolchain + PR: + name: PR Semver - nightly toolchain runs-on: ubuntu-latest strategy: fail-fast: false @@ -21,7 +21,7 @@ jobs: - name: "Binstall cargo-semver-checks" run: cargo binstall cargo-semver-checks --no-confirm - name: "Run semver checker script" - run: ./contrib/check-semver.sh + run: ./contrib/check-semver-pr.sh - name: Save PR number if: ${{ hashFiles('semver-break') != '' }} env: @@ -39,3 +39,21 @@ jobs: with: name: semver-break path: semver-break + + Feature: + name: Non additive cargo features - stable toolchain + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: "Checkout repo" + uses: actions/checkout@v4 + - name: "Install Rustup" + uses: dtolnay/rust-toolchain@stable + - name: "Install cargo-binstall" + uses: cargo-bins/cargo-binstall@main + - name: "Binstall cargo-semver-checks" + run: cargo binstall cargo-semver-checks --no-confirm + - name: "Run semver checker script" + run: ./contrib/check-semver-feature.sh + diff --git a/contrib/check-semver-feature.sh b/contrib/check-semver-feature.sh new file mode 100755 index 000000000..1bdf8ad1f --- /dev/null +++ b/contrib/check-semver-feature.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +# +# Checks semver compatibility between the `--no-features` and `all-features`. +# This is important since it tests for the presence 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-addivite 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" + # 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 \ No newline at end of file diff --git a/contrib/check-semver.sh b/contrib/check-semver-pr.sh similarity index 100% rename from contrib/check-semver.sh rename to contrib/check-semver-pr.sh From 4edd504cb60397cfe143607ef5db8784598cbd06 Mon Sep 17 00:00:00 2001 From: Jose Storopoli Date: Thu, 11 Jul 2024 08:41:13 +0000 Subject: [PATCH 3/5] ci: pin stable in semver-checks and updates weekly --- .../workflows/cron-weekly-update-stable.yml | 40 +++++++++++++++++++ .github/workflows/semver-checks.yml | 8 +++- .github/workflows/stable-version | 1 + 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/cron-weekly-update-stable.yml create mode 100644 .github/workflows/stable-version diff --git a/.github/workflows/cron-weekly-update-stable.yml b/.github/workflows/cron-weekly-update-stable.yml new file mode 100644 index 000000000..df6dfd487 --- /dev/null +++ b/.github/workflows/cron-weekly-update-stable.yml @@ -0,0 +1,40 @@ +name: Update Stable rustc +on: + schedule: + - cron: "0 0 * * 5" # runs every Friday at 00:00 (generally rust releases on Thursday) + workflow_dispatch: # allows manual triggering +jobs: + format: + name: Update stable rustc + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Update semver-checks.yml to use latest stable + run: | + set -x + # Extract the version of the compiler dtolnay/rust-toolchain gives us. + STABLE_VERSION=$(rustc --version | sed -ne 's/^rustc //p' | cut -d ' ' -f1) + # Update the stable version in the reference file. + echo "${STABLE_VERSION}" > ./.github/workflows/stable-version + echo "stable_version=${STABLE_VERSION}" >> $GITHUB_ENV + # If somehow the stable version has not changed. In this case don't make an empty PR. + if ! git diff --exit-code > /dev/null; then + echo "Updated stable. Opening PR." + echo "changes_made=true" >> $GITHUB_ENV + else + echo "Attempted to update stable but the latest-stable date did not change. Not opening any PR." + echo "changes_made=false" >> $GITHUB_ENV + fi + - name: Create Pull Request + if: env.changes_made == 'true' + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.APOELSTRA_CREATE_PR_TOKEN }} + author: Update Stable Rustc Bot + committer: Update Stable Rustc Bot + title: Automated weekly update to rustc stable (to ${{ env.stable_version }}) + body: | + Automated update to Github CI workflow `semver-checks.yml` by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action + commit-message: Automated update to Github CI to rustc stable-${{ env.stable_version }} + branch: create-pull-request/weekly-stable-update diff --git a/.github/workflows/semver-checks.yml b/.github/workflows/semver-checks.yml index 166200585..b0eff52fe 100644 --- a/.github/workflows/semver-checks.yml +++ b/.github/workflows/semver-checks.yml @@ -5,7 +5,7 @@ name: Check semver breaks jobs: PR: - name: PR Semver - nightly toolchain + name: PR Semver - stable toolchain runs-on: ubuntu-latest strategy: fail-fast: false @@ -16,6 +16,9 @@ jobs: fetch-depth: 0 # we need full history for cargo semver-checks - name: "Install Rustup" uses: dtolnay/rust-toolchain@stable + - name: "Select stable-version" + run: | + rustup default $(cat ./.github/workflows/stable-version) - name: "Install cargo-binstall" uses: cargo-bins/cargo-binstall@main - name: "Binstall cargo-semver-checks" @@ -50,6 +53,9 @@ jobs: uses: actions/checkout@v4 - name: "Install Rustup" uses: dtolnay/rust-toolchain@stable + - name: "Select stable-version" + run: | + rustup default $(cat ./.github/workflows/stable-version) - name: "Install cargo-binstall" uses: cargo-bins/cargo-binstall@main - name: "Binstall cargo-semver-checks" diff --git a/.github/workflows/stable-version b/.github/workflows/stable-version new file mode 100644 index 000000000..b3a8c61e6 --- /dev/null +++ b/.github/workflows/stable-version @@ -0,0 +1 @@ +1.79.0 From 8a91015769f37938272ec5bd4aedc6ad004171a2 Mon Sep 17 00:00:00 2001 From: Jose Storopoli Date: Wed, 17 Jul 2024 17:40:07 -0300 Subject: [PATCH 4/5] ci: harden zip command with -n from the man unzip: -n stands for never overwrite existing files. If a file already exists, skip the extraction of that file without prompting. --- .github/workflows/semver-checks-pr-label.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/semver-checks-pr-label.yml b/.github/workflows/semver-checks-pr-label.yml index 2cbfffd64..cab9b65f5 100644 --- a/.github/workflows/semver-checks-pr-label.yml +++ b/.github/workflows/semver-checks-pr-label.yml @@ -50,7 +50,7 @@ jobs: fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/semver-break.zip`, Buffer.from(download.data)); - name: "Unzip artifact" if: ${{ hashFiles('semver-break.zip') != '' }} - run: unzip semver-break.zip + run: unzip -n semver-break.zip - name: "Comment and add label on PR - Semver break" uses: actions/github-script@v7 if: ${{ hashFiles('semver-break') != '' }} From f76f68217b3f29cdfe3ed012091e072d64bfd95a Mon Sep 17 00:00:00 2001 From: Jose Storopoli Date: Wed, 17 Jul 2024 18:20:26 -0300 Subject: [PATCH 5/5] ci: pin cargo-semver-checks version and cron job --- .github/workflows/cargo-semver-checks-version | 1 + ...cron-weekly-update-cargo-semver-checks.yml | 40 +++++++++++++++++++ .github/workflows/semver-checks.yml | 8 ++-- 3 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/cargo-semver-checks-version create mode 100644 .github/workflows/cron-weekly-update-cargo-semver-checks.yml diff --git a/.github/workflows/cargo-semver-checks-version b/.github/workflows/cargo-semver-checks-version new file mode 100644 index 000000000..be386c9ed --- /dev/null +++ b/.github/workflows/cargo-semver-checks-version @@ -0,0 +1 @@ +0.33.0 diff --git a/.github/workflows/cron-weekly-update-cargo-semver-checks.yml b/.github/workflows/cron-weekly-update-cargo-semver-checks.yml new file mode 100644 index 000000000..5f18f42c4 --- /dev/null +++ b/.github/workflows/cron-weekly-update-cargo-semver-checks.yml @@ -0,0 +1,40 @@ +name: Update cargo-semver-checks +on: + schedule: + - cron: "0 0 * * 6" # runs every Saturday at 00:00 + workflow_dispatch: # allows manual triggering +jobs: + format: + name: Update cargo-semver-checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Update semver-checks to use latest crates.io published version + run: | + set -x + # Grab the latest version of cargo semver-checks from crates.io + # that is not yanked. + LATEST_VERSION=$(curl --proto '=https' --tlsv1.3 -sSf https://crates.io/api/v1/crates/cargo-semver-checks/versions | jq -r 'first( .versions[] | select(.yanked =- false) ) | .num') + # Update the latest version in the reference file. + echo "${LATEST_VERSION}" > ./.github/workflows/cargo-semver-checks-version + echo "cargo_semver_checks_version=${LATEST_VERSION}" >> $GITHUB_ENV + # If somehow the latest version has not changed. In this case don't make an empty PR. + if ! git diff --exit-code > /dev/null; then + echo "Updated cargo-semver-checks. Opening PR." + echo "changes_made=true" >> $GITHUB_ENV + else + echo "Attempted to update cargo-semver-checks but the crates.io version did not change. Not opening any PR." + echo "changes_made=false" >> $GITHUB_ENV + fi + - name: Create Pull Request + if: env.changes_made == 'true' + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.APOELSTRA_CREATE_PR_TOKEN }} + author: Update cargo-semver-checks Bot + committer: Update cargo-semver-checks Bot + title: Automated weekly update to cargo-semver-checks (to ${{ env.cargo_semver_checks_version }}) + body: | + Automated update to Github CI workflow `semver-checks.yml` by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action + commit-message: Automated update to Github CI to cargo-semver-checks version-${{ env.cargo_semver_checks_version }} + branch: create-pull-request/weekly-cargo-semver-checks-update diff --git a/.github/workflows/semver-checks.yml b/.github/workflows/semver-checks.yml index b0eff52fe..93f37d269 100644 --- a/.github/workflows/semver-checks.yml +++ b/.github/workflows/semver-checks.yml @@ -21,8 +21,8 @@ jobs: rustup default $(cat ./.github/workflows/stable-version) - name: "Install cargo-binstall" uses: cargo-bins/cargo-binstall@main - - name: "Binstall cargo-semver-checks" - run: cargo binstall cargo-semver-checks --no-confirm + - 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 @@ -58,8 +58,8 @@ jobs: rustup default $(cat ./.github/workflows/stable-version) - name: "Install cargo-binstall" uses: cargo-bins/cargo-binstall@main - - name: "Binstall cargo-semver-checks" - run: cargo binstall cargo-semver-checks --no-confirm + - 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