From e64d03627448a99dbb8fde18c679b40bbb33e39e Mon Sep 17 00:00:00 2001 From: Yann Prono Date: Wed, 5 Feb 2025 21:20:47 +0100 Subject: [PATCH] ci: improve the CI --- .github/workflows/actionlint.yml | 20 ++ .github/workflows/build.yml | 139 ++++++--- .github/workflows/changelog.yml | 68 +++++ .github/workflows/code-coverage.yml | 39 +++ .github/workflows/publish.yml | 67 ++++- .github/workflows/release.yml | 31 -- .github/workflows/security.yml | 33 +- .github/workflows/semver-checks.yml | 112 +++++++ .github/workflows/semver.sh | 447 ++++++++++++++++++++++++++++ .github/workflows/tag.yml | 62 ++++ deny.toml | 235 +++++++++++++++ src/attribute/help_test.rs | 2 +- src/entry/mod_test.rs | 2 +- 13 files changed, 1163 insertions(+), 94 deletions(-) create mode 100644 .github/workflows/actionlint.yml create mode 100644 .github/workflows/changelog.yml create mode 100644 .github/workflows/code-coverage.yml delete mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/semver-checks.yml create mode 100644 .github/workflows/semver.sh create mode 100644 .github/workflows/tag.yml create mode 100644 deny.toml diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml new file mode 100644 index 0000000..941bf36 --- /dev/null +++ b/.github/workflows/actionlint.yml @@ -0,0 +1,20 @@ +name: Actionlint + +on: + workflow_dispatch: + push: + paths: + - .github/workflows/** + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + actionlint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: actionlint + uses: raven-actions/actionlint@v2.0.0 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bb473b9..7bd70b6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,20 +1,25 @@ name: Build on: + schedule: + - cron: "0 0 1 * *" push: + paths-ignore: + - changelog branches-ignore: - main - pull_request: - branches: - - main workflow_dispatch: env: CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 RUSTFLAGS: "-D warnings" - MINIMUM_SUPPORTED_RUST_VERSION: 1.65.0 + RUSTDOCFLAGS: '--deny warnings' + MINIMUM_SUPPORTED_RUST_VERSION: 1.80.1 +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: check: @@ -22,15 +27,17 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master + - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: stable - uses: Swatinem/rust-cache@v2 - name: Run cargo check run: cargo check --all-features --locked --release --all + env: + CARGO_NET_GIT_FETCH_WITH_CLI: true build: - name: Build + name: Build w/o features needs: check runs-on: ubuntu-latest strategy: @@ -39,34 +46,53 @@ jobs: - stable - beta - nightly - - 1.65.0 - features: - - '' - include: - - rust: stable - features: '--all-features' - - rust: stable - features: '--no-default-features' - - rust: stable - features: '--features "display"' - - rust: stable - features: '--features "hash"' - - rust: stable - features: '--features "serialize"' - - rust: stable - features: '--features "deserialize"' - - rust: nightly - features: '--all-features' - - rust: nightly - features: '--no-default-features' steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master + - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ matrix.rust }} - uses: Swatinem/rust-cache@v2 - name: Run cargo build - run: cargo build ${{ matrix.features }} + run: cargo build + + build-for-targets: + name: Build for targets + needs: check + runs-on: ${{ matrix.platforms.os }} + continue-on-error: true + strategy: + matrix: + platforms: + - os: macOS-latest + target: aarch64-apple-darwin + features: "" + - os: macos-latest-large + target: x86_64-apple-darwin + features: "" + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + features: "" + - os: ubuntu-latest + target: aarch64-unknown-linux-gnu + features: "--no-default-features" + - os: windows-latest + target: x86_64-pc-windows-gnu + features: "--no-default-features" + - os: windows-latest + target: x86_64-pc-windows-msvc + features: "--no-default-features" + + steps: + - uses: actions/checkout@v4 + - uses: houseabsolute/actions-rust-cross@v1 + # if: (github.event.pull_request.base.ref == 'main' && matrix.platforms.os == 'macos-latest-large') == false + with: + target: ${{ matrix.platforms.target }} + args: "--locked ${{ matrix.platforms.features }}" + env: + KRB5_CV_ATTR_CONSTRUCTOR_DESTRUCTOR: yes + AC_CV_FUNC_REGCOMP: yes + AC_CV_PRINTF_POSITIONAL: yes clippy: needs: check @@ -74,33 +100,44 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master + - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: stable + components: clippy - uses: Swatinem/rust-cache@v2 - name: Run cargo clippy run: cargo clippy --all-targets --all-features -- --deny warnings - fmt: + format: needs: check - name: Fmt + name: Format runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master + - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: stable + components: rustfmt - uses: Swatinem/rust-cache@v2 - name: Run cargo fmt run: cargo fmt --all -- --check + unused-dependencies: + needs: check + name: Unused dependencies + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: Swatinem/rust-cache@v2 + - uses: bnjbvr/cargo-machete@main + code-coverage: + name: Code coverage needs: check - name: Unit tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master + - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: stable - uses: Swatinem/rust-cache@v2 @@ -112,6 +149,7 @@ jobs: uses: codecov/codecov-action@v5 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + CI: "true" doc: needs: check @@ -119,9 +157,38 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master + - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: stable - uses: Swatinem/rust-cache@v2 - name: Build documentation - run: cargo doc --no-deps --document-private-items --verbose \ No newline at end of file + run: cargo doc --no-deps --document-private-items --verbose + + lychee: + name: Lychee + runs-on: ubuntu-latest + needs: [check] + steps: + - uses: actions/checkout@v4 + - uses: lycheeverse/lychee-action@v2 + name: Link Checker + + typos: + name: Typos + runs-on: ubuntu-latest + needs: [check] + steps: + - name: Checkout Actions Repository + uses: actions/checkout@v4 + - name: Install typos-cli + run: cargo install typos-cli + - name: Run typos-cli + run: typos --exclude ./benches/* + + cargo-deny: + name: Cargo deny + runs-on: ubuntu-latest + needs: [check] + steps: + - uses: actions/checkout@v4 + - uses: EmbarkStudios/cargo-deny-action@v2 \ No newline at end of file diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 0000000..6d80e4c --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,68 @@ +name: Changelog + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: 1 + RUSTFLAGS: "-D warnings" + RUSTDOCFLAGS: '--deny warnings' + +permissions: + id-token: write + packages: write + contents: write + pull-requests: write + +on: + workflow_dispatch: + workflow_call: + +jobs: + changelog: + name: Create changelog + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + with: + fetch-tags: 'true' + fetch-depth: 0 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - id: release + run: echo "version=v$(cargo pkgid --manifest-path crates/bin/Cargo.toml | cut -d '@' -f2)" >> "$GITHUB_OUTPUT" + - name: Install git-cliff + run: cargo install git-cliff + - name: Generate a changelog + run: | + if [ -f CHANGELOG.md ]; then + git-cliff --config github --prepend CHANGELOG.md --latest + else + git-cliff --config github --output CHANGELOG.md + fi + env: + OUTPUT: CHANGELOG.md + GITHUB_REPO: ${{ github.repository }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Commit changelog + run: | + git checkout -b "changelog/${{ steps.release.outputs.version }}" + git config user.name 'github-actions[bot]' + git config user.email 'github-actions[bot]@users.noreply.github.com' + set +e + git add CHANGELOG.md + git commit -m "chore: Update changelog" + git push origin "changelog/${{ steps.release.outputs.version }}" --force + - name: Check out repository + uses: actions/checkout@v4 + with: + ref: "changelog/${{ steps.release.outputs.version }}" + - name: Create pull request for changelog + run: | + alreadyExists=$(gh pr list --json headRefName | jq '.[] | select(.headRefName == "changelog/${{ steps.release.outputs.version }}") | any') + if [[ "$alreadyExists" == "" ]]; then + branch=$(git branch --show-current) + gh pr create --head "$branch" --title "Changelog for ${{ steps.release.outputs.version }}" --body "This PR updates the changelog for version ${{ steps.release.outputs.version }}." + fi + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml new file mode 100644 index 0000000..081c77d --- /dev/null +++ b/.github/workflows/code-coverage.yml @@ -0,0 +1,39 @@ +name: Code coverage + +on: + pull_request: + branches: + - main + paths-ignore: + - CHANGELOG.md + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: 1 + RUSTFLAGS: "-D warnings" + RUSTDOCFLAGS: '--deny warnings' + MINIMUM_SUPPORTED_RUST_VERSION: 1.80.1 + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + code-coverage: + name: Code coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + - uses: Swatinem/rust-cache@v2 + - name: Install cargo-tarpaulin + run: cargo install cargo-tarpaulin + - name: Run cargo tarpaulin + run: cargo tarpaulin --all-features --out Xml + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v5 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3a646b0..7ab5aba 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,33 +1,68 @@ name: Publish +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: 1 + RUSTFLAGS: "-D warnings" + RUSTDOCFLAGS: '--deny warnings' + MINIMUM_SUPPORTED_RUST_VERSION: 1.80.1 + DOCKER_REGISTRY: ghcr.io + +permissions: + id-token: write + packages: write + contents: write + attestations: write + pull-requests: write + on: + workflow_dispatch: push: tags: - - 'v[0-9]+.[0-9]+.[0-9]+' + - '[0-9]+.[0-9]+.[0-9]+' + workflow_call: +# workflow_run: +# workflows: ['Tag'] +# types: +# - completed jobs: - check: + version: + name: Determine version to publish runs-on: ubuntu-latest outputs: - result: ${{ steps.check-branch.outputs.value }} + version: ${{ steps.release.outputs.version }} steps: - uses: actions/checkout@v4 - with: - ref: ${{ github.event.repository.default_branch }} - fetch-depth: 0 - - run: git branch -a --contains ${{ github.ref_name }} 2> /dev/null | grep -q ' main' && echo "value=true" >> "$GITHUB_OUTPUT" || exit 0 - id: check-branch + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - id: release + run: echo "version=$(cargo pkgid --manifest-path crates/bin/Cargo.toml | cut -d '@' -f2)" >> "$GITHUB_OUTPUT" - publish: + create-release: runs-on: ubuntu-latest - needs: check - if: needs.check.outputs.result == 'true' + name: Create release + needs: version steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - - name: Make sure crate is ready - run: cargo publish --dry-run + - name: Create github release + run: gh release create "v${{ needs.version.outputs.version }}" --generate-notes + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + changelog: + needs: [version, create-release] + uses: ./.github/workflows/changelog.yml + name: Create changelog + secrets: inherit + + publish-to-registry: + runs-on: ubuntu-latest + name: Publish to registry + if: github.ref == 'refs/heads/main' + permissions: + contents: write + steps: - name: Publish the crate run: cargo publish --token ${{ secrets.CRATES_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 236f61c..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Release - -on: - workflow_dispatch: - inputs: - version: - type: string - required: true - description: new version to release - -permissions: - contents: write - -jobs: - release: - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - - uses: Swatinem/rust-cache@v2 - - name: Install cargo-release - run: cargo install cargo-release - - name: Configure git - run: | - git config --global user.name "Boat" - git config --global user.email "boat@users.noreply.github.com" - - name: Release new version - run: cargo-release release -v "${{ github.event.inputs.version }}" --no-publish --no-confirm --execute diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index ea0d6e0..0ddf26c 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -1,35 +1,50 @@ name: Security +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: 1 + RUSTFLAGS: "-D warnings" + RUSTDOCFLAGS: '--deny warnings' + MINIMUM_SUPPORTED_RUST_VERSION: 1.80.1 + on: + schedule: + - cron: "0 0 1 * *" pull_request: branches: - main workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read -env: - RUST_BACKTRACE: 1 - CARGO_TERM_COLOR: always - jobs: dependencies: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - - name: Install cargo-outdated - run: cargo install cargo-outdated + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + - name: Install cargo-edit + run: cargo install cargo-edit - name: Check for outdated dependencies - run: cargo outdated --exit-code 1 + run: cargo upgrade --dry-run --locked audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable - name: Install cargo-outdated run: cargo install cargo-audit - - name: Audit dependencies - run: cargo audit + - name: Cargo audit + run: cargo audit \ No newline at end of file diff --git a/.github/workflows/semver-checks.yml b/.github/workflows/semver-checks.yml new file mode 100644 index 0000000..2f9841f --- /dev/null +++ b/.github/workflows/semver-checks.yml @@ -0,0 +1,112 @@ +name: Semver checks + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: 1 + RUSTFLAGS: "-D warnings" + RUSTDOCFLAGS: '--deny warnings' + MINIMUM_SUPPORTED_RUST_VERSION: 1.80.1 + +permissions: + packages: write + contents: write + +on: + pull_request: + types: [opened, reopened, synchronize, ready_for_review] + branches: + - main + paths-ignore: + - CHANGELOG.md + workflow_dispatch: + workflow_run: + workflows: ['Prepare a release'] + types: + - completed + + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + check-branches: + runs-on: ubuntu-latest + steps: + - name: you can't run this action on 'main' branch + run: | + if [[ "${{ github.ref_name }}" = "main" ]]; then + exit 1 + fi + cargo-semver-checks: + needs: check-branches + permissions: + pull-requests: write + contents: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Delete old cargo-semver-checks comments + continue-on-error: true + run: | + gh pr view "${{ github.event.number }}" --json comments --jq '.comments[] | select(.author.login == "github-actions") | .url' | grep -E 'issuecomment-(.+)' | cut -d '-' -f2 > comments.txt + xargs -I {} gh api -X DELETE "/repos/MAIF/yozefu/issues/comments/{}" < comments.txt + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + - uses: Swatinem/rust-cache@v2 + - name: Install cargo-semver-checks + run: cargo install cargo-semver-checks + - name: List the releases on GitHub + id: current + run: echo "version=$(git tag --sort=-creatordate | head -n 1)" >> "$GITHUB_OUTPUT" + - name: Get next version + id: next + run: echo "version=v$(cargo pkgid --manifest-path crates/bin/Cargo.toml | cut -d '@' -f2)" >> "$GITHUB_OUTPUT" + - name: Make sure version is updated + if: ${{ steps.current.outputs.version == steps.next.outputs.version }} + run: | + echo "::warning title=Next version:: Last public version is '${{ steps.current.outputs.version }}' but version of this branch is '${{ steps.next.outputs.version }}'. Did you forget to update the version? More details at https://github.com/MAIF/yozefu/blob/main/docs/release/README.md" + printf 'This pull request is not ready because the crate version is equals to the latest git tag version `' > report.md + printf "${{ steps.next.outputs.version }}" >> report.md + printf '`. I think you forgot to bump the version. More details at https://github.com/MAIF/yozefu/blob/main/docs/release/README.md' >> report.md + gh pr comment ${{ github.event.number }} --body-file ./report.md + exit 1 + env: + GH_TOKEN: ${{ github.token }} + - name: Show release type + id: semver + run: | + chmod u+x ./.github/workflows/semver.sh + echo "release=$(./.github/workflows/semver.sh diff ${{ steps.current.outputs.version }} ${{ steps.next.outputs.version }})" >> "$GITHUB_OUTPUT" + - name: Prepare report.md + if: ${{ steps.next.outputs.version == '' }} + run: | + { + printf "> [!WARNING]" + printf "> According to \`cargo-semver-checks\`, the next release version doesn\'t respect semantic versioning." + printf '```bash' + } > ./report.md + - name: Run cargo semver-checks + if: ${{ steps.next.outputs.version != '' && steps.semver.outputs.release != '' }} + id: check + run: | + printf "\`cargo-semver-checks\` has detected some issues: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\n" > report.md + printf '```bash' >> report.md + cargo semver-checks --color never --package yozefu-lib --package yozefu-app --package yozefu-wasm-types --package yozefu-command --baseline-rev "${{ steps.current.outputs.version }}" --release-type "${{ steps.semver.outputs.release }}" 2>&1 | tee -a report.md + if [[ "${PIPESTATUS[0]}" != "0" ]]; then + printf '```' >> report.md + exit 1 + fi + continue-on-error: true + - name: Publish semver-checks report + if: ${{ steps.check.outcome != 'success' && steps.next.outputs.version == '' }} + run: | + gh pr comment ${{ github.event.number }} --body-file ./report.md + exit 1 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/semver.sh b/.github/workflows/semver.sh new file mode 100644 index 0000000..4e7ca09 --- /dev/null +++ b/.github/workflows/semver.sh @@ -0,0 +1,447 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: Apache-2.0 +# Thank you https://raw.githubusercontent.com/fsaintjacques/semver-tool/master/src/semver + +set -o errexit -o nounset -o pipefail + +NAT='0|[1-9][0-9]*' +ALPHANUM='[0-9]*[A-Za-z-][0-9A-Za-z-]*' +IDENT="$NAT|$ALPHANUM" +FIELD='[0-9A-Za-z-]+' + +SEMVER_REGEX="\ +^[vV]?\ +($NAT)\\.($NAT)\\.($NAT)\ +(\\-(${IDENT})(\\.(${IDENT}))*)?\ +(\\+${FIELD}(\\.${FIELD})*)?$" + +PROG=semver +PROG_VERSION="3.4.0" + +USAGE="\ +Usage: + $PROG bump major + $PROG bump minor + $PROG bump patch + $PROG bump prerel|prerelease [] + $PROG bump build + $PROG bump release + $PROG get major + $PROG get minor + $PROG get patch + $PROG get prerel|prerelease + $PROG get build + $PROG get release + $PROG compare + $PROG diff + $PROG validate + $PROG --help + $PROG --version + +Arguments: + A version must match the following regular expression: + \"${SEMVER_REGEX}\" + In English: + -- The version must match X.Y.Z[-PRERELEASE][+BUILD] + where X, Y and Z are non-negative integers. + -- PRERELEASE is a dot separated sequence of non-negative integers and/or + identifiers composed of alphanumeric characters and hyphens (with + at least one non-digit). Numeric identifiers must not have leading + zeros. A hyphen (\"-\") introduces this optional part. + -- BUILD is a dot separated sequence of identifiers composed of alphanumeric + characters and hyphens. A plus (\"+\") introduces this optional part. + + See definition. + + A string as defined by PRERELEASE above. Or, it can be a PRERELEASE + prototype string followed by a dot. + + A string as defined by BUILD above. + +Options: + -v, --version Print the version of this tool. + -h, --help Print this help message. + +Commands: + bump Bump by one of major, minor, patch; zeroing or removing + subsequent parts. \"bump prerel\" (or its synonym \"bump prerelease\") + sets the PRERELEASE part and removes any BUILD part. A trailing dot + in the argument introduces an incrementing numeric field + which is added or bumped. If no argument is provided, an + incrementing numeric field is introduced/bumped. \"bump build\" sets + the BUILD part. \"bump release\" removes any PRERELEASE or BUILD parts. + The bumped version is written to stdout. + + get Extract given part of , where part is one of major, minor, + patch, prerel (alternatively: prerelease), build, or release. + + compare Compare with , output to stdout the + following values: -1 if is newer, 0 if equal, 1 if + older. The BUILD part is not used in comparisons. + + diff Compare with , output to stdout the + difference between two versions by the release type (MAJOR, MINOR, + PATCH, PRERELEASE, BUILD). + + validate Validate if follows the SEMVER pattern (see + definition). Print 'valid' to stdout if the version is valid, otherwise + print 'invalid'. + +See also: + https://semver.org -- Semantic Versioning 2.0.0" + +function error { + echo -e "$1" >&2 + exit 1 +} + +function usage_help { + error "$USAGE" +} + +function usage_version { + echo -e "${PROG}: $PROG_VERSION" + exit 0 +} + +# normalize the "part" keywords to a canonical string. At present, +# only "prerelease" is normalized to "prerel". + +function normalize_part { + if [ "$1" == "prerelease" ] + then + echo "prerel" + else + echo "$1" + fi +} + +function validate_version { + local version=$1 + if [[ "$version" =~ $SEMVER_REGEX ]]; then + # if a second argument is passed, store the result in var named by $2 + if [ "$#" -eq "2" ]; then + local major=${BASH_REMATCH[1]} + local minor=${BASH_REMATCH[2]} + local patch=${BASH_REMATCH[3]} + local prere=${BASH_REMATCH[4]} + local build=${BASH_REMATCH[8]} + eval "$2=(\"$major\" \"$minor\" \"$patch\" \"$prere\" \"$build\")" + else + echo "$version" + fi + else + error "version $version does not match the semver scheme 'X.Y.Z(-PRERELEASE)(+BUILD)'. See help for more information." + fi +} + +function is_nat { + [[ "$1" =~ ^($NAT)$ ]] +} + +function is_null { + [ -z "$1" ] +} + +function order_nat { + [ "$1" -lt "$2" ] && { echo -1 ; return ; } + [ "$1" -gt "$2" ] && { echo 1 ; return ; } + echo 0 +} + +function order_string { + [[ $1 < $2 ]] && { echo -1 ; return ; } + [[ $1 > $2 ]] && { echo 1 ; return ; } + echo 0 +} + +# given two (named) arrays containing NAT and/or ALPHANUM fields, compare them +# one by one according to semver 2.0.0 spec. Return -1, 0, 1 if left array ($1) +# is less-than, equal, or greater-than the right array ($2). The longer array +# is considered greater-than the shorter if the shorter is a prefix of the longer. +# +function compare_fields { + local l="$1[@]" + local r="$2[@]" + local leftfield=( "${!l}" ) + local rightfield=( "${!r}" ) + local left + local right + + local i=$(( -1 )) + local order=$(( 0 )) + + while true + do + [ $order -ne 0 ] && { echo $order ; return ; } + + : $(( i++ )) + left="${leftfield[$i]}" + right="${rightfield[$i]}" + + is_null "$left" && is_null "$right" && { echo 0 ; return ; } + is_null "$left" && { echo -1 ; return ; } + is_null "$right" && { echo 1 ; return ; } + + is_nat "$left" && is_nat "$right" && { order=$(order_nat "$left" "$right") ; continue ; } + is_nat "$left" && { echo -1 ; return ; } + is_nat "$right" && { echo 1 ; return ; } + { order=$(order_string "$left" "$right") ; continue ; } + done +} + +# shellcheck disable=SC2206 # checked by "validate"; ok to expand prerel id's into array +function compare_version { + local order + validate_version "$1" V + validate_version "$2" V_ + + # compare major, minor, patch + + local left=( "${V[0]}" "${V[1]}" "${V[2]}" ) + local right=( "${V_[0]}" "${V_[1]}" "${V_[2]}" ) + + order=$(compare_fields left right) + [ "$order" -ne 0 ] && { echo "$order" ; return ; } + + # compare pre-release ids when M.m.p are equal + + local prerel="${V[3]:1}" + local prerel_="${V_[3]:1}" + local left=( ${prerel//./ } ) + local right=( ${prerel_//./ } ) + + # if left and right have no pre-release part, then left equals right + # if only one of left/right has pre-release part, that one is less than simple M.m.p + + [ -z "$prerel" ] && [ -z "$prerel_" ] && { echo 0 ; return ; } + [ -z "$prerel" ] && { echo 1 ; return ; } + [ -z "$prerel_" ] && { echo -1 ; return ; } + + # otherwise, compare the pre-release id's + + compare_fields left right +} + +# render_prerel -- return a prerel field with a trailing numeric string +# usage: render_prerel numeric [prefix-string] +# +function render_prerel { + if [ -z "$2" ] + then + echo "${1}" + else + echo "${2}${1}" + fi +} + +# extract_prerel -- extract prefix and trailing numeric portions of a pre-release part +# usage: extract_prerel prerel prerel_parts +# The prefix and trailing numeric parts are returned in "prerel_parts". +# +PREFIX_ALPHANUM='[.0-9A-Za-z-]*[.A-Za-z-]' +DIGITS='[0-9][0-9]*' +EXTRACT_REGEX="^(${PREFIX_ALPHANUM})*(${DIGITS})$" + +function extract_prerel { + local prefix; local numeric; + + if [[ "$1" =~ $EXTRACT_REGEX ]] + then # found prefix and trailing numeric parts + prefix="${BASH_REMATCH[1]}" + numeric="${BASH_REMATCH[2]}" + else # no numeric part + prefix="${1}" + numeric= + fi + + eval "$2=(\"$prefix\" \"$numeric\")" +} + +# bump_prerel -- return the new pre-release part based on previous pre-release part +# and prototype for bump +# usage: bump_prerel proto previous +# +function bump_prerel { + local proto; local prev_prefix; local prev_numeric; + + # case one: no trailing dot in prototype => simply replace previous with proto + if [[ ! ( "$1" =~ \.$ ) ]] + then + echo "$1" + return + fi + + proto="${1%.}" # discard trailing dot marker from prototype + + extract_prerel "${2#-}" prerel_parts # extract parts of previous pre-release +# shellcheck disable=SC2154 + prev_prefix="${prerel_parts[0]}" + prev_numeric="${prerel_parts[1]}" + + # case two: bump or append numeric to previous pre-release part + if [ "$proto" == "+" ] # dummy "+" indicates no prototype argument provided + then + if [ -n "$prev_numeric" ] + then + : $(( ++prev_numeric )) # previous pre-release is already numbered, bump it + render_prerel "$prev_numeric" "$prev_prefix" + else + render_prerel 1 "$prev_prefix" # append starting number + fi + return + fi + + # case three: set, bump, or append using prototype prefix + if [ "$prev_prefix" != "$proto" ] + then + render_prerel 1 "$proto" # proto not same pre-release; set and start at '1' + elif [ -n "$prev_numeric" ] + then + : $(( ++prev_numeric )) # pre-release is numbered; bump it + render_prerel "$prev_numeric" "$prev_prefix" + else + render_prerel 1 "$prev_prefix" # start pre-release at number '1' + fi +} + +function command_bump { + local new; local version; local sub_version; local command; + + command="$(normalize_part "$1")" + + case $# in + 2) case "$command" in + major|minor|patch|prerel|release) sub_version="+."; version=$2;; + *) usage_help;; + esac ;; + 3) case "$command" in + prerel|build) sub_version=$2 version=$3 ;; + *) usage_help;; + esac ;; + *) usage_help;; + esac + + validate_version "$version" parts + # shellcheck disable=SC2154 + local major="${parts[0]}" + local minor="${parts[1]}" + local patch="${parts[2]}" + local prere="${parts[3]}" + local build="${parts[4]}" + + case "$command" in + major) new="$((major + 1)).0.0";; + minor) new="${major}.$((minor + 1)).0";; + patch) new="${major}.${minor}.$((patch + 1))";; + release) new="${major}.${minor}.${patch}";; + prerel) new=$(validate_version "${major}.${minor}.${patch}-$(bump_prerel "$sub_version" "$prere")");; + build) new=$(validate_version "${major}.${minor}.${patch}${prere}+${sub_version}");; + *) usage_help ;; + esac + + echo "$new" + exit 0 +} + +function command_compare { + local v; local v_; + + case $# in + 2) v=$(validate_version "$1"); v_=$(validate_version "$2") ;; + *) usage_help ;; + esac + + set +u # need unset array element to evaluate to null + compare_version "$v" "$v_" + exit 0 +} + +function command_diff { + validate_version "$1" v1_parts + # shellcheck disable=SC2154 + local v1_major="${v1_parts[0]}" + local v1_minor="${v1_parts[1]}" + local v1_patch="${v1_parts[2]}" + local v1_prere="${v1_parts[3]}" + local v1_build="${v1_parts[4]}" + + validate_version "$2" v2_parts + # shellcheck disable=SC2154 + local v2_major="${v2_parts[0]}" + local v2_minor="${v2_parts[1]}" + local v2_patch="${v2_parts[2]}" + local v2_prere="${v2_parts[3]}" + local v2_build="${v2_parts[4]}" + + if [ "${v1_major}" != "${v2_major}" ]; then + echo "major" + elif [ "${v1_minor}" != "${v2_minor}" ]; then + echo "minor" + elif [ "${v1_patch}" != "${v2_patch}" ]; then + echo "patch" + elif [ "${v1_prere}" != "${v2_prere}" ]; then + echo "prerelease" + elif [ "${v1_build}" != "${v2_build}" ]; then + echo "build" + fi +} + +# shellcheck disable=SC2034 +function command_get { + local part version + + if [[ "$#" -ne "2" ]] || [[ -z "$1" ]] || [[ -z "$2" ]]; then + usage_help + exit 0 + fi + + part="$1" + version="$2" + + validate_version "$version" parts + local major="${parts[0]}" + local minor="${parts[1]}" + local patch="${parts[2]}" + local prerel="${parts[3]:1}" + local build="${parts[4]:1}" + local release="${major}.${minor}.${patch}" + + part="$(normalize_part "$part")" + + case "$part" in + major|minor|patch|release|prerel|build) echo "${!part}" ;; + *) usage_help ;; + esac + + exit 0 +} + +function command_validate { + if [[ "$#" -ne "1" ]]; then + usage_help + fi + + if [[ "$1" =~ $SEMVER_REGEX ]]; then + echo "valid" + else + echo "invalid" + fi + + exit 0 +} + +case $# in + 0) echo "Unknown command: $*"; usage_help;; +esac + +case $1 in + --help|-h) echo -e "$USAGE"; exit 0;; + --version|-v) usage_version ;; + bump) shift; command_bump "$@";; + get) shift; command_get "$@";; + compare) shift; command_compare "$@";; + diff) shift; command_diff "$@";; + validate) shift; command_validate "$@";; + *) echo "Unknown arguments: $*"; usage_help;; +esac diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml new file mode 100644 index 0000000..7af8feb --- /dev/null +++ b/.github/workflows/tag.yml @@ -0,0 +1,62 @@ +name: Tag + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: 1 + RUSTFLAGS: "-D warnings" + RUSTDOCFLAGS: '--deny warnings' + MINIMUM_SUPPORTED_RUST_VERSION: 1.80.1 + +permissions: + contents: write + actions: write + packages: write + attestations: write + id-token: write + pull-requests: write +on: + push: + branches: + - main + paths-ignore: + - CHANGELOG.md + +jobs: + tag: + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + outputs: + created: ${{ steps.tag.outputs.created }} + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Get tags + run: git fetch --tags origin + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Configure git + run: | + git config --global user.name "${{ github.actor }}" + git config --global user.email "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com" + git config --global push.autoSetupRemote true + - name: Get the version + id: current + run: echo "version=$(cargo pkgid --manifest-path crates/bin/Cargo.toml | cut -d '@' -f2)" >> "$GITHUB_OUTPUT" + - name: Create git tag + id: tag + run: | + if git tag -a "v${{ steps.current.outputs.version }}" -m "${{ steps.current.outputs.version }}"; then + echo "created=true" >> "$GITHUB_OUTPUT" + else + echo "::notice title=Git tag:: The version ${{ steps.current.outputs.version }} already exists. The \`publish\` workflow won't be executed." + echo "created=false" >> "$GITHUB_OUTPUT" + fi + + publish: + needs: tag + if: ${{ needs.tag.outputs.created == 'true' }} + uses: ./.github/workflows/publish.yml + secrets: inherit diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..aa4a321 --- /dev/null +++ b/deny.toml @@ -0,0 +1,235 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# Root options + +# The graph table configures how the dependency graph is constructed and thus +# which crates the checks are performed against +[graph] +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #"x86_64-unknown-linux-musl", + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] +# When creating the dependency graph used as the source of truth when checks are +# executed, this field can be used to prune crates from the graph, removing them +# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate +# is pruned from the graph, all of its dependencies will also be pruned unless +# they are connected to another crate in the graph that hasn't been pruned, +# so it should be used with care. The identifiers are [Package ID Specifications] +# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) +#exclude = [] +# If true, metadata will be collected with `--all-features`. Note that this can't +# be toggled off if true, if you want to conditionally enable `--all-features` it +# is recommended to pass `--all-features` on the cmd line instead +all-features = false +# If true, metadata will be collected with `--no-default-features`. The same +# caveat with `all-features` applies +no-default-features = false +# If set, these feature will be enabled when collecting metadata. If `--features` +# is specified on the cmd line they will take precedence over this option. +#features = [] + +# The output table provides options for how/if diagnostics are outputted +[output] +# When outputting inclusion graphs in diagnostics that include features, this +# option can be used to specify the depth at which feature edges will be added. +# This option is included since the graphs can be quite large and the addition +# of features from the crate(s) to all of the graph roots can be far too verbose. +# This option can be overridden via `--feature-depth` on the cmd line +feature-depth = 1 + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory databases are cloned/fetched into +#db-path = "$CARGO_HOME/advisory-dbs" +# The url(s) of the advisory databases to use +#db-urls = ["https://github.com/rustsec/advisory-db"] +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + #"RUSTSEC-0000-0000", + #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, + #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish + #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, +] +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "Apache-2.0", + "Unicode-3.0" +] +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], crate = "adler32" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +#[[licenses.clarify]] +# The package spec the clarification applies to +#crate = "ring" +# The SPDX expression for the license requirements of the crate +#expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +#license-files = [ +# Each entry is a crate relative path, and the (opaque) hash of its contents +#{ path = "LICENSE", hash = 0xbd0eed23 } +#] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = false +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "allow" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# The default lint level for `default` features for crates that are members of +# the workspace that is being checked. This can be overridden by allowing/denying +# `default` on a crate-by-crate basis if desired. +workspace-default-features = "allow" +# The default lint level for `default` features for external crates that are not +# members of the workspace. This can be overridden by allowing/denying `default` +# on a crate-by-crate basis if desired. +external-default-features = "allow" +# List of crates that are allowed. Use with care! +allow = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, +] +# List of crates to deny +deny = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, +] + +# List of features to allow/deny +# Each entry the name of a crate and a version range. If version is +# not specified, all versions will be matched. +#[[bans.features]] +#crate = "reqwest" +# Features to not allow +#deny = ["json"] +# Features to allow +#allow = [ +# "rustls", +# "__rustls", +# "__tls", +# "hyper-rustls", +# "rustls", +# "rustls-pemfile", +# "rustls-tls-webpki-roots", +# "tokio-rustls", +# "webpki-roots", +#] +# If true, the allowed features must exactly match the enabled feature set. If +# this is set there is no point setting `deny` +#exact = true + +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite. +skip-tree = [ + #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies + #{ crate = "ansi_term@0.11.0", depth = 20 }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "warn" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "warn" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# github.com organizations to allow git sources for +github = [] +# gitlab.com organizations to allow git sources for +gitlab = [] +# bitbucket.org organizations to allow git sources for +bitbucket = [] diff --git a/src/attribute/help_test.rs b/src/attribute/help_test.rs index 6b322b1..b2de1d3 100644 --- a/src/attribute/help_test.rs +++ b/src/attribute/help_test.rs @@ -26,7 +26,7 @@ fn test_parse_help_no_content() { // 3.2/drivers/net/ethernet/stmicro/stmmac/Kconfig #[test] -fn test_parse_help_prefixed_by_hypen() { +fn test_parse_help_prefixed_by_hyphen() { let input = "-- help\n hello world"; assert_parsing_eq!(parse_help, input, Ok(("", "hello world".to_string()))) } diff --git a/src/entry/mod_test.rs b/src/entry/mod_test.rs index d39bd45..0d1c09c 100644 --- a/src/entry/mod_test.rs +++ b/src/entry/mod_test.rs @@ -33,7 +33,7 @@ fn test_parse_entries() { } #[test] -fn test_double_indented_entrys() { +fn test_double_indented_entries() { let input = r#"mainmenu "MAIN" config A