Added support for nested commands and running cutekit in containers.
This commit is contained in:
		
							parent
							
								
									8a9c4689e9
								
							
						
					
					
						commit
						72c982ab7b
					
				
					 13 changed files with 506 additions and 197 deletions
				
			
		|  | @ -9,6 +9,7 @@ from . import ( | |||
|     graph,  # noqa: F401 this is imported for side effects | ||||
|     model, | ||||
|     plugins, | ||||
|     pods,  # noqa: F401 this is imported for side effects | ||||
|     vt100, | ||||
| ) | ||||
| 
 | ||||
|  | @ -55,10 +56,13 @@ def setupLogger(verbose: bool): | |||
| 
 | ||||
| def main() -> int: | ||||
|     try: | ||||
|         a = cli.parse(sys.argv[1:]) | ||||
|         setupLogger(a.consumeOpt("verbose", False) is True) | ||||
|         plugins.loadAll() | ||||
|         cli.exec(a) | ||||
|         args = cli.parse(sys.argv[1:]) | ||||
|         setupLogger(args.consumeOpt("verbose", False) is True) | ||||
|         safemode = args.consumeOpt("safemode", False) is True | ||||
|         if not safemode: | ||||
|             plugins.loadAll() | ||||
|         pods.reincarnate(args) | ||||
|         cli.exec(args) | ||||
|         print() | ||||
|         return 0 | ||||
|     except RuntimeError as e: | ||||
|  |  | |||
|  | @ -3,32 +3,89 @@ import logging | |||
| import dataclasses as dt | ||||
| 
 | ||||
| from pathlib import Path | ||||
| from typing import TextIO, Union | ||||
| from typing import Callable, TextIO, Union | ||||
| 
 | ||||
| from . import shell, rules, model, ninja, const, cli | ||||
| 
 | ||||
| _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() | ||||
| 
 | ||||
|     for c in registry.iterEnabled(target): | ||||
|     for c in scope.registry.iterEnabled(scope.target): | ||||
|         if "cpp-root-include" in c.props: | ||||
|             res.add(c.dirname()) | ||||
|         elif c.type == model.Kind.LIB: | ||||
|             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() | ||||
| 
 | ||||
|     def sanatize(s: str) -> str: | ||||
|         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 v: | ||||
|                 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)}_value={str(v)}") | ||||
| 
 | ||||
|     return res | ||||
|     return " ".join(res) | ||||
| 
 | ||||
| 
 | ||||
| def buildpath(target: model.Target, component: model.Component, path) -> Path: | ||||
|     return Path(target.builddir) / component.id / path | ||||
| def buildpath(scope: ComponentScope, path) -> Path: | ||||
|     return Path(scope.target.builddir) / scope.component.id / path | ||||
| 
 | ||||
| 
 | ||||
| # --- Compilation ------------------------------------------------------------ # | ||||
| 
 | ||||
| 
 | ||||
| def wilcard(component: model.Component, wildcards: list[str]) -> list[str]: | ||||
|     dirs = [component.dirname()] + list( | ||||
|         map(lambda d: os.path.join(component.dirname(), d), component.subdirs) | ||||
|     ) | ||||
|     return shell.find(dirs, list(wildcards), recusive=False) | ||||
| def subdirs(scope: ComponentScope) -> list[str]: | ||||
|     registry = scope.registry | ||||
|     target = scope.target | ||||
|     component = scope.component | ||||
|     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( | ||||
|     w: ninja.Writer, | ||||
|     target: model.Target, | ||||
|     component: model.Component, | ||||
|     rule: str, | ||||
|     srcs: list[str], | ||||
|     w: ninja.Writer, scope: ComponentScope, rule: str, srcs: list[str] | ||||
| ) -> list[str]: | ||||
|     res: list[str] = [] | ||||
|     for src in srcs: | ||||
|         rel = Path(src).relative_to(component.dirname()) | ||||
|         dest = buildpath(target, component, "obj") / rel.with_suffix(".o") | ||||
|         t = target.tools[rule] | ||||
|         rel = Path(src).relative_to(scope.component.dirname()) | ||||
|         dest = buildpath(scope, "obj") / rel.with_suffix(".o") | ||||
|         t = scope.target.tools[rule] | ||||
|         w.build(str(dest), rule, inputs=src, order_only=t.files) | ||||
|         res.append(str(dest)) | ||||
|     return res | ||||
|  | @ -79,13 +146,12 @@ def listRes(component: model.Component) -> list[str]: | |||
| 
 | ||||
| def compileRes( | ||||
|     w: ninja.Writer, | ||||
|     target: model.Target, | ||||
|     component: model.Component, | ||||
|     scope: ComponentScope, | ||||
| ) -> list[str]: | ||||
|     res: list[str] = [] | ||||
|     for r in listRes(component): | ||||
|         rel = Path(r).relative_to(component.subpath("res")) | ||||
|         dest = buildpath(target, component, "res") / rel | ||||
|     for r in listRes(scope.component): | ||||
|         rel = Path(r).relative_to(scope.component.subpath("res")) | ||||
|         dest = buildpath(scope, "res") / rel | ||||
|         w.build(str(dest), "cp", r) | ||||
|         res.append(str(dest)) | ||||
|     return res | ||||
|  | @ -94,50 +160,55 @@ def compileRes( | |||
| # --- Linking ---------------------------------------------------------------- # | ||||
| 
 | ||||
| 
 | ||||
| def outfile(target: model.Target, component: model.Component) -> str: | ||||
|     if component.type == model.Kind.LIB: | ||||
|         return str(buildpath(target, component, f"lib/{component.id}.a")) | ||||
| def outfile(scope: ComponentScope) -> str: | ||||
|     if scope.component.type == model.Kind.LIB: | ||||
|         return str(buildpath(scope, f"lib/{scope.component.id}.a")) | ||||
|     else: | ||||
|         return str(buildpath(target, component, f"bin/{component.id}.out")) | ||||
|         return str(buildpath(scope, f"bin/{scope.component.id}.out")) | ||||
| 
 | ||||
| 
 | ||||
| def collectLibs( | ||||
|     registry: model.Registry, target: model.Target, component: model.Component | ||||
|     scope: ComponentScope, | ||||
| ) -> list[str]: | ||||
|     res: list[str] = [] | ||||
|     for r in component.resolved[target.id].resolved: | ||||
|         req = registry.lookup(r, model.Component) | ||||
|     for r in scope.component.resolved[scope.target.id].required: | ||||
|         req = scope.registry.lookup(r, model.Component) | ||||
|         assert req is not None  # model.Resolver has already checked this | ||||
| 
 | ||||
|         if r == component.id: | ||||
|         if r == scope.component.id: | ||||
|             continue | ||||
|         if not req.type == model.Kind.LIB: | ||||
|             raise RuntimeError(f"Component {r} is not a library") | ||||
|         res.append(outfile(target, req)) | ||||
|         res.append(outfile(scope.openComponentScope(req))) | ||||
| 
 | ||||
|     return res | ||||
| 
 | ||||
| 
 | ||||
| def link( | ||||
|     w: ninja.Writer, | ||||
|     registry: model.Registry, | ||||
|     target: model.Target, | ||||
|     component: model.Component, | ||||
|     scope: ComponentScope, | ||||
| ) -> str: | ||||
|     w.newline() | ||||
|     out = outfile(target, component) | ||||
|     out = outfile(scope) | ||||
| 
 | ||||
|     objs = [] | ||||
|     objs += compile(w, target, component, "cc", wilcard(component, ["*.c"])) | ||||
|     objs += compile(w, scope, "cc", wilcard(scope, ["*.c"])) | ||||
|     objs += compile( | ||||
|         w, target, component, "cxx", wilcard(component, ["*.cpp", "*.cc", "*.cxx"]) | ||||
|         w, | ||||
|         scope, | ||||
|         "cxx", | ||||
|         wilcard(scope, ["*.cpp", "*.cc", "*.cxx"]), | ||||
|     ) | ||||
|     objs += compile( | ||||
|         w, target, component, "as", wilcard(component, ["*.s", "*.asm", "*.S"]) | ||||
|         w, | ||||
|         scope, | ||||
|         "as", | ||||
|         wilcard(scope, ["*.s", "*.asm", "*.S"]), | ||||
|     ) | ||||
| 
 | ||||
|     res = compileRes(w, target, component) | ||||
|     libs = collectLibs(registry, target, component) | ||||
|     if component.type == model.Kind.LIB: | ||||
|     res = compileRes(w, scope) | ||||
|     libs = collectLibs(scope) | ||||
|     if scope.component.type == model.Kind.LIB: | ||||
|         w.build(out, "ar", objs, implicit=res) | ||||
|     else: | ||||
|         w.build(out, "ld", objs + libs, implicit=res) | ||||
|  | @ -147,32 +218,30 @@ def link( | |||
| # --- 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] = [] | ||||
|     for c in registry.iterEnabled(target): | ||||
|         all.append(link(w, registry, target, c)) | ||||
|     for c in scope.registry.iterEnabled(scope.target): | ||||
|         all.append(link(w, scope.openComponentScope(c))) | ||||
|     w.build("all", "phony", all) | ||||
|     w.default("all") | ||||
|     return all | ||||
| 
 | ||||
| 
 | ||||
| def gen(out: TextIO, target: model.Target, registry: model.Registry): | ||||
| def gen(out: TextIO, scope: TargetScope): | ||||
|     w = ninja.Writer(out) | ||||
| 
 | ||||
|     w.comment("File generated by the build system, do not edit") | ||||
|     w.newline() | ||||
| 
 | ||||
|     w.variable("builddir", target.builddir) | ||||
|     w.variable("hashid", target.hashid) | ||||
|     w.separator("Variables") | ||||
|     for name, compute in _vars.items(): | ||||
|         w.variable(name, compute(scope)) | ||||
|     w.newline() | ||||
| 
 | ||||
|     w.separator("Tools") | ||||
| 
 | ||||
|     w.variable("cincs", " ".join(aggregateCincs(target, registry))) | ||||
|     w.variable("cdefs", " ".join(aggregateCdefs(target))) | ||||
|     w.newline() | ||||
| 
 | ||||
|     for i in target.tools: | ||||
|         tool = target.tools[i] | ||||
|     for i in scope.target.tools: | ||||
|         tool = scope.target.tools[i] | ||||
|         rule = rules.rules[i] | ||||
|         w.variable(i, tool.cmd) | ||||
|         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") | ||||
| 
 | ||||
|     all(w, registry, target) | ||||
| 
 | ||||
| 
 | ||||
| @dt.dataclass | ||||
| class Product: | ||||
|     path: Path | ||||
|     target: model.Target | ||||
|     component: model.Component | ||||
|     all(w, scope) | ||||
| 
 | ||||
| 
 | ||||
| def build( | ||||
|     target: model.Target, | ||||
|     registry: model.Registry, | ||||
|     scope: TargetScope, | ||||
|     components: Union[list[model.Component], model.Component, None] = None, | ||||
| ) -> list[Product]: | ||||
| ) -> list[ProductScope]: | ||||
|     all = False | ||||
|     shell.mkdir(target.builddir) | ||||
|     ninjaPath = os.path.join(target.builddir, "build.ninja") | ||||
|     shell.mkdir(scope.target.builddir) | ||||
|     ninjaPath = os.path.join(scope.target.builddir, "build.ninja") | ||||
| 
 | ||||
|     if not os.path.exists(ninjaPath): | ||||
|         with open(ninjaPath, "w") as f: | ||||
|             gen(f, target, registry) | ||||
|             gen(f, scope) | ||||
| 
 | ||||
|     if components is None: | ||||
|         all = True | ||||
|         components = list(registry.iterEnabled(target)) | ||||
|         components = list(scope.registry.iterEnabled(scope.target)) | ||||
| 
 | ||||
|     if isinstance(components, model.Component): | ||||
|         components = [components] | ||||
| 
 | ||||
|     products: list[Product] = [] | ||||
|     products: list[ProductScope] = [] | ||||
|     for c in components: | ||||
|         r = c.resolved[target.id] | ||||
|         s = scope.openComponentScope(c) | ||||
|         r = c.resolved[scope.target.id] | ||||
|         if not r.enabled: | ||||
|             raise RuntimeError(f"Component {c.id} is disabled: {r.reason}") | ||||
| 
 | ||||
|         products.append( | ||||
|             Product( | ||||
|                 path=Path(outfile(target, c)), | ||||
|                 target=target, | ||||
|                 component=c, | ||||
|             ) | ||||
|         ) | ||||
|         products.append(s.openProductScope(Path(outfile(scope.openComponentScope(c))))) | ||||
| 
 | ||||
|     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 | ||||
| 
 | ||||
|  | @ -239,60 +295,65 @@ def build( | |||
| # --- 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): | ||||
|     registry = model.Registry.use(args) | ||||
|     target = model.Target.use(args) | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| @cli.command("b", "build/build", "Build a component or all components") | ||||
| def buildBuildCmd(args: cli.Args): | ||||
|     scope = TargetScope.use(args) | ||||
|     componentSpec = args.consumeArg() | ||||
|     component = None | ||||
|     if componentSpec is not None: | ||||
|         component = registry.lookup(componentSpec, model.Component) | ||||
|     build(target, registry, component)[0] | ||||
|         component = scope.registry.lookup(componentSpec, model.Component) | ||||
|     build(scope, component)[0] | ||||
| 
 | ||||
| 
 | ||||
| @cli.command("r", "run", "Run a component") | ||||
| def runCmd(args: cli.Args): | ||||
|     registry = model.Registry.use(args) | ||||
|     target = model.Target.use(args) | ||||
| @cli.command("r", "build/run", "Run a component") | ||||
| def buildRunCmd(args: cli.Args): | ||||
|     scope = TargetScope.use(args) | ||||
|     debug = args.consumeOpt("debug", False) is True | ||||
| 
 | ||||
|     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: | ||||
|         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_BUILDDIR"] = target.builddir | ||||
| 
 | ||||
|     shell.exec(*(["lldb", "-o", "run"] if debug else []), str(product.path), *args.args) | ||||
| 
 | ||||
| 
 | ||||
| @cli.command("t", "test", "Run all test targets") | ||||
| def testCmd(args: cli.Args): | ||||
| @cli.command("t", "build/test", "Run all test targets") | ||||
| def buildTestCmd(args: cli.Args): | ||||
|     # This is just a wrapper around the `run` command that try | ||||
|     # to run a special hook component named __tests__. | ||||
|     args.args.insert(0, "__tests__") | ||||
|     runCmd(args) | ||||
|     buildRunCmd(args) | ||||
| 
 | ||||
| 
 | ||||
| @cli.command("d", "debug", "Debug a component") | ||||
| def debugCmd(args: cli.Args): | ||||
| @cli.command("d", "build/debug", "Debug a component") | ||||
| def buildDebugCmd(args: cli.Args): | ||||
|     # This is just a wrapper around the `run` command that | ||||
|     # always enable debug mode. | ||||
|     args.opts["debug"] = True | ||||
|     runCmd(args) | ||||
|     buildRunCmd(args) | ||||
| 
 | ||||
| 
 | ||||
| @cli.command("c", "clean", "Clean build files") | ||||
| def cleanCmd(args: cli.Args): | ||||
| @cli.command("c", "build/clean", "Clean build files") | ||||
| def buildCleanCmd(args: cli.Args): | ||||
|     model.Project.use(args) | ||||
|     shell.rmrf(const.BUILD_DIR) | ||||
| 
 | ||||
| 
 | ||||
| @cli.command("n", "nuke", "Clean all build files and caches") | ||||
| def nukeCmd(args: cli.Args): | ||||
| @cli.command("n", "build/nuke", "Clean all build files and caches") | ||||
| def buildNukeCmd(args: cli.Args): | ||||
|     model.Project.use(args) | ||||
|     shell.rmrf(const.PROJECT_CK_DIR) | ||||
|  |  | |||
|  | @ -80,8 +80,10 @@ class Command: | |||
|     isPlugin: bool | ||||
|     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): | ||||
|  | @ -90,15 +92,18 @@ def command(shortName: Optional[str], longName: str, helpText: str): | |||
| 
 | ||||
|     def wrap(fn: Callable[[Args], None]): | ||||
|         _logger.debug(f"Registering command {longName}") | ||||
|         commands.append( | ||||
|             Command( | ||||
|                 shortName, | ||||
|                 longName, | ||||
|                 helpText, | ||||
|                 Path(calframe[1].filename).parent != Path(__file__).parent, | ||||
|                 fn, | ||||
|             ) | ||||
|         path = longName.split("/") | ||||
|         parent = commands | ||||
|         for p in path[:-1]: | ||||
|             parent = parent[p].subcommands | ||||
|         parent[path[-1]] = Command( | ||||
|             shortName, | ||||
|             path[-1], | ||||
|             helpText, | ||||
|             Path(calframe[1].filename).parent != Path(__file__).parent, | ||||
|             fn, | ||||
|         ) | ||||
| 
 | ||||
|         return fn | ||||
| 
 | ||||
|     return wrap | ||||
|  | @ -127,8 +132,8 @@ def helpCmd(args: Args): | |||
| 
 | ||||
|     print() | ||||
|     vt100.title("Commands") | ||||
|     for cmd in sorted(commands, key=lambda c: c.longName): | ||||
|         if cmd.longName.startswith("_"): | ||||
|     for cmd in sorted(commands.values(), key=lambda c: c.longName): | ||||
|         if cmd.longName.startswith("_") or len(cmd.subcommands) > 0: | ||||
|             continue | ||||
| 
 | ||||
|         pluginText = "" | ||||
|  | @ -139,6 +144,21 @@ def helpCmd(args: Args): | |||
|             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() | ||||
|     vt100.title("Logging") | ||||
|     print("    Logs are stored in:") | ||||
|  | @ -151,15 +171,19 @@ def versionCmd(args: Args): | |||
|     print(f"CuteKit v{const.VERSION_STR}") | ||||
| 
 | ||||
| 
 | ||||
| def exec(args: Args): | ||||
| def exec(args: Args, cmds=commands): | ||||
|     cmd = args.consumeArg() | ||||
| 
 | ||||
|     if cmd is None: | ||||
|         raise RuntimeError("No command specified") | ||||
| 
 | ||||
|     for c in commands: | ||||
|     for c in cmds.values(): | ||||
|         if c.shortName == cmd or c.longName == cmd: | ||||
|             c.callback(args) | ||||
|             return | ||||
|             if len(c.subcommands) > 0: | ||||
|                 exec(args, c.subcommands) | ||||
|                 return | ||||
|             else: | ||||
|                 c.callback(args) | ||||
|                 return | ||||
| 
 | ||||
|     raise RuntimeError(f"Unknown command {cmd}") | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import os | ||||
| 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 ''}" | ||||
| MODULE_DIR = os.path.dirname(os.path.realpath(__file__)) | ||||
| 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 ( | ||||
|             scopeInstance is not None | ||||
|             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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -173,14 +173,19 @@ class Project(Manifest): | |||
|         return _project | ||||
| 
 | ||||
| 
 | ||||
| @cli.command("i", "install", "Install required external packages") | ||||
| def installCmd(args: cli.Args): | ||||
| @cli.command("m", "model", "Manage the model") | ||||
| 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.fetchs(project.extern) | ||||
| 
 | ||||
| 
 | ||||
| @cli.command("I", "init", "Initialize a new project") | ||||
| def initCmd(args: cli.Args): | ||||
| @cli.command("I", "model/init", "Initialize a new project") | ||||
| def modelInitCmd(args: cli.Args): | ||||
|     import requests | ||||
| 
 | ||||
|     repo = args.consumeOpt("repo", const.DEFAULT_REPO_TEMPLATES) | ||||
|  | @ -258,9 +263,15 @@ class Target(Manifest): | |||
|     tools: Tools = dt.field(default_factory=dict) | ||||
|     routing: dict[str, str] = dt.field(default_factory=dict) | ||||
| 
 | ||||
|     _hashid = None | ||||
| 
 | ||||
|     @property | ||||
|     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 | ||||
|     def builddir(self) -> str: | ||||
|  | @ -289,7 +300,8 @@ class Target(Manifest): | |||
| @dt.dataclass | ||||
| class Resolved: | ||||
|     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 | ||||
|     def enabled(self) -> bool: | ||||
|  | @ -436,11 +448,11 @@ class Resolver: | |||
|                 self._cache[keep] = Resolved(reason=reqResolved.reason) | ||||
|                 return self._cache[keep] | ||||
| 
 | ||||
|             result.extend(reqResolved.resolved) | ||||
|             result.extend(reqResolved.required) | ||||
| 
 | ||||
|         stack.pop() | ||||
|         result.insert(0, keep) | ||||
|         self._cache[keep] = Resolved(resolved=utils.uniq(result)) | ||||
|         self._cache[keep] = Resolved(required=utils.uniq(result)) | ||||
|         return self._cache[keep] | ||||
| 
 | ||||
| 
 | ||||
|  | @ -570,6 +582,13 @@ class Registry(DataClassJsonMixin): | |||
|             target.props |= props | ||||
|             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 | ||||
|             for c in r.iter(Component): | ||||
|                 if c.isEnabled(target)[0]: | ||||
|  | @ -577,14 +596,7 @@ class Registry(DataClassJsonMixin): | |||
|                         victim = r.lookup(inject, Component) | ||||
|                         if not victim: | ||||
|                             raise RuntimeError(f"Cannot find component '{inject}'") | ||||
|                         victim.requires += [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 | ||||
|                         victim.resolved[target.id].injected.append(c.id) | ||||
| 
 | ||||
|             # Resolve tooling | ||||
|             tools: Tools = target.tools | ||||
|  | @ -609,8 +621,8 @@ class Registry(DataClassJsonMixin): | |||
|         return r | ||||
| 
 | ||||
| 
 | ||||
| @cli.command("l", "list", "List all components and targets") | ||||
| def listCmd(args: cli.Args): | ||||
| @cli.command("l", "model/list", "List all components and targets") | ||||
| def modelListCmd(args: cli.Args): | ||||
|     registry = Registry.use(args) | ||||
| 
 | ||||
|     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 | ||||
| graphviz ~= 0.20.1 | ||||
| dataclasses-json ~= 0.6.2 | ||||
| docker ~= 6.1.3 | ||||
|  | @ -1,52 +1,39 @@ | |||
| import dataclasses as dt | ||||
| 
 | ||||
| from typing import Optional | ||||
| 
 | ||||
| 
 | ||||
| @dt.dataclass | ||||
| class Rule: | ||||
|     id: str | ||||
|     fileIn: list[str] | ||||
|     fileOut: list[str] | ||||
|     fileOut: str | ||||
|     rule: str | ||||
|     args: list[str] | ||||
|     deps: Optional[str] = None | ||||
| 
 | ||||
|     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 | ||||
|     args: list[str] = dt.field(default_factory=list) | ||||
|     deps: list[str] = dt.field(default_factory=list) | ||||
| 
 | ||||
| 
 | ||||
| rules: dict[str, Rule] = { | ||||
|     "cp": Rule("cp", ["*"], ["*"], "$in $out"), | ||||
|     "cp": Rule("cp", ["*"], "*", "$in $out"), | ||||
|     "cc": Rule( | ||||
|         "cc", | ||||
|         ["*.c"], | ||||
|         ["*.o"], | ||||
|         "*.o", | ||||
|         "-c -o $out $in -MD -MF $out.d $flags $cincs $cdefs", | ||||
|         ["-std=gnu2x", "-Wall", "-Wextra", "-Werror"], | ||||
|         "$out.d", | ||||
|         ["$out.d"], | ||||
|     ), | ||||
|     "cxx": Rule( | ||||
|         "cxx", | ||||
|         ["*.cpp", "*.cc", "*.cxx"], | ||||
|         ["*.o"], | ||||
|         "*.o", | ||||
|         "-c -o $out $in -MD -MF $out.d $flags $cincs $cdefs", | ||||
|         ["-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"), | ||||
|     "ar": Rule("ar", ["*.o"], ["*.a"], "$flags $out $in"), | ||||
|     "ld": Rule("ld", ["*.o", "*.a"], ["*.out"], "-o $out $in $flags"), | ||||
|     "as": Rule("as", ["*.s", "*.asm", "*.S"], "*.o", "-o $out $in $flags"), | ||||
|     "ar": Rule("ar", ["*.o"], "*.a", "$flags $out $in"), | ||||
|     "ld": Rule("ld", ["*.o", "*.a"], "*.out", "-o $out $in $flags"), | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -52,7 +52,7 @@ def sha256sum(path: str) -> str: | |||
| def find( | ||||
|     path: str | list[str], wildcards: list[str] = [], recusive: bool = True | ||||
| ) -> 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] = [] | ||||
| 
 | ||||
|  | @ -88,7 +88,7 @@ def find( | |||
| 
 | ||||
| 
 | ||||
| def mkdir(path: str) -> str: | ||||
|     _logger.info(f"Creating directory {path}") | ||||
|     _logger.debug(f"Creating directory {path}") | ||||
| 
 | ||||
|     try: | ||||
|         os.makedirs(path) | ||||
|  | @ -99,7 +99,7 @@ def mkdir(path: str) -> str: | |||
| 
 | ||||
| 
 | ||||
| def rmrf(path: str) -> bool: | ||||
|     _logger.info(f"Removing directory {path}") | ||||
|     _logger.debug(f"Removing directory {path}") | ||||
| 
 | ||||
|     if not os.path.exists(path): | ||||
|         return False | ||||
|  | @ -118,7 +118,7 @@ def wget(url: str, path: Optional[str] = None) -> str: | |||
|     if os.path.exists(path): | ||||
|         return path | ||||
| 
 | ||||
|     _logger.info(f"Downloading {url} to {path}") | ||||
|     _logger.debug(f"Downloading {url} to {path}") | ||||
| 
 | ||||
|     r = requests.get(url, stream=True) | ||||
|     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: | ||||
|     _logger.info(f"Executing {args}") | ||||
|     _logger.debug(f"Executing {args}") | ||||
| 
 | ||||
|     try: | ||||
|         proc = subprocess.run( | ||||
|  | @ -142,10 +142,10 @@ def exec(*args: str, quiet: bool = False) -> bool: | |||
|         ) | ||||
| 
 | ||||
|         if proc.stdout: | ||||
|             _logger.info(proc.stdout.decode("utf-8")) | ||||
|             _logger.debug(proc.stdout.decode("utf-8")) | ||||
| 
 | ||||
|         if proc.stderr: | ||||
|             _logger.error(proc.stderr.decode("utf-8")) | ||||
|             _logger.debug(proc.stderr.decode("utf-8")) | ||||
| 
 | ||||
|     except FileNotFoundError: | ||||
|         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: | ||||
|     _logger.info(f"Executing {args}") | ||||
|     _logger.debug(f"Executing {args}") | ||||
| 
 | ||||
|     try: | ||||
|         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]: | ||||
|     _logger.info(f"Reading directory {path}") | ||||
|     _logger.debug(f"Reading directory {path}") | ||||
| 
 | ||||
|     try: | ||||
|         return os.listdir(path) | ||||
|  | @ -189,19 +189,19 @@ def readdir(path: str) -> list[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) | ||||
| 
 | ||||
| 
 | ||||
| def mv(src: str, dst: str): | ||||
|     _logger.info(f"Moving {src} to {dst}") | ||||
|     _logger.debug(f"Moving {src} to {dst}") | ||||
| 
 | ||||
|     shutil.move(src, dst) | ||||
| 
 | ||||
| 
 | ||||
| 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) | ||||
| 
 | ||||
|  | @ -241,10 +241,9 @@ def latest(cmd: str) -> str: | |||
|     if cmd in LATEST_CACHE: | ||||
|         return LATEST_CACHE[cmd] | ||||
| 
 | ||||
|     _logger.info(f"Finding latest version of {cmd}") | ||||
|     _logger.debug(f"Finding latest version of {cmd}") | ||||
| 
 | ||||
|     regex: re.Pattern[str] | ||||
| 
 | ||||
|     if platform.system() == "Windows": | ||||
|         regex = re.compile(r"^" + re.escape(cmd) + r"(-.[0-9]+)?(\.exe)?$") | ||||
|     else: | ||||
|  | @ -263,7 +262,7 @@ def latest(cmd: str) -> str: | |||
|     versions.sort() | ||||
|     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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ packages = ["cutekit"] | |||
| 
 | ||||
| [tool.setuptools.dynamic] | ||||
| version = { attr = "cutekit.const.VERSION" } | ||||
| dependencies = { file = ["requirements.txt"] } | ||||
| dependencies = { file = ["cutekit/requirements.txt"] } | ||||
| 
 | ||||
| [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") | ||||
|     assert resolved.reason is None | ||||
|     assert resolved.resolved == ["myapp", "mylib"] | ||||
|     assert resolved.required == ["myapp", "mylib"] | ||||
| 
 | ||||
| 
 | ||||
| def test_indirect_deps(): | ||||
|  | @ -20,7 +20,7 @@ def test_indirect_deps(): | |||
|     r._append(model.Component("myimpl", provides=["myembed"])) | ||||
|     t = model.Target("host") | ||||
|     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(): | ||||
|  | @ -31,11 +31,11 @@ def test_deps_routing(): | |||
|     r._append(model.Component("myimplB", provides=["myembed"])) | ||||
|     t = model.Target("host", routing={"myembed": "myimplB"}) | ||||
|     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"}) | ||||
|     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"}) | ||||
|     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"}) | ||||
|     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"}) | ||||
|     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"}) | ||||
|     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"}) | ||||
|     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"}) | ||||
|     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"}) | ||||
|     res = model.Resolver(r, t) | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue