tools/makepad_builder replaces tools/makepad_loader: one build target shared across app builds, workspace package selection, checkout progress on the public Git API, detached built apps with a completion state, waits for Windows security scans, manual retry after compiler locks, dedicated-folder installer checks, catalog and runtime fixes. tools/web_server and its scripts leave for github.com/makepad/webserver. Arch USB clone/restore scripts, the qwen38 box scripts and the G-belt serial test join tools/. docs/agents records the agent workflow and the remote-control handoff protocol; AGENTS.md forbids vendored sources and bulk imports. Cargo.toml lists apps/wm-dyn, libs/code_language, libs/search, libs/tar, libs/loader_bundle and tools/makepad_builder, and drops the two removed crates. Squashed from work: - Share Builder target across Makepad app builds - Fix Builder workspace package selection - Align Builder checkout progress with public Git API - Detach built apps and show completion state - Wait for Windows security scans - Offer manual retry after Windows compiler locks - docs: the agent workflow of record and the remote-control handoff protocol - builder: dedicated-folder installer checks, catalog and runtime fixes; Windows job objects hold c_void handles - tools: Arch USB clone/restore scripts, the qwen38 box scripts, and the G-belt serial test - tools: the web server moves to makepad/webserver - AGENTS.md: no vendored sources or bulk imports in the tree Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
97 lines
4.8 KiB
Python
97 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 ('wm', 'makepad-ai-hub')}
|
|
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}')
|
|
# These are the two standalone services. Hosted apps retain the normal
|
|
# Cargo launch path; do not collect a set of prebuilt hosted app binaries.
|
|
services = {}
|
|
for name in ('makepad-wm.service', 'makepad-aihub.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',
|
|
'aihub-session.sh': '/usr/local/bin/makepad-aihub-session',
|
|
'wm.env': '/etc/makepad/wm.env',
|
|
'aihub.env': '/etc/makepad/aihub.env',
|
|
'makepad-wm.service': '/etc/systemd/system/makepad-wm.service',
|
|
'makepad-aihub.service': '/etc/systemd/system/makepad-aihub.service',
|
|
}.items():
|
|
add(archive, Path(path), 'config/'+name)
|
|
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, WM/AI Hub services and Cargo cache.', file=sys.stderr)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|