From 4bcc4d2125b4aa4bb0b9842fe6dcd157e5f4e961 Mon Sep 17 00:00:00 2001 From: stifskere Date: Sat, 30 Aug 2025 23:54:01 +0200 Subject: [PATCH 1/2] chore(ci/cd): Add GitHub actions and Makefile like the other repos. --- .github/actions/setup/action.yml | 63 ++++++++++ .github/pull_request_template.md | 2 +- .github/workflows/010-tests.yml | 104 +++++++++++++++++ .github/workflows/020-publish.yml | 155 +++++++++++++++++++++++++ .github/workflows/overall-coverage.yml | 52 --------- .github/workflows/publish.yml | 75 ------------ Makefile | 39 +++++++ 7 files changed, 362 insertions(+), 128 deletions(-) create mode 100644 .github/actions/setup/action.yml create mode 100644 .github/workflows/010-tests.yml create mode 100644 .github/workflows/020-publish.yml delete mode 100644 .github/workflows/overall-coverage.yml delete mode 100644 .github/workflows/publish.yml create mode 100644 Makefile diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 0000000..3eba6a5 --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,63 @@ +# This private action setups all the necessary to run on all the workflows. +# +# By default it installs rust considering the input 'rust-toolchain' to choose +# which toolchain to install, then installs all the required rust packages. +# +# If there is an npm package specified then it also installs node along +# the required packages. + +name: Setup Environment + +inputs: + rust-toolchain: + required: false + default: stable + rust-components: + required: false + default: "" + rust-packages: + required: false + default: "" + npm-packages: + required: false + default: "" + +runs: + using: "composite" + steps: + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "22.18.0" + + + - name: Configure GIT + shell: bash + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + + - name: Setup Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: ${{ inputs.rust-toolchain }} + components: ${{ inputs.rust-components }} + + + - name: Set Rust Bin Environment Variable + shell: bash + run: echo "PATH=$HOME/.cargo/bin:$PATH" >> $GITHUB_ENV + + + - uses: cargo-bins/cargo-binstall@main + - name: Install Rust Packages + if: ${{ inputs.rust-packages != '' }} + shell: bash + run: cargo binstall --force -y ${{ inputs.rust-packages }} + + + - name: Install NPM Packages + if: ${{ inputs.npm-packages != '' }} + shell: bash + run: npm install -g ${{ inputs.npm-packages }} diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6891ff0..10d89d4 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,7 +2,7 @@ - [ ] I've checked existing issues and pull requests - [ ] I've read the [Code of Conduct](../CODE_OF_CONDUCT.md) -- [ ] I've [implemented tests](../TESTING.md) for my changes +- [ ] Are [tests implemented](../TESTING.md) for my changes - [ ] I've listed all my changes in the `Changes` section ## Changes diff --git a/.github/workflows/010-tests.yml b/.github/workflows/010-tests.yml new file mode 100644 index 0000000..bf57fd4 --- /dev/null +++ b/.github/workflows/010-tests.yml @@ -0,0 +1,104 @@ +# This workflow decides whether a new version should be published, +# basically by running tests, lints and version checks to ensure +# everything is as it should be. +# +# This will fail in case +# - Any of the past commits don't follow the conventional commits standard. +# - Cargo Clippy reports a broken rule. +# - Cargo Fmt reports a broken rule. +# - Tests fail +# - Code coverage reports < 80% coverage. +# +# For now this only runs on main and on linux, since this is a library, +# once further standards are enforced this might change. + +name: tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + tests: + runs-on: ubuntu-latest + permissions: + actions: read + + steps: + # Need to checkout the repository before + # using composite action to setup. + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + + # Setup the environment with default + # programs and setup required packages. + - name: Setup Environment + uses: ./.github/actions/setup + with: + rust-toolchain: nightly + rust-components: llvm-tools-preview rustfmt clippy + rust-packages: cargo-llvm-cov + + + # Validate conventional commits from the + # current branch. + - name: Conventional Commit Validation + uses: webiny/action-conventional-commits@v1.3.0 + with: + allowed-commit-types: "feat,fix,docs,test,ci,refactor,perf,chore,revert" + + + # Run code lints and tests + - name: Run Code Lint Checks And Tests + run: | + make test-code; + make test-format; + make test-clippy; + + + # Generate code coverage, this only + # happens in main because its the only + # branch we consideer for coverage. + - name: Generate Code Coverage + if: success() && github.ref == 'refs/heads/main' + run: make test-coverage-export; + + + # Upload the coverage to codecov + # Only main is considered because + # codecov is known to fail on other + # branches due to branch protection + # rules. + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + if: success() && github.ref == 'refs/heads/main' + with: + file: coverage.lcov + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} + slug: FlakySL/actix_failwrap + verbose: true + + + # This is more relevant while merging. + # Since by merge rules we deny failed + # workflows, in the case where coverage is + # below 80% the workflow will fail thus + # The branch won't be able to be merged. + - name: Fail if overall coverage is below 80% + run: | + coverage=$(make test-coverage-get); + coverage=${coverage//[[:space:]}; + coverage=${coverage%.*} + if (( coverage < 80 )) + then + echo "❌ Coverage is below 80% ($coverage%)" + exit 1 + else + echo "✅ Coverage meets requirement ($coverage%)" + fi diff --git a/.github/workflows/020-publish.yml b/.github/workflows/020-publish.yml new file mode 100644 index 0000000..2a65640 --- /dev/null +++ b/.github/workflows/020-publish.yml @@ -0,0 +1,155 @@ +# This action will keep track of the current crate verision, the last +# pushed git tag and determine whether the project should be deployed +# +# Additionally it will keep track whether THE LAST TAG has a release +# assigned, if it doesn't unless otherwise specified it will attempt +# to create one. + +name: publish + +on: + workflow_run: + workflows: + - tests + types: + - completed + +jobs: + publish: + runs-on: ubuntu-latest + # This only runs on main after tests have concluded successfully. + if: github.ref == 'refs/heads/main' && github.event.workflow_run.conclusion == 'success' + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write + + steps: + # Need to checkout the repository before + # using composite action to setup. + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + + # Setup the environment with default + # programs and setup required packages. + - name: Setup Environment + uses: ./.github/actions/setup + with: + npm-packages: "semver" + + + # Parsing all the previous versions + # is essential to determining whether a new + # version should be published. + # + # This exports the crate version and the + # latest git tag which help determine whether + # a new version should be published. + - name: Get And Parse Repository Versions + id: versions + run: | + # The version of the main crate which should be the first on the list in the workspace config. + echo "crate_version=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')" \ + >> $GITHUB_OUTPUT; + + # The last pushed git tag if any. + last_git_tag="$(git describe --tags --abbrev=0 || echo '')"; + if [[ -z "$last_git_tag" ]]; then exit 0; fi; + echo "last_tag=$last_git_tag" >> $GITHUB_OUTPUT; + + + # In the case the last git tag is minor than + # the current crate version, determine that + # a new version should be published to crates.io. + # + # In the case the last git tag doesn't have + # a release associated to it, determine a release + # should be created with git cliff. + - name: Determine What Should Be Done + id: to_do + run: | + crate_version="${{ steps.versions.outputs.crate_version }}"; + last_tag="${{ steps.versions.outputs.last_tag }}"; + + # If there is no last tag, make up 0.0.0. + if [ -z "$last_tag" ]; then + last_tag="0.0.0" + fi + + # If the last published tag is minor than the crate's tag + # determine that the crate should be published. + if semver -r "> $last_tag" "$crate_version"; then + echo "should_publish=true" >> $GITHUB_OUTPUT + else + echo "should_publish=false" >> $GITHUB_OUTPUT + fi + + # If there is no release associated to the last tag + # determine that a release should be created. + if ! gh release view "$last_tag" >/dev/null 2>&1; then + echo "should_release=true" >> $GITHUB_OUTPUT; + else + echo "should_release=false" >> $GITHUB_OUTPUT; + fi + + + # In the case it's determined that the crate should be + # published, then we call cargo publish and create + # a new tag for the publication in the case the publication + # succeeded. + - name: Publish And Create A New Tag + id: publish + if: success() && steps.to_do.outputs.should_publish == 'true' + run: | + crate_version="${{ steps.versions.outputs.crate_version }}" + + # If cargo publish fails a new release won't be made. + # Make sure that it exits in case github actions doesn't halt. + cargo publish || exit 1; + + git tag "$crate_version"; + git push origin "$crate_version"; + + # We make a version override for which the changelog + # will be created instead. + echo "version_override=$crate_version" >> $GITHUB_OUTPUT; + + + # In the case that or either it's determined that + # a release should be created or a new publication + # is done, then we call git-cliff-action@v4 to process + # creating a changelog. + - name: Generate a changelog + uses: orhun/git-cliff-action@v4 + if: > + success() && ( + steps.to_do.outputs.should_release == 'true' || + steps.publish.outputs.version_override != '' + ) + id: git-cliff + with: + config: cliff.toml + args: --verbose --latest --strip header + + + # We call create-release@v1 in the case the same + # condition as generating a changelog succeeds. + - name: Create Release + uses: actions/create-release@v1 + if: > + success() && ( + steps.to_do.outputs.should_release == 'true' || + steps.publish.outputs.version_override != '' + ) + with: + tag_name: ${{ steps.versions.outputs.last_tag }} + release_name: Release ${{ steps.versions.outputs.crate_version }} + body: ${{ steps.git-cliff.outputs.content }} + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/overall-coverage.yml b/.github/workflows/overall-coverage.yml deleted file mode 100644 index 67a1dad..0000000 --- a/.github/workflows/overall-coverage.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: tests - -on: - push: - branches: [main] - pull_request: - branches: - - main - -jobs: - coverage: - name: Collect test coverage - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: - - ubuntu-latest - - windows-latest - # nightly rust might break from time to time - continue-on-error: true - env: - RUSTFLAGS: -D warnings - CARGO_TERM_COLOR: always - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - # Nightly Rust is used for cargo llvm-cov --doc below. - - uses: dtolnay/rust-toolchain@nightly - with: - components: llvm-tools-preview - - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2 - - - name: Install latest nextest release - uses: taiki-e/install-action@d028bcc176afad59ee1e0b7dbba9789b8a1421f8 # v2 - with: - tool: nextest - - name: Install cargo-llvm-cov - uses: taiki-e/install-action@d028bcc176afad59ee1e0b7dbba9789b8a1421f8 # v2 - with: - tool: cargo-llvm-cov - - - name: Collect coverage data - # Generate separate reports for nextest and doctests, and combine them. - run: | - cargo llvm-cov --no-report nextest - cargo llvm-cov --no-report --doc - cargo llvm-cov report --doctests --lcov --output-path lcov.info - - name: Upload coverage data to codecov - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - with: - files: lcov.info diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index f822d9d..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,75 +0,0 @@ -name: publish to crates.io - -on: - workflow_run: - workflows: ["tests"] - types: - - completed - -jobs: - publish: - runs-on: ubuntu-latest - permissions: - contents: write - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set-up Rust - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - - - name: Set-up semver - run: | - curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - - sudo apt install -y nodejs - npm install -g semver - - - name: Get crate version - id: crate_version - run: | - VERSION=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version') - echo "version=$VERSION" >> $GITHUB_OUTPUT - - - name: Get latest git tag - id: latest_tag - run: | - TAG=$(git describe --tags --abbrev=0 || echo "") - echo "tag=$TAG" >> $GITHUB_OUTPUT - - - name: Compare versions - id: should_publish - run: | - VERSION="${{ steps.crate_version.outputs.version }}" - TAG="${{ steps.latest_tag.outputs.tag }}" - - if [ -z "$TAG" ]; then - TAG="0.0.0" - fi - - if semver -r "> $TAG" "$VERSION"; then - echo "publish=true" >> $GITHUB_OUTPUT - else - echo "publish=false" >> $GITHUB_OUTPUT - fi - - - name: Publish to crates.io - if: steps.should_publish.outputs.publish == 'true' - run: | - cargo publish -p cruct_shared - cargo publish -p cruct_proc - cargo publish -p cruct - env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} - - - name: Crate and push new git tag - if: steps.should_publish.outputs.publish == 'true' - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git tag ${{ steps.crate_version.outputs.version }} - git push origin ${{ steps.crate_version.outputs.version }} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..aae44b7 --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +.ONESHELL: +SHELL := /bin/bash + +.SILENT: + +.PHONY: test-code +test-code: + cargo test -- --nocapture --color=always + +.PHONY: test-format +test-format: + cargo +nightly fmt --all -- --check + +.PHONY: test-clippy +test-clippy: + cargo +nightly clippy --all + +.PHONY: test-coverage-get +test-coverage-get: + coverage=$$(cargo llvm-cov -- --nocapture --quiet 2>/dev/null | grep '^TOTAL' | awk '{print $$10}'); + + if [ -z "$$coverage" ] + then + echo "Tests failed."; + exit 1; + fi + + echo "$${coverage/%\%/ }"; + +.PHONY: test-coverage-export +test-coverage-export: + if [ -z "$(export)" ] + then + EXPORT_PATH="./coverage.lcov"; + else + EXPORT_PATH="$(export)"; + fi; + + cargo llvm-cov --lcov -- --nocapture > $$EXPORT_PATH 2>/dev/null; From e78e2373f1f08e3ca8d216f0c16e2fea566ed48a Mon Sep 17 00:00:00 2001 From: stifskere Date: Sat, 30 Aug 2025 23:59:25 +0200 Subject: [PATCH 2/2] chore(quality): Run Cargo fmt. --- cruct_shared/src/parser/tests/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cruct_shared/src/parser/tests/mod.rs b/cruct_shared/src/parser/tests/mod.rs index f350e07..3272f84 100644 --- a/cruct_shared/src/parser/tests/mod.rs +++ b/cruct_shared/src/parser/tests/mod.rs @@ -1,4 +1,4 @@ +mod option; +mod test_json; mod test_toml; mod test_yaml; -mod test_json; -mod option;