#!/bin/sh # Fails if a workspace dependency declared in ROOT/Cargo.toml has no table row in # ROOT/docs/dependencies.md, or if any dependency in a crate manifest lacks # `workspace = true` (on its own line for the plain/dotted form, or as a # `workspace = true` line inside a table-form dependency). A table header such as # `[dependencies.x]` introduces dependency x; the lines that follow it until the # next section header are settings of x, never dependencies themselves. ROOT="${1:-.}" status=0 # Fail closed. if [ ! -d "$ROOT/crates" ]; then echo "check-dep-docs: $ROOT/crates is not a directory" >&2 exit 1 fi if [ ! -f "$ROOT/Cargo.toml" ]; then echo "check-dep-docs: $ROOT/Cargo.toml is not a file" >&2 exit 1 fi if [ ! -f "$ROOT/docs/dependencies.md" ]; then echo "check-dep-docs: $ROOT/docs/dependencies.md is not a file" >&2 exit 1 fi # Workspace crate names: directories under ROOT/crates that contain a Cargo.toml. workspace="" for d in "$ROOT"/crates/*/; do [ -d "$d" ] || continue [ -f "$d/Cargo.toml" ] || continue workspace="$workspace $(basename "$d")" done root_toml="$ROOT/Cargo.toml" docs="$ROOT/docs/dependencies.md" # Part 1: non-workspace-crate workspace dependencies in ROOT/Cargo.toml must have a docs row. dep_keys=$(awk ' BEGIN { in_deps = 0; in_table = 0; depth = 0 } /^[ \t]*\[/ { hdr = $0 sub(/^[ \t]*\[/, "", hdr) sub(/[ \t]*\][ \t]*.*$/, "", hdr) if (hdr ~ /dependencies/) { n = split(hdr, parts, ".") if (parts[n] ~ /dependencies$/) { in_deps = 1; in_table = 0 } else { in_deps = 1; in_table = 1; print parts[n] } } else { in_deps = 0; in_table = 0 } depth = 0 next } in_table { next } in_deps { if (depth == 0) { t = $0 sub(/^[ \t]+/, "", t) if (match(t, /^[A-Za-z0-9][A-Za-z0-9_-]*(\.[A-Za-z0-9_-]+)*[ \t]*=/)) { name = t sub(/[ \t]*=.*/, "", name) sub(/\..*/, "", name) print name } } s = $0 while (match(s, /[{}]/)) { c = substr(s, RSTART, 1) if (c == "{") depth++ else if (depth > 0) depth-- s = substr(s, RSTART + 1) } } ' "$root_toml") for key in $dep_keys; do hit=0 for w in $workspace; do if [ "$key" = "$w" ]; then hit=1; break; fi done if [ "$hit" -eq 0 ]; then if ! awk -v k="$key" 'index($0, "| `" k "` |") == 1 { found = 1; exit } END { exit !found }' "$docs"; then echo "check-dep-docs: $key has no row in $docs" >&2 status=1 fi fi done # Part 2: every dependency in a crate manifest must have `workspace = true`. for crate in "$ROOT"/crates/*/; do [ -d "$crate" ] || continue crate_name=$(basename "$crate") [ -f "$crate/Cargo.toml" ] || continue bad=$(awk -v cn="$crate_name" ' BEGIN { in_deps = 0; in_table = 0; cur = ""; has_ws = 0; depth = 0 } /^[ \t]*\[/ { if (in_table && !has_ws) printf "check-dep-docs: %s table-form dependency %s has no `workspace = true` line\n", cn, cur in_table = 0 hdr = $0 sub(/^[ \t]*\[/, "", hdr) sub(/[ \t]*\][ \t]*.*$/, "", hdr) if (hdr ~ /dependencies/) { n = split(hdr, parts, ".") if (parts[n] ~ /dependencies$/) { in_deps = 1 } else { in_deps = 1; in_table = 1; cur = parts[n]; has_ws = 0 } } else { in_deps = 0 } depth = 0 next } in_table { if ($0 ~ /workspace[ \t]*=[ \t]*true/) has_ws = 1 next } in_deps { if (depth == 0) { t = $0 sub(/^[ \t]+/, "", t) if (match(t, /^[A-Za-z0-9][A-Za-z0-9_-]*(\.[A-Za-z0-9_-]+)*[ \t]*=/)) { if ($0 !~ /workspace[ \t]*=[ \t]*true/) printf "check-dep-docs: %s declares a dependency without `workspace = true`: %s\n", cn, $0 } } s = $0 while (match(s, /[{}]/)) { c = substr(s, RSTART, 1) if (c == "{") depth++ else if (depth > 0) depth-- s = substr(s, RSTART + 1) } } END { if (in_table && !has_ws) printf "check-dep-docs: %s table-form dependency %s has no `workspace = true` line\n", cn, cur } ' "$crate/Cargo.toml") if [ -n "$bad" ]; then printf '%s\n' "$bad" >&2 status=1 fi done exit $status