pthbs

Packaging Through Hashed Build Scripts
git clone https://ccx.te2000.cz/git/pthbs
Log | Files | Refs | Submodules | README

commit e4b7512893a63ef56469a9d60836d84402fde71e
parent e3b3b0f919535f670a5298da48060b7982b17cd3
Author: Jan Pobrislo <ccx@te2000.cz>
Date:   Wed, 30 Apr 2025 20:11:46 +0000

Add tool to check package consistency and external resources

Diffstat:
Mcommand/pthbs-build | 11+++++++++++
Mpthbs.mk | 4++++
Autil/_verify-external-resources.awk | 271+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autil/verify-external-resources | 15+++++++++++++++
4 files changed, 301 insertions(+), 0 deletions(-)

diff --git a/command/pthbs-build b/command/pthbs-build @@ -51,6 +51,10 @@ BEGIN { settings["set_path"] = 1 FS=":" } +function fatal(msg) { + printf "FATAL: pthbs-build (eval): %s %s:%d: \"%s\"\n", msg, FILENAME, FNR, $0 >"/dev/stderr" + exit 1 +} /^#@pragma:/ { if($2 == "nosandbox") { settings["sandbox"] = 0 @@ -60,12 +64,19 @@ BEGIN { fatal("unrecognized @pragma:") } } +/^$/ { nextfile } END { print "setting_sandbox=" (settings["sandbox"]?"true":"false") print "setting_set_path=" (settings["set_path"]?"true":"false") + print "setting_ok=true" } ' "$1" )" +if test true != "$setting_ok"; then + printf '%s\n' >&2 "$0: fatal: could not extract information from '$1'" + exit 111 +fi + if test -f "$pthbs_cache/make/package.sha256.${bsh}.env"; then envfile="$pthbs_cache/make/package.sha256.${bsh}.env" envhash=$(pthbs-getenvhash "$envfile") || exit $? diff --git a/pthbs.mk b/pthbs.mk @@ -17,6 +17,10 @@ export pthbs_source:=$(abspath $(pthbs)) no_default: @echo "pthbs has no default target to build" +.PHONY: check +check: + '$(pthbs_source)/util/verify-external-resources' + $(versions)/environment.%/.env: @echo mkdir "$$(dirname '$@')" @echo touch '$@' diff --git a/util/_verify-external-resources.awk b/util/_verify-external-resources.awk @@ -0,0 +1,271 @@ +#!/bin/awk -f +BEGIN { + load_indexes() + + dep_count = 0 + env_count = 0 + is_envfile = ENVIRON["script"] ~ /\.environment$/ + if(is_envfile) { + envname = substr(ENVIRON["scriptname"], 1, length(ENVIRON["scriptname"])-12) + } + FS="[ /]" +} + +function fatal(msg) { + if(pline) { + printf "FATAL: pthbs-verifypkg: %s %s:%d\n", msg, pfile, pline >"/dev/stderr" + } else { + printf "FATAL: pthbs-verifypkg: %s %s:%d: \"%s\"\n", msg, FILENAME, FNR, $0 >"/dev/stderr" + } + exit 1 +} + +function q(s) { + return "'" s "'" +} +function dbgprint(msg) { + printf "DBG: %s\n", msg >"/dev/stderr" +} +function dbgprint_count(n) { + printf "DBG: %d entries\n", n >"/dev/stderr" +} +function dbgprint_dep(dep_type, dep_value) { + dbgprint(pfile" -> ["dep_type"]"dep_value) +} +function dbgprint_pkg() { + dbgprint(q(pfile)" => "q(pname)) +} + +function vprint(msg) { + printf "%s\n", msg +} + +function load_indexes() { + if(!length(ENVIRON["pthbs_indexdir"])) { + fatal("variable 'pthbs_indexdir' not defined") + } + load_downloadlist(ENVIRON["pthbs_indexdir"]"/downloadlist.sha256") + load_filelist(ENVIRON["pthbs_indexdir"]"/filelist.sha256") + load_commitlist(ENVIRON["pthbs_indexdir"]"/commitlist.sha1") +} + +function load_downloadlist(downloadlist_path, n) { + dbgprint("reading downloadlist: "q(downloadlist_path)) + n = 0 + while(getline <downloadlist_path) { + if(/^$/ || /^#/) { + continue + } + downloadable_hashes["sha256:"$1] = 0 + hash_info["sha256:"$1] = downloadlist_path ":" FNR substr($0, length($1 + 2)) + n += 1 + } + close(downloadlist_path) + dbgprint_count(n) +} + +function load_filelist(filelist_path, n) { + dbgprint("reading filelist: "q(filelist_path)) + n = 0 + while(getline <filelist_path) { + linkable_hashes["sha256:"$1] = 0 + hash_info["sha256:"$1] = filelist_path ":" FNR substr($0, length($1 + 2)) + n += 1 + } + close(filelist_path) + dbgprint_count(n) +} + +function load_commitlist(commitlist_path, n) { + dbgprint("reading commitlist: "q(commitlist_path)) + n = 0 + while(getline <commitlist_path) { + commits[$1] = 0 + n += 1 + } + close(filelist_path) + dbgprint_count(n) +} + +/^[0-9a-f]+ .*\/[-_:.0-9a-zA-Z]+\/[-_:.0-9a-zA-Z]+/ { + pname = $NF + sub(/:.*/, "", pname) + if(pname ~ /\.environment$/) { + pname = "env" + } + pname = $(NF-1)"/"pname"."$1 + pfile = substr($0, length($1) + 3) + pvariant = $(NF-1) + + dbgprint_pkg() + package_names[pname] = 0 + package_variants[pfile] = pvariant + next +} + +{ + fatal("unexpected line") +} +function record_failure(dep_type, dep_value, msg) { + if(!pline) { + fatal("internal error: record_failure() called without pline") + } + failed[pfile] = failed[pfile] pfile ":" pline " [" dep_type "] '" dep_value "'\t" msg "\n" +} + +END { + delete pname + for(pfile in package_variants) { + read_package(pfile) + } + count = 0 + unused = 0 + for(commit_id in commits) { + count += 1 + unused += commits[commit_id] ? 0 : 1 + } + printf "commit ids: %4d unused: %4d\n", count, unused + + count = 0 + unused = 0 + for(i in linkable_hashes) { + count += 1 + unused += linkable_hashes[i] ? 0 : 1 + if(!linkable_hashes[i]) { + vprint(i " unused local file " hash_info[i]) + } + } + printf "local files: %4d unused: %4d\n", count, unused + + count = 0 + unused = 0 + for(i in downloadable_hashes) { + count += 1 + unused += downloadable_hashes[i] ? 0 : 1 + if(!downloadable_hashes[i]) { + vprint(i " unused local file " hash_info[i]) + } + } + printf "downloads: %4d unused: %4d\n", count, unused + + failed_files = 0 + for(pfile in failed) { + failed_files += 1 + printf "errors in file %s:\n%s\n", pfile, failed[pfile] + } + exit failed_files ? 1 : 0 +} + +function read_package(f, line, seen_shebang) { + dbgprint("reading package: "q(f)) + pvariant = package_variants[f] + seen_shebang = 0 + pline = 0 + while(getline line <f) { + pline += 1 + if(line == "") { + break + } + if(pline == 1 && line ~ /^#!.*pthbs-build/) { + seen_shebang = 1 + continue + } + if(line ~ /^#\+/) { + hash_plus(line) + continue + } + if(line ~ /^#@/) { + hash_at(line) + continue + } + fatal("unexpected line") + } + close(f) + if(pline == 0) { + fatal("error reading file: '"f"'") + } + if(!seen_shebang) { + fatal("did not encounter expected shebang line in package") + } + delete pline +} + +function depend_git(commit_id) { + dbgprint_dep("git-sha1", commit_id) + if(commit_id in commits) { + commits[commit_id] += 1 + } else { + #dbgprint("!!! could not resolve '"commit_id"' to repository file in "pfile) + record_failure("git-sha1", commit_id, "could not resolve '"commit_id"' to git repository") + } +} + +function depend_file(hash_type, file_hash) { + dbgprint_dep(hash_type, file_hash) + if((hash_type":"file_hash) in linkable_hashes) { + dbgprint("local") + linkable_hashes[hash_type":"file_hash] += 1 + } else if((hash_type":"file_hash) in downloadable_hashes) { + dbgprint("download") + downloadable_hashes[hash_type":"file_hash] += 1 + } else { + #dbgprint("!!! could not resolve how to get file with "q(hash_type":"file_hash)" in") + record_failure(hash_type, file_hash, "could not resolve how to get file with "q(hash_type":"file_hash)) + } +} + +function at_git(commit_id, dstdir){ + depend_git(commit_id) +} + +function at_untar(extra_opts, hash_type, file_hash, dstdir){ + depend_file(hash_type, file_hash) +} + +function at_filehash(hash_type, file_hash, dst, dstdir){ + depend_file(hash_type, file_hash) +} + +function hash_plus(line, dep) { + if(line == "#+*") { + # settings["sandbox"] = 0 + } else if(!length(ENVIRON["envdir"])) { + dep = pvariant "/" substr(line, 3) + if(!(dep in package_names)) { + fatal("could not resolve '"dep"' to package file in") + } else { + dbgprint_dep("pkg", dep) + package_names[dep] += 1 + } + } +} + +function hash_at(line, a){ + split(line, a, ":") + if(a[1] == "#@git") { + if(match(line, "^#@git:[0-9a-f]+:") == 0) { + fatal("invalid syntax for @git:") + } + at_git(a[2], substr(line, RLENGTH+1)) + } else if(a[1] == "#@sha256") { + if(match(line, "^#@sha256:[0-9a-f]+:") == 0) { + fatal("invalid syntax for @sha256:") + } + at_filehash("sha256", a[2], substr(line, RLENGTH+1)) + } else if(a[1] == "#@untar") { + if(match(line, "^#@untar:[^:]*:sha256:[0-9a-f]+:") == 0) { + fatal("invalid syntax for @untar:") + } + at_untar(a[2], a[3], a[4], substr(line, RLENGTH+1)) + } else if(a[1] == "#@pragma") { + if(a[2] == "nosandbox") { + # settings["sandbox"] = 0 + } else if(a[2] == "nopath") { + # settings["set_path"] = 0 + } else { + fatal("unrecognized @pragma:") + } + } else { + fatal("unrecognized @command:") + } +} diff --git a/util/verify-external-resources b/util/verify-external-resources @@ -0,0 +1,15 @@ +#!/bin/sh +if test -n "$pthbs_xtrace"; then + set -x + if test -n "$BB_ASH_VERSION"; then PS4="+${0##*/}"':${FUNCNAME}:${LINENO} '; fi +fi +if test -z "$pthbs_source"; then + printf '%s\n' >&2 "$0: fatal: pthbs_source env var undefined or empty" + exit 100 +fi +if test -z "$pthbs_indexdir"; then + printf '%s\n' >&2 "$0: fatal: pthbs_indexdir env var undefined or empty" + exit 100 +fi + +find ./variants/*/ -type f -exec sha256sum '{}' + | awk -f "$pthbs_source/util/_verify-external-resources.awk"