project dirs now called 'authored' and 'loadable' core_developer_branch
authorThomas Walker Lynch <eknp9n@reasoningtechnology.com>
Fri, 21 Nov 2025 08:12:39 +0000 (08:12 +0000)
committerThomas Walker Lynch <eknp9n@reasoningtechnology.com>
Fri, 21 Nov 2025 08:12:39 +0000 (08:12 +0000)
developer/bash/git-tar
developer/make/environment_RT_1.mk

index ec2b6eb..79d5a8c 100755 (executable)
-#!/usr/bin/env bash
-# puts a tar file of the repo in the top level scratchdir, filename suffixed with UTC stamp from Z
-
-set -euo pipefail
-
-# 1) ensure we're in a git repo
-git rev-parse --is-inside-work-tree >/dev/null 2>&1 || {
-  echo "Error: not inside a git repository." >&2
-  exit 1
-}
-
-repo_top="$(git rev-parse --show-toplevel)"
-repo_name="$(basename "$repo_top")"
-ref_label="$(git describe --tags --always --dirty 2>/dev/null || git rev-parse --short HEAD)"
-
-# 2) ensure Z is available (prefer PATH; otherwise add repo-local RT-project-share path)
-if ! command -v Z >/dev/null 2>&1; then
-  z_guess="${repo_top}/tool_shared/third_party/RT-project-share/release/bash"
-  if [ -x "${z_guess}/Z" ]; then
-    PATH="${z_guess}:${PATH}"
-  else
-    echo "Error: required program 'Z' not found in PATH." >&2
-    echo "Hint: expected at '${z_guess}/Z' or ensure your env_* is sourced." >&2
-    exit 1
-  fi
-fi
-
-# 3) timestamp and output path
-stamp="$(Z)"
-# trim trailing newline just in case
-stamp="${stamp//$'\n'/}"
-
-mkdir -p "${repo_top}/scratchpad"
-out="${repo_top}/scratchpad/${repo_name}__${ref_label}__${stamp}.tar.gz"
-
-# 4) create archive of HEAD (tracked files only; .gitignore’d stuff omitted)
-git -C "$repo_top" archive --format=tar --prefix="${repo_name}/" HEAD | gzip > "$out"
-
-echo "Wrote ${out}"
+#!/usr/bin/env python3
+# -*- mode: python; coding: utf-8; python-indent-offset: 2; indent-tabs-mode: nil -*-
+
+"""
+git-tar — Create an archive of the current Git repo's ref into ./scratchpad
+
+Commands (order-insensitive):
+  git-tar                         # default: tar.gz (HEAD, ./scratchpad, Z-stamp if importable)
+  git-tar help                    # show help
+  git-tar version                 # show version
+  git-tar ref-<REF>               # choose ref (tag/branch/commit), default HEAD
+  git-tar out-<OUTDIR>            # choose output directory (default: <repo>/scratchpad)
+  git-tar no-stamp                # force omit timestamp even if Z is importable
+  git-tar z-format-<FMT>          # override timestamp format used with Z (optional)
+  git-tar zip                     # write .zip instead of .tar.gz
+  git-tar tar                     # force .tar.gz explicitly
+
+Output names:
+  <repo>__<ref>[__<Z>].tar.gz
+  <repo>__<ref>[__<Z>].zip
+"""
+
+from __future__ import annotations
+import gzip, os, pathlib, subprocess, sys
+from typing import Optional
+import importlib, importlib.util
+from importlib.machinery import SourceFileLoader
+
+VERSION = "1.5"
+
+# ----------------------------------------------------------------------
+# Editable timestamp format (used when calling Z)
+# ----------------------------------------------------------------------
+Z_FORMAT = "%year-%month-%day_%hour%minute%secondZ"
+
+USAGE = f"""git-tar {VERSION}
+
+Usage:
+  git-tar [commands...]
+
+Commands (order-insensitive):
+  help
+  version
+  ref-<REF>
+  out-<OUTDIR>
+  no-stamp
+  z-format-<FMT>
+  zip
+  tar
+
+Examples:
+  git-tar
+  git-tar zip
+  git-tar ref-main out-/tmp
+  git-tar z-format-%year-%month-%dayT%hour:%minute:%second.%scintillaZ
+""".rstrip()
+
+# ----------------------------------------------------------------------
+# git helpers
+# ----------------------------------------------------------------------
+def _run(*args: str, check: bool = True, cwd: Optional[pathlib.Path] = None) -> subprocess.CompletedProcess[str]:
+  return subprocess.run(
+    args
+    ,check=check
+    ,cwd=(str(cwd) if cwd else None)
+    ,text=True
+    ,stdout=subprocess.PIPE
+    ,stderr=subprocess.PIPE
+  )
+
+def _in_git_repo() -> bool:
+  try:
+    return _run("git","rev-parse","--is-inside-work-tree").stdout.strip().lower() == "true"
+  except subprocess.CalledProcessError:
+    return False
+
+def _git_top() -> pathlib.Path:
+  return pathlib.Path(_run("git","rev-parse","--show-toplevel").stdout.strip())
+
+def _git_ref_label(repo_top: pathlib.Path, ref: str) -> str:
+  try:
+    return _run("git","-C",str(repo_top),"describe","--tags","--always","--dirty",ref).stdout.strip()
+  except subprocess.CalledProcessError:
+    return _run("git","-C",str(repo_top),"rev-parse","--short",ref).stdout.strip()
+
+# ----------------------------------------------------------------------
+# Z module discovery (supports extension-less file named "Z")
+# ----------------------------------------------------------------------
+def _import_Z_module(repo_top: pathlib.Path) -> Optional[object]:
+  try:
+    return importlib.import_module("Z")
+  except Exception:
+    pass
+
+  candidates: list[pathlib.Path] = []
+  here = pathlib.Path(__file__).resolve().parent
+  candidates += [here / "Z", here / "Z.py"]
+  candidates += [
+    repo_top / "tool_shared" / "third_party" / "RT-project-share" / "release" / "python" / "Z",
+    repo_top / "tool_shared" / "third_party" / "RT-project-share" / "release" / "python" / "Z.py",
+    repo_top / "tool_shared" / "third_party" / "RT-project-share" / "release" / "bash" / "Z",
+  ]
+  for d in (pathlib.Path(p) for p in (os.getenv("PATH") or "").split(os.pathsep) if p):
+    p = d / "Z"
+    if p.exists() and p.is_file():
+      candidates.append(p)
+
+  for path in candidates:
+    try:
+      if not path.exists() or not path.is_file():
+        continue
+      spec = importlib.util.spec_from_loader("Z", SourceFileLoader("Z", str(path)))
+      if not spec or not spec.loader:
+        continue
+      mod = importlib.util.module_from_spec(spec)
+      spec.loader.exec_module(mod)  # type: ignore[attr-defined]
+      if hasattr(mod,"make_timestamp") or (hasattr(mod,"get_utc_dict") and hasattr(mod,"format_timestamp")):
+        return mod
+    except Exception:
+      continue
+  return None
+
+# ----------------------------------------------------------------------
+# Z stamp helper (format string visible & editable above)
+# ----------------------------------------------------------------------
+def make_z_stamp(zmod: object, z_format: str) -> Optional[str]:
+  try:
+    if hasattr(zmod, "make_timestamp"):
+      s = zmod.make_timestamp(fmt=z_format)  # type: ignore[attr-defined]
+      return (str(s).strip().replace("\n","") or None)
+    if hasattr(zmod, "get_utc_dict") and hasattr(zmod, "format_timestamp"):
+      td = zmod.get_utc_dict()  # type: ignore[attr-defined]
+      s = zmod.format_timestamp(td, z_format)  # type: ignore[attr-defined]
+      return (str(s).strip().replace("\n","") or None)
+  except Exception:
+    return None
+  return None
+
+# ----------------------------------------------------------------------
+# archiving
+# ----------------------------------------------------------------------
+def _stream_git_archive_tar(repo_top: pathlib.Path, prefix: str, ref: str, out_gz_path: pathlib.Path) -> None:
+  proc = subprocess.Popen(
+    ["git","-C",str(repo_top),"archive","--format=tar",f"--prefix={prefix}/",ref]
+    ,stdout=subprocess.PIPE
+  )
+  try:
+    with gzip.open(out_gz_path,"wb") as gz:
+      while True:
+        chunk = proc.stdout.read(1024 * 1024)  # 1 MiB
+        if not chunk:
+          break
+        gz.write(chunk)
+  finally:
+    if proc.stdout:
+      proc.stdout.close()
+    rc = proc.wait()
+    if rc != 0:
+      try:
+        out_gz_path.unlink(missing_ok=True)
+      finally:
+        raise subprocess.CalledProcessError(rc, proc.args)
+
+def _stream_git_archive_zip(repo_top: pathlib.Path, prefix: str, ref: str, out_zip_path: pathlib.Path) -> None:
+  # Directly stream git's zip to file; no Python zip building needed.
+  proc = subprocess.Popen(
+    ["git","-C",str(repo_top),"archive","--format=zip",f"--prefix={prefix}/",ref]
+    ,stdout=subprocess.PIPE
+  )
+  try:
+    with open(out_zip_path, "wb") as f:
+      while True:
+        chunk = proc.stdout.read(1024 * 1024)
+        if not chunk:
+          break
+        f.write(chunk)
+  finally:
+    if proc.stdout:
+      proc.stdout.close()
+    rc = proc.wait()
+    if rc != 0:
+      try:
+        out_zip_path.unlink(missing_ok=True)
+      finally:
+        raise subprocess.CalledProcessError(rc, proc.args)
+
+# ----------------------------------------------------------------------
+# work function
+# ----------------------------------------------------------------------
+def work(
+  ref: str = "HEAD"
+  ,outdir: Optional[pathlib.Path] = None
+  ,force_no_stamp: bool = False
+  ,z_format: Optional[str] = None
+  ,archive_kind: str = "tar"   # "tar" or "zip"
+) -> pathlib.Path:
+  if archive_kind not in ("tar","zip"):
+    raise RuntimeError("archive_kind must be 'tar' or 'zip'")
+
+  if not _in_git_repo():
+    raise RuntimeError("not inside a git repository")
+
+  repo_top = _git_top()
+  repo_name = repo_top.name
+  ref_label = _git_ref_label(repo_top, ref)
+
+  stamp: Optional[str] = None
+  if not force_no_stamp:
+    zmod = _import_Z_module(repo_top)
+    if zmod is not None:
+      stamp = make_z_stamp(zmod, z_format or Z_FORMAT)
+
+  target_dir = (outdir or (repo_top / "scratchpad"))
+  target_dir.mkdir(parents=True, exist_ok=True)
+
+  suffix = ".zip" if archive_kind == "zip" else ".tar.gz"
+  out_name = f"{repo_name}__{ref_label}{('__' + stamp) if stamp else ''}{suffix}"
+  out_path = target_dir / out_name
+
+  if archive_kind == "zip":
+    _stream_git_archive_zip(repo_top, repo_name, ref, out_path)
+  else:
+    _stream_git_archive_tar(repo_top, repo_name, ref, out_path)
+
+  return out_path
+
+# ----------------------------------------------------------------------
+# CLI with command tokens
+# ----------------------------------------------------------------------
+def CLI(argv: Optional[list[str]] = None) -> int:
+  if argv is None:
+    argv = sys.argv[1:]
+
+  # defaults
+  ref = "HEAD"
+  outdir: Optional[pathlib.Path] = None
+  force_no_stamp = False
+  z_format: Optional[str] = None
+  archive_kind = "tar"
+
+  # no args → do the default action
+  if not argv:
+    try:
+      print(f"Wrote {work(ref=ref, outdir=outdir, force_no_stamp=force_no_stamp, z_format=z_format, archive_kind=archive_kind)}")
+      return 0
+    except Exception as e:
+      print(f"git-tar: {e}", file=sys.stderr); return 1
+
+  # consume tokens (order-insensitive)
+  for arg in argv:
+    if arg in ("help","-h","--help"):
+      print(USAGE); return 0
+    if arg == "version":
+      print(f"git-tar {VERSION}"); return 0
+    if arg == "no-stamp":
+      force_no_stamp = True; continue
+    if arg == "zip":
+      archive_kind = "zip"; continue
+    if arg == "tar":
+      archive_kind = "tar"; continue
+    if arg.startswith("ref-"):
+      ref = arg[4:] or ref; continue
+    if arg.startswith("out-"):
+      od = arg[4:]; outdir = pathlib.Path(od).resolve() if od else None; continue
+    if arg.startswith("z-format-"):
+      z_format = arg[len("z-format-"):] or None; continue
+    print(f"git-tar: unknown command '{arg}'", file=sys.stderr); return 1
+
+  # run
+  try:
+    out_path = work(ref=ref, outdir=outdir, force_no_stamp=force_no_stamp, z_format=z_format, archive_kind=archive_kind)
+  except Exception as e:
+    print(f"git-tar: {e}", file=sys.stderr); return 1
+
+  print(f"Wrote {out_path}")
+  return 0
+
+# ----------------------------------------------------------------------
+if __name__ == "__main__":
+  raise SystemExit(CLI())
index d5900f4..fd3758f 100644 (file)
@@ -9,7 +9,7 @@ SHELL=/bin/bash
 
 ECHO := printf "%b\n"
 
-C_SOURCE_DIR     := cc
+C_SOURCE_DIR     := authored
 C                := gcc
 CFLAGS           := -std=gnu11 -Wall -Wextra -Wpedantic -finput-charset=UTF-8
 CFLAGS           += -MMD -MP
@@ -23,7 +23,7 @@ LIBRARY_FILE    := $(LIBRARY_DIR)/lib$(LIBRARY_NAME).a
 
 LN_FLAGS        := -L$(LIBRARY_DIR) -L/lib64 -L/lib
 
-MACHINE_DIR     := scratchpad/machine
+MACHINE_DIR     := scratchpad/loadable
 
 KMOD_SOURCE_DIR  := cc
 KMOD_CCFLAGS    := -I $(KMOD_SOURCE_DIR)