Splux Linux

source-based Linux using SPS

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

bin/src


Shell

25104 bytes at 2909919 · GitHub

#!/bin/sh

sps_prog=src
sps_bin_dir=$(CDPATH= cd -P "$(dirname "$0")" 2>/dev/null && pwd) ||
    exit 1
if [ -z "${SPS_LIBDIR+x}" ]; then
    if [ -r "$sps_bin_dir/../lib/common.sh" ]; then
        SPS_LIBDIR=$sps_bin_dir/../lib
    else
        SPS_LIBDIR=$sps_bin_dir/../lib/sps
    fi
fi
if ! . "$SPS_LIBDIR/common.sh"; then
    printf '%s\n' "src: cannot load $SPS_LIBDIR/common.sh" >&2
    exit 1
fi
sps_load_config || exit $?

sps_index_dir=$SPS_CACHE/indexes
sps_index=$sps_index_dir/packages.index
sps_tab=$(printf '\tX')
sps_tab=${sps_tab%X}

src_quiet=0

src_usage()
{
    printf '%s\n' 'usage: src [-q|--quiet] 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'
    printf '%s\n' '       src list [--raw]'
    printf '%s\n' '       src status [--raw]'
}

src_progress()
{
    [ "${src_quiet:-0}" -eq 0 ] || return 0
    printf '%s: %s\n' "$sps_prog" "$*" >&2
}

# True when the package directory has files/, patches/, or hooks/ (including
# a dangling symlink of those names). Those still need the full definition
# hasher; recipe-only packages can share one sha256sum batch.
src_recipe_needs_support()
{
	src_support_dir=$1
	for src_support_name in files patches hooks; do
		if [ -e "$src_support_dir/$src_support_name" ] ||
		   [ -L "$src_support_dir/$src_support_name" ]; then
			return 0
		fi
	done
	return 1
}

# Print hashing progress. Small repositories still log every recipe; large
# ones log the first, last, and every 25th so stderr stays useful without
# becoming the slow path.
src_progress_hash()
{
	src_ph_repo=$1
	src_ph_n=$2
	src_ph_total=$3
	src_ph_label=$4
	if [ "$src_ph_total" -le 8 ] ||
	   [ "$src_ph_n" -eq 1 ] ||
	   [ "$src_ph_n" -eq "$src_ph_total" ] ||
	   [ $((src_ph_n % 25)) -eq 0 ]; then
		src_progress "$src_ph_repo $src_ph_n/$src_ph_total $src_ph_label"
	fi
}

src_need_index()
{
    [ -r "$sps_index" ] ||
        sps_die "$SPS_EX_NOTFOUND" "repository index not found; run 'src update'"
}

src_cleanup_update()
{
	if [ -n "${src_clone_work:-}" ]; then
		rm -rf "$src_clone_work"
		src_clone_work=
	fi
	if [ -n "${src_work:-}" ]; then
		rm -f "$src_work/repos.raw" "$src_work/repos" \
		      "$src_work/recipes.unsorted" "$src_work/recipes" \
		      "$src_work/simple" "$src_work/simple.raw" \
		      "$src_work/digests" "$src_work/packages.index"
		rmdir "$src_work" 2>/dev/null || :
		src_work=
	fi
	sps_lock_release
}

src_git_verify_checkout()
{
	src_verify_checkout=$1
	src_verify_source=$2

	[ -d "$src_verify_checkout" ] && [ ! -L "$src_verify_checkout" ] ||
		return 1
	src_verify_top=$(git -C "$src_verify_checkout" rev-parse --show-toplevel \
		2>/dev/null) || return 1
	src_verify_top=$(CDPATH= cd -P "$src_verify_top" 2>/dev/null && pwd -P) ||
		return 1
	src_verify_expected=$(CDPATH= cd -P "$src_verify_checkout" 2>/dev/null &&
		pwd -P) || return 1
	[ "$src_verify_top" = "$src_verify_expected" ] || return 1
	src_verify_origin=$(git -C "$src_verify_checkout" remote get-url origin \
		2>/dev/null) || return 1
	[ "$src_verify_origin" = "$src_verify_source" ]
}

src_sync_git()
{
	src_git_name=$1
	src_git_source=$2
	src_git_checkout=$3
	sps_require_cmd git

	src_git_parent=${src_git_checkout%/*}
	[ -n "$src_git_parent" ] || src_git_parent=/
	sps_mkdir "$src_git_parent"
	GIT_TERMINAL_PROMPT=0
	export GIT_TERMINAL_PROMPT

	if [ ! -e "$src_git_checkout" ] && [ ! -L "$src_git_checkout" ]; then
		src_clone_work=$(sps_mktemp_dir "$src_git_parent" \
			"clone-$src_git_name")
		src_clone_checkout=$src_clone_work/repository
		src_progress "cloning '$src_git_name'"
		# Depth 1 is enough for recipes. Later fetches stay full so
		# fast-forward and divergence checks keep working.
		# --no-tags skips tag objects that would otherwise bloat fetch
		# on remotes with a long tag history. Branch tips are still
		# fetched. --progress keeps clone/fetch from looking hung when
		# stderr is not a terminal (setup, logs, scripted live installs).
		if ! git clone --depth 1 --single-branch --no-tags --progress -- \
			"$src_git_source" "$src_clone_checkout"; then
			sps_die "$SPS_EX_NETWORK" \
				"cannot clone repository '$src_git_name'"
		fi
		if ! src_git_verify_checkout "$src_clone_checkout" "$src_git_source"; then
			sps_die "$SPS_EX_PACKAGE" \
				"cloned repository '$src_git_name' failed checkout verification"
		fi
		if [ -e "$src_git_checkout" ] || [ -L "$src_git_checkout" ]; then
			sps_die "$SPS_EX_CONFLICT" \
				"repository checkout appeared while cloning: $src_git_checkout"
		fi
		if ! mv "$src_clone_checkout" "$src_git_checkout"; then
			sps_die "$SPS_EX_PERMISSION" \
				"cannot commit repository checkout: $src_git_checkout"
		fi
		rmdir "$src_clone_work" 2>/dev/null || :
		src_clone_work=
		printf "%s: cloned repository '%s' into %s\n" \
			"$sps_prog" "$src_git_name" "$src_git_checkout" >&2
		return 0
	fi

	if ! src_git_verify_checkout "$src_git_checkout" "$src_git_source"; then
		if [ -d "$src_git_checkout" ] && [ ! -L "$src_git_checkout" ] &&
		   git -C "$src_git_checkout" rev-parse --is-inside-work-tree \
			>/dev/null 2>&1; then
			sps_die "$SPS_EX_CONFLICT" \
				"repository '$src_git_name' origin does not match its configured source"
		fi
		sps_die "$SPS_EX_CONFLICT" \
			"repository checkout is not the expected Git worktree: $src_git_checkout"
	fi

	src_git_dirty=$(git -C "$src_git_checkout" status --porcelain \
		--untracked-files=normal) ||
		sps_die "$SPS_EX_PACKAGE" \
			"cannot inspect repository '$src_git_name'"
	if [ -n "$src_git_dirty" ]; then
		sps_die "$SPS_EX_CONFLICT" \
			"repository '$src_git_name' has local modifications; refusing to update $src_git_checkout"
	fi

	src_progress "fetching '$src_git_name'"
	if ! git -C "$src_git_checkout" fetch --no-tags --progress origin; then
		sps_die "$SPS_EX_NETWORK" \
			"cannot fetch repository '$src_git_name'"
	fi

	if ! src_git_branch=$(git -C "$src_git_checkout" symbolic-ref -q \
		--short HEAD 2>/dev/null); then
		printf "%s: repository '%s' is detached and remains pinned at %s\n" \
			"$sps_prog" "$src_git_name" \
			"$(git -C "$src_git_checkout" rev-parse --short HEAD)" >&2
		return 0
	fi
	if ! src_git_upstream=$(git -C "$src_git_checkout" rev-parse \
		--abbrev-ref --symbolic-full-name '@{upstream}' 2>/dev/null); then
		printf "%s: repository '%s' branch '%s' has no upstream and remains pinned\n" \
			"$sps_prog" "$src_git_name" "$src_git_branch" >&2
		return 0
	fi
	case $src_git_upstream in
		origin/*) ;;
		*)
			printf "%s: repository '%s' tracks '%s', not origin, and remains pinned\n" \
				"$sps_prog" "$src_git_name" "$src_git_upstream" >&2
			return 0
			;;
	esac

	src_git_head=$(git -C "$src_git_checkout" rev-parse HEAD) ||
		sps_die "$SPS_EX_PACKAGE" "cannot read HEAD for repository '$src_git_name'"
	src_git_upstream_head=$(git -C "$src_git_checkout" rev-parse \
		"$src_git_upstream") ||
		sps_die "$SPS_EX_PACKAGE" \
			"cannot read upstream for repository '$src_git_name'"
	if [ "$src_git_head" = "$src_git_upstream_head" ]; then
		src_progress "repository '$src_git_name' is up to date"
		return 0
	fi
	if git -C "$src_git_checkout" merge-base --is-ancestor \
		"$src_git_head" "$src_git_upstream_head"; then
		src_progress "fast-forwarding '$src_git_name'"
		if ! git -C "$src_git_checkout" merge --ff-only \
			"$src_git_upstream" >&2; then
			sps_die "$SPS_EX_CONFLICT" \
				"cannot fast-forward repository '$src_git_name'"
		fi
		return 0
	fi
	if git -C "$src_git_checkout" merge-base --is-ancestor \
		"$src_git_upstream_head" "$src_git_head"; then
		printf "%s: repository '%s' is ahead of %s and was left unchanged\n" \
			"$sps_prog" "$src_git_name" "$src_git_upstream" >&2
		return 0
	fi
	sps_die "$SPS_EX_CONFLICT" \
		"repository '$src_git_name' has diverged from $src_git_upstream; refusing to update"
}

src_update()
{
    while [ "$#" -gt 0 ]; do
        case $1 in
            -q|--quiet) src_quiet=1; shift ;;
            -v|--verbose) src_quiet=0; shift ;;
            *) break ;;
        esac
    done
    [ "$#" -eq 0 ] || {
        src_usage >&2
        exit "$SPS_EX_USAGE"
    }

	src_progress "updating package index"
	sps_mkdir "$sps_index_dir"
	src_work=
	src_clone_work=
	sps_lock_acquire "$SPS_CACHE" .src-update 'repository update' ||
		sps_die "$SPS_EX_CONFLICT" "another repository update is active"
	trap src_cleanup_update 0
	trap 'exit 129' 1
	trap 'exit 130' 2
	trap 'exit 131' 3
	trap 'exit 143' 15
	src_work=$(sps_mktemp_dir "$sps_index_dir" update)

    if ! sps_repo_lines > "$src_work/repos.raw"; then
        sps_die "$SPS_EX_USAGE" "cannot read repository configuration"
    fi
	if ! awk -v action=normalize -v program=src \
		-v repo_root="$SPS_REPO_ROOT" \
		-f "$SPS_LIBDIR/repository.awk" "$src_work/repos.raw" \
        > "$src_work/repos"; then
        sps_die "$SPS_EX_USAGE" "invalid repository configuration"
    fi
    : > "$src_work/packages.index" ||
        sps_die "$SPS_EX_PERMISSION" "cannot create repository index"
    : > "$src_work/digests" ||
        sps_die "$SPS_EX_PERMISSION" "cannot create recipe digest map"

    src_default_arch=$SPS_ARCH
    src_repo_count=0
    src_package_count=0
    src_failed=0

	while IFS="$sps_tab" read -r src_repo_kind src_repo_name \
	      src_repo_source src_repo_path src_repo_priority src_repo_order
	do
		[ -n "$src_repo_name" ] || continue
		src_progress "repository '$src_repo_name' ($src_repo_kind)"
		case $src_repo_kind in
			git) src_sync_git "$src_repo_name" "$src_repo_source" "$src_repo_path" ;;
			dir) ;;
			*)
				printf "%s: internal error: unsupported repository kind '%s'\n" \
					"$sps_prog" "$src_repo_kind" >&2
				src_failed=1
				break
				;;
		esac
        if [ ! -d "$src_repo_path" ]; then
            printf "%s: repository '%s' is not a local directory: %s\n" \
                "$sps_prog" "$src_repo_name" "$src_repo_path" >&2
            src_failed=1
            break
        fi
        src_repo_absolute=$(CDPATH= cd -P "$src_repo_path" 2>/dev/null &&
                            pwd -P) || {
            printf "%s: cannot enter repository '%s': %s\n" \
                "$sps_prog" "$src_repo_name" "$src_repo_path" >&2
            src_failed=1
            break
        }

		src_progress "scanning recipes in '$src_repo_name'"
		if ! find "$src_repo_absolute" \
			\( -type d \( -name .git -o -name files -o -name patches -o \
				-name hooks \) -prune \) -o \
			\( -type f -name recipe -print \) \
			> "$src_work/recipes.unsorted"; then
            printf "%s: cannot scan repository '%s': %s\n" \
                "$sps_prog" "$src_repo_name" "$src_repo_absolute" >&2
            src_failed=1
            break
        fi
        if ! LC_ALL=C sort "$src_work/recipes.unsorted" \
            > "$src_work/recipes"; then
            printf "%s: cannot sort recipes for repository '%s'\n" \
                "$sps_prog" "$src_repo_name" >&2
            src_failed=1
            break
        fi

        : > "$src_work/digests" || {
            printf "%s: cannot create recipe digest map\n" "$sps_prog" >&2
            src_failed=1
            break
        }
        : > "$src_work/simple" || {
            printf "%s: cannot create recipe hash list\n" "$sps_prog" >&2
            src_failed=1
            break
        }
        src_repo_packages=0
        src_recipe_total=$(awk 'END { print NR + 0 }' "$src_work/recipes")
        src_progress "indexing '$src_repo_name' ($src_recipe_total recipes)"
        while IFS= read -r src_recipe || [ -n "$src_recipe" ]
        do
            [ -n "$src_recipe" ] || continue
            if src_recipe_needs_support "${src_recipe%/*}"; then
                continue
            fi
            printf '%s\n' "$src_recipe" >> "$src_work/simple" || {
                printf "%s: cannot record recipe hash list: %s\n" \
                    "$sps_prog" "$src_recipe" >&2
                src_failed=1
                break
            }
        done < "$src_work/recipes"
        [ "$src_failed" -eq 0 ] || break

        src_batch_ok=0
        if command -v sha256sum >/dev/null 2>&1 &&
           command -v xargs >/dev/null 2>&1 &&
           [ -s "$src_work/simple" ]; then
            if tr '\n' '\0' < "$src_work/simple" |
               xargs -0 sha256sum -- > "$src_work/simple.raw"; then
                src_simple_n=$(awk 'END { print NR + 0 }' "$src_work/simple")
                src_hash_n=$(awk 'END { print NR + 0 }' "$src_work/simple.raw")
                if [ "$src_simple_n" -eq "$src_hash_n" ]; then
                    src_batch_ok=1
                fi
            fi
        fi
        if [ "$src_batch_ok" -eq 1 ]; then
            exec 3< "$src_work/simple.raw" || {
                printf "%s: cannot read batched recipe hashes\n" \
                    "$sps_prog" >&2
                src_failed=1
                break
            }
        fi
        while IFS= read -r src_recipe || [ -n "$src_recipe" ]
        do
            [ -n "$src_recipe" ] || continue
            if ! sps_tab_safe "$src_recipe"; then
                printf "%s: recipe path contains a tab or newline: %s\n" \
                    "$sps_prog" "$src_recipe" >&2
                src_failed=1
                break
            fi
            if [ "$src_batch_ok" -eq 1 ] &&
               ! src_recipe_needs_support "${src_recipe%/*}"; then
                read -r src_file_hash src_hash_rest <&3 || {
                    printf "%s: batched recipe hash missing: %s\n" \
                        "$sps_prog" "$src_recipe" >&2
                    src_failed=1
                    break
                }
                src_digest=$(sps_definition_from_recipe_file_hash \
                    "$src_file_hash") || {
                    printf "%s: cannot hash package definition: %s\n" \
                        "$sps_prog" "$src_recipe" >&2
                    src_failed=1
                    break
                }
            else
                src_digest=$(sps_definition_sha256 "$src_recipe") || {
                    printf "%s: cannot hash package definition: %s\n" \
                        "$sps_prog" "$src_recipe" >&2
                    src_failed=1
                    break
                }
            fi
            case $src_digest in
                ''|*[!0123456789abcdefABCDEF]*)
                    printf "%s: invalid package definition SHA-256: %s\n" \
                        "$sps_prog" "$src_recipe" >&2
                    src_failed=1
                    break
                    ;;
            esac
            if [ "${#src_digest}" -ne 64 ]; then
                printf "%s: invalid package definition SHA-256: %s\n" \
                    "$sps_prog" "$src_recipe" >&2
                src_failed=1
                break
            fi
            printf '%s\t%s\n' "$src_recipe" "$src_digest" \
                >> "$src_work/digests" || {
                printf "%s: cannot record recipe digest: %s\n" \
                    "$sps_prog" "$src_recipe" >&2
                src_failed=1
                break
            }
            src_repo_packages=$((src_repo_packages + 1))
            src_recipe_label=${src_recipe%/*}
            src_recipe_label=${src_recipe_label##*/}
            src_progress_hash "$src_repo_name" "$src_repo_packages" \
                "$src_recipe_total" "$src_recipe_label"
        done < "$src_work/recipes"
        if [ "$src_batch_ok" -eq 1 ]; then
            exec 3<&-
        fi

        [ "$src_failed" -eq 0 ] || break
        if [ "$src_repo_packages" -gt 0 ]; then
            src_progress "parsing recipes in '$src_repo_name'"
            if ! awk -v recipe_repo="$src_repo_name" \
                -v recipe_priority="$src_repo_priority" \
                -v recipe_digest_map="$src_work/digests" \
                -v recipe_file_list="$src_work/recipes" \
                -v recipe_default_arch="$src_default_arch" \
                -f "$SPS_LIBDIR/recipe.awk" \
                >> "$src_work/packages.index"; then
                src_failed=1
                break
            fi
        fi
        src_package_count=$((src_package_count + src_repo_packages))
        src_repo_count=$((src_repo_count + 1))
    done < "$src_work/repos"

    if [ "$src_failed" -ne 0 ]; then
        sps_die "$SPS_EX_PACKAGE" \
            "repository update failed; previous index was preserved"
    fi
	src_progress "validating index"
	if ! awk -v action=validate -v program=src \
		-f "$SPS_LIBDIR/index.awk" "$src_work/packages.index"; then
        sps_die "$SPS_EX_PACKAGE" \
			"generated repository index is malformed"
	fi
	src_progress "validating dependency graph"
	if ! awk -v action=validate -v include_build=1 -v program=src \
		-f "$SPS_LIBDIR/version.awk" -f "$SPS_LIBDIR/deps.awk" \
		"$src_work/packages.index"; then
		sps_die "$SPS_EX_DEPENDENCY" \
			"generated repository dependency graph is invalid"
	fi
    if ! mv "$src_work/packages.index" "$sps_index"; then
        sps_die "$SPS_EX_PERMISSION" "cannot commit repository index"
    fi

    printf 'updated %d repositories, %d package records\n' \
        "$src_repo_count" "$src_package_count"
    src_cleanup_update
    trap - 0 1 2 3 15
}

src_repositories()
{
    src_repo_action=$1
    shift
    src_raw=0
    case ${1-} in
        --raw) src_raw=1; shift ;;
    esac
    [ "$#" -eq 0 ] || {
        src_usage >&2
        exit "$SPS_EX_USAGE"
    }
    if [ "$src_repo_action" = list ]; then
        [ "$src_raw" -eq 0 ] || src_repo_action=raw
    else
        [ "$src_raw" -eq 0 ] || src_repo_action=status_raw
    fi
	sps_repo_lines |
		awk -v action="$src_repo_action" -v program=src \
			-v repo_root="$SPS_REPO_ROOT" \
			-v index_file="$sps_index" \
            -f "$SPS_LIBDIR/repository.awk"
}

src_query()
{
    src_action=$1
    shift
    sps_need_arg "$src_action" "${1-}"
    src_query_value=$1
    shift
    src_raw=0
    case ${1-} in
        --raw) src_raw=1; shift ;;
    esac
    [ "$#" -eq 0 ] || {
        src_usage >&2
        exit "$SPS_EX_USAGE"
    }
    src_need_index

    case $src_action in
        search)
            sps_tab_safe "$src_query_value" ||
                sps_die "$SPS_EX_USAGE" "search pattern contains a tab or newline"
            [ "$src_raw" -eq 0 ] ||
                src_action=search_raw
            ;;
        show)
            sps_validate_package_name "$src_query_value" ||
                sps_die "$SPS_EX_USAGE" \
                    "invalid package name '$src_query_value'"
            [ "$src_raw" -eq 0 ] ||
                src_action=exact
            ;;
        which)
            sps_validate_package_name "$src_query_value" ||
                sps_die "$SPS_EX_USAGE" \
                    "invalid package name '$src_query_value'"
            [ "$src_raw" -eq 0 ] ||
                sps_die "$SPS_EX_USAGE" "'src which' does not accept --raw"
            ;;
    esac

    awk -v action="$src_action" -v query="$src_query_value" \
        -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_file=
    src_check_work=
    src_check_cleanup()
    {
        [ -n "${src_check_recipes_file:-}" ] &&
            rm -f "$src_check_recipes_file"
        src_check_recipes_file=
        if [ -n "${src_check_work:-}" ]; then
            rm -f "$src_check_work/packages.index"
            rmdir "$src_check_work" 2>/dev/null || :
            src_check_work=
        fi
    }
    src_check_recipes_file=$(mktemp "${TMPDIR:-/tmp}/sps-src-check.XXXXXX") ||
        exit "$SPS_EX_PERMISSION"
    trap src_check_cleanup 0
    trap 'exit 129' 1
    trap 'exit 130' 2
    trap 'exit 131' 3
    trap 'exit 143' 15
    while [ "$#" -gt 0 ]; do
        case $1 in
            --deps)
                src_check_deps=1
                ;;
            --help|-h)
                printf '%s\n' 'usage: src check RECIPE... [--deps]'
                src_check_cleanup
                trap - 0 1 2 3 15
                exit 0
                ;;
            -*)
                src_usage >&2
                src_check_cleanup
                trap - 0 1 2 3 15
                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
                    src_check_cleanup
                    trap - 0 1 2 3 15
                    sps_die "$SPS_EX_NOTFOUND" \
                        "no readable recipe file at '$1'"
                fi
                printf '%s\n' "$src_check_resolve" >>"$src_check_recipes_file" ||
                    sps_die "$SPS_EX_PERMISSION" "cannot record recipe path"
                ;;
        esac
        shift
    done
    if [ ! -s "$src_check_recipes_file" ]; then
        printf '%s\n' 'usage: src check RECIPE... [--deps]' >&2
        src_check_cleanup
        trap - 0 1 2 3 15
        exit "$SPS_EX_USAGE"
    fi

    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)}
    while IFS= read -r src_check_recipe || [ -n "$src_check_recipe" ]
    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 < "$src_check_recipes_file"

    if [ "$src_check_failed" -ne 0 ]; then
        src_check_cleanup
        trap - 0 1 2 3 15
        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
            src_check_cleanup
            trap - 0 1 2 3 15
            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
            src_check_cleanup
            trap - 0 1 2 3 15
            sps_die "$SPS_EX_DEPENDENCY" "checked dependency graph is invalid"
        fi
    fi

    printf 'checked %d recipe(s)\n' "$src_check_count"
    src_check_cleanup
    trap - 0 1 2 3 15
}

while [ "$#" -gt 0 ]; do
    case $1 in
        -q|--quiet) src_quiet=1; shift ;;
        -v|--verbose) src_quiet=0; shift ;;
        -h|--help)
            src_usage
            exit 0
            ;;
        --) shift; break ;;
        -*)
            src_usage >&2
            exit "$SPS_EX_USAGE"
            ;;
        *) break ;;
    esac
done

case ${1-} in
    update)
        shift
        src_update "$@"
        ;;
    check)
        shift
        src_check "$@"
        ;;
    search|show|which)
        src_command=$1
        shift
        src_query "$src_command" "$@"
        ;;
    list|status)
        src_command=$1
        shift
        src_repositories "$src_command" "$@"
        ;;
    -h|--help)
        src_usage
        ;;
    *)
        src_usage >&2
        exit "$SPS_EX_USAGE"
        ;;
esac