Initial commit.
This commit is contained in:
		
							parent
							
								
									051f708886
								
							
						
					
					
						commit
						d21f41448f
					
				
					 8 changed files with 1010 additions and 0 deletions
				
			
		
							
								
								
									
										8
									
								
								.mypyconfig
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.mypyconfig
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | ||||||
|  | [mypy] | ||||||
|  | disallow_untyped_defs = True | ||||||
|  | disallow_any_unimported = True | ||||||
|  | no_implicit_optional = True | ||||||
|  | check_untyped_defs = True | ||||||
|  | warn_return_any = True | ||||||
|  | show_error_codes = True | ||||||
|  | warn_unused_ignores = True | ||||||
							
								
								
									
										194
									
								
								__main__.py
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										194
									
								
								__main__.py
									
										
									
									
									
										Executable file
									
								
							|  | @ -0,0 +1,194 @@ | ||||||
|  | import shutil | ||||||
|  | import sys | ||||||
|  | import os | ||||||
|  | import random | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | import build | ||||||
|  | import utils | ||||||
|  | from utils import Colors | ||||||
|  | import environments | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | CMDS = {} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def parseOptions(args: list[str]) -> dict: | ||||||
|  |     result = { | ||||||
|  |         'opts': {}, | ||||||
|  |         'args': [] | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for arg in args: | ||||||
|  |         if arg.startswith("--"): | ||||||
|  |             if "=" in arg: | ||||||
|  |                 key, value = arg[2:].split("=", 1) | ||||||
|  |                 result['opts'][key] = value | ||||||
|  |             else: | ||||||
|  |                 result['opts'][arg[2:]] = True | ||||||
|  |         else: | ||||||
|  |             result['args'].append(arg) | ||||||
|  | 
 | ||||||
|  |     return result | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def runCmd(opts: dict, args: list[str]) -> None: | ||||||
|  |     if len(args) == 0: | ||||||
|  |         print(f"Usage: {sys.argv[0]} run <component>") | ||||||
|  |         sys.exit(1) | ||||||
|  | 
 | ||||||
|  |     out = build.buildOne(opts.get('env', 'host-clang'), args[0]) | ||||||
|  | 
 | ||||||
|  |     print(f"{Colors.BOLD}Running: {args[0]}{Colors.RESET}") | ||||||
|  |     utils.runCmd(out, *args[1:]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def kvmAvailable() -> bool: | ||||||
|  |     if os.path.exists("/dev/kvm") and \ | ||||||
|  |             os.access("/dev/kvm", os.R_OK): | ||||||
|  |         return True | ||||||
|  |     return False | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | BOOTAGENT = "loader" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def bootCmd(opts: dict, args: list[str]) -> None: | ||||||
|  |     imageDir = utils.mkdirP(".build/image") | ||||||
|  |     efiBootDir = utils.mkdirP(".build/image/EFI/BOOT") | ||||||
|  |     bootDir = utils.mkdirP(".build/image/boot") | ||||||
|  | 
 | ||||||
|  |     ovmf = utils.downloadFile( | ||||||
|  |         "https://retrage.github.io/edk2-nightly/bin/DEBUGX64_OVMF.fd") | ||||||
|  |     hjert = build.buildOne("kernel-x86_64", "hjert") | ||||||
|  |     shutil.copy(hjert, f"{bootDir}/kernel.elf") | ||||||
|  | 
 | ||||||
|  |     if BOOTAGENT == "loader": | ||||||
|  |         loader = build.buildOne("efi-x86_64", "loader") | ||||||
|  |         shutil.copy(loader, f"{efiBootDir}/BOOTX64.EFI") | ||||||
|  |     elif BOOTAGENT == "limine": | ||||||
|  |         limine = utils.downloadFile( | ||||||
|  |             "https://github.com/limine-bootloader/limine/raw/v3.0-branch-binary/BOOTX64.EFI") | ||||||
|  |         limineSys = utils.downloadFile( | ||||||
|  |             "https://github.com/limine-bootloader/limine/raw/v3.0-branch-binary/limine.sys") | ||||||
|  |         shutil.copy(limineSys, f"{bootDir}/limine.sys") | ||||||
|  |         shutil.copy('meta/images/limine-x86_64/limine.cfg', | ||||||
|  |                     f"{bootDir}/limine.cfg") | ||||||
|  |         shutil.copy(limine, f"{efiBootDir}/BOOTX64.EFI") | ||||||
|  | 
 | ||||||
|  |     qemuCmd = [ | ||||||
|  |         "qemu-system-x86_64", | ||||||
|  |         "-no-reboot", | ||||||
|  |         "-d", "guest_errors", | ||||||
|  |         "-serial", "mon:stdio", | ||||||
|  |         "-bios", ovmf, | ||||||
|  |         "-m", "256M", | ||||||
|  |         "-smp", "4", | ||||||
|  |         "-drive", f"file=fat:rw:{imageDir},media=disk,format=raw", | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     if kvmAvailable(): | ||||||
|  |         qemuCmd += ["-enable-kvm"] | ||||||
|  |     else: | ||||||
|  |         print("KVM not available, using QEMU-TCG") | ||||||
|  | 
 | ||||||
|  |     utils.runCmd(*qemuCmd) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def buildCmd(opts: dict, args: list[str]) -> None: | ||||||
|  |     env = opts.get('env', 'host-clang') | ||||||
|  |      | ||||||
|  |     if len(args) == 0: | ||||||
|  |         build.buildAll(env) | ||||||
|  |     else: | ||||||
|  |         for component in args: | ||||||
|  |             build.buildOne(env, component) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cleanCmd(opts: dict, args: list[str]) -> None: | ||||||
|  |     shutil.rmtree(".build", ignore_errors=True) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def nukeCmd(opts: dict, args: list[str]) -> None: | ||||||
|  |     shutil.rmtree(".build", ignore_errors=True) | ||||||
|  |     shutil.rmtree(".cache", ignore_errors=True) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def idCmd(opts: dict, args: list[str]) -> None: | ||||||
|  |     i = hex(random.randint(0, 2**64)) | ||||||
|  |     print("64bit: " + i) | ||||||
|  |     print("32bit: " + i[:10]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def helpCmd(opts: dict, args: list[str]) -> None: | ||||||
|  |     print(f"Usage: {sys.argv[0]} <command> [options...] [<args...>]") | ||||||
|  |     print("") | ||||||
|  | 
 | ||||||
|  |     print("Description:") | ||||||
|  |     print("   The skift operating system build system.") | ||||||
|  |     print("") | ||||||
|  | 
 | ||||||
|  |     print("Commands:") | ||||||
|  |     for cmd in CMDS: | ||||||
|  |         print("  " + cmd + " - " + CMDS[cmd]["desc"]) | ||||||
|  |     print("") | ||||||
|  | 
 | ||||||
|  |     print("Enviroments:") | ||||||
|  |     for env in environments.available(): | ||||||
|  |         print("  " + env) | ||||||
|  |     print("") | ||||||
|  | 
 | ||||||
|  |     print("Variants:") | ||||||
|  |     for var in environments.VARIANTS: | ||||||
|  |         print("  " + var) | ||||||
|  |     print("") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | CMDS = { | ||||||
|  |     "run": { | ||||||
|  |         "func": runCmd, | ||||||
|  |         "desc": "Run a component on the host", | ||||||
|  |     }, | ||||||
|  |     "boot": { | ||||||
|  |         "func": bootCmd, | ||||||
|  |         "desc": "Boot a component in a QEMU instance", | ||||||
|  |     }, | ||||||
|  |     "build": { | ||||||
|  |         "func": buildCmd, | ||||||
|  |         "desc": "Build one or more components", | ||||||
|  |     }, | ||||||
|  |     "clean": { | ||||||
|  |         "func": cleanCmd, | ||||||
|  |         "desc": "Clean the build directory", | ||||||
|  |     }, | ||||||
|  |     "nuke": { | ||||||
|  |         "func": nukeCmd, | ||||||
|  |         "desc": "Clean the build directory and cache", | ||||||
|  |     }, | ||||||
|  |     "id": { | ||||||
|  |         "func": idCmd, | ||||||
|  |         "desc": "Generate a 64bit random id", | ||||||
|  |     }, | ||||||
|  |     "help": { | ||||||
|  |         "func": helpCmd, | ||||||
|  |         "desc": "Show this help message", | ||||||
|  |     }, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     try: | ||||||
|  |         if len(sys.argv) < 2: | ||||||
|  |             helpCmd({}, []) | ||||||
|  |         else: | ||||||
|  |             o = parseOptions(sys.argv[2:]) | ||||||
|  |             if not sys.argv[1] in CMDS: | ||||||
|  |                 print(f"Unknown command: {sys.argv[1]}") | ||||||
|  |                 print("") | ||||||
|  |                 print(f"Use '{sys.argv[0]} help' for a list of commands") | ||||||
|  |                 sys.exit(1) | ||||||
|  |             CMDS[sys.argv[1]]["func"](o['opts'], o['args']) | ||||||
|  |             sys.exit(0) | ||||||
|  |     except utils.CliException as e: | ||||||
|  |         print() | ||||||
|  |         print(f"{Colors.RED}{e.msg}{Colors.RESET}") | ||||||
|  |         sys.exit(1) | ||||||
							
								
								
									
										117
									
								
								build.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								build.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,117 @@ | ||||||
|  | from os import environ | ||||||
|  | from typing import TextIO, Tuple | ||||||
|  | import json | ||||||
|  | 
 | ||||||
|  | import ninja | ||||||
|  | import manifests as m | ||||||
|  | import environments as e | ||||||
|  | import copy | ||||||
|  | import utils | ||||||
|  | from utils import Colors | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def genNinja(out: TextIO, manifests: dict, env: dict) -> None: | ||||||
|  |     env = copy.deepcopy(env) | ||||||
|  | 
 | ||||||
|  |     env["cflags"] += [m.cincludes(manifests)] | ||||||
|  |     env["cxxflags"] += [m.cincludes(manifests)] | ||||||
|  | 
 | ||||||
|  |     writer = ninja.Writer(out) | ||||||
|  | 
 | ||||||
|  |     writer.comment("Generated by the meta build system") | ||||||
|  |     writer.newline() | ||||||
|  | 
 | ||||||
|  |     writer.comment("Environment:") | ||||||
|  |     for key in env: | ||||||
|  |         if isinstance(env[key], list): | ||||||
|  |             writer.variable(key, " ".join(env[key])) | ||||||
|  |         else: | ||||||
|  |             writer.variable(key, env[key]) | ||||||
|  |     writer.newline() | ||||||
|  | 
 | ||||||
|  |     writer.comment("Rules:") | ||||||
|  |     writer.rule( | ||||||
|  |         "cc", "$cc -c -o $out $in -MD -MF $out.d $cflags", depfile="$out.d") | ||||||
|  |     writer.rule( | ||||||
|  |         "cxx", "$cxx -c -o $out $in -MD -MF $out.d $cxxflags", depfile="$out.d") | ||||||
|  |     writer.rule("ld", "$ld -o $out $in $ldflags") | ||||||
|  |     writer.rule("ar", "$ar crs $out $in") | ||||||
|  |     writer.rule("as", "$as -o $out $in $asflags") | ||||||
|  |     writer.newline() | ||||||
|  | 
 | ||||||
|  |     writer.comment("Build:") | ||||||
|  |     all = [] | ||||||
|  |     for key in manifests: | ||||||
|  |         item = manifests[key] | ||||||
|  | 
 | ||||||
|  |         writer.comment("Project: " + item["id"]) | ||||||
|  | 
 | ||||||
|  |         for obj in item["objs"]: | ||||||
|  |             if obj[1].endswith(".c"): | ||||||
|  |                 writer.build(obj[0], "cc", obj[1]) | ||||||
|  |             elif obj[1].endswith(".cpp"): | ||||||
|  |                 writer.build(obj[0], "cxx", obj[1]) | ||||||
|  |             elif obj[1].endswith(".s"): | ||||||
|  |                 writer.build(obj[0], "as", obj[1]) | ||||||
|  | 
 | ||||||
|  |         writer.newline() | ||||||
|  | 
 | ||||||
|  |         objs = [x[0] for x in item["objs"]] | ||||||
|  | 
 | ||||||
|  |         if item["type"] == "lib": | ||||||
|  |             writer.build(item["out"], "ar", objs) | ||||||
|  |         else: | ||||||
|  |             objs = objs + item["libs"] | ||||||
|  |             writer.build(item["out"], "ld", objs) | ||||||
|  | 
 | ||||||
|  |         all.append(item["out"]) | ||||||
|  | 
 | ||||||
|  |         writer.newline() | ||||||
|  | 
 | ||||||
|  |     writer.comment("Phony:") | ||||||
|  |     writer.build("all", "phony", all) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def prepare(envName: str) -> Tuple[dict, dict]: | ||||||
|  |     env = e.load(envName) | ||||||
|  |     manifests = m.loadAll("./src", env) | ||||||
|  |     utils.mkdirP(env["dir"]) | ||||||
|  |     genNinja(open(env["ninjafile"], "w"), manifests, env) | ||||||
|  | 
 | ||||||
|  |     meta = {} | ||||||
|  |     meta["id"] = envName | ||||||
|  |     meta["type"] = "artifacts" | ||||||
|  |     meta["components"] = manifests | ||||||
|  |     meta["toolchain"] = env | ||||||
|  | 
 | ||||||
|  |     with open(env["dir"] + "/manifest.json", "w") as f: | ||||||
|  |         json.dump(meta, f, indent=4) | ||||||
|  | 
 | ||||||
|  |     return env, manifests | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def buildAll(envName: str) -> None: | ||||||
|  |     environment, _ = prepare(envName) | ||||||
|  |     print(f"{Colors.BOLD}Building all targets for {envName}{Colors.RESET}") | ||||||
|  |     try: | ||||||
|  |         utils.runCmd("ninja", "-j", "1", "-f", environment["ninjafile"]) | ||||||
|  |     except: | ||||||
|  |         raise utils.CliException( | ||||||
|  |             "Failed to build all for " + environment["key"]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def buildOne(envName: str, target: str) -> str: | ||||||
|  |     print(f"{Colors.BOLD}Building {target} for {envName}{Colors.RESET}") | ||||||
|  |     environment, manifests = prepare(envName) | ||||||
|  | 
 | ||||||
|  |     if not target in manifests: | ||||||
|  |         raise utils.CliException("Unknown target: " + target) | ||||||
|  | 
 | ||||||
|  |     try: | ||||||
|  |         utils.runCmd("ninja", "-j", "1", "-f", | ||||||
|  |                      environment["ninjafile"], manifests[target]["out"]) | ||||||
|  |     except: | ||||||
|  |         raise utils.CliException( | ||||||
|  |             f"Failed to build {target} for {environment['key']}") | ||||||
|  | 
 | ||||||
|  |     return manifests[target]["out"] | ||||||
							
								
								
									
										115
									
								
								environments.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								environments.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,115 @@ | ||||||
|  | 
 | ||||||
|  | import copy | ||||||
|  | import os | ||||||
|  | 
 | ||||||
|  | import utils | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | PASSED_TO_BUILD = [ | ||||||
|  |     "toolchain", "arch", "sub", "vendor", "sys", "abi", "encoding", "freestanding", "variant"] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def enableCache(env: dict) -> dict: | ||||||
|  |     env = copy.deepcopy(env) | ||||||
|  |     env["cc"] = "ccache " + env["cc"] | ||||||
|  |     env["cxx"] = "ccache " + env["cxx"] | ||||||
|  |     return env | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def enableSan(env: dict) -> dict: | ||||||
|  |     if (env["freestanding"]): | ||||||
|  |         return env | ||||||
|  |     env = copy.deepcopy(env) | ||||||
|  |     env["cflags"] += ["-fsanitize=address", "-fsanitize=undefined"] | ||||||
|  |     env["cxxflags"] += ["-fsanitize=address", "-fsanitize=undefined"] | ||||||
|  |     env["ldflags"] += ["-fsanitize=address", "-fsanitize=undefined"] | ||||||
|  |     return env | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def enableColors(env: dict) -> dict: | ||||||
|  |     env = copy.deepcopy(env) | ||||||
|  |     if (env["toolchain"] == "clang"): | ||||||
|  |         env["cflags"] += ["-fcolor-diagnostics"] | ||||||
|  |         env["cxxflags"] += ["-fcolor-diagnostics"] | ||||||
|  |     elif (env["toolchain"] == "gcc"): | ||||||
|  |         env["cflags"] += ["-fdiagnostics-color=alaways"] | ||||||
|  |         env["cxxflags"] += ["-fdiagnostics-color=always"] | ||||||
|  | 
 | ||||||
|  |     return env | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def enableOptimizer(env: dict, level: str) -> dict: | ||||||
|  |     env = copy.deepcopy(env) | ||||||
|  |     env["cflags"] += ["-O%s" % level] | ||||||
|  |     env["cxxflags"] += ["-O%s" % level] | ||||||
|  |     return env | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def available() -> list: | ||||||
|  |     return [file.removesuffix(".json") for file in os.listdir("meta/toolchains") if file.endswith(".json")] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | VARIANTS = ["debug", "devel", "release", "sanatize"] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def load(env: str) -> dict: | ||||||
|  |     variant = "devel" | ||||||
|  |     if ":" in env: | ||||||
|  |         env, variant = env.split(":") | ||||||
|  | 
 | ||||||
|  |     if not env in available(): | ||||||
|  |         raise utils.CliException(f"Environment '{env}' not available") | ||||||
|  | 
 | ||||||
|  |     if not variant in VARIANTS: | ||||||
|  |         raise utils.CliException(f"Variant '{variant}' not available") | ||||||
|  | 
 | ||||||
|  |     result = utils.loadJson(f"meta/toolchains/{env}.json") | ||||||
|  |     result["variant"] = variant | ||||||
|  | 
 | ||||||
|  |     for key in PASSED_TO_BUILD: | ||||||
|  |         if isinstance(result[key], bool): | ||||||
|  |             if result[key]: | ||||||
|  |                 result["cflags"] += [f"-D__sdk_{key}__"] | ||||||
|  |                 result["cxxflags"] += [f"-D__sdk_{key}__"] | ||||||
|  |         else: | ||||||
|  |             result["cflags"] += [f"-D__sdk_{key}_{result[key]}__"] | ||||||
|  |             result["cxxflags"] += [f"-D__sdk_{key}_{result[key]}__"] | ||||||
|  | 
 | ||||||
|  |     result["cflags"] += [ | ||||||
|  |         "-std=gnu2x", | ||||||
|  |         "-Isrc", | ||||||
|  |         "-Wall", | ||||||
|  |         "-Wextra", | ||||||
|  |         "-Werror" | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     result["cxxflags"] += [ | ||||||
|  |         "-std=gnu++2b", | ||||||
|  |         "-Isrc", | ||||||
|  |         "-Wall", | ||||||
|  |         "-Wextra", | ||||||
|  |         "-Werror", | ||||||
|  |         "-fno-exceptions", | ||||||
|  |         "-fno-rtti" | ||||||
|  |     ] | ||||||
|  | 
 | ||||||
|  |     result["hash"] = utils.objSha256(result, PASSED_TO_BUILD) | ||||||
|  |     result["key"] = utils.objKey(result, PASSED_TO_BUILD) | ||||||
|  |     result["dir"] = f".build/{result['hash'][:8]}" | ||||||
|  |     result["bindir"] = f"{result['dir']}/bin" | ||||||
|  |     result["objdir"] = f"{result['dir']}/obj" | ||||||
|  |     result["ninjafile"] = result["dir"] + "/build.ninja" | ||||||
|  | 
 | ||||||
|  |     result = enableColors(result) | ||||||
|  | 
 | ||||||
|  |     if variant == "debug": | ||||||
|  |         result = enableOptimizer(result, "g") | ||||||
|  |     elif variant == "devel": | ||||||
|  |         result = enableOptimizer(result, "2") | ||||||
|  |     elif variant == "release": | ||||||
|  |         result = enableOptimizer(result, "3") | ||||||
|  |     elif variant == "sanatize": | ||||||
|  |         result = enableOptimizer(result, "g") | ||||||
|  |         result = enableSan(result) | ||||||
|  | 
 | ||||||
|  |     return result | ||||||
							
								
								
									
										166
									
								
								manifests.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								manifests.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,166 @@ | ||||||
|  | import os | ||||||
|  | import json | ||||||
|  | import copy | ||||||
|  | 
 | ||||||
|  | import utils | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def loadJsons(basedir: str) -> dict: | ||||||
|  |     result = {} | ||||||
|  |     for root, dirs, files in os.walk(basedir): | ||||||
|  |         for filename in files: | ||||||
|  |             if filename == 'manifest.json': | ||||||
|  |                 filename = os.path.join(root, filename) | ||||||
|  |                 try: | ||||||
|  |                     with open(filename) as f: | ||||||
|  |                         manifest = json.load(f) | ||||||
|  |                         manifest["dir"] = os.path.dirname(filename) | ||||||
|  |                         result[manifest["id"]] = manifest | ||||||
|  |                 except Exception as e: | ||||||
|  |                     raise utils.CliException( | ||||||
|  |                         f"Failed to load manifest {filename}: {e}") | ||||||
|  | 
 | ||||||
|  |     return result | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def filter(manifests: dict, env: dict) -> dict: | ||||||
|  |     result = {} | ||||||
|  |     for id in manifests: | ||||||
|  |         manifest = manifests[id] | ||||||
|  |         accepted = True | ||||||
|  | 
 | ||||||
|  |         if "requires" in manifest: | ||||||
|  |             for req in manifest["requires"]: | ||||||
|  |                 if not env[req] in manifest["requires"][req]: | ||||||
|  |                     accepted = False | ||||||
|  |                     break | ||||||
|  | 
 | ||||||
|  |         if accepted: | ||||||
|  |             result[id] = manifest | ||||||
|  | 
 | ||||||
|  |     return result | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def doInjects(manifests: dict) -> dict: | ||||||
|  |     manifests = copy.deepcopy(manifests) | ||||||
|  |     for key in manifests: | ||||||
|  |         item = manifests[key] | ||||||
|  |         if "inject" in item: | ||||||
|  |             for inject in item["inject"]: | ||||||
|  |                 if inject in manifests: | ||||||
|  |                     manifests[inject]["deps"].append(key) | ||||||
|  |     return manifests | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def resolveDeps(manifests: dict) -> dict: | ||||||
|  |     manifests = copy.deepcopy(manifests) | ||||||
|  | 
 | ||||||
|  |     def resolve(key: str, stack: list[str] = []) -> list[str]: | ||||||
|  |         result: list[str] = [] | ||||||
|  |         if key in stack: | ||||||
|  |             raise utils.CliException("Circular dependency detected: " + | ||||||
|  |                                      str(stack) + " -> " + key) | ||||||
|  | 
 | ||||||
|  |         if not key in manifests: | ||||||
|  |             raise utils.CliException("Unknown dependency: " + key) | ||||||
|  | 
 | ||||||
|  |         if "deps" in manifests[key]: | ||||||
|  |             stack.append(key) | ||||||
|  |             result.extend(manifests[key]["deps"]) | ||||||
|  |             for dep in manifests[key]["deps"]: | ||||||
|  |                 result += resolve(dep, stack) | ||||||
|  |             stack.pop() | ||||||
|  |         return result | ||||||
|  | 
 | ||||||
|  |     for key in manifests: | ||||||
|  |         manifests[key]["deps"] = utils.stripDups(resolve(key)) | ||||||
|  | 
 | ||||||
|  |     return manifests | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def findFiles(manifests: dict) -> dict: | ||||||
|  |     manifests = copy.deepcopy(manifests) | ||||||
|  | 
 | ||||||
|  |     for key in manifests: | ||||||
|  |         item = manifests[key] | ||||||
|  |         path = manifests[key]["dir"] | ||||||
|  |         testsPath = os.path.join(path, "tests") | ||||||
|  |         assetsPath = os.path.join(path, "assets") | ||||||
|  | 
 | ||||||
|  |         item["tests"] = utils.findFiles(testsPath, [".c", ".cpp"]) | ||||||
|  |         item["srcs"] = utils.findFiles(path, [".c", ".cpp", ".s"]) | ||||||
|  |         item["assets"] = utils.findFiles(assetsPath) | ||||||
|  | 
 | ||||||
|  |     return manifests | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def prepareTests(manifests: dict) -> dict: | ||||||
|  |     if not "tests" in manifests: | ||||||
|  |         return manifests | ||||||
|  |     manifests = copy.deepcopy(manifests) | ||||||
|  |     tests = manifests["tests"] | ||||||
|  | 
 | ||||||
|  |     for key in manifests: | ||||||
|  |         item = manifests[key] | ||||||
|  |         if "tests" in item and len(item["tests"]) > 0: | ||||||
|  |             tests["deps"] += [item["id"]] | ||||||
|  |             tests["srcs"] += item["tests"] | ||||||
|  | 
 | ||||||
|  |     return manifests | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def prepareInOut(manifests: dict, env: dict) -> dict: | ||||||
|  |     manifests = copy.deepcopy(manifests) | ||||||
|  |     for key in manifests: | ||||||
|  |         item = manifests[key] | ||||||
|  |         basedir = os.path.dirname(item["dir"]) | ||||||
|  | 
 | ||||||
|  |         item["objs"] = [(x.replace(basedir, env["objdir"] + "/") + ".o", x) | ||||||
|  |                         for x in item["srcs"]] | ||||||
|  | 
 | ||||||
|  |         if item["type"] == "lib": | ||||||
|  |             item["out"] = env["bindir"] + "/" + key + ".a" | ||||||
|  |         elif item["type"] == "exe": | ||||||
|  |             item["out"] = env["bindir"] + "/" + key | ||||||
|  |         else: | ||||||
|  |             raise utils.CliException("Unknown type: " + item["type"]) | ||||||
|  | 
 | ||||||
|  |     for key in manifests: | ||||||
|  |         item = manifests[key] | ||||||
|  |         item["libs"] = [manifests[x]["out"] | ||||||
|  |                         for x in item["deps"] if manifests[x]["type"] == "lib"] | ||||||
|  |     return manifests | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def cincludes(manifests: dict) -> str: | ||||||
|  |     include_paths = [] | ||||||
|  | 
 | ||||||
|  |     for key in manifests: | ||||||
|  |         item = manifests[key] | ||||||
|  |         if "root-include" in item: | ||||||
|  |             include_paths.append(item["dir"]) | ||||||
|  | 
 | ||||||
|  |     if len(include_paths) == 0: | ||||||
|  |         return "" | ||||||
|  | 
 | ||||||
|  |     return " -I" + " -I".join(include_paths) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | cache: dict = {} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def loadAll(basedir: str, env: dict) -> dict: | ||||||
|  |     cacheKey = basedir + ":" + env["id"] | ||||||
|  |     if cacheKey in cache: | ||||||
|  |         return cache[cacheKey] | ||||||
|  | 
 | ||||||
|  |     manifests = loadJsons(basedir) | ||||||
|  |     manifests = filter(manifests, env) | ||||||
|  |     manifests = doInjects(manifests) | ||||||
|  |     manifests = resolveDeps(manifests) | ||||||
|  |     manifests = findFiles(manifests) | ||||||
|  |     manifests = prepareTests(manifests) | ||||||
|  |     manifests = prepareInOut(manifests, env) | ||||||
|  | 
 | ||||||
|  |     cache[cacheKey] = manifests | ||||||
|  |     return manifests | ||||||
							
								
								
									
										220
									
								
								ninja.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										220
									
								
								ninja.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,220 @@ | ||||||
|  | #!/usr/bin/python | ||||||
|  | 
 | ||||||
|  | # Copyright 2011 Google Inc. All Rights Reserved. | ||||||
|  | # | ||||||
|  | # Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  | # you may not use this file except in compliance with the License. | ||||||
|  | # You may obtain a copy of the License at | ||||||
|  | # | ||||||
|  | #     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  | # | ||||||
|  | # Unless required by applicable law or agreed to in writing, software | ||||||
|  | # distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  | # See the License for the specific language governing permissions and | ||||||
|  | # limitations under the License. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | """Python module for generating .ninja files. | ||||||
|  | 
 | ||||||
|  | Note that this is emphatically not a required piece of Ninja; it's | ||||||
|  | just a helpful utility for build-file-generation systems that already | ||||||
|  | use Python. | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | import re | ||||||
|  | import textwrap | ||||||
|  | from typing import Any, TextIO, Union | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def escape_path(word: str) -> str: | ||||||
|  |     return word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | VarValue = Union[int, str, list[str], None] | ||||||
|  | VarPath = Union[str, list[str], None] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Writer(object): | ||||||
|  |     def __init__(self, output: TextIO, width: int = 78): | ||||||
|  |         self.output = output | ||||||
|  |         self.width = width | ||||||
|  | 
 | ||||||
|  |     def newline(self) -> None: | ||||||
|  |         self.output.write('\n') | ||||||
|  | 
 | ||||||
|  |     def comment(self, text: str) -> None: | ||||||
|  |         for line in textwrap.wrap(text, self.width - 2, break_long_words=False, | ||||||
|  |                                   break_on_hyphens=False): | ||||||
|  |             self.output.write('# ' + line + '\n') | ||||||
|  | 
 | ||||||
|  |     def variable(self, key: str, value: VarValue, indent: int = 0) -> None: | ||||||
|  |         if value is None: | ||||||
|  |             return | ||||||
|  |         if isinstance(value, list): | ||||||
|  |             value = ' '.join(filter(None, value))  # Filter out empty strings. | ||||||
|  |         self._line('%s = %s' % (key, value), indent) | ||||||
|  | 
 | ||||||
|  |     def pool(self, name: str, depth: int) -> None: | ||||||
|  |         self._line('pool %s' % name) | ||||||
|  |         self.variable('depth', depth, indent=1) | ||||||
|  | 
 | ||||||
|  |     def rule(self, | ||||||
|  |              name: str, | ||||||
|  |              command: VarValue, | ||||||
|  |              description: Union[str, None] = None, | ||||||
|  |              depfile: VarValue = None, | ||||||
|  |              generator: VarValue = False, | ||||||
|  |              pool: VarValue = None, | ||||||
|  |              restat: bool = False, | ||||||
|  |              rspfile: VarValue = None, | ||||||
|  |              rspfile_content: VarValue = None, | ||||||
|  |              deps: VarValue = None) -> None: | ||||||
|  |         self._line('rule %s' % name) | ||||||
|  |         self.variable('command', command, indent=1) | ||||||
|  |         if description: | ||||||
|  |             self.variable('description', description, indent=1) | ||||||
|  |         if depfile: | ||||||
|  |             self.variable('depfile', depfile, indent=1) | ||||||
|  |         if generator: | ||||||
|  |             self.variable('generator', '1', indent=1) | ||||||
|  |         if pool: | ||||||
|  |             self.variable('pool', pool, indent=1) | ||||||
|  |         if restat: | ||||||
|  |             self.variable('restat', '1', indent=1) | ||||||
|  |         if rspfile: | ||||||
|  |             self.variable('rspfile', rspfile, indent=1) | ||||||
|  |         if rspfile_content: | ||||||
|  |             self.variable('rspfile_content', rspfile_content, indent=1) | ||||||
|  |         if deps: | ||||||
|  |             self.variable('deps', deps, indent=1) | ||||||
|  | 
 | ||||||
|  |     def build(self, | ||||||
|  |               outputs: Union[str, list[str]], | ||||||
|  |               rule: str, | ||||||
|  |               inputs: Union[VarPath, None], | ||||||
|  |               implicit: VarPath = None, | ||||||
|  |               order_only: VarPath = None, | ||||||
|  |               variables: Union[dict[str, str], None] = None, | ||||||
|  |               implicit_outputs: VarPath = None, | ||||||
|  |               pool: Union[str, None] = None, | ||||||
|  |               dyndep: Union[str, None] = None) -> list[str]: | ||||||
|  |         outputs = as_list(outputs) | ||||||
|  |         out_outputs = [escape_path(x) for x in outputs] | ||||||
|  |         all_inputs = [escape_path(x) for x in as_list(inputs)] | ||||||
|  | 
 | ||||||
|  |         if implicit: | ||||||
|  |             implicit = [escape_path(x) for x in as_list(implicit)] | ||||||
|  |             all_inputs.append('|') | ||||||
|  |             all_inputs.extend(implicit) | ||||||
|  |         if order_only: | ||||||
|  |             order_only = [escape_path(x) for x in as_list(order_only)] | ||||||
|  |             all_inputs.append('||') | ||||||
|  |             all_inputs.extend(order_only) | ||||||
|  |         if implicit_outputs: | ||||||
|  |             implicit_outputs = [escape_path(x) | ||||||
|  |                                 for x in as_list(implicit_outputs)] | ||||||
|  |             out_outputs.append('|') | ||||||
|  |             out_outputs.extend(implicit_outputs) | ||||||
|  | 
 | ||||||
|  |         self._line('build %s: %s' % (' '.join(out_outputs), | ||||||
|  |                                      ' '.join([rule] + all_inputs))) | ||||||
|  |         if pool is not None: | ||||||
|  |             self._line('  pool = %s' % pool) | ||||||
|  |         if dyndep is not None: | ||||||
|  |             self._line('  dyndep = %s' % dyndep) | ||||||
|  | 
 | ||||||
|  |         if variables: | ||||||
|  |             iterator = iter(variables.items()) | ||||||
|  | 
 | ||||||
|  |             for key, val in iterator: | ||||||
|  |                 self.variable(key, val, indent=1) | ||||||
|  | 
 | ||||||
|  |         return outputs | ||||||
|  | 
 | ||||||
|  |     def include(self, path: str) -> None: | ||||||
|  |         self._line('include %s' % path) | ||||||
|  | 
 | ||||||
|  |     def subninja(self, path: str) -> None: | ||||||
|  |         self._line('subninja %s' % path) | ||||||
|  | 
 | ||||||
|  |     def default(self, paths: VarPath) -> None: | ||||||
|  |         self._line('default %s' % ' '.join(as_list(paths))) | ||||||
|  | 
 | ||||||
|  |     def _count_dollars_before_index(self, s: str, i: int) -> int: | ||||||
|  |         """Returns the number of '$' characters right in front of s[i].""" | ||||||
|  |         dollar_count = 0 | ||||||
|  |         dollar_index = i - 1 | ||||||
|  |         while dollar_index > 0 and s[dollar_index] == '$': | ||||||
|  |             dollar_count += 1 | ||||||
|  |             dollar_index -= 1 | ||||||
|  |         return dollar_count | ||||||
|  | 
 | ||||||
|  |     def _line(self, text: str, indent: int = 0) -> None: | ||||||
|  |         """Write 'text' word-wrapped at self.width characters.""" | ||||||
|  |         leading_space = '  ' * indent | ||||||
|  |         while len(leading_space) + len(text) > self.width: | ||||||
|  |             # The text is too wide; wrap if possible. | ||||||
|  | 
 | ||||||
|  |             # Find the rightmost space that would obey our width constraint and | ||||||
|  |             # that's not an escaped space. | ||||||
|  |             available_space = self.width - len(leading_space) - len(' $') | ||||||
|  |             space = available_space | ||||||
|  |             while True: | ||||||
|  |                 space = text.rfind(' ', 0, space) | ||||||
|  |                 if (space < 0 or | ||||||
|  |                         self._count_dollars_before_index(text, space) % 2 == 0): | ||||||
|  |                     break | ||||||
|  | 
 | ||||||
|  |             if space < 0: | ||||||
|  |                 # No such space; just use the first unescaped space we can find. | ||||||
|  |                 space = available_space - 1 | ||||||
|  |                 while True: | ||||||
|  |                     space = text.find(' ', space + 1) | ||||||
|  |                     if (space < 0 or | ||||||
|  |                             self._count_dollars_before_index(text, space) % 2 == 0): | ||||||
|  |                         break | ||||||
|  |             if space < 0: | ||||||
|  |                 # Give up on breaking. | ||||||
|  |                 break | ||||||
|  | 
 | ||||||
|  |             self.output.write(leading_space + text[0:space] + ' $\n') | ||||||
|  |             text = text[space+1:] | ||||||
|  | 
 | ||||||
|  |             # Subsequent lines are continuations, so indent them. | ||||||
|  |             leading_space = '  ' * (indent+2) | ||||||
|  | 
 | ||||||
|  |         self.output.write(leading_space + text + '\n') | ||||||
|  | 
 | ||||||
|  |     def close(self) -> None: | ||||||
|  |         self.output.close() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def as_list(input: Any) -> list: | ||||||
|  |     if input is None: | ||||||
|  |         return [] | ||||||
|  |     if isinstance(input, list): | ||||||
|  |         return input | ||||||
|  |     return [input] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def escape(string: str) -> str: | ||||||
|  |     """Escape a string such that it can be embedded into a Ninja file without | ||||||
|  |     further interpretation.""" | ||||||
|  |     assert '\n' not in string, 'Ninja syntax does not allow newlines' | ||||||
|  |     # We only have one special metacharacter: '$'. | ||||||
|  |     return string.replace('$', '$$') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def expand(string: str, vars: dict[str, str], local_vars: dict[str, str] = {}) -> str: | ||||||
|  |     """Expand a string containing $vars as Ninja would. | ||||||
|  | 
 | ||||||
|  |     Note: doesn't handle the full Ninja variable syntax, but it's enough | ||||||
|  |     to make configure.py's use of it work. | ||||||
|  |     """ | ||||||
|  |     def exp(m: Any) -> Any: | ||||||
|  |         var = m.group(1) | ||||||
|  |         if var == '$': | ||||||
|  |             return '$' | ||||||
|  |         return local_vars.get(var, vars.get(var, '')) | ||||||
|  |     return re.sub(r'\$(\$|\w*)', exp, string) | ||||||
							
								
								
									
										1
									
								
								requirements.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								requirements.txt
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | requests ~= 2.28.0 | ||||||
							
								
								
									
										189
									
								
								utils.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								utils.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,189 @@ | ||||||
|  | from copy import copy | ||||||
|  | import errno | ||||||
|  | import os | ||||||
|  | import hashlib | ||||||
|  | import signal | ||||||
|  | import requests | ||||||
|  | import subprocess | ||||||
|  | import json | ||||||
|  | import copy | ||||||
|  | from types import SimpleNamespace | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Colors: | ||||||
|  |     BLACK = "\033[0;30m" | ||||||
|  |     RED = "\033[0;31m" | ||||||
|  |     GREEN = "\033[0;32m" | ||||||
|  |     BROWN = "\033[0;33m" | ||||||
|  |     BLUE = "\033[0;34m" | ||||||
|  |     PURPLE = "\033[0;35m" | ||||||
|  |     CYAN = "\033[0;36m" | ||||||
|  |     LIGHT_GRAY = "\033[0;37m" | ||||||
|  |     DARK_GRAY = "\033[1;30m" | ||||||
|  |     LIGHT_RED = "\033[1;31m" | ||||||
|  |     LIGHT_GREEN = "\033[1;32m" | ||||||
|  |     YELLOW = "\033[1;33m" | ||||||
|  |     LIGHT_BLUE = "\033[1;34m" | ||||||
|  |     LIGHT_PURPLE = "\033[1;35m" | ||||||
|  |     LIGHT_CYAN = "\033[1;36m" | ||||||
|  |     LIGHT_WHITE = "\033[1;37m" | ||||||
|  |     BOLD = "\033[1m" | ||||||
|  |     FAINT = "\033[2m" | ||||||
|  |     ITALIC = "\033[3m" | ||||||
|  |     UNDERLINE = "\033[4m" | ||||||
|  |     BLINK = "\033[5m" | ||||||
|  |     NEGATIVE = "\033[7m" | ||||||
|  |     CROSSED = "\033[9m" | ||||||
|  |     RESET = "\033[0m" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class CliException(Exception): | ||||||
|  |     def __init__(self, msg: str): | ||||||
|  |         self.msg = msg | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def stripDups(l: list[str]) -> list[str]: | ||||||
|  |     # Remove duplicates from a list | ||||||
|  |     # by keeping only the last occurence | ||||||
|  |     result: list[str] = [] | ||||||
|  |     for item in l: | ||||||
|  |         if item in result: | ||||||
|  |             result.remove(item) | ||||||
|  |         result.append(item) | ||||||
|  |     return result | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def findFiles(dir: str, exts: list[str] = []) -> list[str]: | ||||||
|  |     if not os.path.isdir(dir): | ||||||
|  |         return [] | ||||||
|  | 
 | ||||||
|  |     result: list[str] = [] | ||||||
|  | 
 | ||||||
|  |     for f in os.listdir(dir): | ||||||
|  |         if len(exts) == 0: | ||||||
|  |             result.append(f) | ||||||
|  |         else: | ||||||
|  |             for ext in exts: | ||||||
|  |                 if f.endswith(ext): | ||||||
|  |                     result.append(os.path.join(dir, f)) | ||||||
|  |                     break | ||||||
|  | 
 | ||||||
|  |     return result | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def hashFile(filename: str) -> str: | ||||||
|  |     with open(filename, "rb") as f: | ||||||
|  |         return hashlib.sha256(f.read()).hexdigest() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def objSha256(obj: dict, keys: list[str] = []) -> str: | ||||||
|  |     toHash = {} | ||||||
|  |     if len(keys) == 0: | ||||||
|  |         toHash = obj | ||||||
|  |     else: | ||||||
|  |         for key in keys: | ||||||
|  |             if key in obj: | ||||||
|  |                 toHash[key] = obj[key] | ||||||
|  | 
 | ||||||
|  |     data = json.dumps(toHash, sort_keys=True) | ||||||
|  |     return hashlib.sha256(data.encode("utf-8")).hexdigest() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def objKey(obj: dict, keys: list[str]) -> str: | ||||||
|  |     toKey = [] | ||||||
|  |     for key in keys: | ||||||
|  |         if key in obj: | ||||||
|  |             if isinstance(obj[key], bool): | ||||||
|  |                 if obj[key]: | ||||||
|  |                     toKey.append(key) | ||||||
|  |             else: | ||||||
|  |                 toKey.append(obj[key]) | ||||||
|  | 
 | ||||||
|  |     return "-".join(toKey) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def mkdirP(path: str) -> str: | ||||||
|  |     try: | ||||||
|  |         os.makedirs(path) | ||||||
|  |     except OSError as exc: | ||||||
|  |         if exc.errno == errno.EEXIST and os.path.isdir(path): | ||||||
|  |             pass | ||||||
|  |         else: | ||||||
|  |             raise | ||||||
|  |     return path | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def downloadFile(url: str) -> str: | ||||||
|  |     dest = ".cache/remote/" + hashlib.sha256(url.encode('utf-8')).hexdigest() | ||||||
|  |     tmp = dest + ".tmp" | ||||||
|  | 
 | ||||||
|  |     if os.path.isfile(dest): | ||||||
|  |         return dest | ||||||
|  | 
 | ||||||
|  |     print(f"Downloading {url} to {dest}") | ||||||
|  | 
 | ||||||
|  |     try: | ||||||
|  |         r = requests.get(url, stream=True) | ||||||
|  |         r.raise_for_status() | ||||||
|  |         mkdirP(os.path.dirname(dest)) | ||||||
|  |         with open(tmp, 'wb') as f: | ||||||
|  |             for chunk in r.iter_content(chunk_size=8192): | ||||||
|  |                 if chunk: | ||||||
|  |                     f.write(chunk) | ||||||
|  | 
 | ||||||
|  |         os.rename(tmp, dest) | ||||||
|  |         return dest | ||||||
|  |     except: | ||||||
|  |         raise CliException(f"Failed to download {url}") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def runCmd(*args: str) -> bool: | ||||||
|  |     try: | ||||||
|  |         proc = subprocess.run(args) | ||||||
|  |     except FileNotFoundError: | ||||||
|  |         raise CliException(f"Failed to run {args[0]}: command not found") | ||||||
|  | 
 | ||||||
|  |     if proc.returncode == -signal.SIGSEGV: | ||||||
|  |         raise CliException("Segmentation fault") | ||||||
|  | 
 | ||||||
|  |     if proc.returncode != 0: | ||||||
|  |         raise CliException( | ||||||
|  |             f"Failed to run {' '.join(args)}: process exited with code {proc.returncode}") | ||||||
|  | 
 | ||||||
|  |     return True | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | CACHE = {} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def processJson(e: any) -> any: | ||||||
|  |     if isinstance(e, dict): | ||||||
|  |         for k in e: | ||||||
|  |             e[processJson(k)] = processJson(e[k]) | ||||||
|  |     elif isinstance(e, list): | ||||||
|  |         for i in range(len(e)): | ||||||
|  |             e[i] = processJson(e[i]) | ||||||
|  |     elif isinstance(e, str): | ||||||
|  |         if e == "@sysname": | ||||||
|  |             e = os.uname().sysname.lower() | ||||||
|  |         elif e.startswith("@include("): | ||||||
|  |             e = loadJson(e[9:-1]) | ||||||
|  | 
 | ||||||
|  |     return e | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def loadJson(filename: str) -> dict: | ||||||
|  |     result = {} | ||||||
|  |     if filename in CACHE: | ||||||
|  |         result = CACHE[filename] | ||||||
|  |     else: | ||||||
|  |         with open(filename) as f: | ||||||
|  |             result = json.load(f) | ||||||
|  | 
 | ||||||
|  |             result["dir"] = os.path.dirname(filename) | ||||||
|  |             result["json"] = filename | ||||||
|  |             result = processJson(result) | ||||||
|  |             CACHE[filename] = result | ||||||
|  | 
 | ||||||
|  |     result = copy.deepcopy(result) | ||||||
|  |     return result | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue