Phase 20: package Linux builds and document release and maintenance workflows

This commit is contained in:
Johannes Findeisen 2026-10-27 06:44:22 +01:00
commit 830b6e1914
22 changed files with 490 additions and 17 deletions

76
tools/package.py Normal file
View file

@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""Stage a Linux binary/development archive without modifying a live prefix."""
import argparse
import gzip
import hashlib
import io
import json
import os
from pathlib import Path
import platform
import re
import shlex
import subprocess
import tarfile
import tempfile
from dist import version
def main():
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('--build-dir', type=Path, required=True)
p.add_argument('--output', type=Path, required=True)
p.add_argument('--gui', action='store_true')
a = p.parse_args()
epoch = int(os.environ.get('SOURCE_DATE_EPOCH', '0'))
if not 0 <= epoch <= 4294967295:
p.error('SOURCE_DATE_EPOCH must fit uint32')
name = f'xtc-{version(Path.cwd())}-linux-{platform.machine()}'
a.output.parent.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(prefix='xtc-binary-stage-') as temporary:
stage = Path(temporary)
env = dict(os.environ, MAKEFLAGS='', MFLAGS='')
subprocess.run(shlex.split(env.get('MAKE', 'make')) + ['install-gui' if a.gui else 'install',
f'BUILD_DIR={a.build_dir.resolve()}', f'DESTDIR={stage}', 'PREFIX=/usr'], env=env, check=True)
pc = stage / 'usr/lib/pkgconfig/xtc.pc'
pc.write_text(pc.read_text().replace('prefix=/usr\n', 'prefix=${pcfiledir}/../..\n'))
entries = []
needed = {}
for path in sorted(stage.rglob('*')):
if not path.is_file():
continue
assert not path.is_symlink()
relative = path.relative_to(stage).as_posix()
entries.append(dict(path=relative, size=path.stat().st_size,
sha256=hashlib.sha256(path.read_bytes()).hexdigest()))
if relative in ('usr/bin/xtc', 'usr/bin/xtc-check'):
dynamic = subprocess.check_output(['readelf', '-d', str(path)], text=True)
needed[relative] = re.findall(r'\(NEEDED\).*\[(.*?)\]', dynamic)
manifest = dict(schema=1, version=version(Path.cwd()), architecture=platform.machine(),
libc=list(platform.libc_ver()), needed=needed, files=entries,
scope='System-linked Linux build; no libraries, plugins or media bundled.')
(stage / 'usr/share/doc/xtc/BINARY_MANIFEST.json').write_text(json.dumps(manifest, indent=2) + '\n')
fd, pending = tempfile.mkstemp(prefix='.xtc-package-', dir=a.output.parent)
try:
with os.fdopen(fd, 'wb') as raw, gzip.GzipFile(fileobj=raw, filename='', mode='wb', mtime=epoch) as gz:
with tarfile.open(fileobj=gz, mode='w', format=tarfile.PAX_FORMAT) as tar:
for path in sorted(stage.rglob('*')):
if not path.is_file():
continue
data = path.read_bytes()
info = tarfile.TarInfo(name + '/' + path.relative_to(stage).as_posix())
info.size = len(data)
info.mode = 0o755 if os.access(path, os.X_OK) else 0o644
info.mtime = epoch
tar.addfile(info, io.BytesIO(data))
os.replace(pending, a.output)
finally:
if os.path.exists(pending):
os.unlink(pending)
sha = hashlib.sha256(a.output.read_bytes()).hexdigest()
a.output.with_name(a.output.name + '.sha256').write_text(f'{sha} {a.output.name}\n')
print(f'{a.output}: {sha}')
if __name__ == '__main__':
main()

View file

@ -8,7 +8,8 @@ import stat
def owned_files(source):
files = ['lib/libxtc.a', 'lib/pkgconfig/xtc.pc', 'bin/xtc-check', 'bin/xtc',
'share/applications/xtc.desktop', 'share/licenses/xtc/LICENSE',
'share/doc/xtc/README.md']
'share/doc/xtc/README.md', 'share/doc/xtc/performance-baseline.json',
'share/doc/xtc/BINARY_MANIFEST.json']
files += ['include/xtc/' + p.name for p in sorted((source / 'include/xtc').glob('*.h'))]
files += ['share/doc/xtc/' + p.name for p in sorted((source / 'docs').glob('*.md'))]
files += ['share/doc/xtc/decisions/' + p.name for p in sorted((source / 'docs/decisions').glob('*.md'))]