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