Splux Linux

source-based Linux using SPS

SPS · log · files · refs · tags · releases · people · languages · GitHub · commit

ab9f886


src: add 'check' command to validate recipes without building

Date
Commitab9f88607b771080bf900555d0ad488518726e7c
GitHubmirror

Parents

Stat

 bin/src                 | 121 ++++++++++++++++++++++++++++++++++++++++++++++++
 man/src.1               |  13 ++++++
 tests/test_src_check.sh |  97 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 231 insertions(+)

Diff

diff --git a/bin/src b/bin/src
index f91cc16..c7a8531 100755
--- a/bin/src
+++ b/bin/src
@@ -24,6 +24,7 @@ sps_tab=${sps_tab%X}
 src_usage()
 {
     printf '%s\n' 'usage: src update'
+    printf '%s\n' '       src check RECIPE... [--deps]'
     printf '%s\n' '       src search PATTERN [--raw]'
     printf '%s\n' '       src show PACKAGE [--raw]'
     printf '%s\n' '       src which PACKAGE'
@@ -407,11 +408,131 @@ src_query()
         -v program=src -f "$SPS_LIBDIR/index.awk" "$sps_index"
 }
 
+# src_check validates one or more recipes with the same parser that src update
+# uses, without downloading sources or running any build phase. It accepts a
+# recipe file or a directory containing a 'recipe' file. A failed recipe is
+# reported and the command exits nonzero, so it is safe to gate automation on.
+src_check()
+{
+    src_check_deps=0
+    src_check_recipes=
+    while [ "$#" -gt 0 ]; do
+        case $1 in
+            --deps)
+                src_check_deps=1
+                ;;
+            --help|-h)
+                printf '%s\n' 'usage: src check RECIPE... [--deps]'
+                exit 0
+                ;;
+            -*)
+                src_usage >&2
+                exit "$SPS_EX_USAGE"
+                ;;
+            *)
+                if [ -d "$1" ] && [ ! -L "$1" ]; then
+                    src_check_resolve="$1/recipe"
+                else
+                    src_check_resolve=$1
+                fi
+                if [ ! -f "$src_check_resolve" ] ||
+                   [ -L "$src_check_resolve" ] ||
+                   [ ! -r "$src_check_resolve" ]; then
+                    sps_die "$SPS_EX_NOTFOUND" \
+                        "no readable recipe file at '$1'"
+                fi
+                src_check_recipes="$src_check_recipes $src_check_resolve"
+                ;;
+        esac
+        shift
+    done
+    [ -n "$src_check_recipes" ] || {
+        printf '%s\n' 'usage: src check RECIPE... [--deps]' >&2
+        exit "$SPS_EX_USAGE"
+    }
+
+    if [ "$src_check_deps" -eq 1 ]; then
+        src_check_work=$(sps_mktemp_dir "$SPS_BUILD/.check") || exit $?
+        src_check_index=$src_check_work/packages.index
+    else
+        src_check_index=/dev/null
+    fi
+    src_check_failed=0
+    src_check_count=0
+    src_check_arch=${SPS_ARCH:-$(uname -m 2>/dev/null || printf unknown)}
+    for src_check_recipe in $src_check_recipes; do
+        [ -n "$src_check_recipe" ] || continue
+        src_check_count=$((src_check_count + 1))
+        src_check_digest=$(sps_definition_sha256 "$src_check_recipe") || {
+            printf 'check: FAILED %s (definition digest)\n' \
+                "$src_check_recipe" >&2
+            src_check_failed=1
+            continue
+        }
+        src_check_name=$(awk -v recipe_path="$src_check_recipe" '
+            /^[ \t]*name[ \t]+/ {
+                line = $0
+                sub(/^[ \t]*name[ \t]+/, "", line)
+                sub(/[ \t\r]+$/, "", line)
+                print line
+                exit
+            }
+        ' "$src_check_recipe")
+        if [ "$src_check_deps" -eq 1 ]; then
+            src_check_target=$src_check_index
+        else
+            src_check_target=/dev/null
+        fi
+        if awk -v recipe_repo=check -v recipe_priority=100 \
+                -v recipe_path="$src_check_recipe" \
+                -v recipe_sha256="$src_check_digest" \
+                -v recipe_default_arch="$src_check_arch" \
+                -f "$SPS_LIBDIR/recipe.awk" "$src_check_recipe" \
+                >> "$src_check_target" 2>/dev/null; then
+            printf 'check: ok   %s (%s)\n' "$src_check_recipe" \
+                "$src_check_name"
+        else
+            printf 'check: FAILED %s (%s)\n' "$src_check_recipe" \
+                "$src_check_name" >&2
+            src_check_failed=1
+        fi
+    done
+
+    if [ "$src_check_failed" -ne 0 ]; then
+        rm -f "$src_check_index" 2>/dev/null || :
+        rmdir "$src_check_work" 2>/dev/null || :
+        sps_die "$SPS_EX_PACKAGE" \
+            "$src_check_count recipe(s) checked: at least one failed"
+    fi
+
+    if [ "$src_check_deps" -eq 1 ]; then
+        if ! awk -v action=validate -v program=src \
+                -f "$SPS_LIBDIR/index.awk" "$src_check_index"; then
+            rmdir "$src_check_work" 2>/dev/null || :
+            sps_die "$SPS_EX_PACKAGE" "generated check index is malformed"
+        fi
+        if ! awk -v action=validate -v program=src \
+                -f "$SPS_LIBDIR/version.awk" -f "$SPS_LIBDIR/deps.awk" \
+                "$src_check_index"; then
+            rmdir "$src_check_work" 2>/dev/null || :
+            sps_die "$SPS_EX_DEPENDENCY" "checked dependency graph is invalid"
+        fi
+        rm -f "$src_check_index" 2>/dev/null || :
+        rmdir "$src_check_work" 2>/dev/null || :
+    fi
+
+    printf 'checked %d recipe(s)\n' "$src_check_count"
+}
+
 case ${1-} in
     update)
         shift
         src_update "$@"
         ;;
+    check)
+        shift
+        src_check "$@"
+        ;;
     search|show|which)
         src_command=$1
         shift
diff --git a/man/src.1 b/man/src.1
index a4a1e26..7e5ca88 100644
--- a/man/src.1
+++ b/man/src.1
@@ -4,6 +4,7 @@ src \- update and query SPS source repository indexes
 .SH SYNOPSIS
 .nf
 src update
+src check RECIPE... [--deps]
 src search PATTERN [--raw]
 src show PACKAGE [--raw]
 src which PACKAGE
@@ -23,6 +24,18 @@ fast-forward safe tracking branches, scan configured trees, validate recipes
 and the selected dependency graph, and atomically replace the local package
 index. Invalid input leaves the previous index in place.
 .TP
+.BI check " recipe ..."
+Validate each recipe with the same parser used by
+.B update
+without downloading sources or running any build phase. Each argument is a
+recipe file or a directory containing a
+.B recipe
+file. Failed recipes are reported and the command exits nonzero, so it is safe
+to gate automation on it. With
+.B --deps
+a temporary index of the checked recipes is also validated as a dependency
+graph.
+.TP
 .BI search " pattern"
 Search indexed names and descriptions.
 .TP
diff --git a/tests/test_src_check.sh b/tests/test_src_check.sh
new file mode 100644
index 0000000..aee1ccc
--- /dev/null
+++ b/tests/test_src_check.sh
@@ -0,0 +1,97 @@
+#!/bin/sh
+set -eu
+
+case $0 in */*) test_dir=${0%/*} ;; *) test_dir=. ;; esac
+project_dir=$(CDPATH= cd "$test_dir/.." 2>/dev/null && pwd)
+src=$project_dir/bin/src
+
+fail()
+{
+    printf 'test_src_check: %s\n' "$*" >&2
+    exit 1
+}
+
+expect_status()
+{
+    expected=$1
+    shift
+    set +e
+    "$@" >"$tmp/out" 2>"$tmp/err"
+    got=$?
+    set -e
+    [ "$got" -eq "$expected" ] ||
+        fail "expected status $expected, got $got for: $*"
+}
+
+tmp=$(mktemp -d "${TMPDIR:-/tmp}/sps-src-check.XXXXXX")
+trap 'rm -rf "$tmp"' 0 1 2 3 15
+mkdir -p "$tmp/root" "$tmp/cache" "$tmp/db" "$tmp/build" "$tmp/ok" "$tmp/bad" \
+    "$tmp/missing-dep"
+
+SPS_ROOT=$tmp/root
+SPS_DB=$tmp/db
+SPS_CACHE=$tmp/cache
+SPS_BUILD=$tmp/build
+SPS_CONFIG=/dev/null
+SPS_REPOS_CONFIG=/dev/null
+export SPS_ROOT SPS_DB SPS_CACHE SPS_BUILD SPS_CONFIG SPS_REPOS_CONFIG
+
+# A complete, valid recipe passes.
+cat >"$tmp/ok/recipe" <<'EOF'
+name        validpkg
+version     1.0
+release     2
+description A complete, valid test recipe
+source      https://example.invalid/validpkg-1.0.tar.gz
+hash        sha256:0000000000000000000000000000000000000000000000000000000000000000
+install     true
+EOF
+expect_status 0 "$src" check "$tmp/ok"
+$src check "$tmp/ok" >/dev/null 2>&1 || fail "valid recipe rejected"
+$src check "$tmp/ok/recipe" | grep -q '^check: ok ' ||
+    fail "valid recipe was not reported ok"
+
+# A directory argument resolves to its recipe file.
+$src check "$tmp/ok/recipe" >/dev/null 2>&1 || fail "file recipe rejected"
+
+# Missing the required install command is a syntax failure.
+cat >"$tmp/bad/recipe" <<'EOF'
+name        badpkg
+version     1.0
+release     1
+description Recipe missing its install command
+EOF
+expect_status 4 "$src" check "$tmp/bad"
+
+# A nonexistent path is reported and exits with the not-found status.
+expect_status 3 "$src" check "$tmp/nonexistent"
+
+# --help exits cleanly.
+expect_status 0 "$src" check --help
+
+# An unknown option is a usage error.
+expect_status 2 "$src" check --no-such-option "$tmp/ok"
+
+# --deps validates a self-consistent graph.
+cat >"$tmp/missing-dep/recipe" <<'EOF'
+name        needsnothing
+version     1
+release     1
+description No dependencies to declare
+install     true
+EOF
+expect_status 0 "$src" check --deps "$tmp/missing-dep"
+
+# --deps flags an unresolvable declared dependency.
+cat >"$tmp/bad/deps" <<'EOF'
+name        needsghost
+version     1
+release     1
+description Declares a dependency that is not in the checked set
+depend      ghost-pkg
+install     true
+EOF
+mv "$tmp/bad/deps" "$tmp/bad/recipe2"
+expect_status 5 "$src" check --deps "$tmp/bad/recipe2"
+
+printf '%s\n' 'test_src_check: ok'