From: Thomas Walker Lynch Date: Wed, 29 Oct 2025 16:10:24 +0000 (+0000) Subject: more sophisticated release script X-Git-Url: https://git.reasoningtechnology.com/style/static/git-favicon.png?a=commitdiff_plain;h=99674899986d1d31b97282ea5b8b48c156e125aa;p=Rabbit%2F.git more sophisticated release script --- diff --git a/developer/document/tests.sh b/developer/document/tests.sh deleted file mode 100644 index 6892145..0000000 --- a/developer/document/tests.sh +++ /dev/null @@ -1,10 +0,0 @@ - - -2025-05-19T09:56:17Z[Harmony] -Thomas-developer@StanleyPark§/home/Thomas/subu_data/developer/Harmony/developer/python§ -> python test_isqrt.py 500000 20000 -.................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................... -✅ Passed: 500000 -❌ Failed: 0 - - diff --git a/developer/tool/makefile_mod b/developer/tool/makefile_mod deleted file mode 100644 index 50485ca..0000000 --- a/developer/tool/makefile_mod +++ /dev/null @@ -1,27 +0,0 @@ -# tool/makefile - -RT_project_share:=$(REPO_HOME)/tool_shared/third_party/RT-project-share/release -include $(RT_project_share)/make/environment_RT_0 - -KBUILD_SRCDIR_List := $(SRCDIR_List) -KBUILD_SRC_List := $(foreach dir, $(KBUILD_SRCDIR_List), $(wildcard $(dir)/*.mod.c)) -KBUILD_BASE_List := $(sort $(patsubst %.mod.c, %, $(notdir $(KBUILD_SRC_List)))) - -DEPFILE := $(TMPDIR)/makefile-cc.deps - --include $(DEPFILE) -include $(RT_project_share)/make/targets_developer -include $(RT_project_share)/make/targets_kernel - -# ---------------------------------------------------------------------- -# --- PUBLIC TARGET ORCHESTRATION (Must be last to win precedence) --- -# ---------------------------------------------------------------------- - -# 1. DEFAULT TARGET: Make the 'all' target the default for running 'make'. -# This must be defined last to override the 'all: usage' from the included targets files. -.PHONY: all -all: kernel_module - -# 2. CLEAN TARGET: Orchestrate the cleanup process. -.PHONY: clean -clean: clean_developer clean_kernel diff --git a/developer/tool/release b/developer/tool/release index 63f0378..e29cb43 100755 --- a/developer/tool/release +++ b/developer/tool/release @@ -1,56 +1,287 @@ -#!/usr/bin/env bash -script_afp=$(realpath "${BASH_SOURCE[0]}") - -# This is a bespoke script for copying files into ../release. -# Files in ../release can be used by the tester role. -# On a release branch, files in the ../release constitute the public release. - -# conventional preliminaries - - env_must_be="developer/tool/env" - if [ "$ENV" != "$env_must_be" ]; then - echo "$(script_fp):: error: must be run in the $env_must_be environment" - exit 1 - fi - - cd "$REPO_HOME"/developer || exit 1 - -#set -e -#set -x - -cd "$REPO_HOME/developer" - -release_dir="$REPO_HOME/release" -ko_dir="scratchpad/kmod" -machine_executable_list="scratchpad/hello" - -# Enable nullglob so an empty match yields an empty list (not the literal pattern) -shopt -s nullglob - -# zero or more machine executables -for f in $machine_executable_list; do - if [[ -x "$f" ]]; then - echo "+ install -m 0500 '$f' '$release_dir/machine/'" - install -m 0550 "$f" "$release_dir/machine/" - else - echo "(info) did not find '$f'" - fi -done - -# zero or more Kernel modules -# release/kmod created 750 so developer can write to it, group can read it -ko_list=("$ko_dir"/*.ko) -if (( ${#ko_list[@]} )); then - if [[ ! -d "$release_dir/kmod" ]]; then - echo "+ install -d '$release_dir/kmod'" - install -m 750 -d "$release_dir/kmod" - fi - for f in "${ko_list[@]}"; do - echo "+ install -m 0440 '$f' '$release_dir/kmod/'" - install -m 440 "$f" "$release_dir/kmod/" - done -else - echo "(info) no kmod artifacts found in $ko_dir; skipping kmod release" -fi - -echo "$script_fn done." +#!/usr/bin/env -S python3 -B +# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*- + +import os, sys, shutil, stat, pwd, grp, glob, tempfile + +HELP = """usage: release {write|clean|ls|help|dry write} [DIR] + write [DIR] Writes released files into $REPO_HOME/release. If [DIR] is specified, only writes files found in scratchpad/DIR. + clean [DIR] Remove the contents of the release directories. If [DIR] is specified, clean only the contents of that release directory. + ls List release/ as an indented tree: PERMS OWNER NAME (root-level dotfiles printed first). + help Show this message. + dry write [DIR] + Preview what write would do without modifying the filesystem. +""" + +ENV_MUST_BE = "developer/tool/env" +DEFAULT_DIR_MODE = 0o750 +PERM_BY_DIR = { + "kmod": 0o440, + "machine": 0o550, + "python3": 0o550, + "shell": 0o550, +} + +def exit_with_status(msg, code=1): + print(f"release: {msg}", file=sys.stderr) + sys.exit(code) + +def assert_env(): + env = os.environ.get("ENV", "") + if env != ENV_MUST_BE: + hint = ( + "ENV is not 'developer/tool/env'.\n" + "Enter the project with: source ./env_developer\n" + "That script exports: ROLE=developer; ENV=$ROLE/tool/env" + ) + exit_with_status(f"bad environment: ENV='{env}'. {hint}") + +def repo_home(): + rh = os.environ.get("REPO_HOME") + if not rh: + exit_with_status("REPO_HOME not set (did you 'source ./env_developer'?)") + return rh + +def dpath(*parts): + return os.path.join(repo_home(), "developer", *parts) + +def rpath(*parts): + return os.path.join(repo_home(), "release", *parts) + +def dev_root(): + return dpath() + +def rel_root(): + return rpath() + +def _display_src(p_abs: str) -> str: + # Developer paths shown relative to $REPO_HOME/developer + try: + if os.path.commonpath([dev_root()]) == os.path.commonpath([dev_root(), p_abs]): + return os.path.relpath(p_abs, dev_root()) + except Exception: + pass + return p_abs + +def _display_dst(p_abs: str) -> str: + # Release paths shown as literal '$REPO_HOME/release/' + try: + rel = os.path.relpath(p_abs, rel_root()) + rel = "" if rel == "." else rel + return "$REPO_HOME/release" + ("/" + rel if rel else "") + except Exception: + return p_abs + +def ensure_mode(path, mode): + try: os.chmod(path, mode) + except Exception: pass + +def ensure_dir(path, mode=DEFAULT_DIR_MODE, dry=False): + if dry: + if not os.path.isdir(path): + shown = _display_dst(path) if path.startswith(rel_root()) else ( + os.path.relpath(path, dev_root()) if path.startswith(dev_root()) else path + ) + print(f"(dry) mkdir -m {oct(mode)[2:]} '{shown}'") + return + os.makedirs(path, exist_ok=True) + ensure_mode(path, mode) + +def filemode(m): + try: return stat.filemode(m) + except Exception: return oct(m & 0o777) + +def owner_group(st): + try: return f"{pwd.getpwuid(st.st_uid).pw_name}:{grp.getgrgid(st.st_gid).gr_name}" + except Exception: return f"{st.st_uid}:{st.st_gid}" + +# ---------- LS with two-pass column width for owner:group ---------- +def list_tree(root): + if not os.path.isdir(root): + return + + # gather entries in display order, record owner:group widths + entries = [] # list of (is_dir, depth, perms, ownergrp, name) + def gather(path: str, depth: int, is_root: bool): + try: + it = list(os.scandir(path)) + except FileNotFoundError: + return + dirs = [e for e in it if e.is_dir(follow_symlinks=False)] + files = [e for e in it if not e.is_dir(follow_symlinks=False)] + dirs.sort(key=lambda e: e.name) + files.sort(key=lambda e: e.name) + + if is_root: + # root-level: dotfiles first + for f in (e for e in files if e.name.startswith(".")): + st = os.lstat(f.path) + entries.append((False, depth, filemode(st.st_mode), owner_group(st), f.name)) + for d in dirs: + st = os.lstat(d.path) + entries.append((True, depth, filemode(st.st_mode), owner_group(st), d.name + "/")) + gather(d.path, depth + 1, False) + for f in (e for e in files if not e.name.startswith(".")): + st = os.lstat(f.path) + entries.append((False, depth, filemode(st.st_mode), owner_group(st), f.name)) + else: + # subdirs: dirs then files (dotfiles naturally sort first) + for d in dirs: + st = os.lstat(d.path) + entries.append((True, depth, filemode(st.st_mode), owner_group(st), d.name + "/")) + gather(d.path, depth + 1, False) + for f in files: + st = os.lstat(f.path) + entries.append((False, depth, filemode(st.st_mode), owner_group(st), f.name)) + + gather(root, depth=1, is_root=True) + + # compute max width for owner:group column + ogw = 0 + for (_isdir, _depth, _perms, ownergrp, _name) in entries: + if len(ownergrp) > ogw: + ogw = len(ownergrp) + + # print + print("release/") + for (isdir, depth, perms, ownergrp, name) in entries: + indent = " " * depth + # perms first, owner:group padded next, then name with tree indent + print(f"{perms} {ownergrp:<{ogw}} {indent}{name}") + +# ---------- end LS ---------- + +def iter_src_files(topdir, src_root): + base = os.path.join(src_root, topdir) if topdir else src_root + if not os.path.isdir(base): + return + yield + if topdir == "kmod": + for p in sorted(glob.glob(os.path.join(base, "*.ko"))): + yield (p, os.path.basename(p)) + else: + for root, dirs, files in os.walk(base): + dirs.sort(); files.sort() + for fn in files: + src = os.path.join(root, fn) + rel = os.path.relpath(src, base) + yield (src, rel) + +def target_mode(topdir): + return PERM_BY_DIR.get(topdir, 0o440) + +def copy_one(src_abs, dst_abs, mode, dry=False): + src_show = _display_src(src_abs) + dst_show = _display_dst(dst_abs) + parent = os.path.dirname(dst_abs) + os.makedirs(parent, exist_ok=True) + + if dry: + if os.path.exists(dst_abs): + print(f"(dry) unlink '{dst_show}'") + print(f"(dry) install -m {oct(mode)[2:]} -D '{src_show}' '{dst_show}'") + return + + # Replace even if dst exists and is read-only: write temp then atomic replace. + fd, tmp_path = tempfile.mkstemp(prefix=".tmp.", dir=parent) + try: + with os.fdopen(fd, "wb") as tmpf, open(src_abs, "rb") as sf: + shutil.copyfileobj(sf, tmpf) + tmpf.flush() + os.chmod(tmp_path, mode) + os.replace(tmp_path, dst_abs) + finally: + try: + if os.path.exists(tmp_path): + os.unlink(tmp_path) + except Exception: + pass + + print(f"+ install -m {oct(mode)[2:]} '{src_show}' '{dst_show}'") + +def write_one_dir(topdir, dry): + rel_root_dir = rpath() + src_root = dpath("scratchpad") + src_dir = os.path.join(src_root, topdir) + dst_dir = os.path.join(rel_root_dir, topdir) + + if not os.path.isdir(src_dir): + exit_with_status( + f"cannot write: expected '{_display_src(src_dir)}' to exist. " + f"Create scratchpad/{topdir} (Makefiles may need to populate it)." + ) + + ensure_dir(dst_dir, DEFAULT_DIR_MODE, dry=dry) + + wrote = False + mode = target_mode(topdir) + for src_abs, rel in iter_src_files(topdir, src_root): + dst_abs = os.path.join(dst_dir, rel) + copy_one(src_abs, dst_abs, mode, dry=dry) + wrote = True + if not wrote: + msg = "no matching artifacts found" + if topdir == "kmod": msg += " (looking for *.ko)" + print(f"(info) {msg} in {_display_src(src_dir)}") + +def cmd_write(dir_arg, dry=False): + assert_env() + ensure_dir(rpath(), DEFAULT_DIR_MODE, dry=dry) + + src_root = dpath("scratchpad") + if not os.path.isdir(src_root): + exit_with_status(f"cannot find developer scratchpad at '{_display_src(src_root)}'") + + if dir_arg: + write_one_dir(dir_arg, dry=dry) + else: + subs = sorted([e.name for e in os.scandir(src_root) if e.is_dir(follow_symlinks=False)]) + if not subs: + print(f"(info) nothing to release; no subdirectories found under {_display_src(src_root)}") + return + for td in subs: + write_one_dir(td, dry=dry) + +def _clean_contents(dir_path): + if not os.path.isdir(dir_path): return + for name in os.listdir(dir_path): + p = os.path.join(dir_path, name) + if os.path.isdir(p) and not os.path.islink(p): + shutil.rmtree(p, ignore_errors=True) + else: + try: os.unlink(p) + except FileNotFoundError: pass + +def cmd_clean(dir_arg): + assert_env() + rel_root_dir = rpath() + if not os.path.isdir(rel_root_dir): + return + if dir_arg: + _clean_contents(os.path.join(rel_root_dir, dir_arg)) + else: + for e in os.scandir(rel_root_dir): + if e.is_dir(follow_symlinks=False): + _clean_contents(e.path) + +def CLI(): + if len(sys.argv) < 2: + print(HELP); return + cmd, *args = sys.argv[1:] + if cmd == "write": + cmd_write(args[0] if args else None, dry=False) + elif cmd == "clean": + cmd_clean(args[0] if args else None) + elif cmd == "ls": + list_tree(rpath()) + elif cmd == "help": + print(HELP) + elif cmd == "dry": + if args and args[0] == "write": + cmd_write(args[1] if len(args) >= 2 else None, dry=True) + else: + print(HELP) + else: + print(HELP) + +if __name__ == "__main__": + CLI() diff --git a/developer/tool/release_clean b/developer/tool/release_clean deleted file mode 100755 index fc09a13..0000000 --- a/developer/tool/release_clean +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash -script_afp=$(realpath "${BASH_SOURCE[0]}") - -# before running this, make library is built and is in the scratchpad directory - -# input guards - - env_must_be="developer/tool/env" - if [ "$ENV" != "$env_must_be" ]; then - echo "$(script_fp):: error: must be run in the $env_must_be environment" - exit 1 - fi - -set -e -set -x - - cd "$REPO_HOME"/developer || exit 1 - - release_dir=$(release_dir) - - if [ ! -d ${release_dir} ]; then - echo "$(script_fp):: no release directory: " ${release_dir} - exit 1 - fi - - rm_na -rf ${release_dir}/* - -set +x -echo "$(script_fn) done." - diff --git a/release/machine/hello b/release/machine/hello new file mode 100755 index 0000000..9f26c0f Binary files /dev/null and b/release/machine/hello differ diff --git a/release/python3/.githolder b/release/python3/.githolder deleted file mode 100644 index e69de29..0000000 diff --git a/release/shell/.githolder b/release/shell/.githolder deleted file mode 100644 index e69de29..0000000