--- /dev/null
+#!/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 = 0o700 # 077-congruent dirs
+
+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:
+ 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:
+ 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 (two-pass owner:group width) ----------
+def list_tree(root):
+ if not os.path.isdir(root):
+ return
+ entries = []
+ 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:
+ 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:
+ 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)
+
+ ogw = 0
+ for (_isdir, _depth, _perms, ownergrp, _name) in entries:
+ if len(ownergrp) > ogw: ogw = len(ownergrp)
+
+ print("release/")
+ for (_isdir, depth, perms, ownergrp, name) in entries:
+ indent = " " * depth
+ 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_from_source(src_abs: str) -> int:
+ """077 policy: files 0600; if source has owner-exec, make 0700."""
+ try:
+ sm = stat.S_IMODE(os.stat(src_abs).st_mode)
+ except FileNotFoundError:
+ return 0o600
+ return 0o700 if (sm & stat.S_IXUSR) else 0o600
+
+def copy_one(src_abs, dst_abs, 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)
+ target_mode = _target_mode_from_source(src_abs)
+
+ def _is_writable_dir(p): return os.access(p, os.W_OK)
+ flip_needed = not _is_writable_dir(parent)
+ restore_mode = None
+ parent_show = _display_dst(parent)
+
+ if dry:
+ if flip_needed:
+ print(f"(dry) chmod u+w '{parent_show}'")
+ if os.path.exists(dst_abs):
+ print(f"(dry) unlink '{dst_show}'")
+ # show final mode we will set
+ print(f"(dry) install -m {oct(target_mode)[2:]} -D '{src_show}' '{dst_show}'")
+ if flip_needed:
+ print(f"(dry) chmod u-w '{parent_show}'")
+ return
+
+ try:
+ if flip_needed:
+ try:
+ st_parent = os.stat(parent)
+ restore_mode = stat.S_IMODE(st_parent.st_mode)
+ os.chmod(parent, restore_mode | stat.S_IWUSR)
+ except PermissionError:
+ exit_with_status(f"cannot write: parent dir not writable and chmod failed on {parent_show}")
+
+ # Atomic replace with enforced 077-compliant mode
+ 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, target_mode)
+ os.replace(tmp_path, dst_abs)
+ finally:
+ try:
+ if os.path.exists(tmp_path):
+ os.unlink(tmp_path)
+ except Exception:
+ pass
+ finally:
+ if restore_mode is not None:
+ try: os.chmod(parent, restore_mode)
+ except Exception: pass
+
+ print(f"+ install -m {oct(target_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
+ 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, 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()
dirname "$script_afp"
}
-# assume this script is located $REPO_HOME/tools_shared/authored and work backwards
+# assume this script is located $REPO_HOME/shared/authored and work backwards
# to get $REPO_HOME, etc.
REPO_HOME=$(dirname "$(dirname "$(script_adp)")")
--- /dev/null
+../authored/version
\ No newline at end of file
+++ /dev/null
-../authored/gitignore_treewalk.py
\ No newline at end of file
+++ /dev/null
-../shared/authored/Harmony_sync/CLI.py
\ No newline at end of file
+++ /dev/null
-#!/usr/bin/env -S python3 -B
-# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
-
-"""
-set_project_permissions — normalize a freshly cloned project to Harmony policies.
-
-usage:
- set_project_permissions [default]
- set_project_permissions help | --help | -h
-
-notes:
- • Must be run from the toolsmith environment (ENV=tool/env, ROLE=toolsmith).
- • Starts at $REPO_HOME.
- • Baseline is umask-077 congruence:
- - directories → 0700
- - files → 0600, but preserve owner-exec (→ 0700 for executables)
- applied to the entire repo, including release/, EXCEPT:
- - release/kmod/*.ko → 0440
- • Skips .git/ and symlinks.
-"""
-
-import os, sys, stat
-
-# Must match shared/authored/env policy:
-DEFAULT_UMASK = 0o077 # reminder only; effective modes below implement 077 congruence.
-
-DIR_MODE_077 = 0o700
-
-def die(msg, code=1):
- print(f"set_project_permissions: {msg}", file=sys.stderr)
- sys.exit(code)
-
-def require_toolsmith_env():
- env = os.environ.get("ENV", "")
- role = os.environ.get("ROLE", "")
- if env != "tool/env" or role != "toolsmith":
- hint = (
- "This script should be run from the toolsmith environment.\n"
- "Try: source ./env_toolsmith (then re-run: set_project_permissions default)"
- )
- die(f"bad environment: ENV='{env}' ROLE='{role}'.\n{hint}")
-
-def repo_home():
- rh = os.environ.get("REPO_HOME")
- if not rh:
- die("REPO_HOME is not set (did you source shared/authored/env?)")
- return os.path.realpath(rh)
-
-def show_path(p, rh):
- return p.replace(rh, "$REPO_HOME", 1) if p.startswith(rh) else p
-
-def is_git_dir(path):
- return os.path.basename(path.rstrip(os.sep)) == ".git"
-
-def file_target_mode_077_preserve_exec(current_mode: int) -> int:
- # Base 0600, add owner exec if currently set; drop all group/other.
- target = 0o600
- if current_mode & stat.S_IXUSR:
- target |= stat.S_IXUSR
- return target
-
-def set_mode_if_needed(path, target, rh):
- try:
- st = os.lstat(path)
- except FileNotFoundError:
- return 0
- cur = stat.S_IMODE(st.st_mode)
- if cur == target:
- return 0
- os.chmod(path, target)
- print(f"+ chmod {oct(target)[2:]} '{show_path(path, rh)}'")
- return 1
-
-def apply_policy(rh):
- changed = 0
- release_root = os.path.join(rh, "release")
- for dirpath, dirnames, filenames in os.walk(rh, topdown=True, followlinks=False):
- # prune .git
- dirnames[:] = [d for d in dirnames if d != ".git"]
-
- # directories: 0700 everywhere (incl. release/)
- changed += set_mode_if_needed(dirpath, DIR_MODE_077, rh)
-
- # files: 0600 (+owner exec) everywhere, except release/kmod/*.ko → 0440
- rel_from_repo = os.path.relpath(dirpath, rh)
- under_release = rel_from_repo == "release" or rel_from_repo.startswith("release"+os.sep)
- top_under_release = ""
- if under_release:
- rel_from_release = os.path.relpath(dirpath, release_root)
- top_under_release = (rel_from_release.split(os.sep, 1)[0] if rel_from_release != "." else "")
-
- for fn in filenames:
- p = os.path.join(dirpath, fn)
- if os.path.islink(p):
- continue
- try:
- st = os.lstat(p)
- except FileNotFoundError:
- continue
-
- if under_release and top_under_release == "kmod" and fn.endswith(".ko"):
- target = 0o440
- else:
- target = file_target_mode_077_preserve_exec(stat.S_IMODE(st.st_mode))
-
- changed += set_mode_if_needed(p, target, rh)
- return changed
-
-def cmd_default():
- require_toolsmith_env()
- rh = repo_home()
- total = apply_policy(rh)
- print(f"changes: {total}")
-
-def main():
- if len(sys.argv) == 1 or sys.argv[1] in ("default",):
- return cmd_default()
- if sys.argv[1] in ("help", "--help", "-h"):
- print(__doc__.strip()); return 0
- # unknown command → help
- print(__doc__.strip()); return 1
-
-if __name__ == "__main__":
- sys.exit(main())
+++ /dev/null
-#!/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 = 0o700 # 077-congruent dirs
-
-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:
- 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:
- 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 (two-pass owner:group width) ----------
-def list_tree(root):
- if not os.path.isdir(root):
- return
- entries = []
- 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:
- 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:
- 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)
-
- ogw = 0
- for (_isdir, _depth, _perms, ownergrp, _name) in entries:
- if len(ownergrp) > ogw: ogw = len(ownergrp)
-
- print("release/")
- for (_isdir, depth, perms, ownergrp, name) in entries:
- indent = " " * depth
- 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_from_source(src_abs: str) -> int:
- """077 policy: files 0600; if source has owner-exec, make 0700."""
- try:
- sm = stat.S_IMODE(os.stat(src_abs).st_mode)
- except FileNotFoundError:
- return 0o600
- return 0o700 if (sm & stat.S_IXUSR) else 0o600
-
-def copy_one(src_abs, dst_abs, 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)
- target_mode = _target_mode_from_source(src_abs)
-
- def _is_writable_dir(p): return os.access(p, os.W_OK)
- flip_needed = not _is_writable_dir(parent)
- restore_mode = None
- parent_show = _display_dst(parent)
-
- if dry:
- if flip_needed:
- print(f"(dry) chmod u+w '{parent_show}'")
- if os.path.exists(dst_abs):
- print(f"(dry) unlink '{dst_show}'")
- # show final mode we will set
- print(f"(dry) install -m {oct(target_mode)[2:]} -D '{src_show}' '{dst_show}'")
- if flip_needed:
- print(f"(dry) chmod u-w '{parent_show}'")
- return
-
- try:
- if flip_needed:
- try:
- st_parent = os.stat(parent)
- restore_mode = stat.S_IMODE(st_parent.st_mode)
- os.chmod(parent, restore_mode | stat.S_IWUSR)
- except PermissionError:
- exit_with_status(f"cannot write: parent dir not writable and chmod failed on {parent_show}")
-
- # Atomic replace with enforced 077-compliant mode
- 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, target_mode)
- os.replace(tmp_path, dst_abs)
- finally:
- try:
- if os.path.exists(tmp_path):
- os.unlink(tmp_path)
- except Exception:
- pass
- finally:
- if restore_mode is not None:
- try: os.chmod(parent, restore_mode)
- except Exception: pass
-
- print(f"+ install -m {oct(target_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
- 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, 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()