makepad/tools/arch_usb/clone_export.py
Admin d29ff812b7 tools, libs and the rest
git pack reads and cache bytes, image tiles for phone encoders, video
coverage tool, cargo launch helper, CI and builder updates; arch/ is
removed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 07:24:22 +02:00

99 lines
4.8 KiB
Python

#!/usr/bin/env python3
"""Read-only exporter, sent over SSH by build.py --clone (no remote install)."""
import hashlib
import io
import json
import os
from pathlib import Path
import subprocess
import sys
import tarfile
import time
def main():
root = Path(sys.argv[1]).resolve()
if not (root/'Cargo.toml').is_file():
raise RuntimeError(f'Not a Makepad checkout: {root}')
binaries = {name: root/'target/release'/name for name in ('makepad-app-wm',)}
for name, path in binaries.items():
with path.open('rb') as stream:
if stream.read(4) != b'\x7fELF':
raise RuntimeError(f'Missing Linux release binary: {name}')
# The WM is the one standalone service. Hosted apps and the AI hub (the WM's own
# child, apps/wm/src/hub_child.rs) keep the normal Cargo launch path; do not
# collect prebuilt binaries for them.
services = {}
for name in ('makepad-wm.service',):
properties = subprocess.check_output(
['systemctl', 'show', name, '-p', 'ActiveState', '-p', 'WorkingDirectory', '-p', 'ExecStart'], text=True)
if 'ActiveState=active\n' not in properties or f'WorkingDirectory={root}\n' not in properties:
raise RuntimeError(f'{name} is not active from {root}')
services[name] = properties
cargo = Path.home()/'.cargo'
cef = (root/'local/cef-prebuilt/current-linux64').resolve(strict=True)
manifest = {'exported_at': time.strftime('%Y-%m-%dT%H:%M:%S%z'),
'source_root': str(root), 'services': services,
'cef_directory': cef.name, 'binary_sha256': {}}
listed = subprocess.check_output(
['git', 'ls-files', '-z', '--cached', '--others', '--exclude-standard'], cwd=root).split(b'\0')
names = {Path(raw.decode('utf-8', 'surrogateescape')) for raw in listed if raw}
if (root/'Cargo.lock').exists(): names.add(Path('Cargo.lock'))
source_count = 0
def add(archive, path, name):
before = path.stat()
def portable_link(info):
if info.issym() and info.linkname.startswith('/'):
info.linkname = os.path.relpath(path.resolve(), path.parent)
return info
archive.add(path, arcname=name, recursive=False, filter=portable_link)
after = path.stat()
if (before.st_size, before.st_mtime_ns) != (after.st_size, after.st_mtime_ns):
raise RuntimeError(f'File changed during export; retry: {path}')
with tarfile.open(fileobj=sys.stdout.buffer, mode='w|gz', compresslevel=3) as archive:
for relative in sorted(names):
if relative.is_absolute() or '..' in relative.parts:
raise RuntimeError(f'Invalid source path: {relative}')
if any(part in ('local', '.git', '.claude', '.grok', '__pycache__') or part.startswith('target') for part in relative.parts):
continue
if relative.name.startswith('.env') or relative.suffix in ('.log', '.pem', '.key', '.pyc'):
continue
path = root/relative
if not path.is_file() and not path.is_symlink(): continue
if path.is_symlink() and not path.resolve().is_relative_to(root): continue
add(archive, path, 'source/'+relative.as_posix())
source_count += 1
for name, path in binaries.items():
with path.open('rb') as stream:
manifest['binary_sha256'][name] = hashlib.file_digest(stream, 'sha256').hexdigest()
add(archive, path, 'binaries/'+name)
# No Cargo credentials or host-specific Cargo configuration.
for directory in ('registry', 'git'):
base = cargo/directory
if not base.is_dir(): continue
for path in sorted(base.rglob('*')):
if not path.is_file() and not path.is_symlink(): continue
if path.is_symlink() and not path.resolve().is_relative_to(cargo): continue
add(archive, path, 'cargo/'+path.relative_to(cargo).as_posix())
for name, path in {
'wm-session.sh': '/usr/local/bin/makepad-wm-session',
'wm.env': '/etc/makepad/wm.env',
'makepad-wm.service': '/etc/systemd/system/makepad-wm.service',
}.items():
add(archive, Path(path), 'config/'+name)
# The WM's AI hub settings, when this machine has its own.
hub_conf = Path.home()/'.makepad/wm/ai-hub.conf'
if hub_conf.is_file():
add(archive, hub_conf, 'config/ai-hub.conf')
manifest['source_files'] = source_count
data = (json.dumps(manifest, indent=2)+'\n').encode()
info = tarfile.TarInfo('manifest.json')
info.size = len(data)
archive.addfile(info, io.BytesIO(data))
print(f'Exported {source_count} source files, the WM service, the AI hub settings and Cargo cache.', file=sys.stderr)
if __name__ == '__main__':
main()