#!/usr/bin/env just --justfile
main_crate := 'map-tile-toolkit'
# How to call the current just executable. Note that just_executable() may have `\` in Windows paths, so we need to quote it.
just := quote(just_executable())
# cargo-binstall needs a workaround due to caching when used in CI
binstall_args := if env('CI', '') != '' {'--no-confirm --no-track --disable-telemetry'} else {''}
# location of the coverage output, used by CI
coverage_lcov := 'target/llvm-cov/lcov.info'
# All targets except benches (running gungraun benches needs Valgrind; see `bench`/`bench-check`).
non_bench_targets := '--lib --bins --tests --examples'
# if running in CI, treat warnings as errors by setting CARGO_BUILD_WARNINGS to 'deny' unless it is already set
# Use `CI=true just ci-test` to run the same tests as in GitHub CI.
# Use `just env-info` to see the current value of CARGO_BUILD_WARNINGS
ci_mode := if env('CI', '') != '' {'1'} else {''}
export CARGO_BUILD_WARNINGS := env('CARGO_BUILD_WARNINGS', if ci_mode == '1' {'deny'} else {'warn'})
export RUST_BACKTRACE := env('RUST_BACKTRACE', if ci_mode == '1' {'1'} else {'0'})
@_default:
{{just}} --list
# Run slicing benchmarks (gungraun/Valgrind instruction counts); needs `valgrind` + `cargo install
# gungraun-runner`. Optional substring filter, e.g. `just bench big`, `just bench single`, `just bench
# all` (passed to gungraun as the glob `*<filter>*` over benchmark module paths).
bench filter='':
cargo bench {{ if filter != '' { "-- '*" + filter + "*'" } else { '' } }}
# Run only the large-polyline benchmarks (many / a few / a single tile)
bench-big: (bench 'big')
# Report benchmark instruction counts as Markdown, vs `base_ref` if given (the CI PR comment)
ci-bench output='target/bench/summary.md' base_ref='': (assert-cmd 'valgrind') (assert-cmd 'python3')
benches/ci-bench.sh {{quote(output)}} {{quote(base_ref)}}
# Profile a `profile` example case under the `hotpath` profiler (per-function timing + allocations),
# e.g. `just hotpath big-all-multi 5000`. Set HOTPATH_OUTPUT_FORMAT=json + HOTPATH_OUTPUT_PATH for a
# machine-readable report (this is what the Hotpath Profile CI does).
hotpath name='big-all-multi' *args:
cargo run --release --example profile --features 'hotpath,hotpath-alloc' -- {{name}} {{args}}
# Flamegraph one profiling case (see `examples/profile.rs`), e.g. `just flamegraph big-all-multi 5000`
# Profile the `profile` example for one case with cargo-flamegraph, writing target/flamegraphs/<name>.svg
flamegraph name *args: (cargo-install 'cargo-flamegraph' 'flamegraph')
#!/usr/bin/env bash
set -euo pipefail
# perf may need `sudo sysctl kernel.perf_event_paranoid=1` (or run this recipe as root).
mkdir -p target/flamegraphs
svg="target/flamegraphs/{{name}}.svg"
cargo flamegraph --profile profiling --example profile --output "$svg" -- {{name}} {{args}}
echo "Wrote $svg"
# Flamegraph every profiling case into target/flamegraphs/<name>.svg (args optional; default iters per case), e.g. `just flamegraph-all 5000`
flamegraph-all: \
(flamegraph 'all') \
(flamegraph 'one') \
(flamegraph 'big-all-multi') \
(flamegraph 'big-all-few') \
(flamegraph 'big-all-single') \
(flamegraph 'big-one-multi') \
(flamegraph 'big-one-few') \
(flamegraph 'big-one-single') \
(flamegraph 'poly-all-multi') \
(flamegraph 'poly-all-huge')
# Fuzz a target with cargo-fuzz (needs nightly), e.g. `just fuzz slice_equivalence -- -max_total_time=60`
fuzz target='slice_equivalence' *args: (cargo-install 'cargo-fuzz')
# Pin the gnu host: the sanitizer is incompatible with a statically linked (musl) libc.
cargo +nightly fuzz run --target "$(rustc -vV | sed -n 's/^host: //p')" {{target}} {{args}}
# Compile-check every fuzz target (needs nightly) so they don't bit-rot
fuzz-build: (cargo-install 'cargo-fuzz')
cargo +nightly fuzz build --target "$(rustc -vV | sed -n 's/^host: //p')"
# Run integration tests and save its output as the new expected output
bless *args: (cargo-install 'cargo-insta')
cargo insta test --accept --include-ignored --unreferenced=delete --all-features {{args}}
# Build the project
build:
cargo build --workspace --all-features --all-targets
# Quick compile without building a binary
check:
cargo check --workspace --all-features --all-targets
# Generate LCOV coverage report for CI to upload to codecov.io
ci-coverage: env-info && \
(_coverage '--lcov' '--output-path' quote(coverage_lcov))
rm -rf {{quote(parent_directory(coverage_lcov))}}
mkdir -p {{quote(parent_directory(coverage_lcov))}}
# Run all tests as expected by CI
ci-test: env-info test-fmt clippy test test-doc bench-check && assert-git-is-clean
# Compile default features with minimal dependencies on the configured MSRV
ci-test-msrv:
{{just}} ci_mode=0 env-info _check-msrv-default
{{just}} assert-git-is-clean
# Set toolchain and run ci-test-msrv
ci-test-msrv-with-toolchain:
RUSTUP_TOOLCHAIN="$({{just}} get-msrv)" {{just}} ci-test-msrv
# Clean all build artifacts
clean:
cargo clean
rm -f Cargo.lock
# Run cargo clippy to lint the code
clippy *args:
cargo clippy --workspace --all-features --all-targets {{args}}
# Generate and open the HTML coverage report
coverage: (_coverage '--open')
# Clean, collect, and aggregate coverage using the requested report arguments
_coverage *report_args: (cargo-install 'cargo-llvm-cov')
#!/usr/bin/env bash
set -euo pipefail
# A full clean: `clean --workspace` keeps test binaries built with other features or sources, and
# the report would merge their stale line mappings in. It also removes the report directory.
if rustup toolchain list | grep nightly &> /dev/null; then
cargo +nightly llvm-cov clean
mkdir -p {{quote(parent_directory(coverage_lcov))}}
cargo +nightly llvm-cov --no-report --workspace --all-features {{non_bench_targets}}
cargo +nightly llvm-cov --no-report --doctests --workspace --all-features
cargo +nightly llvm-cov report --include-build-script {{report_args}}
else
cargo llvm-cov clean
mkdir -p {{quote(parent_directory(coverage_lcov))}}
cargo llvm-cov --no-report --workspace --all-features {{non_bench_targets}}
cargo llvm-cov report --include-build-script {{report_args}}
fi
# Build and open code documentation
docs *args='--open':
DOCS_RS=1 cargo doc --no-deps {{args}} --workspace --all-features
# Print environment info
env-info:
@echo "Running for '{{main_crate}}' crate {{if ci_mode == '1' {'in CI mode'} else {'in dev mode'} }} on {{os()}} / {{arch()}}"
@echo "PWD {{justfile_directory()}}"
{{just}} --version
rustc --version
cargo --version
rustup --version
@echo "CARGO_BUILD_WARNINGS='$CARGO_BUILD_WARNINGS'"
@echo "RUST_BACKTRACE='$RUST_BACKTRACE'"
# Regenerate tests/visualize.qgz (QGIS) from every GeoJSON file under tests/, keeping its view state
visualize-tests: (assert-cmd 'python3')
python3 tests/visualize_tests.py tests/visualize.qgz
@echo "--------- done --------"
@echo "Start QGIS and open this file:"
@echo "$(realpath tests/visualize.qgz)"
# Reformat all code `cargo fmt`. If nightly is available, use it for better results
fmt:
#!/usr/bin/env bash
set -euo pipefail
if (rustup toolchain list | grep nightly && rustup component list --toolchain nightly | grep rustfmt) &> /dev/null; then
echo 'Reformatting Rust code using nightly Rust fmt to sort imports'
cargo +nightly fmt --all -- --config imports_granularity=Module,group_imports=StdExternalCrate
else
echo 'Reformatting Rust with the stable cargo fmt. Install nightly with `rustup install nightly` for better results'
cargo fmt --all
fi
# Reformat all Cargo.toml files using cargo-sort
fmt-toml *args: (cargo-install 'cargo-sort')
cargo sort --workspace --grouped {{args}}
# Reformat all GeoJSON tests
fmt-geojson: (assert-cmd 'node') (assert-cmd 'npm') (assert-cmd 'jq')
#!/usr/bin/env bash
set -euo pipefail
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
npm install --silent --no-fund --no-audit --prefix "$tmp" json-stringify-pretty-compact
cat > "$tmp/fmt.mjs" <<'EOF'
import stringify from 'json-stringify-pretty-compact';
import { readFileSync, writeFileSync } from 'node:fs';
for (const p of process.argv.slice(2))
writeFileSync(p, stringify(JSON.parse(readFileSync(p, 'utf8'))) + '\n');
console.log(`Formatted ${process.argv.length - 2} GeoJSON files`);
EOF
# Round every number nested anywhere inside features[].geometry.coordinates to an integer.
round_coords() {
for p in "$@"; do
jq '.features[].geometry.coordinates |= walk(if type == "number" then round else . end)' "$p" > "$p.tmp"
mv "$p.tmp" "$p"
done
}
round_coords "{{justfile_directory()}}"/tests/polylines/{fixtures,bad-fixtures}/*.geojson
round_coords "{{justfile_directory()}}"/tests/polygons/fixtures/*.geojson
round_coords "{{justfile_directory()}}"/tests/points/fixtures/*.geojson
# The shell expands the file list (absolute paths); run node from the temp dir so the bare import
# resolves against the throwaway install.
cd "$tmp"
node fmt.mjs "{{justfile_directory()}}"/tests/polylines/{fixtures,bad-fixtures}/*.geojson
node fmt.mjs "{{justfile_directory()}}"/tests/polygons/fixtures/*.geojson
node fmt.mjs "{{justfile_directory()}}"/tests/points/fixtures/*.geojson
# Get a package field from the metadata
get-crate-field field package=main_crate: (assert-cmd 'jq')
@cargo metadata --no-deps --format-version 1 | jq -e -r '.packages | map(select(.name == "{{package}}")) | first | .{{field}} // error("Field \"{{field}}\" is missing in Cargo.toml for package {{package}}")'
# Get the minimum supported Rust version (MSRV) for the crate
get-msrv package=main_crate: (get-crate-field 'rust_version' package)
# Find the minimum supported Rust version (MSRV), update Cargo.toml, and test minimal dependencies
msrv: (cargo-install 'cargo-msrv')
cargo msrv find --write-msrv --ignore-lockfile -- {{just}} _check-msrv-default
# Compile the crate's default features using a dynamically generated minimal Cargo.lock
_check-msrv-default: (cargo-install 'cargo-minimal-versions') (cargo-install 'cargo-hack')
#!/usr/bin/env bash
set -euo pipefail
# cargo-msrv probes with rustup, but nested cargo subcommands may otherwise
# fall back to the default Cargo and emit flags unsupported by the candidate rustc.
toolchain="$(rustc --version | cut -d' ' -f2)"
export RUSTUP_TOOLCHAIN="$toolchain"
export CARGO="$(rustup which --toolchain "$toolchain" cargo)"
cargo minimal-versions check --direct --package {{main_crate}}
# Run cargo-release
release *args='': (cargo-install 'release-plz')
release-plz {{args}}
# Check semver compatibility with prior published version. Install it with `cargo install cargo-semver-checks`
semver *args: (cargo-install 'cargo-semver-checks')
cargo semver-checks --all-features {{args}}
# Run all tests (every target except benches — running gungraun benches needs Valgrind; see `bench`)
test:
cargo test --workspace --all-features {{non_bench_targets}}
cargo test --doc --workspace --all-features
# Compile-check the benchmarks without running them
bench-check:
cargo bench --no-run --workspace --all-features
# Test documentation generation
test-doc: (docs '')
# Test code formatting
test-fmt: && (fmt-toml '--check' '--check-format')
cargo fmt --all -- --check
# Find unused dependencies. Uses `cargo-udeps`
udeps: (cargo-install 'cargo-udeps')
cargo +nightly udeps --workspace --all-features --all-targets
# Update all dependencies, including breaking changes. Requires nightly toolchain (install with `rustup install nightly`)
update:
cargo +nightly -Z unstable-options update --breaking
cargo update
# Ensure that a certain command is available
[private]
assert-cmd command:
@if ! type {{command}} > /dev/null; then \
echo "Command '{{command}}' could not be found. Please make sure it has been installed on your computer." ;\
exit 1 ;\
fi
# Make sure the git repo has no uncommitted changes. Fails if CI envvar is set.
[private]
assert-git-is-clean:
#!/usr/bin/env bash
set -euo pipefail
if [ -n "$(git status --porcelain --untracked-files=all)" ]; then
>&2 echo "::error::git repo is not clean. Make sure compilation and tests artifacts are in the .gitignore, and no repo files are modified."
if [[ "{{ci_mode}}" == "1" ]]; then
>&2 echo "::group::git status"
git status
>&2 echo "::endgroup::"
>&2 echo "::group::git diff (tracked changes)"
git add . --intent-to-add
git --no-pager diff
>&2 echo "::endgroup::"
exit 1
else
>&2 echo "git repo is not clean, but not failing because CI mode is not enabled."
fi
fi
# Check if a certain Cargo command is installed, and install it if needed
[private]
cargo-install $COMMAND $INSTALL_CMD='' *args='':
#!/usr/bin/env bash
set -euo pipefail
unset CARGO_BUILD_WARNINGS
if ! command -v $COMMAND > /dev/null; then
echo "$COMMAND could not be found. Installing..."
if ! command -v cargo-binstall > /dev/null; then
set -x
cargo install ${INSTALL_CMD:-$COMMAND} --locked {{args}}
{ set +x; } 2>/dev/null
else
set -x
cargo binstall ${INSTALL_CMD:-$COMMAND} {{binstall_args}} --locked
{ set +x; } 2>/dev/null
fi
fi