Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
129
tundra/tools/build-system-image.py
Normal file
129
tundra/tools/build-system-image.py
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
"""Assemble a private Sargo system image, including its Android HAL reference."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
PROJECT = ROOT.parent
|
||||
ANDROID_SHA = '132d0e18c6a6a111293447e940267f35aa2b14fa00a872ef160b20dc2014d886'
|
||||
|
||||
|
||||
def digest(path):
|
||||
with path.open('rb') as stream:
|
||||
return hashlib.file_digest(stream, 'sha256').hexdigest()
|
||||
|
||||
|
||||
def module(name, path):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
result = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(result)
|
||||
return result
|
||||
|
||||
|
||||
def pack(root, image):
|
||||
if not os.environ.get('FAKEROOTKEY'):
|
||||
raise RuntimeError('Image ownership must be prepared under fakeroot')
|
||||
for directory, dirs, files in os.walk(root):
|
||||
for path in [Path(directory), *[Path(directory)/n for n in dirs+files]]:
|
||||
os.lchown(path, 0, 0)
|
||||
# The trial session refreshes its local Gecko host files before starting.
|
||||
for base in (root/'opt/vulpes', root/'home/droidian'):
|
||||
for directory, dirs, files in os.walk(base):
|
||||
for path in [Path(directory), *[Path(directory)/n for n in dirs+files]]:
|
||||
os.lchown(path, 32011, 32011)
|
||||
features = 'none,has_journal,ext_attr,dir_index,filetype,extent,sparse_super,large_file,uninit_bg,dir_nlink,extra_isize'
|
||||
subprocess.run(['mke2fs', '-q', '-t', 'ext4', '-F', '-b', '4096', '-I', '256',
|
||||
'-O', features, '-L', 'TUNDRA_SYSTEM', '-d', str(root), str(image)], check=True)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--output', type=Path, required=True)
|
||||
parser.add_argument('--reference', type=Path, default=ROOT/'downloads/sargo-installed/runtime-reference-20260925')
|
||||
parser.add_argument('--product', type=Path, default=ROOT/'out/droidian/product')
|
||||
parser.add_argument('--pack', nargs=2, type=Path, help=argparse.SUPPRESS)
|
||||
args = parser.parse_args()
|
||||
if args.pack:
|
||||
pack(*args.pack)
|
||||
return
|
||||
if os.geteuid() == 0:
|
||||
parser.error('Build as a regular user')
|
||||
android = ROOT/'downloads/sargo-installed/android-rootfs.img'
|
||||
if digest(android) != ANDROID_SHA:
|
||||
raise ValueError('Android HAL reference changed')
|
||||
lock = json.loads((args.product/'engine-lock.json').read_text())
|
||||
current = json.loads((PROJECT/'engine-lock.json').read_text())
|
||||
if lock.get('architecture') != 'aarch64' or lock['version'] != current['version']:
|
||||
raise ValueError('Prepare the matching ARM64 engine first')
|
||||
output = args.output.resolve()
|
||||
output.mkdir(parents=True, exist_ok=False)
|
||||
root = output/'root'
|
||||
staging = module('stage', ROOT/'tools/stage-runtime.py').stage(args.reference, root)
|
||||
product = root/'opt/vulpes'
|
||||
# Read UI/services from the common checkout, and only the engine/EGL adapter
|
||||
# from the checked ARM64 product. Never copy profiles or phone configuration.
|
||||
for name in ('assets', 'host', 'services', 'adapters', 'overrides', 'gaia/shared', 'gaia/apps/system'):
|
||||
shutil.copytree(PROJECT/name, product/name, symlinks=True,
|
||||
ignore=shutil.ignore_patterns('__pycache__', 'test', 'tests'))
|
||||
for name in ('tools', 'tests', 'logs', 'profiles'):
|
||||
(product/name).mkdir(exist_ok=True)
|
||||
for name in ('serve-gaia.py', 'project.py', 'run-host.py'):
|
||||
shutil.copy2(PROJECT/'tools'/name, product/'tools'/name)
|
||||
shutil.copy2(PROJECT/'tests/ProbeChild.sys.mjs', product/'tests/ProbeChild.sys.mjs')
|
||||
shutil.copy2(PROJECT/'release.json', product/'release.json')
|
||||
shutil.copy2(args.product/'engine-lock.json', product/'engine-lock.json')
|
||||
shutil.copytree(args.product/'engines', product/'engines', symlinks=True)
|
||||
shutil.copytree(ROOT/'droidian', product/'trial', ignore=shutil.ignore_patterns('__pycache__'))
|
||||
for name in ('libEGL_vulpes_hybris.so', 'manifest.json'):
|
||||
shutil.copy2(args.product/'trial/graphics'/name, product/'trial/graphics'/name)
|
||||
for name in ('home/droidian', 'etc/dbus-1', 'reference'):
|
||||
(root/name).mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(android, root/'reference/android-rootfs.img')
|
||||
module('native', ROOT/'runtime/prepare-native.py').prepare(root)
|
||||
shutil.copy2(ROOT/'droidian/initialize-runtime.sh', root/'etc/tundra/initialize-runtime.sh')
|
||||
# Caches are architecture dependent; generate them with the target binaries.
|
||||
(root/'etc/systemd/system/tundra-initialize.service').write_text('''[Unit]
|
||||
Description=Initialize Tundra runtime caches
|
||||
Before=tundra-hal.service tundra-display.service
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/sh /etc/tundra/initialize-runtime.sh
|
||||
RemainAfterExit=yes
|
||||
''')
|
||||
for service in ('tundra-hal', 'tundra-display'):
|
||||
folder = root/f'etc/systemd/system/{service}.service.d'
|
||||
folder.mkdir(exist_ok=True)
|
||||
(folder/'initialize.conf').write_text('[Unit]\nRequires=tundra-initialize.service\nAfter=tundra-initialize.service\n')
|
||||
image = output/'tundra-sargo-system.ext4'
|
||||
with image.open('xb') as stream:
|
||||
stream.truncate(3*1024**3)
|
||||
subprocess.run(['fakeroot', '--', sys.executable, str(Path(__file__).resolve()),
|
||||
'--output', str(output), '--pack', str(root), str(image)], check=True)
|
||||
check = subprocess.run(['e2fsck', '-f', '-n', str(image)], capture_output=True, text=True)
|
||||
(output/'filesystem-check.txt').write_text(check.stdout+check.stderr)
|
||||
if check.returncode:
|
||||
raise RuntimeError('System image failed filesystem verification')
|
||||
manifest = {'schema': 1, 'device': 'sargo', 'image': image.name,
|
||||
'imageSha256': digest(image), 'bytes': image.stat().st_size,
|
||||
'androidSha256': ANDROID_SHA, 'engine': lock,
|
||||
'release': json.loads((product/'release.json').read_text()),
|
||||
'staging': staging, 'filesystemCheckPassed': True,
|
||||
'flashReady': False, 'hardwareBootTested': False,
|
||||
'scope': 'Self-contained userspace; vendor/firmware partitions still required; RAM boot candidate',
|
||||
'sourceHashes': {str(p.relative_to(PROJECT)): digest(p) for p in
|
||||
[Path(__file__).resolve(), ROOT/'tools/stage-runtime.py',
|
||||
*sorted((ROOT/'runtime').glob('*.py'))]}}
|
||||
(output/'system.json').write_text(json.dumps(manifest, indent=2)+'\n')
|
||||
print(image, manifest['imageSha256'], flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Loading…
Reference in a new issue