|
| 1 | +#!/usr/bin/env -S just --working-directory . --justfile |
| 2 | + |
| 3 | +clippy := "cargo clippy --quiet --workspace --no-deps --all-targets" |
| 4 | +clippy_args := "-D warnings" |
| 5 | +nextest_args := "--locked --workspace" |
| 6 | +udeps_args := "--quiet --workspace --all-features --all-targets" |
| 7 | + |
| 8 | +# Perform all checks |
| 9 | +[parallel] |
| 10 | +check: spell fmt doc lints deps unused-deps recipes test integration-test |
| 11 | + |
| 12 | +# Check spelling |
| 13 | +[group('ci')] |
| 14 | +[metadata('pacman', 'codespell')] |
| 15 | +spell: |
| 16 | + codespell |
| 17 | + |
| 18 | +# Check source code formatting |
| 19 | +[group('ci')] |
| 20 | +[metadata('pacman', 'rustup')] |
| 21 | +fmt: |
| 22 | + just --unstable --fmt --check |
| 23 | + # We're using nightly to properly group imports, see .rustfmt.toml |
| 24 | + rustup component add --toolchain nightly rustfmt |
| 25 | + cargo +nightly fmt --quiet --all -- --check |
| 26 | + |
| 27 | +# Lint the source code |
| 28 | +[group('ci')] |
| 29 | +[metadata('gitlabci-job', '{"artifacts":{"when":"always","paths":["target/clippy"]}}')] |
| 30 | +[metadata('pacman', 'rust', 'python')] |
| 31 | +lints: |
| 32 | + #!/usr/bin/bash |
| 33 | + set -euo pipefail |
| 34 | + |
| 35 | + if [ "${CI:-}" = "true" ]; then |
| 36 | + ARGS=(--message-format=json) |
| 37 | + else |
| 38 | + ARGS=() |
| 39 | + fi |
| 40 | + |
| 41 | + mkdir -p target |
| 42 | + {{ clippy }} "${ARGS[@]}" -- {{ clippy_args }} | tee target/clippy |
| 43 | + |
| 44 | +# Create lints report |
| 45 | +[metadata('gitlabci-job', '{"stage":"deploy","when":"always","artifacts":{"reports":{"codequality":"target/codeclimate.json"}}}')] |
| 46 | +[metadata('pacman', 'cargo-sonar', 'rust')] |
| 47 | +create-codeclimate: |
| 48 | + # deny reports can be tested by adding a yanked dep e.g. `cargo add ed25519-dalek@0.9.0` |
| 49 | + cargo codeclimate \ |
| 50 | + --clippy --clippy-path "target/clippy" \ |
| 51 | + --deny --deny-path "target/deny" \ |
| 52 | + --udeps --udeps-path "target/udeps" \ |
| 53 | + --codeclimate-path target/codeclimate.json |
| 54 | + |
| 55 | +# Check for issues with dependencies |
| 56 | +[group('ci')] |
| 57 | +[metadata('gitlabci-job', '{"artifacts":{"when":"always","paths":["target/deny"]}}')] |
| 58 | +[metadata('pacman', 'cargo-deny')] |
| 59 | +deps: |
| 60 | + #!/usr/bin/bash |
| 61 | + set -euo pipefail |
| 62 | + |
| 63 | + if [ "${CI:-}" = "true" ]; then |
| 64 | + ARGS=(--format json) |
| 65 | + else |
| 66 | + ARGS=() |
| 67 | + fi |
| 68 | + |
| 69 | + mkdir -p target |
| 70 | + cargo deny "${ARGS[@]}" check -D advisory-not-detected -D license-not-encountered -D no-license-field 2>&1 | tee target/deny |
| 71 | + |
| 72 | +# Check for unused dependencies |
| 73 | +[group('ci')] |
| 74 | +[metadata('gitlabci-job', '{"artifacts":{"when":"always","paths":["target/udeps"]}}')] |
| 75 | +[metadata('pacman', 'cargo-machete', 'cargo-udeps', 'rust', 'python')] |
| 76 | +unused-deps: |
| 77 | + #!/usr/bin/bash |
| 78 | + set -euo pipefail |
| 79 | + |
| 80 | + if [ "${CI:-}" = "true" ]; then |
| 81 | + ARGS=(--output json) |
| 82 | + else |
| 83 | + ARGS=() |
| 84 | + fi |
| 85 | + |
| 86 | + mkdir -p target |
| 87 | + cargo +nightly udeps {{ udeps_args }} "${ARGS[@]}" | tee target/udeps |
| 88 | + cargo +nightly machete |
| 89 | + |
| 90 | +# Run unit tests |
| 91 | +[metadata('pacman', 'cargo-nextest')] |
| 92 | +test: |
| 93 | + #!/usr/bin/bash |
| 94 | + set -euxo pipefail |
| 95 | + |
| 96 | + if [ "${CI:-}" = "true" ]; then |
| 97 | + PROFILE=ci |
| 98 | + else |
| 99 | + PROFILE=default |
| 100 | + fi |
| 101 | + |
| 102 | + cargo +nightly nextest run {{ nextest_args }} --profile "$PROFILE" |
| 103 | + cargo +nightly nextest run --no-default-features {{ nextest_args }} --profile "$PROFILE" |
| 104 | + |
| 105 | +# Run integration tests |
| 106 | +[metadata('pacman', 'git', 'jq', 'openssh', 'tangler', 'tree')] |
| 107 | +integration-test: |
| 108 | + #!/usr/bin/bash |
| 109 | + set -euo pipefail |
| 110 | + cargo +nightly build --locked |
| 111 | + target=$(cargo +nightly metadata --format-version 1 | jq --raw-output '.target_directory') |
| 112 | + tangler sh < README.md | sed --quiet --regexp-extended 's/^\$ (.*)/\1/p' | PATH="$target/debug:$PATH" bash -euxo pipefail - |
| 113 | + |
| 114 | +# Report on all tests |
| 115 | +[group('ci')] |
| 116 | +[metadata('gitlabci-job', '{"coverage":"/Line coverage: ([0-9.]*)%/","artifacts":{"when":"always","reports":{"junit":"target/nextest/ci/junit.xml","metrics":"target/metrics.txt","coverage_report":{"coverage_format":"cobertura","path":"target/coverage.xml"}}}}')] |
| 117 | +[metadata('pacman', 'rust', 'cargo-llvm-cov', 'rustup', 'python')] |
| 118 | +report-test: |
| 119 | + #!/usr/bin/bash |
| 120 | + # enabling "x" here will garble text output that's parsed by GitLab for code coverage |
| 121 | + set -euo pipefail |
| 122 | + |
| 123 | + rustup component add --toolchain nightly llvm-tools-preview |
| 124 | + |
| 125 | + # shellcheck disable=SC1090 |
| 126 | + source <(cargo +nightly llvm-cov show-env --export-prefix --doctests --branch) |
| 127 | + cargo +nightly llvm-cov clean |
| 128 | + |
| 129 | + just test integration-test |
| 130 | + |
| 131 | + # explicitly use "target" (even if CARGO_TARGET_DIR is somewhere else) so that |
| 132 | + # local tools (such as https://github.com/ryanluker/vscode-coverage-gutters) can find the file |
| 133 | + cargo +nightly llvm-cov --quiet report --cobertura --output-path target/coverage.xml > /dev/null 2>&1 |
| 134 | + |
| 135 | + LINE_RATE=$(head target/coverage.xml | sed -nE 's/(.*coverage.*line-rate=")([^"]*)".*/\2/p') |
| 136 | + LINE_PERCENT=$(echo "$LINE_RATE" | awk '{print $1 * 100}') |
| 137 | + printf 'Line coverage: %s%%\n' "$LINE_PERCENT" |
| 138 | + |
| 139 | + BRANCH_RATE=$(head target/coverage.xml | sed -nE 's/(.*coverage.*branch-rate=")([^"]*)".*/\2/p') |
| 140 | + printf 'line_coverage_ratio %s\nbranch_coverage_ratio %s\n' "$LINE_RATE" "$BRANCH_RATE" > target/metrics.txt |
| 141 | + |
| 142 | +# Generate HTML report for the coverage |
| 143 | +coverage-html-report: report-test |
| 144 | + #!/usr/bin/bash |
| 145 | + set -euo pipefail |
| 146 | + # shellcheck disable=SC1090 |
| 147 | + source <(cargo +nightly llvm-cov show-env --export-prefix) |
| 148 | + cargo +nightly llvm-cov --quiet report --html > /dev/null 2>&1 |
| 149 | + printf "The coverage report is in file://%s/llvm-cov/html/index.html\n" "${CARGO_TARGET_DIR:-target}" |
| 150 | + |
| 151 | +# Build docs |
| 152 | +[group('ci')] |
| 153 | +[metadata('pacman', 'rust', 'python')] |
| 154 | +doc: |
| 155 | + RUSTDOCFLAGS='-D warnings' cargo doc --quiet --no-deps --document-private-items |
| 156 | + |
| 157 | +# Check commit messages |
| 158 | +[metadata('pacman', 'codespell', 'git')] |
| 159 | +commits: |
| 160 | + #!/usr/bin/env bash |
| 161 | + set -Eeuo pipefail |
| 162 | + |
| 163 | + # fetch default branch if it is set |
| 164 | + if [[ -v CI_DEFAULT_BRANCH ]]; then |
| 165 | + git fetch origin "$CI_DEFAULT_BRANCH" |
| 166 | + refs="origin/$CI_DEFAULT_BRANCH" |
| 167 | + else |
| 168 | + refs="main" |
| 169 | + fi |
| 170 | + |
| 171 | + commits=$(git rev-list "${refs}..") |
| 172 | + for commit in $commits; do |
| 173 | + MSG="$(git show -s --format=%B "$commit")" |
| 174 | + CODESPELL_RC="$(mktemp)" |
| 175 | + git show "$commit:.codespellrc" > "$CODESPELL_RC" |
| 176 | + if ! grep -q "Signed-off-by: " <<< "$MSG"; then |
| 177 | + printf "⛔ Commit %s lacks \"Signed-off-by\" line.\n" "$commit" |
| 178 | + printf "%s\n" \ |
| 179 | + " Please use:" \ |
| 180 | + " git rebase --signoff main && git push --force-with-lease" \ |
| 181 | + " See https://developercertificate.org/ for more details." |
| 182 | + exit 1; |
| 183 | + elif ! codespell --config "$CODESPELL_RC" - <<< "$MSG"; then |
| 184 | + printf "⛔ The spelling in commit %s needs improvement.\n" "$commit" |
| 185 | + exit 1; |
| 186 | + elif grep "WIP: " <<< "$MSG"; then |
| 187 | + printf "⛔ Commit %s includes a 'WIP' marker which should be removed.\n" "$commit" |
| 188 | + exit 1; |
| 189 | + else |
| 190 | + printf "✅ Commit %s is good.\n" "$commit" |
| 191 | + fi |
| 192 | + done |
| 193 | + |
| 194 | +# Lint justfile recipes |
| 195 | +[group('ci')] |
| 196 | +[metadata('pacman', 'nodejs', 'shellcheck')] |
| 197 | +recipes: |
| 198 | + #!/usr/bin/env bash |
| 199 | + set -euo pipefail |
| 200 | + T=$(mktemp -d) |
| 201 | + node scripts/ci/export-shell.ts "$T" |
| 202 | + for file in "$T"/*.sh; do |
| 203 | + echo "Checking $file..." |
| 204 | + shellcheck --shell bash "$file" |
| 205 | + done |
| 206 | + |
| 207 | +# Fixes common issues. Files need to be git add'ed |
| 208 | +fix: |
| 209 | + #!/usr/bin/env bash |
| 210 | + set -euo pipefail |
| 211 | + if ! git diff-files --quiet ; then |
| 212 | + echo "Working tree has changes. Please stage them: git add ." |
| 213 | + exit 1 |
| 214 | + fi |
| 215 | + |
| 216 | + codespell --write-changes |
| 217 | + just --unstable --fmt |
| 218 | + # try to fix rustc issues |
| 219 | + cargo fix --allow-staged |
| 220 | + # try to fix clippy issues |
| 221 | + cargo clippy --fix --allow-staged |
| 222 | + |
| 223 | + # fmt must be last as clippy changes may break formatting |
| 224 | + cargo +nightly fmt --all |
0 commit comments