Added support for nested commands and running cutekit in containers.
This commit is contained in:
parent
8a9c4689e9
commit
72c982ab7b
|
@ -9,6 +9,7 @@ from . import (
|
||||||
graph, # noqa: F401 this is imported for side effects
|
graph, # noqa: F401 this is imported for side effects
|
||||||
model,
|
model,
|
||||||
plugins,
|
plugins,
|
||||||
|
pods, # noqa: F401 this is imported for side effects
|
||||||
vt100,
|
vt100,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -55,10 +56,13 @@ def setupLogger(verbose: bool):
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
try:
|
try:
|
||||||
a = cli.parse(sys.argv[1:])
|
args = cli.parse(sys.argv[1:])
|
||||||
setupLogger(a.consumeOpt("verbose", False) is True)
|
setupLogger(args.consumeOpt("verbose", False) is True)
|
||||||
plugins.loadAll()
|
safemode = args.consumeOpt("safemode", False) is True
|
||||||
cli.exec(a)
|
if not safemode:
|
||||||
|
plugins.loadAll()
|
||||||
|
pods.reincarnate(args)
|
||||||
|
cli.exec(args)
|
||||||
print()
|
print()
|
||||||
return 0
|
return 0
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
|
|
@ -3,32 +3,89 @@ import logging
|
||||||
import dataclasses as dt
|
import dataclasses as dt
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TextIO, Union
|
from typing import Callable, TextIO, Union
|
||||||
|
|
||||||
from . import shell, rules, model, ninja, const, cli
|
from . import shell, rules, model, ninja, const, cli
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def aggregateCincs(target: model.Target, registry: model.Registry) -> set[str]:
|
@dt.dataclass
|
||||||
|
class TargetScope:
|
||||||
|
registry: model.Registry
|
||||||
|
target: model.Target
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def use(args: cli.Args) -> "TargetScope":
|
||||||
|
registry = model.Registry.use(args)
|
||||||
|
target = model.Target.use(args)
|
||||||
|
return TargetScope(registry, target)
|
||||||
|
|
||||||
|
def openComponentScope(self, c: model.Component):
|
||||||
|
return ComponentScope(self.registry, self.target, c)
|
||||||
|
|
||||||
|
|
||||||
|
@dt.dataclass
|
||||||
|
class ComponentScope(TargetScope):
|
||||||
|
component: model.Component
|
||||||
|
|
||||||
|
def openComponentScope(self, c: model.Component):
|
||||||
|
return ComponentScope(self.registry, self.target, c)
|
||||||
|
|
||||||
|
def openProductScope(self, path: Path):
|
||||||
|
return ProductScope(self.registry, self.target, self.component, path)
|
||||||
|
|
||||||
|
|
||||||
|
@dt.dataclass
|
||||||
|
class ProductScope(ComponentScope):
|
||||||
|
path: Path
|
||||||
|
|
||||||
|
|
||||||
|
# --- Variables -------------------------------------------------------------- #
|
||||||
|
|
||||||
|
Compute = Callable[[TargetScope], str]
|
||||||
|
_vars: dict[str, Compute] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def var(name: str) -> Callable[[Compute], Compute]:
|
||||||
|
def decorator(func: Compute):
|
||||||
|
_vars[name] = func
|
||||||
|
return func
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
@var("buildir")
|
||||||
|
def _computeBuildir(scope: TargetScope) -> str:
|
||||||
|
return scope.target.builddir
|
||||||
|
|
||||||
|
|
||||||
|
@var("hashid")
|
||||||
|
def _computeHashid(scope: TargetScope) -> str:
|
||||||
|
return scope.target.hashid
|
||||||
|
|
||||||
|
|
||||||
|
@var("cincs")
|
||||||
|
def _computeCinc(scope: TargetScope) -> str:
|
||||||
res = set()
|
res = set()
|
||||||
|
|
||||||
for c in registry.iterEnabled(target):
|
for c in scope.registry.iterEnabled(scope.target):
|
||||||
if "cpp-root-include" in c.props:
|
if "cpp-root-include" in c.props:
|
||||||
res.add(c.dirname())
|
res.add(c.dirname())
|
||||||
elif c.type == model.Kind.LIB:
|
elif c.type == model.Kind.LIB:
|
||||||
res.add(str(Path(c.dirname()).parent))
|
res.add(str(Path(c.dirname()).parent))
|
||||||
|
|
||||||
return set(map(lambda i: f"-I{i}", res))
|
return " ".join(set(map(lambda i: f"-I{i}", res)))
|
||||||
|
|
||||||
|
|
||||||
def aggregateCdefs(target: model.Target) -> set[str]:
|
@var("cdefs")
|
||||||
|
def _computeCdef(scope: TargetScope) -> str:
|
||||||
res = set()
|
res = set()
|
||||||
|
|
||||||
def sanatize(s: str) -> str:
|
def sanatize(s: str) -> str:
|
||||||
return s.lower().replace(" ", "_").replace("-", "_").replace(".", "_")
|
return s.lower().replace(" ", "_").replace("-", "_").replace(".", "_")
|
||||||
|
|
||||||
for k, v in target.props.items():
|
for k, v in scope.target.props.items():
|
||||||
if isinstance(v, bool):
|
if isinstance(v, bool):
|
||||||
if v:
|
if v:
|
||||||
res.add(f"-D__ck_{sanatize(k)}__")
|
res.add(f"-D__ck_{sanatize(k)}__")
|
||||||
|
@ -36,35 +93,45 @@ def aggregateCdefs(target: model.Target) -> set[str]:
|
||||||
res.add(f"-D__ck_{sanatize(k)}_{sanatize(str(v))}__")
|
res.add(f"-D__ck_{sanatize(k)}_{sanatize(str(v))}__")
|
||||||
res.add(f"-D__ck_{sanatize(k)}_value={str(v)}")
|
res.add(f"-D__ck_{sanatize(k)}_value={str(v)}")
|
||||||
|
|
||||||
return res
|
return " ".join(res)
|
||||||
|
|
||||||
|
|
||||||
def buildpath(target: model.Target, component: model.Component, path) -> Path:
|
def buildpath(scope: ComponentScope, path) -> Path:
|
||||||
return Path(target.builddir) / component.id / path
|
return Path(scope.target.builddir) / scope.component.id / path
|
||||||
|
|
||||||
|
|
||||||
# --- Compilation ------------------------------------------------------------ #
|
# --- Compilation ------------------------------------------------------------ #
|
||||||
|
|
||||||
|
|
||||||
def wilcard(component: model.Component, wildcards: list[str]) -> list[str]:
|
def subdirs(scope: ComponentScope) -> list[str]:
|
||||||
dirs = [component.dirname()] + list(
|
registry = scope.registry
|
||||||
map(lambda d: os.path.join(component.dirname(), d), component.subdirs)
|
target = scope.target
|
||||||
)
|
component = scope.component
|
||||||
return shell.find(dirs, list(wildcards), recusive=False)
|
result = [component.dirname()]
|
||||||
|
|
||||||
|
for subs in component.subdirs:
|
||||||
|
result.append(os.path.join(component.dirname(), subs))
|
||||||
|
|
||||||
|
for inj in component.resolved[target.id].injected:
|
||||||
|
injected = registry.lookup(inj, model.Component)
|
||||||
|
assert injected is not None # model.Resolver has already checked this
|
||||||
|
result.extend(subdirs(scope))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def wilcard(scope: ComponentScope, wildcards: list[str]) -> list[str]:
|
||||||
|
return shell.find(subdirs(scope), list(wildcards), recusive=False)
|
||||||
|
|
||||||
|
|
||||||
def compile(
|
def compile(
|
||||||
w: ninja.Writer,
|
w: ninja.Writer, scope: ComponentScope, rule: str, srcs: list[str]
|
||||||
target: model.Target,
|
|
||||||
component: model.Component,
|
|
||||||
rule: str,
|
|
||||||
srcs: list[str],
|
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
res: list[str] = []
|
res: list[str] = []
|
||||||
for src in srcs:
|
for src in srcs:
|
||||||
rel = Path(src).relative_to(component.dirname())
|
rel = Path(src).relative_to(scope.component.dirname())
|
||||||
dest = buildpath(target, component, "obj") / rel.with_suffix(".o")
|
dest = buildpath(scope, "obj") / rel.with_suffix(".o")
|
||||||
t = target.tools[rule]
|
t = scope.target.tools[rule]
|
||||||
w.build(str(dest), rule, inputs=src, order_only=t.files)
|
w.build(str(dest), rule, inputs=src, order_only=t.files)
|
||||||
res.append(str(dest))
|
res.append(str(dest))
|
||||||
return res
|
return res
|
||||||
|
@ -79,13 +146,12 @@ def listRes(component: model.Component) -> list[str]:
|
||||||
|
|
||||||
def compileRes(
|
def compileRes(
|
||||||
w: ninja.Writer,
|
w: ninja.Writer,
|
||||||
target: model.Target,
|
scope: ComponentScope,
|
||||||
component: model.Component,
|
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
res: list[str] = []
|
res: list[str] = []
|
||||||
for r in listRes(component):
|
for r in listRes(scope.component):
|
||||||
rel = Path(r).relative_to(component.subpath("res"))
|
rel = Path(r).relative_to(scope.component.subpath("res"))
|
||||||
dest = buildpath(target, component, "res") / rel
|
dest = buildpath(scope, "res") / rel
|
||||||
w.build(str(dest), "cp", r)
|
w.build(str(dest), "cp", r)
|
||||||
res.append(str(dest))
|
res.append(str(dest))
|
||||||
return res
|
return res
|
||||||
|
@ -94,50 +160,55 @@ def compileRes(
|
||||||
# --- Linking ---------------------------------------------------------------- #
|
# --- Linking ---------------------------------------------------------------- #
|
||||||
|
|
||||||
|
|
||||||
def outfile(target: model.Target, component: model.Component) -> str:
|
def outfile(scope: ComponentScope) -> str:
|
||||||
if component.type == model.Kind.LIB:
|
if scope.component.type == model.Kind.LIB:
|
||||||
return str(buildpath(target, component, f"lib/{component.id}.a"))
|
return str(buildpath(scope, f"lib/{scope.component.id}.a"))
|
||||||
else:
|
else:
|
||||||
return str(buildpath(target, component, f"bin/{component.id}.out"))
|
return str(buildpath(scope, f"bin/{scope.component.id}.out"))
|
||||||
|
|
||||||
|
|
||||||
def collectLibs(
|
def collectLibs(
|
||||||
registry: model.Registry, target: model.Target, component: model.Component
|
scope: ComponentScope,
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
res: list[str] = []
|
res: list[str] = []
|
||||||
for r in component.resolved[target.id].resolved:
|
for r in scope.component.resolved[scope.target.id].required:
|
||||||
req = registry.lookup(r, model.Component)
|
req = scope.registry.lookup(r, model.Component)
|
||||||
assert req is not None # model.Resolver has already checked this
|
assert req is not None # model.Resolver has already checked this
|
||||||
|
|
||||||
if r == component.id:
|
if r == scope.component.id:
|
||||||
continue
|
continue
|
||||||
if not req.type == model.Kind.LIB:
|
if not req.type == model.Kind.LIB:
|
||||||
raise RuntimeError(f"Component {r} is not a library")
|
raise RuntimeError(f"Component {r} is not a library")
|
||||||
res.append(outfile(target, req))
|
res.append(outfile(scope.openComponentScope(req)))
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
def link(
|
def link(
|
||||||
w: ninja.Writer,
|
w: ninja.Writer,
|
||||||
registry: model.Registry,
|
scope: ComponentScope,
|
||||||
target: model.Target,
|
|
||||||
component: model.Component,
|
|
||||||
) -> str:
|
) -> str:
|
||||||
w.newline()
|
w.newline()
|
||||||
out = outfile(target, component)
|
out = outfile(scope)
|
||||||
|
|
||||||
objs = []
|
objs = []
|
||||||
objs += compile(w, target, component, "cc", wilcard(component, ["*.c"]))
|
objs += compile(w, scope, "cc", wilcard(scope, ["*.c"]))
|
||||||
objs += compile(
|
objs += compile(
|
||||||
w, target, component, "cxx", wilcard(component, ["*.cpp", "*.cc", "*.cxx"])
|
w,
|
||||||
|
scope,
|
||||||
|
"cxx",
|
||||||
|
wilcard(scope, ["*.cpp", "*.cc", "*.cxx"]),
|
||||||
)
|
)
|
||||||
objs += compile(
|
objs += compile(
|
||||||
w, target, component, "as", wilcard(component, ["*.s", "*.asm", "*.S"])
|
w,
|
||||||
|
scope,
|
||||||
|
"as",
|
||||||
|
wilcard(scope, ["*.s", "*.asm", "*.S"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
res = compileRes(w, target, component)
|
res = compileRes(w, scope)
|
||||||
libs = collectLibs(registry, target, component)
|
libs = collectLibs(scope)
|
||||||
if component.type == model.Kind.LIB:
|
if scope.component.type == model.Kind.LIB:
|
||||||
w.build(out, "ar", objs, implicit=res)
|
w.build(out, "ar", objs, implicit=res)
|
||||||
else:
|
else:
|
||||||
w.build(out, "ld", objs + libs, implicit=res)
|
w.build(out, "ld", objs + libs, implicit=res)
|
||||||
|
@ -147,32 +218,30 @@ def link(
|
||||||
# --- Phony ------------------------------------------------------------------ #
|
# --- Phony ------------------------------------------------------------------ #
|
||||||
|
|
||||||
|
|
||||||
def all(w: ninja.Writer, registry: model.Registry, target: model.Target) -> list[str]:
|
def all(w: ninja.Writer, scope: TargetScope) -> list[str]:
|
||||||
all: list[str] = []
|
all: list[str] = []
|
||||||
for c in registry.iterEnabled(target):
|
for c in scope.registry.iterEnabled(scope.target):
|
||||||
all.append(link(w, registry, target, c))
|
all.append(link(w, scope.openComponentScope(c)))
|
||||||
w.build("all", "phony", all)
|
w.build("all", "phony", all)
|
||||||
w.default("all")
|
w.default("all")
|
||||||
return all
|
return all
|
||||||
|
|
||||||
|
|
||||||
def gen(out: TextIO, target: model.Target, registry: model.Registry):
|
def gen(out: TextIO, scope: TargetScope):
|
||||||
w = ninja.Writer(out)
|
w = ninja.Writer(out)
|
||||||
|
|
||||||
w.comment("File generated by the build system, do not edit")
|
w.comment("File generated by the build system, do not edit")
|
||||||
w.newline()
|
w.newline()
|
||||||
|
|
||||||
w.variable("builddir", target.builddir)
|
w.separator("Variables")
|
||||||
w.variable("hashid", target.hashid)
|
for name, compute in _vars.items():
|
||||||
|
w.variable(name, compute(scope))
|
||||||
|
w.newline()
|
||||||
|
|
||||||
w.separator("Tools")
|
w.separator("Tools")
|
||||||
|
|
||||||
w.variable("cincs", " ".join(aggregateCincs(target, registry)))
|
for i in scope.target.tools:
|
||||||
w.variable("cdefs", " ".join(aggregateCdefs(target)))
|
tool = scope.target.tools[i]
|
||||||
w.newline()
|
|
||||||
|
|
||||||
for i in target.tools:
|
|
||||||
tool = target.tools[i]
|
|
||||||
rule = rules.rules[i]
|
rule = rules.rules[i]
|
||||||
w.variable(i, tool.cmd)
|
w.variable(i, tool.cmd)
|
||||||
w.variable(i + "flags", " ".join(rule.args + tool.args))
|
w.variable(i + "flags", " ".join(rule.args + tool.args))
|
||||||
|
@ -185,53 +254,40 @@ def gen(out: TextIO, target: model.Target, registry: model.Registry):
|
||||||
|
|
||||||
w.separator("Build")
|
w.separator("Build")
|
||||||
|
|
||||||
all(w, registry, target)
|
all(w, scope)
|
||||||
|
|
||||||
|
|
||||||
@dt.dataclass
|
|
||||||
class Product:
|
|
||||||
path: Path
|
|
||||||
target: model.Target
|
|
||||||
component: model.Component
|
|
||||||
|
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
target: model.Target,
|
scope: TargetScope,
|
||||||
registry: model.Registry,
|
|
||||||
components: Union[list[model.Component], model.Component, None] = None,
|
components: Union[list[model.Component], model.Component, None] = None,
|
||||||
) -> list[Product]:
|
) -> list[ProductScope]:
|
||||||
all = False
|
all = False
|
||||||
shell.mkdir(target.builddir)
|
shell.mkdir(scope.target.builddir)
|
||||||
ninjaPath = os.path.join(target.builddir, "build.ninja")
|
ninjaPath = os.path.join(scope.target.builddir, "build.ninja")
|
||||||
|
|
||||||
if not os.path.exists(ninjaPath):
|
if not os.path.exists(ninjaPath):
|
||||||
with open(ninjaPath, "w") as f:
|
with open(ninjaPath, "w") as f:
|
||||||
gen(f, target, registry)
|
gen(f, scope)
|
||||||
|
|
||||||
if components is None:
|
if components is None:
|
||||||
all = True
|
all = True
|
||||||
components = list(registry.iterEnabled(target))
|
components = list(scope.registry.iterEnabled(scope.target))
|
||||||
|
|
||||||
if isinstance(components, model.Component):
|
if isinstance(components, model.Component):
|
||||||
components = [components]
|
components = [components]
|
||||||
|
|
||||||
products: list[Product] = []
|
products: list[ProductScope] = []
|
||||||
for c in components:
|
for c in components:
|
||||||
r = c.resolved[target.id]
|
s = scope.openComponentScope(c)
|
||||||
|
r = c.resolved[scope.target.id]
|
||||||
if not r.enabled:
|
if not r.enabled:
|
||||||
raise RuntimeError(f"Component {c.id} is disabled: {r.reason}")
|
raise RuntimeError(f"Component {c.id} is disabled: {r.reason}")
|
||||||
|
|
||||||
products.append(
|
products.append(s.openProductScope(Path(outfile(scope.openComponentScope(c)))))
|
||||||
Product(
|
|
||||||
path=Path(outfile(target, c)),
|
|
||||||
target=target,
|
|
||||||
component=c,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
outs = list(map(lambda p: str(p.path), products))
|
outs = list(map(lambda p: str(p.path), products))
|
||||||
|
|
||||||
shell.exec("ninja", "-f", ninjaPath, *(outs if not all else []))
|
# shell.exec("ninja", "-f", ninjaPath, *(outs if not all else []))
|
||||||
|
|
||||||
return products
|
return products
|
||||||
|
|
||||||
|
@ -239,60 +295,65 @@ def build(
|
||||||
# --- Commands --------------------------------------------------------------- #
|
# --- Commands --------------------------------------------------------------- #
|
||||||
|
|
||||||
|
|
||||||
@cli.command("b", "build", "Build a component or all components")
|
@cli.command("b", "build", "Build/Run/Clean a component or all components")
|
||||||
def buildCmd(args: cli.Args):
|
def buildCmd(args: cli.Args):
|
||||||
registry = model.Registry.use(args)
|
pass
|
||||||
target = model.Target.use(args)
|
|
||||||
|
|
||||||
|
@cli.command("b", "build/build", "Build a component or all components")
|
||||||
|
def buildBuildCmd(args: cli.Args):
|
||||||
|
scope = TargetScope.use(args)
|
||||||
componentSpec = args.consumeArg()
|
componentSpec = args.consumeArg()
|
||||||
component = None
|
component = None
|
||||||
if componentSpec is not None:
|
if componentSpec is not None:
|
||||||
component = registry.lookup(componentSpec, model.Component)
|
component = scope.registry.lookup(componentSpec, model.Component)
|
||||||
build(target, registry, component)[0]
|
build(scope, component)[0]
|
||||||
|
|
||||||
|
|
||||||
@cli.command("r", "run", "Run a component")
|
@cli.command("r", "build/run", "Run a component")
|
||||||
def runCmd(args: cli.Args):
|
def buildRunCmd(args: cli.Args):
|
||||||
registry = model.Registry.use(args)
|
scope = TargetScope.use(args)
|
||||||
target = model.Target.use(args)
|
|
||||||
debug = args.consumeOpt("debug", False) is True
|
debug = args.consumeOpt("debug", False) is True
|
||||||
|
|
||||||
componentSpec = args.consumeArg() or "__main__"
|
componentSpec = args.consumeArg() or "__main__"
|
||||||
component = registry.lookup(componentSpec, model.Component, includeProvides=True)
|
component = scope.registry.lookup(
|
||||||
|
componentSpec, model.Component, includeProvides=True
|
||||||
|
)
|
||||||
if component is None:
|
if component is None:
|
||||||
raise RuntimeError(f"Component {componentSpec} not found")
|
raise RuntimeError(f"Component {componentSpec} not found")
|
||||||
|
|
||||||
product = build(target, registry, component)[0]
|
product = build(scope, component)[0]
|
||||||
|
|
||||||
os.environ["CK_TARGET"] = target.id
|
os.environ["CK_TARGET"] = product.target.id
|
||||||
|
os.environ["CK_BUILDDIR"] = product.target.builddir
|
||||||
os.environ["CK_COMPONENT"] = product.component.id
|
os.environ["CK_COMPONENT"] = product.component.id
|
||||||
os.environ["CK_BUILDDIR"] = target.builddir
|
|
||||||
|
|
||||||
shell.exec(*(["lldb", "-o", "run"] if debug else []), str(product.path), *args.args)
|
shell.exec(*(["lldb", "-o", "run"] if debug else []), str(product.path), *args.args)
|
||||||
|
|
||||||
|
|
||||||
@cli.command("t", "test", "Run all test targets")
|
@cli.command("t", "build/test", "Run all test targets")
|
||||||
def testCmd(args: cli.Args):
|
def buildTestCmd(args: cli.Args):
|
||||||
# This is just a wrapper around the `run` command that try
|
# This is just a wrapper around the `run` command that try
|
||||||
# to run a special hook component named __tests__.
|
# to run a special hook component named __tests__.
|
||||||
args.args.insert(0, "__tests__")
|
args.args.insert(0, "__tests__")
|
||||||
runCmd(args)
|
buildRunCmd(args)
|
||||||
|
|
||||||
|
|
||||||
@cli.command("d", "debug", "Debug a component")
|
@cli.command("d", "build/debug", "Debug a component")
|
||||||
def debugCmd(args: cli.Args):
|
def buildDebugCmd(args: cli.Args):
|
||||||
# This is just a wrapper around the `run` command that
|
# This is just a wrapper around the `run` command that
|
||||||
# always enable debug mode.
|
# always enable debug mode.
|
||||||
args.opts["debug"] = True
|
args.opts["debug"] = True
|
||||||
runCmd(args)
|
buildRunCmd(args)
|
||||||
|
|
||||||
|
|
||||||
@cli.command("c", "clean", "Clean build files")
|
@cli.command("c", "build/clean", "Clean build files")
|
||||||
def cleanCmd(args: cli.Args):
|
def buildCleanCmd(args: cli.Args):
|
||||||
model.Project.use(args)
|
model.Project.use(args)
|
||||||
shell.rmrf(const.BUILD_DIR)
|
shell.rmrf(const.BUILD_DIR)
|
||||||
|
|
||||||
|
|
||||||
@cli.command("n", "nuke", "Clean all build files and caches")
|
@cli.command("n", "build/nuke", "Clean all build files and caches")
|
||||||
def nukeCmd(args: cli.Args):
|
def buildNukeCmd(args: cli.Args):
|
||||||
model.Project.use(args)
|
model.Project.use(args)
|
||||||
shell.rmrf(const.PROJECT_CK_DIR)
|
shell.rmrf(const.PROJECT_CK_DIR)
|
||||||
|
|
|
@ -80,8 +80,10 @@ class Command:
|
||||||
isPlugin: bool
|
isPlugin: bool
|
||||||
callback: Callback
|
callback: Callback
|
||||||
|
|
||||||
|
subcommands: dict[str, "Command"] = dt.field(default_factory=dict)
|
||||||
|
|
||||||
commands: list[Command] = []
|
|
||||||
|
commands: dict[str, Command] = {}
|
||||||
|
|
||||||
|
|
||||||
def command(shortName: Optional[str], longName: str, helpText: str):
|
def command(shortName: Optional[str], longName: str, helpText: str):
|
||||||
|
@ -90,15 +92,18 @@ def command(shortName: Optional[str], longName: str, helpText: str):
|
||||||
|
|
||||||
def wrap(fn: Callable[[Args], None]):
|
def wrap(fn: Callable[[Args], None]):
|
||||||
_logger.debug(f"Registering command {longName}")
|
_logger.debug(f"Registering command {longName}")
|
||||||
commands.append(
|
path = longName.split("/")
|
||||||
Command(
|
parent = commands
|
||||||
shortName,
|
for p in path[:-1]:
|
||||||
longName,
|
parent = parent[p].subcommands
|
||||||
helpText,
|
parent[path[-1]] = Command(
|
||||||
Path(calframe[1].filename).parent != Path(__file__).parent,
|
shortName,
|
||||||
fn,
|
path[-1],
|
||||||
)
|
helpText,
|
||||||
|
Path(calframe[1].filename).parent != Path(__file__).parent,
|
||||||
|
fn,
|
||||||
)
|
)
|
||||||
|
|
||||||
return fn
|
return fn
|
||||||
|
|
||||||
return wrap
|
return wrap
|
||||||
|
@ -127,8 +132,8 @@ def helpCmd(args: Args):
|
||||||
|
|
||||||
print()
|
print()
|
||||||
vt100.title("Commands")
|
vt100.title("Commands")
|
||||||
for cmd in sorted(commands, key=lambda c: c.longName):
|
for cmd in sorted(commands.values(), key=lambda c: c.longName):
|
||||||
if cmd.longName.startswith("_"):
|
if cmd.longName.startswith("_") or len(cmd.subcommands) > 0:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
pluginText = ""
|
pluginText = ""
|
||||||
|
@ -139,6 +144,21 @@ def helpCmd(args: Args):
|
||||||
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}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for cmd in sorted(commands.values(), key=lambda c: c.longName):
|
||||||
|
if cmd.longName.startswith("_") or len(cmd.subcommands) == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
print()
|
||||||
|
vt100.title(f"{cmd.longName.capitalize()} - {cmd.helpText}")
|
||||||
|
for subcmd in sorted(cmd.subcommands.values(), key=lambda c: c.longName):
|
||||||
|
pluginText = ""
|
||||||
|
if subcmd.isPlugin:
|
||||||
|
pluginText = f"{vt100.CYAN}(plugin){vt100.RESET}"
|
||||||
|
|
||||||
|
print(
|
||||||
|
f" {vt100.GREEN}{subcmd.shortName or ' '}{vt100.RESET} {subcmd.longName} - {subcmd.helpText} {pluginText}"
|
||||||
|
)
|
||||||
|
|
||||||
print()
|
print()
|
||||||
vt100.title("Logging")
|
vt100.title("Logging")
|
||||||
print(" Logs are stored in:")
|
print(" Logs are stored in:")
|
||||||
|
@ -151,15 +171,19 @@ def versionCmd(args: Args):
|
||||||
print(f"CuteKit v{const.VERSION_STR}")
|
print(f"CuteKit v{const.VERSION_STR}")
|
||||||
|
|
||||||
|
|
||||||
def exec(args: Args):
|
def exec(args: Args, cmds=commands):
|
||||||
cmd = args.consumeArg()
|
cmd = args.consumeArg()
|
||||||
|
|
||||||
if cmd is None:
|
if cmd is None:
|
||||||
raise RuntimeError("No command specified")
|
raise RuntimeError("No command specified")
|
||||||
|
|
||||||
for c in commands:
|
for c in cmds.values():
|
||||||
if c.shortName == cmd or c.longName == cmd:
|
if c.shortName == cmd or c.longName == cmd:
|
||||||
c.callback(args)
|
if len(c.subcommands) > 0:
|
||||||
return
|
exec(args, c.subcommands)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
c.callback(args)
|
||||||
|
return
|
||||||
|
|
||||||
raise RuntimeError(f"Unknown command {cmd}")
|
raise RuntimeError(f"Unknown command {cmd}")
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
VERSION = (0, 6, 0, "dev")
|
VERSION = (0, 7, 0, "dev")
|
||||||
VERSION_STR = f"{VERSION[0]}.{VERSION[1]}.{VERSION[2]}{'-' + VERSION[3] if len(VERSION) >= 4 else ''}"
|
VERSION_STR = f"{VERSION[0]}.{VERSION[1]}.{VERSION[2]}{'-' + VERSION[3] if len(VERSION) >= 4 else ''}"
|
||||||
MODULE_DIR = os.path.dirname(os.path.realpath(__file__))
|
MODULE_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||||
ARGV0 = os.path.basename(sys.argv[0])
|
ARGV0 = os.path.basename(sys.argv[0])
|
||||||
|
|
20
cutekit/entrypoint.sh
Executable file
20
cutekit/entrypoint.sh
Executable file
|
@ -0,0 +1,20 @@
|
||||||
|
#!/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
export PY=python3.11
|
||||||
|
|
||||||
|
if [ ! -d "/tools/venv" ]; then
|
||||||
|
echo "Creating virtual environment..."
|
||||||
|
|
||||||
|
$PY -m venv /tools/venv
|
||||||
|
source /tools/venv/bin/activate
|
||||||
|
$PY -m ensurepip
|
||||||
|
$PY -m pip install -r /tools/cutekit/requirements.txt
|
||||||
|
echo "Virtual environment created."
|
||||||
|
else
|
||||||
|
source /tools/venv/bin/activate
|
||||||
|
fi
|
||||||
|
|
||||||
|
export PYTHONPATH=/tools
|
||||||
|
$PY -m cutekit "$@"
|
|
@ -36,7 +36,7 @@ def view(
|
||||||
if (
|
if (
|
||||||
scopeInstance is not None
|
scopeInstance is not None
|
||||||
and component.id != scope
|
and component.id != scope
|
||||||
and component.id not in scopeInstance.resolved[target.id].resolved
|
and component.id not in scopeInstance.resolved[target.id].required
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
|
@ -173,14 +173,19 @@ class Project(Manifest):
|
||||||
return _project
|
return _project
|
||||||
|
|
||||||
|
|
||||||
@cli.command("i", "install", "Install required external packages")
|
@cli.command("m", "model", "Manage the model")
|
||||||
def installCmd(args: cli.Args):
|
def modelCmd(args: cli.Args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command("i", "model/install", "Install required external packages")
|
||||||
|
def modelInstallCmd(args: cli.Args):
|
||||||
project = Project.use(args)
|
project = Project.use(args)
|
||||||
Project.fetchs(project.extern)
|
Project.fetchs(project.extern)
|
||||||
|
|
||||||
|
|
||||||
@cli.command("I", "init", "Initialize a new project")
|
@cli.command("I", "model/init", "Initialize a new project")
|
||||||
def initCmd(args: cli.Args):
|
def modelInitCmd(args: cli.Args):
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
repo = args.consumeOpt("repo", const.DEFAULT_REPO_TEMPLATES)
|
repo = args.consumeOpt("repo", const.DEFAULT_REPO_TEMPLATES)
|
||||||
|
@ -258,9 +263,15 @@ class Target(Manifest):
|
||||||
tools: Tools = dt.field(default_factory=dict)
|
tools: Tools = dt.field(default_factory=dict)
|
||||||
routing: dict[str, str] = dt.field(default_factory=dict)
|
routing: dict[str, str] = dt.field(default_factory=dict)
|
||||||
|
|
||||||
|
_hashid = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hashid(self) -> str:
|
def hashid(self) -> str:
|
||||||
return utils.hash((self.props, [v.to_dict() for k, v in self.tools.items()]))
|
if self._hashid is None:
|
||||||
|
self._hashid = utils.hash(
|
||||||
|
(self.props, [v.to_dict() for k, v in self.tools.items()])
|
||||||
|
)
|
||||||
|
return self._hashid
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def builddir(self) -> str:
|
def builddir(self) -> str:
|
||||||
|
@ -289,7 +300,8 @@ class Target(Manifest):
|
||||||
@dt.dataclass
|
@dt.dataclass
|
||||||
class Resolved:
|
class Resolved:
|
||||||
reason: Optional[str] = None
|
reason: Optional[str] = None
|
||||||
resolved: list[str] = dt.field(default_factory=list)
|
required: list[str] = dt.field(default_factory=list)
|
||||||
|
injected: list[str] = dt.field(default_factory=list)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enabled(self) -> bool:
|
def enabled(self) -> bool:
|
||||||
|
@ -436,11 +448,11 @@ class Resolver:
|
||||||
self._cache[keep] = Resolved(reason=reqResolved.reason)
|
self._cache[keep] = Resolved(reason=reqResolved.reason)
|
||||||
return self._cache[keep]
|
return self._cache[keep]
|
||||||
|
|
||||||
result.extend(reqResolved.resolved)
|
result.extend(reqResolved.required)
|
||||||
|
|
||||||
stack.pop()
|
stack.pop()
|
||||||
result.insert(0, keep)
|
result.insert(0, keep)
|
||||||
self._cache[keep] = Resolved(resolved=utils.uniq(result))
|
self._cache[keep] = Resolved(required=utils.uniq(result))
|
||||||
return self._cache[keep]
|
return self._cache[keep]
|
||||||
|
|
||||||
|
|
||||||
|
@ -570,6 +582,13 @@ class Registry(DataClassJsonMixin):
|
||||||
target.props |= props
|
target.props |= props
|
||||||
resolver = Resolver(r, target)
|
resolver = Resolver(r, target)
|
||||||
|
|
||||||
|
# Resolve all components
|
||||||
|
for c in r.iter(Component):
|
||||||
|
resolved = resolver.resolve(c.id)
|
||||||
|
if resolved.reason:
|
||||||
|
_logger.info(f"Component '{c.id}' disabled: {resolved.reason}")
|
||||||
|
c.resolved[target.id] = resolved
|
||||||
|
|
||||||
# Apply injects
|
# Apply injects
|
||||||
for c in r.iter(Component):
|
for c in r.iter(Component):
|
||||||
if c.isEnabled(target)[0]:
|
if c.isEnabled(target)[0]:
|
||||||
|
@ -577,14 +596,7 @@ class Registry(DataClassJsonMixin):
|
||||||
victim = r.lookup(inject, Component)
|
victim = r.lookup(inject, Component)
|
||||||
if not victim:
|
if not victim:
|
||||||
raise RuntimeError(f"Cannot find component '{inject}'")
|
raise RuntimeError(f"Cannot find component '{inject}'")
|
||||||
victim.requires += [c.id]
|
victim.resolved[target.id].injected.append(c.id)
|
||||||
|
|
||||||
# Resolve all components
|
|
||||||
for c in r.iter(Component):
|
|
||||||
resolved = resolver.resolve(c.id)
|
|
||||||
if resolved.reason:
|
|
||||||
_logger.info(f"Component '{c.id}' disabled: {resolved.reason}")
|
|
||||||
c.resolved[target.id] = resolved
|
|
||||||
|
|
||||||
# Resolve tooling
|
# Resolve tooling
|
||||||
tools: Tools = target.tools
|
tools: Tools = target.tools
|
||||||
|
@ -609,8 +621,8 @@ class Registry(DataClassJsonMixin):
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
@cli.command("l", "list", "List all components and targets")
|
@cli.command("l", "model/list", "List all components and targets")
|
||||||
def listCmd(args: cli.Args):
|
def modelListCmd(args: cli.Args):
|
||||||
registry = Registry.use(args)
|
registry = Registry.use(args)
|
||||||
|
|
||||||
components = list(registry.iter(Component))
|
components = list(registry.iter(Component))
|
||||||
|
|
201
cutekit/pods.py
Normal file
201
cutekit/pods.py
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
import sys
|
||||||
|
import docker # type: ignore
|
||||||
|
import os
|
||||||
|
import dataclasses as dt
|
||||||
|
|
||||||
|
from . import cli, model, shell, vt100
|
||||||
|
|
||||||
|
|
||||||
|
podPrefix = "CK__"
|
||||||
|
projectRoot = "/self"
|
||||||
|
toolingRoot = "/tools"
|
||||||
|
defaultPodName = f"{podPrefix}default"
|
||||||
|
defaultPodImage = "ubuntu"
|
||||||
|
|
||||||
|
|
||||||
|
@dt.dataclass
|
||||||
|
class Image:
|
||||||
|
id: str
|
||||||
|
image: str
|
||||||
|
init: list[str]
|
||||||
|
|
||||||
|
|
||||||
|
@dt.dataclass
|
||||||
|
class Pod:
|
||||||
|
name: str
|
||||||
|
image: Image
|
||||||
|
|
||||||
|
|
||||||
|
IMAGES: dict[str, Image] = {
|
||||||
|
"ubuntu": Image(
|
||||||
|
"ubuntu",
|
||||||
|
"ubuntu:jammy",
|
||||||
|
[
|
||||||
|
"apt-get update",
|
||||||
|
"apt-get install -y python3.11 python3.11-venv ninja-build",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
"debian": Image(
|
||||||
|
"debian",
|
||||||
|
"debian:bookworm",
|
||||||
|
[
|
||||||
|
"apt-get update",
|
||||||
|
"apt-get install -y python3 python3-pip python3-venv ninja-build",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
"alpine": Image(
|
||||||
|
"alpine",
|
||||||
|
"alpine:3.18",
|
||||||
|
[
|
||||||
|
"apk update",
|
||||||
|
"apk add python3 python3-dev py3-pip py3-venv build-base linux-headers ninja",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
"arch": Image(
|
||||||
|
"arch",
|
||||||
|
"archlinux:latest",
|
||||||
|
[
|
||||||
|
"pacman -Syu --noconfirm",
|
||||||
|
"pacman -S --noconfirm python python-pip python-virtualenv ninja",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
"fedora": Image(
|
||||||
|
"fedora",
|
||||||
|
"fedora:39",
|
||||||
|
[
|
||||||
|
"dnf update -y",
|
||||||
|
"dnf install -y python3 python3-pip python3-venv ninja-build",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def reincarnate(args: cli.Args):
|
||||||
|
"""
|
||||||
|
Reincarnate cutekit within a docker container, this is
|
||||||
|
useful for cross-compiling
|
||||||
|
"""
|
||||||
|
pod = args.consumeOpt("pod", False)
|
||||||
|
if not pod:
|
||||||
|
return
|
||||||
|
if isinstance(pod, str):
|
||||||
|
pod = pod.strip()
|
||||||
|
pod = podPrefix + pod
|
||||||
|
if pod is True:
|
||||||
|
pod = defaultPodName
|
||||||
|
assert isinstance(pod, str)
|
||||||
|
model.Project.ensure()
|
||||||
|
print(f"Reincarnating into pod '{pod[len(podPrefix) :]}'...")
|
||||||
|
try:
|
||||||
|
shell.exec(
|
||||||
|
"docker",
|
||||||
|
"exec",
|
||||||
|
"-w",
|
||||||
|
projectRoot,
|
||||||
|
"-it",
|
||||||
|
pod,
|
||||||
|
"/tools/cutekit/entrypoint.sh",
|
||||||
|
*args.args,
|
||||||
|
)
|
||||||
|
sys.exit(0)
|
||||||
|
except Exception:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command("p", "pod", "Manage pods")
|
||||||
|
def podCmd(args: cli.Args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command("c", "pod/create", "Create a new pod")
|
||||||
|
def podCreateCmd(args: cli.Args):
|
||||||
|
"""
|
||||||
|
Create a new development pod with cutekit installed and the current
|
||||||
|
project mounted at /self
|
||||||
|
"""
|
||||||
|
project = model.Project.ensure()
|
||||||
|
|
||||||
|
name = str(args.consumeOpt("name", defaultPodName))
|
||||||
|
if not name.startswith(podPrefix):
|
||||||
|
name = f"{podPrefix}{name}"
|
||||||
|
image = IMAGES[str(args.consumeOpt("image", defaultPodImage))]
|
||||||
|
|
||||||
|
client = docker.from_env()
|
||||||
|
try:
|
||||||
|
client.containers.get(name)
|
||||||
|
raise RuntimeError(f"Pod '{name[len(podPrefix):]}' already exists")
|
||||||
|
except docker.errors.NotFound:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print(f"Staring pod '{name[len(podPrefix) :]}'...")
|
||||||
|
|
||||||
|
container = client.containers.run(
|
||||||
|
image.image,
|
||||||
|
"sleep infinity",
|
||||||
|
name=name,
|
||||||
|
volumes={
|
||||||
|
os.path.abspath(os.path.dirname(__file__)): {
|
||||||
|
"bind": toolingRoot + "/cutekit",
|
||||||
|
"mode": "ro",
|
||||||
|
},
|
||||||
|
os.path.abspath(project.dirname()): {"bind": projectRoot, "mode": "rw"},
|
||||||
|
},
|
||||||
|
detach=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Initializing pod '{name[len(podPrefix) :]}'...")
|
||||||
|
for cmd in image.init:
|
||||||
|
print(vt100.p(cmd))
|
||||||
|
exitCode, ouput = container.exec_run(f"/bin/bash -c '{cmd}'", demux=True)
|
||||||
|
if exitCode != 0:
|
||||||
|
raise Exception(f"Failed to initialize pod with command '{cmd}'")
|
||||||
|
|
||||||
|
print(f"Created pod '{name[len(podPrefix) :]}' from image '{image.image}'")
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command("k", "pod/kill", "Stop and remove a pod")
|
||||||
|
def podKillCmd(args: cli.Args):
|
||||||
|
client = docker.from_env()
|
||||||
|
name = str(args.consumeOpt("name", defaultPodName))
|
||||||
|
if not name.startswith(podPrefix):
|
||||||
|
name = f"{podPrefix}{name}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
container = client.containers.get(name)
|
||||||
|
container.stop()
|
||||||
|
container.remove()
|
||||||
|
print(f"Pod '{name[len(podPrefix) :]}' killed")
|
||||||
|
except docker.errors.NotFound:
|
||||||
|
raise RuntimeError(f"Pod '{name[len(podPrefix):]}' does not exist")
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command("s", "pod/shell", "Open a shell in a pod")
|
||||||
|
def podShellCmd(args: cli.Args):
|
||||||
|
args.args.insert(0, "/bin/bash")
|
||||||
|
podExecCmd(args)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command("l", "pod/list", "List all pods")
|
||||||
|
def podListCmd(args: cli.Args):
|
||||||
|
client = docker.from_env()
|
||||||
|
hasPods = False
|
||||||
|
for container in client.containers.list(all=True):
|
||||||
|
if not container.name.startswith(podPrefix):
|
||||||
|
continue
|
||||||
|
print(container.name[len(podPrefix) :], container.status)
|
||||||
|
hasPods = True
|
||||||
|
|
||||||
|
if not hasPods:
|
||||||
|
print(vt100.p("(No pod found)"))
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command("e", "pod/exec", "Execute a command in a pod")
|
||||||
|
def podExecCmd(args: cli.Args):
|
||||||
|
name = str(args.consumeOpt("name", defaultPodName))
|
||||||
|
if not name.startswith(podPrefix):
|
||||||
|
name = f"{podPrefix}{name}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
shell.exec("docker", "exec", "-it", name, *args.args)
|
||||||
|
except Exception:
|
||||||
|
raise RuntimeError(f"Pod '{name[len(podPrefix):]}' does not exist")
|
|
@ -1,3 +1,4 @@
|
||||||
requests ~= 2.31.0
|
requests ~= 2.31.0
|
||||||
graphviz ~= 0.20.1
|
graphviz ~= 0.20.1
|
||||||
dataclasses-json ~= 0.6.2
|
dataclasses-json ~= 0.6.2
|
||||||
|
docker ~= 6.1.3
|
|
@ -1,52 +1,39 @@
|
||||||
|
import dataclasses as dt
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
@dt.dataclass
|
||||||
class Rule:
|
class Rule:
|
||||||
id: str
|
id: str
|
||||||
fileIn: list[str]
|
fileIn: list[str]
|
||||||
fileOut: list[str]
|
fileOut: str
|
||||||
rule: str
|
rule: str
|
||||||
args: list[str]
|
args: list[str] = dt.field(default_factory=list)
|
||||||
deps: Optional[str] = None
|
deps: list[str] = dt.field(default_factory=list)
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
id: str,
|
|
||||||
fileIn: list[str],
|
|
||||||
fileOut: list[str],
|
|
||||||
rule: str,
|
|
||||||
args: list[str] = [],
|
|
||||||
deps: Optional[str] = None,
|
|
||||||
):
|
|
||||||
self.id = id
|
|
||||||
self.fileIn = fileIn
|
|
||||||
self.fileOut = fileOut
|
|
||||||
self.rule = rule
|
|
||||||
self.args = args
|
|
||||||
self.deps = deps
|
|
||||||
|
|
||||||
|
|
||||||
rules: dict[str, Rule] = {
|
rules: dict[str, Rule] = {
|
||||||
"cp": Rule("cp", ["*"], ["*"], "$in $out"),
|
"cp": Rule("cp", ["*"], "*", "$in $out"),
|
||||||
"cc": Rule(
|
"cc": Rule(
|
||||||
"cc",
|
"cc",
|
||||||
["*.c"],
|
["*.c"],
|
||||||
["*.o"],
|
"*.o",
|
||||||
"-c -o $out $in -MD -MF $out.d $flags $cincs $cdefs",
|
"-c -o $out $in -MD -MF $out.d $flags $cincs $cdefs",
|
||||||
["-std=gnu2x", "-Wall", "-Wextra", "-Werror"],
|
["-std=gnu2x", "-Wall", "-Wextra", "-Werror"],
|
||||||
"$out.d",
|
["$out.d"],
|
||||||
),
|
),
|
||||||
"cxx": Rule(
|
"cxx": Rule(
|
||||||
"cxx",
|
"cxx",
|
||||||
["*.cpp", "*.cc", "*.cxx"],
|
["*.cpp", "*.cc", "*.cxx"],
|
||||||
["*.o"],
|
"*.o",
|
||||||
"-c -o $out $in -MD -MF $out.d $flags $cincs $cdefs",
|
"-c -o $out $in -MD -MF $out.d $flags $cincs $cdefs",
|
||||||
["-std=gnu++2b", "-Wall", "-Wextra", "-Werror", "-fno-exceptions", "-fno-rtti"],
|
["-std=gnu++2b", "-Wall", "-Wextra", "-Werror", "-fno-exceptions", "-fno-rtti"],
|
||||||
"$out.d",
|
["$out.d"],
|
||||||
),
|
),
|
||||||
"as": Rule("as", ["*.s", "*.asm", "*.S"], ["*.o"], "-o $out $in $flags"),
|
"as": Rule("as", ["*.s", "*.asm", "*.S"], "*.o", "-o $out $in $flags"),
|
||||||
"ar": Rule("ar", ["*.o"], ["*.a"], "$flags $out $in"),
|
"ar": Rule("ar", ["*.o"], "*.a", "$flags $out $in"),
|
||||||
"ld": Rule("ld", ["*.o", "*.a"], ["*.out"], "-o $out $in $flags"),
|
"ld": Rule("ld", ["*.o", "*.a"], "*.out", "-o $out $in $flags"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ def sha256sum(path: str) -> str:
|
||||||
def find(
|
def find(
|
||||||
path: str | list[str], wildcards: list[str] = [], recusive: bool = True
|
path: str | list[str], wildcards: list[str] = [], recusive: bool = True
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
_logger.info(f"Looking for files in {path} matching {wildcards}")
|
_logger.debug(f"Looking for files in {path} matching {wildcards}")
|
||||||
|
|
||||||
result: list[str] = []
|
result: list[str] = []
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ def find(
|
||||||
|
|
||||||
|
|
||||||
def mkdir(path: str) -> str:
|
def mkdir(path: str) -> str:
|
||||||
_logger.info(f"Creating directory {path}")
|
_logger.debug(f"Creating directory {path}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.makedirs(path)
|
os.makedirs(path)
|
||||||
|
@ -99,7 +99,7 @@ def mkdir(path: str) -> str:
|
||||||
|
|
||||||
|
|
||||||
def rmrf(path: str) -> bool:
|
def rmrf(path: str) -> bool:
|
||||||
_logger.info(f"Removing directory {path}")
|
_logger.debug(f"Removing directory {path}")
|
||||||
|
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
return False
|
return False
|
||||||
|
@ -118,7 +118,7 @@ def wget(url: str, path: Optional[str] = None) -> str:
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
return path
|
return path
|
||||||
|
|
||||||
_logger.info(f"Downloading {url} to {path}")
|
_logger.debug(f"Downloading {url} to {path}")
|
||||||
|
|
||||||
r = requests.get(url, stream=True)
|
r = requests.get(url, stream=True)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
@ -132,7 +132,7 @@ def wget(url: str, path: Optional[str] = None) -> str:
|
||||||
|
|
||||||
|
|
||||||
def exec(*args: str, quiet: bool = False) -> bool:
|
def exec(*args: str, quiet: bool = False) -> bool:
|
||||||
_logger.info(f"Executing {args}")
|
_logger.debug(f"Executing {args}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
|
@ -142,10 +142,10 @@ def exec(*args: str, quiet: bool = False) -> bool:
|
||||||
)
|
)
|
||||||
|
|
||||||
if proc.stdout:
|
if proc.stdout:
|
||||||
_logger.info(proc.stdout.decode("utf-8"))
|
_logger.debug(proc.stdout.decode("utf-8"))
|
||||||
|
|
||||||
if proc.stderr:
|
if proc.stderr:
|
||||||
_logger.error(proc.stderr.decode("utf-8"))
|
_logger.debug(proc.stderr.decode("utf-8"))
|
||||||
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
raise RuntimeError(f"{args[0]}: Command not found")
|
raise RuntimeError(f"{args[0]}: Command not found")
|
||||||
|
@ -163,7 +163,7 @@ def exec(*args: str, quiet: bool = False) -> bool:
|
||||||
|
|
||||||
|
|
||||||
def popen(*args: str) -> str:
|
def popen(*args: str) -> str:
|
||||||
_logger.info(f"Executing {args}")
|
_logger.debug(f"Executing {args}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
proc = subprocess.run(args, stdout=subprocess.PIPE, stderr=sys.stderr)
|
proc = subprocess.run(args, stdout=subprocess.PIPE, stderr=sys.stderr)
|
||||||
|
@ -180,7 +180,7 @@ def popen(*args: str) -> str:
|
||||||
|
|
||||||
|
|
||||||
def readdir(path: str) -> list[str]:
|
def readdir(path: str) -> list[str]:
|
||||||
_logger.info(f"Reading directory {path}")
|
_logger.debug(f"Reading directory {path}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return os.listdir(path)
|
return os.listdir(path)
|
||||||
|
@ -189,19 +189,19 @@ def readdir(path: str) -> list[str]:
|
||||||
|
|
||||||
|
|
||||||
def cp(src: str, dst: str):
|
def cp(src: str, dst: str):
|
||||||
_logger.info(f"Copying {src} to {dst}")
|
_logger.debug(f"Copying {src} to {dst}")
|
||||||
|
|
||||||
shutil.copy(src, dst)
|
shutil.copy(src, dst)
|
||||||
|
|
||||||
|
|
||||||
def mv(src: str, dst: str):
|
def mv(src: str, dst: str):
|
||||||
_logger.info(f"Moving {src} to {dst}")
|
_logger.debug(f"Moving {src} to {dst}")
|
||||||
|
|
||||||
shutil.move(src, dst)
|
shutil.move(src, dst)
|
||||||
|
|
||||||
|
|
||||||
def cpTree(src: str, dst: str):
|
def cpTree(src: str, dst: str):
|
||||||
_logger.info(f"Copying {src} to {dst}")
|
_logger.debug(f"Copying {src} to {dst}")
|
||||||
|
|
||||||
shutil.copytree(src, dst, dirs_exist_ok=True)
|
shutil.copytree(src, dst, dirs_exist_ok=True)
|
||||||
|
|
||||||
|
@ -241,10 +241,9 @@ def latest(cmd: str) -> str:
|
||||||
if cmd in LATEST_CACHE:
|
if cmd in LATEST_CACHE:
|
||||||
return LATEST_CACHE[cmd]
|
return LATEST_CACHE[cmd]
|
||||||
|
|
||||||
_logger.info(f"Finding latest version of {cmd}")
|
_logger.debug(f"Finding latest version of {cmd}")
|
||||||
|
|
||||||
regex: re.Pattern[str]
|
regex: re.Pattern[str]
|
||||||
|
|
||||||
if platform.system() == "Windows":
|
if platform.system() == "Windows":
|
||||||
regex = re.compile(r"^" + re.escape(cmd) + r"(-.[0-9]+)?(\.exe)?$")
|
regex = re.compile(r"^" + re.escape(cmd) + r"(-.[0-9]+)?(\.exe)?$")
|
||||||
else:
|
else:
|
||||||
|
@ -263,7 +262,7 @@ def latest(cmd: str) -> str:
|
||||||
versions.sort()
|
versions.sort()
|
||||||
chosen = versions[-1]
|
chosen = versions[-1]
|
||||||
|
|
||||||
_logger.info(f"Chosen {chosen} as latest version of {cmd}")
|
_logger.debug(f"Chosen {chosen} as latest version of {cmd}")
|
||||||
|
|
||||||
LATEST_CACHE[cmd] = chosen
|
LATEST_CACHE[cmd] = chosen
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ packages = ["cutekit"]
|
||||||
|
|
||||||
[tool.setuptools.dynamic]
|
[tool.setuptools.dynamic]
|
||||||
version = { attr = "cutekit.const.VERSION" }
|
version = { attr = "cutekit.const.VERSION" }
|
||||||
dependencies = { file = ["requirements.txt"] }
|
dependencies = { file = ["cutekit/requirements.txt"] }
|
||||||
|
|
||||||
[tool.setuptools.package-data]
|
[tool.setuptools.package-data]
|
||||||
"cutekit" = ["py.typed"]
|
"cutekit" = ["py.typed", "requirements.txt", "pods-entry.sh"]
|
||||||
|
|
|
@ -10,7 +10,7 @@ def test_direct_deps():
|
||||||
|
|
||||||
resolved = res.resolve("myapp")
|
resolved = res.resolve("myapp")
|
||||||
assert resolved.reason is None
|
assert resolved.reason is None
|
||||||
assert resolved.resolved == ["myapp", "mylib"]
|
assert resolved.required == ["myapp", "mylib"]
|
||||||
|
|
||||||
|
|
||||||
def test_indirect_deps():
|
def test_indirect_deps():
|
||||||
|
@ -20,7 +20,7 @@ def test_indirect_deps():
|
||||||
r._append(model.Component("myimpl", provides=["myembed"]))
|
r._append(model.Component("myimpl", provides=["myembed"]))
|
||||||
t = model.Target("host")
|
t = model.Target("host")
|
||||||
res = model.Resolver(r, t)
|
res = model.Resolver(r, t)
|
||||||
assert res.resolve("myapp").resolved == ["myapp", "mylib", "myimpl"]
|
assert res.resolve("myapp").required == ["myapp", "mylib", "myimpl"]
|
||||||
|
|
||||||
|
|
||||||
def test_deps_routing():
|
def test_deps_routing():
|
||||||
|
@ -31,11 +31,11 @@ def test_deps_routing():
|
||||||
r._append(model.Component("myimplB", provides=["myembed"]))
|
r._append(model.Component("myimplB", provides=["myembed"]))
|
||||||
t = model.Target("host", routing={"myembed": "myimplB"})
|
t = model.Target("host", routing={"myembed": "myimplB"})
|
||||||
res = model.Resolver(r, t)
|
res = model.Resolver(r, t)
|
||||||
assert res.resolve("myapp").resolved == ["myapp", "mylib", "myimplB"]
|
assert res.resolve("myapp").required == ["myapp", "mylib", "myimplB"]
|
||||||
|
|
||||||
t = model.Target("host", routing={"myembed": "myimplA"})
|
t = model.Target("host", routing={"myembed": "myimplA"})
|
||||||
res = model.Resolver(r, t)
|
res = model.Resolver(r, t)
|
||||||
assert res.resolve("myapp").resolved == ["myapp", "mylib", "myimplA"]
|
assert res.resolve("myapp").required == ["myapp", "mylib", "myimplA"]
|
||||||
|
|
||||||
t = model.Target("host", routing={"myembed": "myimplC"})
|
t = model.Target("host", routing={"myembed": "myimplC"})
|
||||||
res = model.Resolver(r, t)
|
res = model.Resolver(r, t)
|
||||||
|
@ -54,11 +54,11 @@ def test_deps_routing_with_props():
|
||||||
)
|
)
|
||||||
t = model.Target("host", routing={"myembed": "myimplB"}, props={"myprop": "b"})
|
t = model.Target("host", routing={"myembed": "myimplB"}, props={"myprop": "b"})
|
||||||
res = model.Resolver(r, t)
|
res = model.Resolver(r, t)
|
||||||
assert res.resolve("myapp").resolved == ["myapp", "mylib", "myimplB"]
|
assert res.resolve("myapp").required == ["myapp", "mylib", "myimplB"]
|
||||||
|
|
||||||
t = model.Target("host", routing={"myembed": "myimplA"}, props={"myprop": "a"})
|
t = model.Target("host", routing={"myembed": "myimplA"}, props={"myprop": "a"})
|
||||||
res = model.Resolver(r, t)
|
res = model.Resolver(r, t)
|
||||||
assert res.resolve("myapp").resolved == ["myapp", "mylib", "myimplA"]
|
assert res.resolve("myapp").required == ["myapp", "mylib", "myimplA"]
|
||||||
|
|
||||||
t = model.Target("host", routing={"myembed": "myimplC"}, props={"myprop": "c"})
|
t = model.Target("host", routing={"myembed": "myimplC"}, props={"myprop": "c"})
|
||||||
res = model.Resolver(r, t)
|
res = model.Resolver(r, t)
|
||||||
|
@ -79,11 +79,11 @@ def test_deps_routing_with_props_and_requires():
|
||||||
)
|
)
|
||||||
t = model.Target("host", routing={"myembed": "myimplB"}, props={"myprop": "b"})
|
t = model.Target("host", routing={"myembed": "myimplB"}, props={"myprop": "b"})
|
||||||
res = model.Resolver(r, t)
|
res = model.Resolver(r, t)
|
||||||
assert res.resolve("myapp").resolved == ["myapp", "mylib", "myimplB"]
|
assert res.resolve("myapp").required == ["myapp", "mylib", "myimplB"]
|
||||||
|
|
||||||
t = model.Target("host", routing={"myembed": "myimplA"}, props={"myprop": "a"})
|
t = model.Target("host", routing={"myembed": "myimplA"}, props={"myprop": "a"})
|
||||||
res = model.Resolver(r, t)
|
res = model.Resolver(r, t)
|
||||||
assert res.resolve("myapp").resolved == ["myapp", "mylib", "myimplA"]
|
assert res.resolve("myapp").required == ["myapp", "mylib", "myimplA"]
|
||||||
|
|
||||||
t = model.Target("host", routing={"myembed": "myimplC"}, props={"myprop": "c"})
|
t = model.Target("host", routing={"myembed": "myimplC"}, props={"myprop": "c"})
|
||||||
res = model.Resolver(r, t)
|
res = model.Resolver(r, t)
|
||||||
|
|
Loading…
Reference in a new issue