Properly propagate props from the command line + Some code cleanup.

This commit is contained in:
Sleepy Monax 2023-10-22 14:19:59 +02:00
parent 6dd4a49043
commit 81f10de24e
10 changed files with 159 additions and 120 deletions

View file

@ -27,14 +27,14 @@ def setupLogger(verbose: bool):
level=logging.INFO,
filename=logFile,
filemode="w",
format=f"%(asctime)s %(levelname)s %(name)s: %(message)s",
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
def main() -> int:
try:
a = parse(sys.argv[1:])
setupLogger(a.consumeOpt("verbose", False) == True)
setupLogger(a.consumeOpt("verbose", False) is True)
plugins.loadAll()
cmds.exec(a)
print()
@ -47,4 +47,4 @@ def main() -> int:
return 1
except KeyboardInterrupt:
print()
return 1
return 1

View file

@ -14,9 +14,10 @@ class Args:
def consumePrefix(self, prefix: str) -> dict[str, Value]:
result: dict[str, Value] = {}
for key, value in self.opts.items():
copy = self.opts.copy()
for key, value in copy.items():
if key.startswith(prefix):
result[key[len(prefix):]] = value
result[key[len(prefix) :]] = value
del self.opts[key]
return result

View file

@ -21,8 +21,7 @@ def gen(out: TextIO, context: Context):
writer.separator("Tools")
writer.variable("cincs", " ".join(
map(lambda i: f"-I{i}", context.cincls())))
writer.variable("cincs", " ".join(map(lambda i: f"-I{i}", context.cincls())))
writer.variable("cdefs", " ".join(context.cdefs()))
@ -35,10 +34,12 @@ def gen(out: TextIO, context: Context):
tool = target.tools[i]
rule = rules.rules[i]
writer.variable(i, tool.cmd)
writer.variable(
i + "flags", " ".join(rule.args + tool.args))
writer.variable(i + "flags", " ".join(rule.args + tool.args))
writer.rule(
i, f"{tool.cmd} {rule.rule.replace('$flags',f'${i}flags')}", depfile=rule.deps)
i,
f"{tool.cmd} {rule.rule.replace('$flags',f'${i}flags')}",
depfile=rule.deps,
)
writer.newline()
writer.separator("Components")
@ -56,7 +57,7 @@ def gen(out: TextIO, context: Context):
if r is None:
raise RuntimeError(f"Unknown rule for file {obj[0]}")
t = target.tools[r.id]
writer.build(obj[1], r.id, obj[0], order_only=t.files)
writer.build(obj[1], r.id, obj[0], order_only=t.files)
for asset in assets:
writer.build(asset[1], "cp", asset[0])
@ -64,8 +65,12 @@ def gen(out: TextIO, context: Context):
writer.newline()
if instance.isLib():
writer.build(instance.outfile(), "ar",
list(map(lambda o: o[1], objects)), implicit=list(map(lambda o: o[1], assets)))
writer.build(
instance.outfile(),
"ar",
list(map(lambda o: o[1], objects)),
implicit=list(map(lambda o: o[1], assets)),
)
else:
libraries: list[str] = []
@ -80,8 +85,12 @@ def gen(out: TextIO, context: Context):
libraries.append(reqInstance.outfile())
writer.build(instance.outfile(), "ld", list(
map(lambda o: o[1], objects)) + libraries, implicit=list(map(lambda o: o[1], assets)))
writer.build(
instance.outfile(),
"ld",
list(map(lambda o: o[1], objects)) + libraries,
implicit=list(map(lambda o: o[1], assets)),
)
all.append(instance.outfile())
@ -109,9 +118,10 @@ def build(componentSpec: str, targetSpec: str, props: Props = {}) -> ComponentIn
if not instance.enabled:
raise RuntimeError(
f"Component {componentSpec} is disabled: {instance.disableReason}")
f"Component {componentSpec} is disabled: {instance.disableReason}"
)
shell.exec(f"ninja", "-f", ninjaPath, instance.outfile())
shell.exec("ninja", "-f", ninjaPath, instance.outfile())
return instance
@ -127,8 +137,8 @@ class Paths:
self.obj = obj
def buildAll(targetSpec: str) -> Context:
context = contextFor(targetSpec)
def buildAll(targetSpec: str, props: Props = {}) -> Context:
context = contextFor(targetSpec, props)
shell.mkdir(context.builddir())
ninjaPath = os.path.join(context.builddir(), "build.ninja")
@ -136,7 +146,7 @@ def buildAll(targetSpec: str) -> Context:
with open(ninjaPath, "w") as f:
gen(f, context)
shell.exec(f"ninja", "-v", "-f", ninjaPath)
shell.exec("ninja", "-v", "-f", ninjaPath)
return context
@ -150,7 +160,7 @@ def testAll(targetSpec: str):
with open(ninjaPath, "w") as f:
gen(f, context)
shell.exec(f"ninja", "-v", "-f", ninjaPath, "all")
shell.exec("ninja", "-v", "-f", ninjaPath, "all")
for instance in context.enabledInstances():
if instance.isLib():

View file

@ -22,7 +22,13 @@ class Cmd:
callback: Callable[[Args], NoReturn]
isPlugin: bool = False
def __init__(self, shortName: Optional[str], longName: str, helpText: str, callback: Callable[[Args], NoReturn]):
def __init__(
self,
shortName: Optional[str],
longName: str,
helpText: str,
callback: Callable[[Args], NoReturn],
):
self.shortName = shortName
self.longName = longName
self.helpText = helpText
@ -41,15 +47,15 @@ def append(cmd: Cmd):
def runCmd(args: Args):
project.chdir()
targetSpec = cast(str, args.consumeOpt(
"target", "host-" + shell.uname().machine))
targetSpec = cast(str, args.consumeOpt("target", "host-" + shell.uname().machine))
props = args.consumePrefix("prop:")
componentSpec = args.consumeArg()
if componentSpec is None:
raise RuntimeError("Component not specified")
component = builder.build(componentSpec, targetSpec)
component = builder.build(componentSpec, targetSpec, props)
os.environ["CK_TARGET"] = component.context.target.id
os.environ["CK_COMPONENT"] = component.id()
@ -64,8 +70,7 @@ cmds += [Cmd("r", "run", "Run the target", runCmd)]
def testCmd(args: Args):
project.chdir()
targetSpec = cast(str, args.consumeOpt(
"target", "host-" + shell.uname().machine))
targetSpec = cast(str, args.consumeOpt("target", "host-" + shell.uname().machine))
builder.testAll(targetSpec)
@ -75,15 +80,15 @@ cmds += [Cmd("t", "test", "Run all test targets", testCmd)]
def debugCmd(args: Args):
project.chdir()
targetSpec = cast(str, args.consumeOpt(
"target", "host-" + shell.uname().machine))
targetSpec = cast(str, args.consumeOpt("target", "host-" + shell.uname().machine))
props = args.consumePrefix("prop:")
componentSpec = args.consumeArg()
if componentSpec is None:
raise RuntimeError("Component not specified")
component = builder.build(componentSpec, targetSpec)
component = builder.build(componentSpec, targetSpec, props)
os.environ["CK_TARGET"] = component.context.target.id
os.environ["CK_COMPONENT"] = component.id()
@ -98,15 +103,14 @@ cmds += [Cmd("d", "debug", "Debug the target", debugCmd)]
def buildCmd(args: Args):
project.chdir()
targetSpec = cast(str, args.consumeOpt(
"target", "host-" + shell.uname().machine))
targetSpec = cast(str, args.consumeOpt("target", "host-" + shell.uname().machine))
props = args.consumePrefix("prop:")
componentSpec = args.consumeArg()
if componentSpec is None:
builder.buildAll(targetSpec)
builder.buildAll(targetSpec, props)
else:
builder.build(componentSpec, targetSpec)
builder.build(componentSpec, targetSpec, props)
cmds += [Cmd("b", "build", "Build the target", buildCmd)]
@ -120,16 +124,15 @@ def listCmd(args: Args):
vt100.title("Components")
if len(components) == 0:
print(f" (No components available)")
print(" (No components available)")
else:
print(vt100.indent(vt100.wordwrap(
", ".join(map(lambda m: m.id, components)))))
print(vt100.indent(vt100.wordwrap(", ".join(map(lambda m: m.id, components)))))
print()
vt100.title("Targets")
if len(targets) == 0:
print(f" (No targets available)")
print(" (No targets available)")
else:
print(vt100.indent(vt100.wordwrap(", ".join(map(lambda m: m.id, targets)))))
@ -171,11 +174,12 @@ def helpCmd(args: Args):
pluginText = f"{vt100.CYAN}(plugin){vt100.RESET}"
print(
f" {vt100.GREEN}{cmd.shortName or ' '}{vt100.RESET} {cmd.longName} - {cmd.helpText} {pluginText}")
f" {vt100.GREEN}{cmd.shortName or ' '}{vt100.RESET} {cmd.longName} - {cmd.helpText} {pluginText}"
)
print()
vt100.title("Logging")
print(f" Logs are stored in:")
print(" Logs are stored in:")
print(f" - {const.PROJECT_LOG_FILE}")
print(f" - {const.GLOBAL_LOG_FILE}")
@ -193,17 +197,15 @@ cmds += [Cmd("v", "version", "Show current version", versionCmd)]
def graphCmd(args: Args):
project.chdir()
targetSpec = cast(str, args.consumeOpt(
"target", "host-" + shell.uname().machine))
targetSpec = cast(str, args.consumeOpt("target", "host-" + shell.uname().machine))
scope: Optional[str] = cast(Optional[str], args.tryConsumeOpt("scope"))
onlyLibs: bool = args.consumeOpt("only-libs", False) == True
showDisabled: bool = args.consumeOpt("show-disabled", False) == True
onlyLibs: bool = args.consumeOpt("only-libs", False) is True
showDisabled: bool = args.consumeOpt("show-disabled", False) is True
context = contextFor(targetSpec)
graph.view(context, scope=scope, showExe=not onlyLibs,
showDisabled=showDisabled)
graph.view(context, scope=scope, showExe=not onlyLibs, showDisabled=showDisabled)
cmds += [Cmd("g", "graph", "Show dependency graph", graphCmd)]
@ -218,8 +220,9 @@ def grabExtern(extern: dict[str, Extern]):
continue
print(f"Installing {extSpec}-{ext.tag} from {ext.git}...")
shell.popen("git", "clone", "--depth", "1", "--branch",
ext.tag, ext.git, extPath)
shell.popen(
"git", "clone", "--depth", "1", "--branch", ext.tag, ext.git, extPath
)
if os.path.exists(os.path.join(extPath, "project.json")):
grabExtern(context.loadProject(extPath).extern)
@ -238,31 +241,33 @@ cmds += [Cmd("i", "install", "Install all the external packages", installCmd)]
def initCmd(args: Args):
import requests
repo = args.consumeOpt('repo', const.DEFAULT_REPO_TEMPLATES)
list = args.consumeOpt('list')
repo = args.consumeOpt("repo", const.DEFAULT_REPO_TEMPLATES)
list = args.consumeOpt("list")
template = args.consumeArg()
name = args.consumeArg()
logger.info("Fetching registry...")
r = requests.get(
f'https://raw.githubusercontent.com/{repo}/main/registry.json')
r = requests.get(f"https://raw.githubusercontent.com/{repo}/main/registry.json")
if r.status_code != 200:
logger.error('Failed to fetch registry')
logger.error("Failed to fetch registry")
exit(1)
registry = r.json()
if list:
print('\n'.join(
f"* {entry['id']} - {entry['description']}" for entry in registry))
print(
"\n".join(f"* {entry['id']} - {entry['description']}" for entry in registry)
)
return
if not template:
raise RuntimeError('Template not specified')
raise RuntimeError("Template not specified")
def template_match(t: Json) -> str:
return t["id"] == template
template_match: Callable[[Json], str] = lambda t: t['id'] == template
if not any(filter(template_match, registry)):
raise LookupError(f"Couldn't find a template named {template}")
@ -279,9 +284,12 @@ def initCmd(args: Args):
print("We suggest that you begin by typing:")
print(f" {vt100.GREEN}cd {name}{vt100.RESET}")
print(f" {vt100.GREEN}cutekit install{vt100.BRIGHT_BLACK} # Install external packages{vt100.RESET}")
print(
f" {vt100.GREEN}cutekit build{vt100.BRIGHT_BLACK} # Build the project{vt100.RESET}")
f" {vt100.GREEN}cutekit install{vt100.BRIGHT_BLACK} # Install external packages{vt100.RESET}"
)
print(
f" {vt100.GREEN}cutekit build{vt100.BRIGHT_BLACK} # Build the project{vt100.RESET}"
)
cmds += [Cmd("I", "init", "Initialize a new project", initCmd)]

View file

@ -21,13 +21,13 @@ UNSUPORTED_MANIFEST = {
def ensureSupportedManifest(manifest: Any, path: str):
if not "$schema" in manifest:
if "$schema" not in manifest:
raise RuntimeError(f"Missing $schema in {path}")
if manifest["$schema"] in UNSUPORTED_MANIFEST:
raise RuntimeError(
f"Unsupported manifest schema {manifest['$schema']} in {path}: {UNSUPORTED_MANIFEST[manifest['$schema']]}")
if not manifest["$schema"] in SUPPORTED_MANIFEST:
if manifest["$schema"] not in SUPPORTED_MANIFEST:
raise RuntimeError(
f"Unsupported manifest schema {manifest['$schema']} in {path}")

View file

@ -11,7 +11,7 @@ BUILD_DIR = os.path.join(PROJECT_CK_DIR, "build")
CACHE_DIR = os.path.join(PROJECT_CK_DIR, "cache")
EXTERN_DIR = os.path.join(PROJECT_CK_DIR, "extern")
SRC_DIR = "src"
META_DIR = f"meta"
META_DIR = "meta"
TARGETS_DIR = os.path.join(META_DIR, "targets")
DEFAULT_REPO_TEMPLATES = "cute-engineering/cutekit-templates"
DESCRIPTION = "A build system and package manager for low-level software development"

View file

@ -288,7 +288,7 @@ def contextFor(targetSpec: str, props: Props = {}) -> Context:
instances: list[ComponentInstance] = list(
map(lambda c: instanciateDisabled(c, target), disabled))
instances += cast(list[ComponentInstance], list(filter(lambda e: e != None, map(lambda c: instanciate(
instances += cast(list[ComponentInstance], list(filter(lambda e: e is not None, map(lambda c: instanciate(
c.id, components, target), components))))
context[targetSpec] = Context(

View file

@ -10,15 +10,21 @@ Builtin = Callable[..., Json]
BUILTINS: Final[dict[str, Builtin]] = {
"uname": lambda arg, ctx: getattr(shell.uname(), arg).lower(),
"include": lambda arg, ctx: evalRead(arg),
"evalRead": lambda arg, ctx: evalRead(arg),
"join": lambda lhs, rhs, ctx: cast(Json, {**lhs, **rhs} if isinstance(lhs, dict) else lhs + rhs),
"include": lambda arg, ctx: evalRead(arg, compatibilityCheck=False),
"evalRead": lambda arg, ctx: evalRead(arg, compatibilityCheck=False),
"join": lambda lhs, rhs, ctx: cast(
Json, {**lhs, **rhs} if isinstance(lhs, dict) else lhs + rhs
),
"concat": lambda *args, ctx: "".join(args),
"first": lambda arg, ctx: arg[0],
"last": lambda arg, ctx: arg[-1],
"eval": lambda arg, ctx: eval(arg, ctx["filepath"]),
"read": lambda arg, ctx: read(arg),
"exec": lambda *args, ctx: shell.popen(*args).splitlines(),
"latest": lambda arg, ctx: shell.latest(arg),
"abspath": lambda *args, ctx: os.path.normpath(os.path.join(os.path.dirname(ctx["filepath"]), *args))
"abspath": lambda *args, ctx: os.path.normpath(
os.path.join(os.path.dirname(ctx["filepath"]), *args)
),
}
@ -33,7 +39,9 @@ def eval(jexpr: Json, filePath: str) -> Json:
if len(jexpr) > 0 and isinstance(jexpr[0], str) and jexpr[0].startswith("@"):
funcName = jexpr[0][1:]
if funcName in BUILTINS:
return BUILTINS[funcName](*eval(jexpr[1:], filePath), ctx={"filepath": filePath})
return BUILTINS[funcName](
*eval(jexpr[1:], filePath), ctx={"filepath": filePath}
)
raise RuntimeError(f"Unknown macro {funcName}")
else:
@ -50,7 +58,8 @@ def read(path: str) -> Json:
raise RuntimeError(f"Failed to read {path}")
def evalRead(path: str) -> Json:
def evalRead(path: str, compatibilityCheck: bool = True) -> Json:
data = read(path)
ensureSupportedManifest(data, path)
if compatibilityCheck:
ensureSupportedManifest(data, path)
return eval(data, path)

View file

@ -24,14 +24,16 @@ class Manifest:
type: Type = Type.UNKNOWN
path: str = ""
def __init__(self, json: Json = None, path: str = "", strict: bool = True, **kwargs: Any):
def __init__(
self, json: Json = None, path: str = "", strict: bool = True, **kwargs: Any
):
if json is not None:
if not "id" in json:
if "id" not in json:
raise RuntimeError("Missing id")
self.id = json["id"]
if not "type" in json and strict:
if "type" not in json and strict:
raise RuntimeError("Missing type")
self.type = Type(json["type"])
@ -44,11 +46,7 @@ class Manifest:
setattr(self, key, kwargs[key])
def toJson(self) -> Json:
return {
"id": self.id,
"type": self.type.value,
"path": self.path
}
return {"id": self.id, "type": self.type.value, "path": self.path}
def __str__(self):
return f"Manifest(id={self.id}, type={self.type}, path={self.path})"
@ -66,12 +64,12 @@ class Extern:
def __init__(self, json: Json = None, strict: bool = True, **kwargs: Any):
if json is not None:
if not "git" in json and strict:
if "git" not in json and strict:
raise RuntimeError("Missing git")
self.git = json["git"]
if not "tag" in json and strict:
if "tag" not in json and strict:
raise RuntimeError("Missing tag")
self.tag = json["tag"]
@ -82,10 +80,7 @@ class Extern:
setattr(self, key, kwargs[key])
def toJson(self) -> Json:
return {
"git": self.git,
"tag": self.tag
}
return {"git": self.git, "tag": self.tag}
def __str__(self):
return f"Extern(git={self.git}, tag={self.tag})"
@ -98,15 +93,16 @@ class ProjectManifest(Manifest):
description: str = ""
extern: dict[str, Extern] = {}
def __init__(self, json: Json = None, path: str = "", strict: bool = True, **kwargs: Any):
def __init__(
self, json: Json = None, path: str = "", strict: bool = True, **kwargs: Any
):
if json is not None:
if not "description" in json and strict:
if "description" not in json and strict:
raise RuntimeError("Missing description")
self.description = json["description"]
self.extern = {k: Extern(v)
for k, v in json.get("extern", {}).items()}
self.extern = {k: Extern(v) for k, v in json.get("extern", {}).items()}
elif strict:
raise RuntimeError("Missing json")
@ -116,7 +112,7 @@ class ProjectManifest(Manifest):
return {
**super().toJson(),
"description": self.description,
"extern": {k: v.toJson() for k, v in self.extern.items()}
"extern": {k: v.toJson() for k, v in self.extern.items()},
}
def __str__(self):
@ -133,12 +129,12 @@ class Tool:
def __init__(self, json: Json = None, strict: bool = True, **kwargs: Any):
if json is not None:
if not "cmd" in json and strict:
if "cmd" not in json and strict:
raise RuntimeError("Missing cmd")
self.cmd = json.get("cmd", self.cmd)
if not "args" in json and strict:
if "args" not in json and strict:
raise RuntimeError("Missing args")
self.args = json.get("args", [])
@ -151,11 +147,7 @@ class Tool:
setattr(self, key, kwargs[key])
def toJson(self) -> Json:
return {
"cmd": self.cmd,
"args": self.args,
"files": self.files
}
return {"cmd": self.cmd, "args": self.args, "files": self.files}
def __str__(self):
return f"Tool(cmd={self.cmd}, args={self.args}, files={self.files})"
@ -172,14 +164,16 @@ class TargetManifest(Manifest):
tools: Tools
routing: dict[str, str]
def __init__(self, json: Json = None, path: str = "", strict: bool = True, **kwargs: Any):
def __init__(
self, json: Json = None, path: str = "", strict: bool = True, **kwargs: Any
):
if json is not None:
if not "props" in json and strict:
if "props" not in json and strict:
raise RuntimeError("Missing props")
self.props = json["props"]
if not "tools" in json and strict:
if "tools" not in json and strict:
raise RuntimeError("Missing tools")
self.tools = {k: Tool(v) for k, v in json["tools"].items()}
@ -193,27 +187,34 @@ class TargetManifest(Manifest):
**super().toJson(),
"props": self.props,
"tools": {k: v.toJson() for k, v in self.tools.items()},
"routing": self.routing
"routing": self.routing,
}
def __repr__(self):
return f"TargetManifest({self.id})"
def route(self, componentSpec: str):
return self.routing[componentSpec] if componentSpec in self.routing else componentSpec
return (
self.routing[componentSpec]
if componentSpec in self.routing
else componentSpec
)
def cdefs(self) -> list[str]:
defines: list[str] = []
def sanatize(s: str) -> str:
return s.lower().replace(" ", "_").replace("-", "_").replace(".", "_")
for key in self.props:
macroname = key.lower().replace("-", "_")
prop = self.props[key]
macrovalue = str(prop).lower().replace(" ", "_").replace("-", "_")
propStr = str(prop)
if isinstance(prop, bool):
if prop:
defines += [f"-D__ck_{macroname}__"]
defines += [f"-D__ck_{sanatize(key)}__"]
else:
defines += [f"-D__ck_{macroname}_{macrovalue}__"]
defines += [f"-D__ck_{sanatize(key)}_{sanatize(propStr)}__"]
defines += [f"-D__ck_{sanatize(key)}_value={propStr}"]
return defines
@ -227,17 +228,24 @@ class ComponentManifest(Manifest):
provides: list[str] = []
subdirs: list[str] = []
def __init__(self, json: Json = None, path: str = "", strict: bool = True, **kwargs: Any):
def __init__(
self, json: Json = None, path: str = "", strict: bool = True, **kwargs: Any
):
if json is not None:
self.decription = json.get("description", self.decription)
self.props = json.get("props", self.props)
self.tools = {k: Tool(v, strict=False)
for k, v in json.get("tools", {}).items()}
self.tools = {
k: Tool(v, strict=False) for k, v in json.get("tools", {}).items()
}
self.enableIf = json.get("enableIf", self.enableIf)
self.requires = json.get("requires", self.requires)
self.provides = json.get("provides", self.provides)
self.subdirs = list(map(lambda x: os.path.join(os.path.dirname(
path), x), json.get("subdirs", [""])))
self.subdirs = list(
map(
lambda x: os.path.join(os.path.dirname(path), x),
json.get("subdirs", [""]),
)
)
super().__init__(json, path, strict, **kwargs)
@ -250,7 +258,7 @@ class ComponentManifest(Manifest):
"enableIf": self.enableIf,
"requires": self.requires,
"provides": self.provides,
"subdirs": self.subdirs
"subdirs": self.subdirs,
}
def __repr__(self):
@ -258,15 +266,18 @@ class ComponentManifest(Manifest):
def isEnabled(self, target: TargetManifest) -> tuple[bool, str]:
for k, v in self.enableIf.items():
if not k in target.props:
logger.info(
f"Component {self.id} disabled by missing {k} in target")
if k not in target.props:
logger.info(f"Component {self.id} disabled by missing {k} in target")
return False, f"Missing props '{k}' in target"
if not target.props[k] in v:
if target.props[k] not in v:
vStrs = [f"'{str(x)}'" for x in v]
logger.info(
f"Component {self.id} disabled by {k}={target.props[k]} not in {v}")
return False, f"Props missmatch for '{k}': Got '{target.props[k]}' but expected {', '.join(vStrs)}"
f"Component {self.id} disabled by {k}={target.props[k]} not in {v}"
)
return (
False,
f"Props missmatch for '{k}': Got '{target.props[k]}' but expected {', '.join(vStrs)}",
)
return True, ""

View file

@ -23,11 +23,11 @@ def loadAll():
logger.info("Loading plugins...")
projectRoot = project.root()
if projectRoot is None:
logger.info("Not in project, skipping plugin loading")
return
pj = context.loadProject(projectRoot)
paths = list(map(lambda e: os.path.join(const.EXTERN_DIR, e), pj.extern.keys())) + ["."]