Allow custom props on a per build basis and move all temporary files to .osdk.
This commit is contained in:
parent
963334caed
commit
7980e89913
5 changed files with 115 additions and 64 deletions
|
@ -31,39 +31,53 @@ def parseOptions(args: list[str]) -> dict:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def propsFromOptions(opt: dict) -> dict:
|
||||||
|
result = {}
|
||||||
|
for key in opt:
|
||||||
|
if key.startswith("prop:"):
|
||||||
|
result[key[5:]] = opt[key]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def runCmd(opts: dict, args: list[str]) -> None:
|
def runCmd(opts: dict, args: list[str]) -> None:
|
||||||
|
props = propsFromOptions(opts)
|
||||||
|
|
||||||
if len(args) == 0:
|
if len(args) == 0:
|
||||||
print(f"Usage: {args[0]} run <component>")
|
print(f"Usage: {args[0]} run <component>")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
out = build.buildOne(opts.get('target', 'default'), args[0])
|
out = build.buildOne(opts.get('target', 'default'), args[0], props)
|
||||||
|
|
||||||
print(f"{utils.Colors.BOLD}Running: {args[0]}{utils.Colors.RESET}")
|
print(f"{utils.Colors.BOLD}Running: {args[0]}{utils.Colors.RESET}")
|
||||||
utils.runCmd(out, *args[1:])
|
utils.runCmd(out, *args[1:])
|
||||||
|
print()
|
||||||
|
print(f"{utils.Colors.GREEN}Process exited with success{utils.Colors.RESET}")
|
||||||
|
|
||||||
|
|
||||||
def buildCmd(opts: dict, args: list[str]) -> None:
|
def buildCmd(opts: dict, args: list[str]) -> None:
|
||||||
|
props = propsFromOptions(opts)
|
||||||
allTargets = opts.get('all-targets', False)
|
allTargets = opts.get('all-targets', False)
|
||||||
targetName = opts.get('target', 'default')
|
targetName = opts.get('target', 'default')
|
||||||
|
|
||||||
if allTargets:
|
if allTargets:
|
||||||
for target in targets.available():
|
for target in targets.available():
|
||||||
if len(args) == 0:
|
if len(args) == 0:
|
||||||
build.buildAll(target)
|
build.buildAll(target, props)
|
||||||
else:
|
else:
|
||||||
for component in args:
|
for component in args:
|
||||||
build.buildOne(target, component)
|
build.buildOne(target, component, props)
|
||||||
else:
|
else:
|
||||||
if len(args) == 0:
|
if len(args) == 0:
|
||||||
build.buildAll(targetName)
|
build.buildAll(targetName, props)
|
||||||
else:
|
else:
|
||||||
for component in args:
|
for component in args:
|
||||||
build.buildOne(targetName, component)
|
build.buildOne(targetName, component, props)
|
||||||
|
|
||||||
|
|
||||||
def listCmd(opts: dict, args: list[str]) -> None:
|
def listCmd(opts: dict, args: list[str]) -> None:
|
||||||
|
props = propsFromOptions(opts)
|
||||||
targetName = opts.get('target', 'default')
|
targetName = opts.get('target', 'default')
|
||||||
target = targets.load(targetName)
|
target = targets.load(targetName, props)
|
||||||
components = manifests.loadAll("src", target)
|
components = manifests.loadAll("src", target)
|
||||||
|
|
||||||
print(f"Available components for target '{targetName}':")
|
print(f"Available components for target '{targetName}':")
|
||||||
|
@ -75,12 +89,11 @@ def listCmd(opts: dict, args: list[str]) -> None:
|
||||||
|
|
||||||
|
|
||||||
def cleanCmd(opts: dict, args: list[str]) -> None:
|
def cleanCmd(opts: dict, args: list[str]) -> None:
|
||||||
shutil.rmtree(".build", ignore_errors=True)
|
shutil.rmtree(".osdk/build", ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
def nukeCmd(opts: dict, args: list[str]) -> None:
|
def nukeCmd(opts: dict, args: list[str]) -> None:
|
||||||
shutil.rmtree(".build", ignore_errors=True)
|
shutil.rmtree(".osdk", ignore_errors=True)
|
||||||
shutil.rmtree(".cache", ignore_errors=True)
|
|
||||||
|
|
||||||
|
|
||||||
def helpCmd(opts: dict, args: list[str]) -> None:
|
def helpCmd(opts: dict, args: list[str]) -> None:
|
||||||
|
|
|
@ -8,6 +8,16 @@ from . import targets
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
|
|
||||||
|
def mergeToolsArgs(tool, layers):
|
||||||
|
args = []
|
||||||
|
for layer in layers:
|
||||||
|
args.extend(layer
|
||||||
|
.get("tools", {})
|
||||||
|
.get(tool, {})
|
||||||
|
.get("args", []))
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
def genNinja(out: TextIO, manifests: dict, target: dict) -> None:
|
def genNinja(out: TextIO, manifests: dict, target: dict) -> None:
|
||||||
target = copy.deepcopy(target)
|
target = copy.deepcopy(target)
|
||||||
target = targets.patchToolArgs(target, "cc", [m.cincludes(manifests)])
|
target = targets.patchToolArgs(target, "cc", [m.cincludes(manifests)])
|
||||||
|
@ -15,14 +25,15 @@ def genNinja(out: TextIO, manifests: dict, target: dict) -> None:
|
||||||
|
|
||||||
writer = ninja.Writer(out)
|
writer = ninja.Writer(out)
|
||||||
|
|
||||||
writer.comment("Generated by the meta build system")
|
writer.comment("File generated by the build system, do not edit")
|
||||||
writer.newline()
|
writer.newline()
|
||||||
|
|
||||||
writer.comment("Tools:")
|
writer.comment("Tools:")
|
||||||
for key in target["tools"]:
|
for key in target["tools"]:
|
||||||
tool = target["tools"][key]
|
tool = target["tools"][key]
|
||||||
writer.variable(key, tool["cmd"])
|
writer.variable(key, tool["cmd"])
|
||||||
writer.variable(key + "flags", " ".join(tool["args"]))
|
writer.variable(
|
||||||
|
key + "flags", " ".join(mergeToolsArgs(key, [target] + list(manifests.values()))))
|
||||||
writer.newline()
|
writer.newline()
|
||||||
|
|
||||||
writer.newline()
|
writer.newline()
|
||||||
|
@ -82,8 +93,8 @@ def genNinja(out: TextIO, manifests: dict, target: dict) -> None:
|
||||||
writer.build("all", "phony", all)
|
writer.build("all", "phony", all)
|
||||||
|
|
||||||
|
|
||||||
def prepare(targetId: str) -> Tuple[dict, dict]:
|
def prepare(targetId: str, props: dict) -> Tuple[dict, dict]:
|
||||||
target = targets.load(targetId)
|
target = targets.load(targetId, props)
|
||||||
manifests = m.loadAll("src", target)
|
manifests = m.loadAll("src", target)
|
||||||
utils.mkdirP(target["dir"])
|
utils.mkdirP(target["dir"])
|
||||||
genNinja(open(target["ninjafile"], "w"), manifests, target)
|
genNinja(open(target["ninjafile"], "w"), manifests, target)
|
||||||
|
@ -103,9 +114,10 @@ def prepare(targetId: str) -> Tuple[dict, dict]:
|
||||||
return target, manifests
|
return target, manifests
|
||||||
|
|
||||||
|
|
||||||
def buildAll(targetId: str) -> None:
|
def buildAll(targetId: str, props: dict = {}) -> None:
|
||||||
target, _ = prepare(targetId)
|
target, _ = prepare(targetId, props)
|
||||||
print(f"{utils.Colors.BOLD}Building all components for target '{targetId}{utils.Colors.RESET}'")
|
print(f"{utils.Colors.BOLD}Building all components for target '{targetId}{utils.Colors.RESET}'")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
utils.runCmd("ninja", "-v", "-j", "1", "-f", target["ninjafile"])
|
utils.runCmd("ninja", "-v", "-j", "1", "-f", target["ninjafile"])
|
||||||
except:
|
except:
|
||||||
|
@ -113,9 +125,10 @@ def buildAll(targetId: str) -> None:
|
||||||
"Failed to build all for " + target["key"])
|
"Failed to build all for " + target["key"])
|
||||||
|
|
||||||
|
|
||||||
def buildOne(targetId: str, componentId: str) -> str:
|
def buildOne(targetId: str, componentId: str, props: dict = {}) -> str:
|
||||||
print(f"{utils.Colors.BOLD}Building {componentId} for target '{targetId}'{utils.Colors.RESET}")
|
print(f"{utils.Colors.BOLD}Building {componentId} for target '{targetId}'{utils.Colors.RESET}")
|
||||||
target, manifests = prepare(targetId)
|
|
||||||
|
target, manifests = prepare(targetId, props)
|
||||||
|
|
||||||
if not componentId in manifests:
|
if not componentId in manifests:
|
||||||
raise utils.CliException("Unknown component: " + componentId)
|
raise utils.CliException("Unknown component: " + componentId)
|
||||||
|
|
|
@ -11,14 +11,8 @@ def loadJsons(basedir: str) -> dict:
|
||||||
for filename in files:
|
for filename in files:
|
||||||
if filename == 'manifest.json':
|
if filename == 'manifest.json':
|
||||||
filename = os.path.join(root, filename)
|
filename = os.path.join(root, filename)
|
||||||
try:
|
manifest = utils.loadJson(filename)
|
||||||
with open(filename) as f:
|
result[manifest["id"]] = manifest
|
||||||
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
|
return result
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ def available() -> list:
|
||||||
VARIANTS = ["debug", "devel", "release", "sanitize"]
|
VARIANTS = ["debug", "devel", "release", "sanitize"]
|
||||||
|
|
||||||
|
|
||||||
def load(targetId: str) -> dict:
|
def load(targetId: str, props: dict) -> dict:
|
||||||
targetName = targetId
|
targetName = targetId
|
||||||
targetVariant = "devel"
|
targetVariant = "devel"
|
||||||
if ":" in targetName:
|
if ":" in targetName:
|
||||||
|
@ -90,16 +90,19 @@ def load(targetId: str) -> dict:
|
||||||
|
|
||||||
target = utils.loadJson(f"meta/targets/{targetName}.json")
|
target = utils.loadJson(f"meta/targets/{targetName}.json")
|
||||||
target["props"]["variant"] = targetVariant
|
target["props"]["variant"] = targetVariant
|
||||||
|
target["props"] = {**target["props"], **props}
|
||||||
|
|
||||||
defines = []
|
defines = []
|
||||||
|
|
||||||
for key in target["props"]:
|
for key in target["props"]:
|
||||||
|
macroname = key.lower().replace("-", "_")
|
||||||
prop = target["props"][key]
|
prop = target["props"][key]
|
||||||
|
macrovalue = str(prop).lower().replace(" ", "_").replace("-", "_")
|
||||||
if isinstance(prop, bool):
|
if isinstance(prop, bool):
|
||||||
if prop:
|
if prop:
|
||||||
defines += [f"-D__osdk_{key}__"]
|
defines += [f"-D__osdk_{macroname}__"]
|
||||||
else:
|
else:
|
||||||
defines += [f"-D__osdk_{key}_{prop}__"]
|
defines += [f"-D__osdk_{macroname}_{macrovalue}__"]
|
||||||
|
|
||||||
target = patchToolArgs(target, "cc", [
|
target = patchToolArgs(target, "cc", [
|
||||||
"-std=gnu2x",
|
"-std=gnu2x",
|
||||||
|
@ -123,7 +126,7 @@ def load(targetId: str) -> dict:
|
||||||
|
|
||||||
target["hash"] = utils.objSha256(target, ["props", "tools"])
|
target["hash"] = utils.objSha256(target, ["props", "tools"])
|
||||||
target["key"] = utils.objKey(target["props"])
|
target["key"] = utils.objKey(target["props"])
|
||||||
target["dir"] = f".build/{target['hash'][:8]}"
|
target["dir"] = f".osdk/build/{target['hash'][:8]}"
|
||||||
target["bindir"] = f"{target['dir']}/bin"
|
target["bindir"] = f"{target['dir']}/bin"
|
||||||
target["objdir"] = f"{target['dir']}/obj"
|
target["objdir"] = f"{target['dir']}/obj"
|
||||||
target["ninjafile"] = target["dir"] + "/build.ninja"
|
target["ninjafile"] = target["dir"] + "/build.ninja"
|
||||||
|
|
|
@ -89,24 +89,26 @@ def objSha256(obj: dict, keys: list[str] = []) -> str:
|
||||||
return hashlib.sha256(data.encode("utf-8")).hexdigest()
|
return hashlib.sha256(data.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def toCamelCase(s: str) -> str:
|
||||||
|
s = ''.join(x for x in s.title() if x != '_' and x != '-')
|
||||||
|
s = s[0].lower() + s[1:]
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
def objKey(obj: dict, keys: list[str] = []) -> str:
|
def objKey(obj: dict, keys: list[str] = []) -> str:
|
||||||
toKey = []
|
toKey = []
|
||||||
|
|
||||||
if len(keys) == 0:
|
if len(keys) == 0:
|
||||||
for key in obj:
|
keys = list(obj.keys())
|
||||||
|
keys.sort()
|
||||||
|
|
||||||
|
for key in keys:
|
||||||
|
if key in obj:
|
||||||
if isinstance(obj[key], bool):
|
if isinstance(obj[key], bool):
|
||||||
if obj[key]:
|
if obj[key]:
|
||||||
toKey.append(key)
|
toKey.append(key)
|
||||||
else:
|
else:
|
||||||
toKey.append(obj[key])
|
toKey.append(f"{toCamelCase(key)}({obj[key]})")
|
||||||
else:
|
|
||||||
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)
|
return "-".join(toKey)
|
||||||
|
|
||||||
|
@ -123,7 +125,7 @@ def mkdirP(path: str) -> str:
|
||||||
|
|
||||||
|
|
||||||
def downloadFile(url: str) -> str:
|
def downloadFile(url: str) -> str:
|
||||||
dest = ".cache/remote/" + hashlib.sha256(url.encode('utf-8')).hexdigest()
|
dest = ".osdk/cache/" + hashlib.sha256(url.encode('utf-8')).hexdigest()
|
||||||
tmp = dest + ".tmp"
|
tmp = dest + ".tmp"
|
||||||
|
|
||||||
if os.path.isfile(dest):
|
if os.path.isfile(dest):
|
||||||
|
@ -162,48 +164,74 @@ def runCmd(*args: str) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def getCmdOutput(*args: str) -> str:
|
||||||
|
try:
|
||||||
|
proc = subprocess.run(args, stdout=subprocess.PIPE)
|
||||||
|
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 proc.stdout.decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
CACHE = {}
|
CACHE = {}
|
||||||
|
|
||||||
|
|
||||||
MACROS = {
|
MACROS = {
|
||||||
"uname": lambda what: getattr(os.uname(), what),
|
"uname": lambda what: getattr(os.uname(), what).lower(),
|
||||||
"include": lambda *path: loadJson(''.join(path)),
|
"include": lambda *path: loadJson(''.join(path)),
|
||||||
"merge": lambda lhs, rhs: {**lhs, **rhs},
|
"join": lambda lhs, rhs: {**lhs, **rhs} if isinstance(lhs, dict) else lhs + rhs,
|
||||||
|
"concat": lambda *args: ''.join(args),
|
||||||
|
"exec": lambda *args: getCmdOutput(*args).splitlines(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def processJson(e: any) -> any:
|
def isJexpr(jexpr: list) -> bool:
|
||||||
|
return isinstance(jexpr, list) and len(jexpr) > 0 and isinstance(jexpr[0], str) and jexpr[0].startswith("@")
|
||||||
|
|
||||||
|
|
||||||
|
def jsonEval(jexpr: list) -> any:
|
||||||
|
macro = jexpr[0][1:]
|
||||||
|
if not macro in MACROS:
|
||||||
|
raise CliException(f"Unknown macro {macro}")
|
||||||
|
return MACROS[macro](*list(map((lambda x: jsonWalk(x)), jexpr[1:])))
|
||||||
|
|
||||||
|
|
||||||
|
def jsonWalk(e: any) -> any:
|
||||||
if isinstance(e, dict):
|
if isinstance(e, dict):
|
||||||
for k in e:
|
for k in e:
|
||||||
e[processJson(k)] = processJson(e[k])
|
e[jsonWalk(k)] = jsonWalk(e[k])
|
||||||
elif isinstance(e, list) and len(e) > 0 and isinstance(e[0], str) and e[0].startswith("@"):
|
elif isJexpr(e):
|
||||||
macro = e[0][1:]
|
return jsonEval(e)
|
||||||
|
|
||||||
if not macro in MACROS:
|
|
||||||
raise CliException(f"Unknown macro {macro}")
|
|
||||||
|
|
||||||
return MACROS[macro](*list(map((lambda x: processJson(x)), e[1:])))
|
|
||||||
elif isinstance(e, list):
|
elif isinstance(e, list):
|
||||||
for i in range(len(e)):
|
for i in range(len(e)):
|
||||||
e[i] = processJson(e[i])
|
e[i] = jsonWalk(e[i])
|
||||||
|
|
||||||
return e
|
return e
|
||||||
|
|
||||||
|
|
||||||
def loadJson(filename: str) -> dict:
|
def loadJson(filename: str) -> dict:
|
||||||
result = {}
|
try:
|
||||||
if filename in CACHE:
|
result = {}
|
||||||
result = CACHE[filename]
|
if filename in CACHE:
|
||||||
else:
|
result = CACHE[filename]
|
||||||
with open(filename) as f:
|
else:
|
||||||
result = processJson(json.load(f))
|
with open(filename) as f:
|
||||||
|
result = jsonWalk(json.load(f))
|
||||||
|
result["dir"] = os.path.dirname(filename)
|
||||||
|
result["json"] = filename
|
||||||
|
CACHE[filename] = result
|
||||||
|
|
||||||
result["dir"] = os.path.dirname(filename)
|
result = copy.deepcopy(result)
|
||||||
result["json"] = filename
|
return result
|
||||||
CACHE[filename] = result
|
except Exception as e:
|
||||||
|
raise CliException(f"Failed to load json {filename}: {e}")
|
||||||
result = copy.deepcopy(result)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def tryListDir(path: str) -> list[str]:
|
def tryListDir(path: str) -> list[str]:
|
||||||
|
|
Loading…
Add table
Reference in a new issue