#!/usr/bin/env python3 """Build helpers shared by Mark VIII deploy scripts. stamp_version() stamps release metadata into BUILD COPIES ONLY: callers always pass a staging/build directory, never the source tree, so the repo stays clean while every deployed artifact reports the same VERSION. """ import json import os import re import sys VERSION_RE = re.compile(r'^[0-9][A-Za-z0-9._-]{0,31}$') SERVER_VERSION_LINE = "SERVER_VERSION = '%s'\n" def _stamp_manifest(path, version): try: with open(path, encoding='utf-8') as f: data = json.load(f) except (OSError, ValueError): return False if not isinstance(data, dict): return False data['version'] = version with open(path, 'w', encoding='ascii') as f: json.dump(data, f, indent=2) f.write('\n') return True def _stamp_payload_sh(path, version): try: with open(path, encoding='utf-8') as f: lines = f.readlines() except OSError: return False out = [] replaced = False for line in lines: m = None if replaced \ else re.match(r'^(\s*#\s*[Vv]ersion:).*$', line) if m: out.append(m.group(1) + ' ' + version + '\n') replaced = True else: out.append(line) if not replaced: insert = 1 if lines and lines[0].startswith('#!') else 0 out.insert(insert, '# Version: %s\n' % version) with open(path, 'w', encoding='utf-8') as f: f.writelines(out) return True def _server_insert_index(lines): """Index just past any shebang/comments/blanks and module docstring.""" i = 0 n = len(lines) if i < n and lines[i].startswith('#!'): i += 1 while i < n and (lines[i].strip().startswith('#') or not lines[i].strip()): i += 1 if i < n: stripped = lines[i].lstrip() quote = stripped[:3] if quote in ('"""', "'''"): closed_here = quote in stripped[3:] i += 1 if not closed_here: while i < n and quote not in lines[i]: i += 1 i += 1 return min(i, len(lines)) def _stamp_server_py(path, version): try: with open(path, encoding='utf-8') as f: lines = f.readlines() except OSError: return False lines = [l for l in lines if not l.startswith('SERVER_VERSION')] idx = _server_insert_index(lines) lines.insert(idx, SERVER_VERSION_LINE % version) with open(path, 'w', encoding='utf-8') as f: f.writelines(lines) return True def stamp_version(build_dir, version): """Stamp into build copies found under build_dir. Updates every ``_hak5_manifest.json`` (version field), ``payload.sh`` (header Version line) and ``server.py`` (injected SERVER_VERSION constant near the top). Idempotent: re-running never duplicates the injected constant or header. Returns the list of stamped paths.""" if not VERSION_RE.match(str(version)): raise ValueError('invalid version string: %r' % (version,)) stamped = [] for root, dirs, files in os.walk(build_dir): for fname in ('_hak5_manifest.json', 'payload.sh', 'server.py'): if fname in files: path = os.path.join(root, fname) if fname == '_hak5_manifest.json': ok = _stamp_manifest(path, version) elif fname == 'payload.sh': ok = _stamp_payload_sh(path, version) else: ok = _stamp_server_py(path, version) if ok: stamped.append(path) return sorted(stamped) if __name__ == '__main__': if len(sys.argv) != 3: print('usage: build_common.py ', file=sys.stderr) sys.exit(2) for path in stamp_version(sys.argv[1], sys.argv[2]): print('stamped: %s' % path)