#!/usr/bin/env -S just --working-directory . --justfile
clippy := "cargo clippy --quiet --workspace --no-deps --all-targets"
clippy_args := "-D warnings"
nextest_args := "--locked --workspace"
udeps_args := "--quiet --workspace --all-features --all-targets"
# Perform all checks
[parallel]
check: spell fmt fmt-readme doc lints deps unused-deps recipes test integration-test
# Check spelling
[group('ci')]
[metadata('pacman', 'codespell')]
spell:
codespell
# Check source code formatting
[group('ci')]
[metadata('pacman', 'rustup')]
fmt:
just --unstable --fmt --check
# We're using nightly to properly group imports, see .rustfmt.toml
rustup component add --toolchain nightly rustfmt
cargo +nightly fmt --quiet --all -- --check
# Check formatting of code blocks in README.md
[group('ci')]
[metadata('pacman', 'ruff')]
fmt-readme:
ruff format --preview --check README.md
# Lint the source code
[group('ci')]
[metadata('gitlabci-job', '{"artifacts":{"when":"always","paths":["target/clippy"]}}')]
[metadata('pacman', 'rust', 'python')]
lints:
#!/usr/bin/bash
set -euo pipefail
if [ "${CI:-}" = "true" ]; then
ARGS=(--message-format=json)
else
ARGS=()
fi
mkdir -p target
{{ clippy }} "${ARGS[@]}" -- {{ clippy_args }} | tee target/clippy
# Create lints report
[metadata('gitlabci-job', '{"stage":"deploy","when":"always","artifacts":{"reports":{"codequality":"target/codeclimate.json"}}}')]
[metadata('pacman', 'cargo-sonar', 'rust')]
create-codeclimate:
# deny reports can be tested by adding a yanked dep e.g. `cargo add ed25519-dalek@0.9.0`
cargo codeclimate \
--clippy --clippy-path "target/clippy" \
--deny --deny-path "target/deny" \
--udeps --udeps-path "target/udeps" \
--codeclimate-path target/codeclimate.json
# Check for issues with dependencies
[group('ci')]
[metadata('gitlabci-job', '{"artifacts":{"when":"always","paths":["target/deny"]}}')]
[metadata('pacman', 'cargo-deny', 'git')]
deps:
#!/usr/bin/bash
set -euo pipefail
if [ "${CI:-}" = "true" ]; then
ARGS=(--format json)
else
ARGS=()
fi
mkdir -p target
cargo deny "${ARGS[@]}" check -D advisory-not-detected -D license-not-encountered -D no-license-field 2>&1 | tee target/deny
# Check for unused dependencies
[group('ci')]
[metadata('gitlabci-job', '{"artifacts":{"when":"always","paths":["target/udeps"]}}')]
[metadata('pacman', 'cargo-machete', 'cargo-udeps', 'rust', 'python')]
unused-deps:
#!/usr/bin/bash
set -euo pipefail
if [ "${CI:-}" = "true" ]; then
ARGS=(--output json)
else
ARGS=()
fi
mkdir -p target
cargo +nightly udeps {{ udeps_args }} "${ARGS[@]}" | tee target/udeps
cargo +nightly machete
# Run unit tests
[metadata('pacman', 'cargo-nextest')]
test:
#!/usr/bin/bash
set -euxo pipefail
if [ "${CI:-}" = "true" ]; then
PROFILE=ci
else
PROFILE=default
fi
cargo +nightly nextest run {{ nextest_args }} --profile "$PROFILE"
cargo +nightly nextest run --no-default-features {{ nextest_args }} --profile "$PROFILE"
# Run integration tests
[metadata('pacman', 'git', 'jq', 'openssh', 'tangler', 'tree')]
integration-test:
#!/usr/bin/bash
set -euo pipefail
cargo +nightly build --locked
target=$(cargo +nightly metadata --format-version 1 | jq --raw-output '.target_directory')
tangler sh < README.md | sed --quiet --regexp-extended 's/^\$ (.*)/\1/p' | PATH="$target/debug:$PATH" bash -euxo pipefail -
# Report on all tests
[group('ci')]
[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"}}}}')]
[metadata('pacman', 'rust', 'cargo-llvm-cov', 'rustup', 'python')]
report-test:
#!/usr/bin/bash
# enabling "x" here will garble text output that's parsed by GitLab for code coverage
set -euo pipefail
rustup component add --toolchain nightly llvm-tools-preview
# shellcheck disable=SC1090
source <(cargo +nightly llvm-cov show-env --export-prefix --doctests --branch)
cargo +nightly llvm-cov clean
just test integration-test
# explicitly use "target" (even if CARGO_TARGET_DIR is somewhere else) so that
# local tools (such as https://github.com/ryanluker/vscode-coverage-gutters) can find the file
cargo +nightly llvm-cov --quiet report --cobertura --output-path target/coverage.xml > /dev/null 2>&1
LINE_RATE=$(head target/coverage.xml | sed -nE 's/(.*coverage.*line-rate=")([^"]*)".*/\2/p')
LINE_PERCENT=$(echo "$LINE_RATE" | awk '{print $1 * 100}')
printf 'Line coverage: %s%%\n' "$LINE_PERCENT"
BRANCH_RATE=$(head target/coverage.xml | sed -nE 's/(.*coverage.*branch-rate=")([^"]*)".*/\2/p')
printf 'line_coverage_ratio %s\nbranch_coverage_ratio %s\n' "$LINE_RATE" "$BRANCH_RATE" > target/metrics.txt
# Generate HTML report for the coverage
coverage-html-report: report-test
#!/usr/bin/bash
set -euo pipefail
# shellcheck disable=SC1090
source <(cargo +nightly llvm-cov show-env --export-prefix)
cargo +nightly llvm-cov --quiet report --html > /dev/null 2>&1
printf "The coverage report is in file://%s/llvm-cov/html/index.html\n" "${CARGO_TARGET_DIR:-target}"
# Build docs
[group('ci')]
[metadata('pacman', 'rust', 'python')]
doc:
RUSTDOCFLAGS='-D warnings' cargo doc --quiet --no-deps --document-private-items
# Check commit messages
[metadata('pacman', 'codespell', 'git')]
commits:
#!/usr/bin/env bash
set -Eeuo pipefail
# fetch default branch if it is set
if [[ -v CI_DEFAULT_BRANCH ]]; then
git fetch origin "$CI_DEFAULT_BRANCH"
refs="origin/$CI_DEFAULT_BRANCH"
else
refs="main"
fi
commits=$(git rev-list "${refs}..")
for commit in $commits; do
MSG="$(git show -s --format=%B "$commit")"
CODESPELL_RC="$(mktemp)"
git show "$commit:.codespellrc" > "$CODESPELL_RC"
if ! grep -q "Signed-off-by: " <<< "$MSG"; then
printf "⛔ Commit %s lacks \"Signed-off-by\" line.\n" "$commit"
printf "%s\n" \
" Please use:" \
" git rebase --signoff main && git push --force-with-lease" \
" See https://developercertificate.org/ for more details."
exit 1;
elif ! codespell --config "$CODESPELL_RC" - <<< "$MSG"; then
printf "⛔ The spelling in commit %s needs improvement.\n" "$commit"
exit 1;
elif grep "WIP: " <<< "$MSG"; then
printf "⛔ Commit %s includes a 'WIP' marker which should be removed.\n" "$commit"
exit 1;
else
printf "✅ Commit %s is good.\n" "$commit"
fi
done
# Lint justfile recipes
[group('ci')]
[metadata('pacman', 'nodejs', 'shellcheck')]
recipes:
#!/usr/bin/env bash
set -euo pipefail
T=$(mktemp -d)
node scripts/ci/export-shell.ts "$T"
for file in "$T"/*.sh; do
echo "Checking $file..."
shellcheck --shell bash "$file"
done
# Fixes common issues. Files need to be git add'ed
[metadata('pacman', 'codespell', 'ruff', 'cargo')]
fix:
#!/usr/bin/env bash
set -euo pipefail
if ! git diff-files --quiet ; then
echo "Working tree has changes. Please stage them: git add ."
exit 1
fi
codespell --write-changes
ruff format --preview README.md
just --unstable --fmt
# try to fix rustc issues
cargo fix --allow-staged --allow-dirty
# try to fix clippy issues
cargo clippy --fix --allow-staged --allow-dirty
# fmt must be last as clippy changes may break formatting
cargo +nightly fmt --all