89 lines
4.3 KiB
Python
89 lines
4.3 KiB
Python
#!/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 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)
|
|
driver = stage / 'cmake-driver'
|
|
cache = (a.build_dir / 'CMakeCache.txt').read_text()
|
|
generator = next(line.partition('=')[2] for line in cache.splitlines()
|
|
if line.startswith('CMAKE_GENERATOR:INTERNAL='))
|
|
jack = 'OFF' if 'XTC_WITH_JACK:BOOL=OFF' in cache else 'ON'
|
|
subprocess.run(['cmake', '-G', generator, '-S', str(Path.cwd()), '-B', str(driver),
|
|
'-DBUILD_TESTING=OFF', '-DCMAKE_INSTALL_PREFIX=/usr', '-DCMAKE_INSTALL_LIBDIR=lib',
|
|
f'-DXTC_BUILD_GUI={"ON" if a.gui else "OFF"}',
|
|
'-DXTC_INSTALL_PKGCONFIGDIR=lib/pkgconfig',
|
|
f'-DXTC_INSTALL_BUILD_DIR={a.build_dir.resolve()}',
|
|
f'-DXTC_WITH_JACK={jack}', f'-DXTC_INSTALL_GUI={"ON" if a.gui else "OFF"}'],
|
|
env=env, check=True)
|
|
subprocess.run(['cmake', '--install', str(driver)], env=dict(env, DESTDIR=str(stage)), check=True)
|
|
# Only the installed /usr tree belongs to the archive.
|
|
import shutil
|
|
shutil.rmtree(driver)
|
|
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()
|