diff --git a/.github/workflows/ci-rs.yml b/.github/workflows/ci-rs.yml new file mode 100644 index 0000000..448a281 --- /dev/null +++ b/.github/workflows/ci-rs.yml @@ -0,0 +1,216 @@ +name: Continuous integration 🦀 + +on: + push: + branches: + - main + pull_request: + branches: + - main + merge_group: + types: [checks_requested] + workflow_dispatch: {} + +env: + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "--cfg=ci_run" + MIRIFLAGS: '-Zmiri-permissive-provenance' # Required due to warnings in bitvec 1.0.1 + CI: true # insta snapshots behave differently on ci + SCCACHE_GHA_ENABLED: "true" + RUSTC_WRAPPER: "sccache" + HUGR_TEST_SCHEMA: "1" + +jobs: + # Check if changes were made to the relevant files. + # Always returns true if running on the default branch, to ensure all changes are throughly checked. + changes: + name: Check for changes in Rust files + runs-on: ubuntu-latest + # Required permissions + permissions: + pull-requests: read + # Set job outputs to values from filter step + outputs: + rust: ${{ github.ref_name == github.event.repository.default_branch || steps.filter.outputs.rust }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: .github/change-filters.yml + + check: + needs: changes + if: ${{ needs.changes.outputs.rust == 'true' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: mozilla-actions/sccache-action@v0.0.4 + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + - install-llvm: + run: "sudo apt-get update && sudo apt-get install -y llvm-14" + - name: Check formatting + run: cargo fmt -- --check + - name: Run clippy + run: cargo clippy --all-targets --all-features --workspace -- -D warnings + - name: Build docs + run: cargo doc --no-deps --all-features --workspace + env: + RUSTDOCFLAGS: "-Dwarnings" + + + benches: + name: Build benchmarks 🏋️ + needs: changes + # if: ${{ needs.changes.outputs.rust == 'true' && github.event_name != 'merge_group' }} + if: false # TODO + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: mozilla-actions/sccache-action@v0.0.4 + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable + - name: Build benchmarks with no features + run: cargo bench --verbose --no-run --workspace --no-default-features + - name: Build benchmarks with all features + run: cargo bench --verbose --no-run --workspace --all-features + + # Run tests on Rust stable + tests-stable-no-features: + needs: changes + if: ${{ needs.changes.outputs.rust == 'true' }} + runs-on: ubuntu-latest + name: tests (Rust stable, no features) + steps: + - uses: actions/checkout@v4 + - uses: mozilla-actions/sccache-action@v0.0.4 + - install-llvm: + run: "sudo apt-get update && sudo apt-get install -y llvm-14" + - id: toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: 'stable' + - name: Configure default rust toolchain + run: rustup override set ${{steps.toolchain.outputs.name}} + - name: Build with no features + run: cargo test --verbose --workspace --no-default-features --no-run + - name: Tests with no features + run: cargo test --verbose --workspace --no-default-features + + # Run tests on Rust stable + tests-stable-all-features: + needs: changes + if: ${{ needs.changes.outputs.rust == 'true' }} + runs-on: ubuntu-latest + name: tests (Rust stable, all features) + steps: + - uses: actions/checkout@v4 + - uses: mozilla-actions/sccache-action@v0.0.4 + - install-llvm: + run: "sudo apt-get update && sudo apt-get install -y llvm-14" + - id: toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: 'stable' + - name: Configure default rust toolchain + run: rustup override set ${{steps.toolchain.outputs.name}} + - name: Build with all features + run: cargo test --verbose --workspace --all-features --no-run + - name: Tests with all features + run: cargo test --verbose --workspace --all-features + + # Run tests on other toolchains + tests-other: + needs: changes + if: ${{ needs.changes.outputs.rust == 'true' && github.event_name != 'merge_group' }} + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + # Pinned nightly version until this gets resolved: + # https://github.com/rust-lang/rust/issues/125474 + rust: ['1.75', beta, 'nightly-2024-05-22'] + name: tests (Rust ${{ matrix.rust }}) + steps: + - uses: actions/checkout@v4 + - uses: mozilla-actions/sccache-action@v0.0.4 + - install-llvm: + run: "sudo apt-get update && sudo apt-get install -y llvm-14" + - id: toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - name: Configure default rust toolchain + run: rustup override set ${{steps.toolchain.outputs.name}} + - name: Build with no features + run: cargo test --verbose --workspace --no-default-features --no-run + - name: Tests with no features + run: cargo test --verbose --workspace --no-default-features + - name: Build with all features + run: cargo test --verbose --workspace --all-features --no-run + - name: Tests with all features + run: cargo test --verbose --workspace --all-features + + # This is a meta job to mark successful completion of the required checks, + # even if they are skipped due to no changes in the relevant files. + required-checks: + name: Required checks 🦀 + needs: [changes, check, tests-stable-no-features, tests-stable-all-features] + if: ${{ !cancelled() }} + runs-on: ubuntu-latest + steps: + - name: Fail if required checks failed + # This condition should simply be `if: failure() || cancelled()`, + # but there seems to be a bug in the github workflow runner. + # + # See https://github.com/orgs/community/discussions/80788 + if: | + needs.changes.result == 'failure' || needs.changes.result == 'cancelled' || + needs.check.result == 'failure' || needs.check.result == 'cancelled' || + needs.tests-stable-no-features.result == 'failure' || needs.tests-stable-no-features.result == 'cancelled' || + needs.tests-stable-all-features.result == 'failure' || needs.tests-stable-all-features.result == 'cancelled' + run: | + echo "Required checks failed" + echo "Please check the logs for more information" + exit 1 + - name: Pass if required checks passed + run: | + echo "All required checks passed" + + coverage: + needs: [changes, tests-stable-no-features, tests-stable-all-features, tests-other, check] + # Run only if there are changes in the relevant files and the check job passed or was skipped + # if: always() && !failure() && !cancelled() && needs.changes.outputs.rust == 'true' && github.event_name != 'merge_group' + if: false # todo + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: mozilla-actions/sccache-action@v0.0.4 + - uses: dtolnay/rust-toolchain@master + with: + # Nightly is required to count doctests coverage + # + # Pinned nightly version until this gets resolved + # https://github.com/rust-lang/rust/issues/125474 + toolchain: 'nightly-2024-05-22' + components: llvm-tools-preview + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + - name: Run tests with coverage instrumentation + run: | + cargo llvm-cov clean --workspace + cargo llvm-cov --no-report --workspace --no-default-features --doctests + cargo llvm-cov --no-report --workspace --all-features --doctests + - name: Generate coverage report + run: cargo llvm-cov --all-features report --codecov --output-path coverage.json + - name: Upload coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + files: coverage.json + name: rust + flags: rust + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/drop-cache.yml b/.github/workflows/drop-cache.yml new file mode 100644 index 0000000..f550ba4 --- /dev/null +++ b/.github/workflows/drop-cache.yml @@ -0,0 +1,33 @@ +name: cleanup caches by a branch +on: + pull_request: + types: + - closed + +jobs: + cleanup: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Cleanup + run: | + gh extension install actions/gh-actions-cache + + REPO=${{ github.repository }} + BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge" + + echo "Fetching list of cache key" + cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 ) + + ## Setting this to not fail the workflow while deleting cache keys. + set +e + echo "Deleting caches..." + for cacheKey in $cacheKeysForPR + do + gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm + done + echo "Done" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml new file mode 100644 index 0000000..d33312f --- /dev/null +++ b/.github/workflows/pr-title.yml @@ -0,0 +1,163 @@ +name: Check Conventional Commits format + +on: + pull_request_target: + branches: + - main + types: + - opened + - edited + - synchronize + - labeled + - unlabeled + merge_group: + types: [checks_requested] + +permissions: + pull-requests: write + +jobs: + main: + name: Validate Conventional Commit PR title + runs-on: ubuntu-latest + # The action does not support running on merge_group events, + # but if the check succeeds in the PR there is no need to check it again. + if: github.event_name == 'pull_request_target' + outputs: + # Whether the PR title indicates a breaking change. + breaking: ${{ steps.breaking.outputs.breaking }} + # Whether the PR body contains a "BREAKING CHANGE:" footer describing the breaking change. + has_breaking_footer: ${{ steps.breaking.outputs.has_breaking_footer }} + steps: + - name: Validate the PR title format + uses: amannn/action-semantic-pull-request@v5 + id: lint_pr_title + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Configure which types are allowed (newline-delimited). + # Default: https://github.com/commitizen/conventional-commit-types + types: | + feat + fix + docs + style + refactor + perf + test + ci + chore + revert + # Configure which scopes are allowed (newline-delimited). + # These are regex patterns auto-wrapped in `^ $`. + #scopes: | + # .* + # Configure that a scope must always be provided. + requireScope: false + # Configure which scopes are disallowed in PR titles (newline-delimited). + # For instance by setting the value below, `chore(release): ...` (lowercase) + # and `ci(e2e,release): ...` (unknown scope) will be rejected. + # These are regex patterns auto-wrapped in `^ $`. + #disallowScopes: | + # release + # [A-Z]+ + # Configure additional validation for the subject based on a regex. + # This example ensures the subject doesn't start with an uppercase character. + #subjectPattern: ^(?![A-Z]).+$ + # If `subjectPattern` is configured, you can use this property to override + # the default error message that is shown when the pattern doesn't match. + # The variables `subject` and `title` can be used within the message. + #subjectPatternError: | + # The subject "{subject}" found in the pull request title "{title}" + # didn't match the configured pattern. Please ensure that the subject + # doesn't start with an uppercase character. + # If the PR contains one of these newline-delimited labels, the + # validation is skipped. If you want to rerun the validation when + # labels change, you might want to use the `labeled` and `unlabeled` + # event triggers in your workflow. + ignoreLabels: | + ignore-semantic-pull-request + + # `action-semantic-pull-request` does not parse the title, so it cannot + # detect if it is marked as a breaking change. + # + # Since at this point we know the PR title is a valid conventional commit, + # we can use a simple regex that looks for a '!:' sequence. It could be + # more complex, but we don't care about false positives. + - name: Check for breaking change flag + id: breaking + run: | + if [[ "${PR_TITLE}" =~ ^.*\!:.*$ ]]; then + echo "breaking=true" >> $GITHUB_OUTPUT + else + echo "breaking=false" >> $GITHUB_OUTPUT + fi + + # Check if the PR comment has a "BREAKING CHANGE:" footer describing + # the breaking change. + if [[ "${PR_BODY}" != *"BREAKING CHANGE:"* ]]; then + echo "has_breaking_footer=false" >> $GITHUB_OUTPUT + else + echo "has_breaking_footer=true" >> $GITHUB_OUTPUT + fi + env: + PR_TITLE: ${{ github.event.pull_request.title }} + PR_BODY: ${{ github.event.pull_request.body }} + + # Post a help comment if the PR title indicates a breaking change but does + # not contain a "BREAKING CHANGE:" footer. + - name: Require "BREAKING CHANGE:" footer for breaking changes + id: breaking-comment + if: ${{ steps.breaking.outputs.breaking == 'true' && steps.breaking.outputs.has_breaking_footer == 'false' }} + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: pr-title-lint-error + message: | + Hey there and thank you for opening this pull request! 👋🏼 + + It looks like your proposed title indicates a breaking change. If that's the case, + please make sure to include a "BREAKING CHANGE:" footer in the body of the pull request + describing the breaking change and any migration instructions. + GITHUB_TOKEN: ${{ secrets.HUGRBOT_PAT }} + - name: Fail if the footer is required but missing + if: ${{ steps.breaking.outputs.breaking == 'true' && steps.breaking.outputs.has_breaking_footer == 'false' }} + run: exit 1 + + - name: Post a comment if the PR badly formatted + uses: marocchino/sticky-pull-request-comment@v2 + # When the previous steps fails, the workflow would stop. By adding this + # condition you can continue the execution with the populated error message. + if: always() && (steps.lint_pr_title.outputs.error_message != null) + with: + header: pr-title-lint-error + message: | + Hey there and thank you for opening this pull request! 👋🏼 + + We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) + and it looks like your proposed title needs to be adjusted. + + Your title should look like this. The scope field is optional. + ``` + (): + ``` + + If the PR includes a breaking change, mark it with an exclamation mark: + ``` + !: + ``` + and include a "BREAKING CHANGE:" footer in the body of the pull request. + + Details: + ``` + ${{ steps.lint_pr_title.outputs.error_message }} + ``` + GITHUB_TOKEN: ${{ secrets.HUGRBOT_PAT }} + + # Delete previous comments when the issues have been resolved + # This step doesn't run if any of the previous checks fails. + - name: Delete previous comments + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: pr-title-lint-error + delete: true + GITHUB_TOKEN: ${{ secrets.HUGRBOT_PAT }} diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml new file mode 100644 index 0000000..e48f285 --- /dev/null +++ b/.github/workflows/release-plz.yml @@ -0,0 +1,29 @@ +# Automatic changelog, version bumping, and semver-checks with release-plz for rust projects +name: Release-plz 🦀 + +permissions: + pull-requests: write + contents: write + +on: + push: + branches: + - main + +jobs: + release-plz: + name: Release-plz + if: false # TODO + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + - name: Run release-plz + uses: MarcoIeni/release-plz-action@v0.5 + env: + GITHUB_TOKEN: ${{ secrets.HUGRBOT_PAT }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/.github/workflows/unsoundness.yml b/.github/workflows/unsoundness.yml new file mode 100644 index 0000000..96fd1e8 --- /dev/null +++ b/.github/workflows/unsoundness.yml @@ -0,0 +1,40 @@ +name: Unsoundness checks + +on: + push: + branches: + - main + workflow_dispatch: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "--cfg=ci_run" + # Permissive provenance is required due to warnings in bitvec 1.0.1 + # Proptest flags required to fix https://github.com/proptest-rs/proptest/issues/253 + MIRIFLAGS: '-Zmiri-permissive-provenance -Zmiri-env-forward=PROPTEST_DISABLE_FAILURE_PERSISTENCE' + PROPTEST_DISABLE_FAILURE_PERSISTENCE: true + +jobs: + + miri: + name: "Miri" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - install-llvm: + run: "sudo apt-get update && sudo apt-get install -y llvm-14" + - name: Install Miri + run: | + rustup toolchain install nightly --component miri + rustup override set nightly + cargo miri setup + - uses: Swatinem/rust-cache@v2 + with: + prefix-key: v0-miri + - name: Test with Miri + run: cargo miri test