diff --git a/.gitignore b/.gitignore index 92605fd..59fc3d9 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ __pycache__/ *.key .env local.toml +unixnu.local.toml diff --git a/components.toml b/components.toml index f424efe..505cf96 100644 --- a/components.toml +++ b/components.toml @@ -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 = [] diff --git a/drivers/DarwinBoot b/drivers/DarwinBoot index 0639d4b..3325e35 160000 --- a/drivers/DarwinBoot +++ b/drivers/DarwinBoot @@ -1 +1 @@ -Subproject commit 0639d4b7a621b62120f16d5ba2c29ebdbd653a8f +Subproject commit 3325e352d7df68a9f9ad74832ed94a6acbbd7390 diff --git a/scripts/unixnu/cli.py b/scripts/unixnu/cli.py index 76cefb4..cb476af 100644 --- a/scripts/unixnu/cli.py +++ b/scripts/unixnu/cli.py @@ -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, diff --git a/scripts/unixnu/config.py b/scripts/unixnu/config.py index 62ca126..0d96c63 100644 --- a/scripts/unixnu/config.py +++ b/scripts/unixnu/config.py @@ -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' diff --git a/scripts/unixnu/payload.py b/scripts/unixnu/payload.py index e21376f..1bb9ad0 100644 --- a/scripts/unixnu/payload.py +++ b/scripts/unixnu/payload.py @@ -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 diff --git a/unixnu.example.toml b/unixnu.example.toml new file mode 100644 index 0000000..b6e9af0 --- /dev/null +++ b/unixnu.example.toml @@ -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-" +uefi = "../uefi-" +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 = "" +jobs = 8 +target = "DEBUG" +kernel_args = ["-v", "serial=3", "debug=0x14e"]