feat(build): load local inputs and add build step shortcuts

This commit is contained in:
rponeawa committed 2026-10-25 03:02:54 +08:00
1 parent 9521f49712
commit 80cf8d4923
7 files changed
+79 -3

No files matched your search

+1
View File
@@ -49,3 +49,4 @@ __pycache__/
*.key
.env
local.toml
unixnu.local.toml
+1 -1
View File
@@ -63,7 +63,7 @@ kind = "driver"
function = "UEFI XNU loader"
scope = "firmware"
repository = "https://github.com/UniXNU/DarwinBoot.git"
revision = "0639d4b7a621b62120f16d5ba2c29ebdbd653a8f"
revision = "3325e352d7df68a9f9ad74832ed94a6acbbd7390"
path = "drivers/DarwinBoot"
hardware = ["ARM64 UEFI"]
os_abi = []
+10 -1
View File
@@ -15,6 +15,7 @@ OPTIONAL_STEPS = ('userdata',) # large; built only when named
def common():
p = argparse.ArgumentParser(add_help=False)
p.add_argument('--config', metavar='FILE', help='local build inputs (default: unixnu.local.toml in the UniXNU checkout)')
p.add_argument('-D', '--device', metavar='DIR', help='device repository (remembered; or UNIXNU_DEVICE)')
p.add_argument('--out', metavar='DIR', help='output root (default: out/ here, or UNIXNU_OUT)')
p.add_argument('-j', '--jobs', type=int, help='parallel jobs')
@@ -36,7 +37,7 @@ def common():
p.add_argument('--unique-chip-id', metavar='HEX', help='platform seed for the device tree; or UNIXNU_UNIQUE_CHIP_ID')
p.add_argument('--ipsw', metavar='PATH_OR_URL', help='the target IPSW: a local file or an https URL (default: url of board.toml [ipsw]); or UNIXNU_IPSW')
p.add_argument('--dry-run', action='store_true', help='userdata: check sizes and pins, write nothing')
p.add_argument('--target', default='DEBUG', choices=('DEBUG', 'RELEASE'), help='EDK II build target')
p.add_argument('--target', choices=('DEBUG', 'RELEASE'), help='EDK II build target')
return p
@@ -52,6 +53,9 @@ def parser():
sub.add_parser('tree', parents=[c], help='assemble the build tree')
b = sub.add_parser('build', parents=[c], help='build kernel, package and firmware')
b.add_argument('steps', nargs='*', choices=STEPS + OPTIONAL_STEPS + ('all', 'prelink'), default=['all'])
for step in STEPS + OPTIONAL_STEPS + ('prelink',):
shortcut = sub.add_parser(step, parents=[c], help=f'alias for build {step}')
shortcut.set_defaults(steps=[step])
i = sub.add_parser('bootimg', parents=[c], help='pack the boot image from the firmware volume and a device tree')
i.add_argument('--fd', metavar='FILE', help='firmware volume with UniXNU, DarwinBoot and payload filesystem support (default: build firmware output under --uefi)')
i.add_argument('--dtb', metavar='FILE', help='compiled device tree; or UNIXNU_DTB')
@@ -218,6 +222,7 @@ def cmd_status(ctx):
if ctx.args.json:
print(json.dumps({'schema_version': ctx.schema_version, 'device': str(ctx.device), 'name': ctx.name,
'output': str(ctx.work), 'configuration': ctx.config_name,
'build_config': ctx.args.build_config,
'config_path': str(ctx.config_path), 'configured': ctx.config_path.is_file(),
'profiles': {key: str(value) for key, value in ctx.profile_roots.items()},
'configuration_sources': [str(path) for path in ctx.profile_files],
@@ -226,6 +231,8 @@ def cmd_status(ctx):
return
print(f'device {ctx.device} ({ctx.name})' + (' [EXAMPLE]' if ctx.example else ''))
print(f'output {ctx.work}')
if ctx.args.build_config:
print(f'inputs {ctx.args.build_config}')
print(f'config {ctx.config_path if ctx.config_path.is_file() else "none (run defconfig)"}')
if ctx.schema_version == 2:
for name, path in ctx.profile_roots.items():
@@ -268,6 +275,8 @@ def main(argv=None):
print('unixnu: developed and tested on macOS and Linux only; the kernel step needs Xcode or the Linux toolchain, '
'and --skip-setup needs hdiutil', file=sys.stderr)
args = parser().parse_args(argv)
if args.command in STEPS + OPTIONAL_STEPS + ('prelink',):
args.command = 'build'
try:
ctx = Context(args)
{'defconfig': cmd_defconfig, 'fetch': cmd_fetch, 'tree': lambda c: prepare(c), 'build': cmd_build,
+51
View File
@@ -29,6 +29,56 @@ def load_toml(path):
raise Error(f'{path}: {error}') from None
BUILD_PATHS = {'device', 'out', 'uefi', 'edk2', 'kernelcache', 'kernel',
'trust_cache', 'owner_trust_cache', 'data_ramdisk', 'gpu_addon',
'dtb', 'fd', 'ramdisk', 'output'}
BUILD_ENV = {'device': 'UNIXNU_DEVICE', 'out': 'UNIXNU_OUT', 'uefi': 'UNIXNU_UEFI',
'edk2': 'UNIXNU_EDK2', 'kernelcache': 'UNIXNU_KERNELCACHE',
'dtb': 'UNIXNU_DTB', 'ipsw': 'UNIXNU_IPSW',
'unique_chip_id': 'UNIXNU_UNIQUE_CHIP_ID', 'jobs': 'JOBS'}
def load_build_options(args):
"""Apply local build inputs below explicit flags and environment variables."""
explicit = getattr(args, 'config', None)
path = expand(explicit) if explicit else ROOT / 'unixnu.local.toml'
args.build_config = None
if not explicit and not path.is_file():
args.target = args.target or 'DEBUG'
return
document = load_toml(path)
if set(document) - {'build'} or not isinstance(document.get('build'), dict):
raise Error(f'{path}: expected a [build] table')
options = document['build']
allowed = BUILD_PATHS | {'ipsw', 'unique_chip_id', 'jobs', 'target', 'kernel_args'}
for key, value in options.items():
if key not in allowed:
raise Error(f'{path}: unknown build option {key}')
if key == 'kernel_args':
if not isinstance(value, list) or any(not isinstance(item, str) for item in value):
raise Error(f'{path}: kernel_args must be an array of strings')
extra = getattr(args, 'kernel_arg', None) or []
names = {item.split('=', 1)[0] for item in extra}
args.kernel_arg = [item for item in value if item.split('=', 1)[0] not in names] + extra
continue
if key == 'jobs':
if type(value) is not int or value < 1:
raise Error(f'{path}: jobs must be a positive integer')
elif not isinstance(value, str) or not value:
raise Error(f'{path}: {key} must be a nonempty string')
if key == 'target' and value not in ('DEBUG', 'RELEASE'):
raise Error(f'{path}: target must be DEBUG or RELEASE')
if getattr(args, key, None) is not None or os.environ.get(BUILD_ENV.get(key, '')):
continue
if key in BUILD_PATHS or (key == 'ipsw' and '://' not in value):
if not (key == 'ramdisk' and value == 'none'):
local = Path(os.path.expandvars(value)).expanduser()
value = str((path.parent / local).resolve())
setattr(args, key, value)
args.target = args.target or 'DEBUG'
args.build_config = str(path)
def parse_config(text):
"""Parse a defconfig: CONFIG_X=y, =n, ="text", =123 and "# CONFIG_X is not set"."""
values = {}
@@ -62,6 +112,7 @@ class Context:
def __init__(self, args):
self.root = ROOT
load_build_options(args)
self.args = args
self.out_root = expand(args.out or os.environ.get('UNIXNU_OUT') or ROOT / 'out')
self.state_path = self.out_root / 'state.json'
+2
View File
@@ -95,6 +95,7 @@ def build(ctx, kernel=None):
names = {item.split('=', 1)[0] for item in extra}
arguments = [item for item in spec.get('kernel_args', []) if item.split('=', 1)[0] not in names] + extra
line = check_args(arguments)
log(f'kernel args: {line}')
check_native_root(kernel, arguments)
owner_trust = getattr(args, 'owner_trust_cache', None)
trust = expand(owner_trust or args.trust_cache) if (owner_trust or getattr(args, 'trust_cache', None)) else None
@@ -212,4 +213,5 @@ def build(ctx, kernel=None):
report.write_text(json.dumps(data_report, indent=2) + '\n')
shutil.rmtree(stage)
log(f'payload: {image} ({image.stat().st_size >> 20} MiB)')
log('next: run ./unixnu bootimg to pack this payload into the boot image')
return image
+13
View File
@@ -0,0 +1,13 @@
# Copy to unixnu.local.toml and fill in the paths for your device.
# Relative paths start at this file's directory.
[build]
device = "../UniXNU-<device>"
uefi = "../uefi-<device>"
dtb = "/path/to/firmware.dtb"
kernelcache = "/path/to/kernelcache"
trust_cache = "/path/to/trustcache.bundle"
data_ramdisk = "/path/to/data-seed.dbr"
unique_chip_id = "<hex>"
jobs = 8
target = "DEBUG"
kernel_args = ["-v", "serial=3", "debug=0x14e"]