Cleanups imports and got ride of cutekit.project
This commit is contained in:
		
							parent
							
								
									3a78537dff
								
							
						
					
					
						commit
						8d1ca3095a
					
				
					 13 changed files with 224 additions and 222 deletions
				
			
		|  | @ -2,7 +2,7 @@ import sys | |||
| import os | ||||
| import logging | ||||
| 
 | ||||
| from cutekit import const, project, vt100, plugins, cmds, cli | ||||
| from . import const, model, vt100, plugins, cmds, cli | ||||
| 
 | ||||
| 
 | ||||
| def setupLogger(verbose: bool): | ||||
|  | @ -13,7 +13,7 @@ def setupLogger(verbose: bool): | |||
|             datefmt="%Y-%m-%d %H:%M:%S", | ||||
|         ) | ||||
|     else: | ||||
|         projectRoot = project.root() | ||||
|         projectRoot = model.Project.root() | ||||
|         logFile = const.GLOBAL_LOG_FILE | ||||
|         if projectRoot is not None: | ||||
|             logFile = os.path.join(projectRoot, const.PROJECT_LOG_FILE) | ||||
|  |  | |||
|  | @ -2,16 +2,13 @@ import os | |||
| import logging | ||||
| from typing import TextIO | ||||
| 
 | ||||
| from cutekit.model import Props | ||||
| from cutekit.ninja import Writer | ||||
| from cutekit.context import ComponentInstance, Context, contextFor | ||||
| from cutekit import shell, rules | ||||
| from . import shell, rules, model, ninja, context | ||||
| 
 | ||||
| _logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| def gen(out: TextIO, context: Context): | ||||
|     writer = Writer(out) | ||||
| def gen(out: TextIO, context: context.Context): | ||||
|     writer = ninja.Writer(out) | ||||
| 
 | ||||
|     target = context.target | ||||
| 
 | ||||
|  | @ -102,16 +99,18 @@ def gen(out: TextIO, context: Context): | |||
|     writer.default("all") | ||||
| 
 | ||||
| 
 | ||||
| def build(componentSpec: str, targetSpec: str, props: Props = {}) -> ComponentInstance: | ||||
|     context = contextFor(targetSpec, props) | ||||
| def build( | ||||
|     componentSpec: str, targetSpec: str, props: model.Props = {} | ||||
| ) -> context.ComponentInstance: | ||||
|     ctx = context.contextFor(targetSpec, props) | ||||
| 
 | ||||
|     shell.mkdir(context.builddir()) | ||||
|     ninjaPath = os.path.join(context.builddir(), "build.ninja") | ||||
|     shell.mkdir(ctx.builddir()) | ||||
|     ninjaPath = os.path.join(ctx.builddir(), "build.ninja") | ||||
| 
 | ||||
|     with open(ninjaPath, "w") as f: | ||||
|         gen(f, context) | ||||
|         gen(f, ctx) | ||||
| 
 | ||||
|     instance = context.componentByName(componentSpec) | ||||
|     instance = ctx.componentByName(componentSpec) | ||||
| 
 | ||||
|     if instance is None: | ||||
|         raise RuntimeError(f"Component {componentSpec} not found") | ||||
|  | @ -137,32 +136,32 @@ class Paths: | |||
|         self.obj = obj | ||||
| 
 | ||||
| 
 | ||||
| def buildAll(targetSpec: str, props: Props = {}) -> Context: | ||||
|     context = contextFor(targetSpec, props) | ||||
| def buildAll(targetSpec: str, props: model.Props = {}) -> context.Context: | ||||
|     ctx = context.contextFor(targetSpec, props) | ||||
| 
 | ||||
|     shell.mkdir(context.builddir()) | ||||
|     ninjaPath = os.path.join(context.builddir(), "build.ninja") | ||||
|     shell.mkdir(ctx.builddir()) | ||||
|     ninjaPath = os.path.join(ctx.builddir(), "build.ninja") | ||||
| 
 | ||||
|     with open(ninjaPath, "w") as f: | ||||
|         gen(f, context) | ||||
|         gen(f, ctx) | ||||
| 
 | ||||
|     shell.exec("ninja", "-v", "-f", ninjaPath) | ||||
| 
 | ||||
|     return context | ||||
|     return ctx | ||||
| 
 | ||||
| 
 | ||||
| def testAll(targetSpec: str): | ||||
|     context = contextFor(targetSpec) | ||||
|     ctx = context.contextFor(targetSpec) | ||||
| 
 | ||||
|     shell.mkdir(context.builddir()) | ||||
|     ninjaPath = os.path.join(context.builddir(), "build.ninja") | ||||
|     shell.mkdir(ctx.builddir()) | ||||
|     ninjaPath = os.path.join(ctx.builddir(), "build.ninja") | ||||
| 
 | ||||
|     with open(ninjaPath, "w") as f: | ||||
|         gen(f, context) | ||||
|         gen(f, ctx) | ||||
| 
 | ||||
|     shell.exec("ninja", "-v", "-f", ninjaPath, "all") | ||||
| 
 | ||||
|     for instance in context.enabledInstances(): | ||||
|     for instance in ctx.enabledInstances(): | ||||
|         if instance.isLib(): | ||||
|             continue | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,13 +3,12 @@ import os | |||
| import sys | ||||
| 
 | ||||
| 
 | ||||
| from cutekit import ( | ||||
| from . import ( | ||||
|     context, | ||||
|     shell, | ||||
|     const, | ||||
|     vt100, | ||||
|     builder, | ||||
|     project, | ||||
|     cli, | ||||
|     model, | ||||
|     jexpr, | ||||
|  | @ -21,7 +20,7 @@ _logger = logging.getLogger(__name__) | |||
| 
 | ||||
| @cli.command("p", "project", "Show project information") | ||||
| def runCmd(args: cli.Args): | ||||
|     project.chdir() | ||||
|     model.Project.chdir() | ||||
| 
 | ||||
|     targetSpec = str(args.consumeOpt("target", "host-" + shell.uname().machine)) | ||||
|     props = args.consumePrefix("prop:") | ||||
|  | @ -42,7 +41,7 @@ def runCmd(args: cli.Args): | |||
| 
 | ||||
| @cli.command("t", "test", "Run all test targets") | ||||
| def testCmd(args: cli.Args): | ||||
|     project.chdir() | ||||
|     model.Project.chdir() | ||||
| 
 | ||||
|     targetSpec = str(args.consumeOpt("target", "host-" + shell.uname().machine)) | ||||
|     builder.testAll(targetSpec) | ||||
|  | @ -50,7 +49,7 @@ def testCmd(args: cli.Args): | |||
| 
 | ||||
| @cli.command("d", "debug", "Debug a component") | ||||
| def debugCmd(args: cli.Args): | ||||
|     project.chdir() | ||||
|     model.Project.chdir() | ||||
| 
 | ||||
|     targetSpec = str(args.consumeOpt("target", "host-" + shell.uname().machine)) | ||||
|     props = args.consumePrefix("prop:") | ||||
|  | @ -71,7 +70,7 @@ def debugCmd(args: cli.Args): | |||
| 
 | ||||
| @cli.command("b", "build", "Build a component or all components") | ||||
| def buildCmd(args: cli.Args): | ||||
|     project.chdir() | ||||
|     model.Project.chdir() | ||||
| 
 | ||||
|     targetSpec = str(args.consumeOpt("target", "host-" + shell.uname().machine)) | ||||
|     props = args.consumePrefix("prop:") | ||||
|  | @ -85,7 +84,7 @@ def buildCmd(args: cli.Args): | |||
| 
 | ||||
| @cli.command("l", "list", "List all components and targets") | ||||
| def listCmd(args: cli.Args): | ||||
|     project.chdir() | ||||
|     model.Project.chdir() | ||||
| 
 | ||||
|     components = context.loadAllComponents() | ||||
|     targets = context.loadAllTargets() | ||||
|  | @ -109,13 +108,13 @@ def listCmd(args: cli.Args): | |||
| 
 | ||||
| @cli.command("c", "clean", "Clean build files") | ||||
| def cleanCmd(args: cli.Args): | ||||
|     project.chdir() | ||||
|     model.Project.chdir() | ||||
|     shell.rmrf(const.BUILD_DIR) | ||||
| 
 | ||||
| 
 | ||||
| @cli.command("n", "nuke", "Clean all build files and caches") | ||||
| def nukeCmd(args: cli.Args): | ||||
|     project.chdir() | ||||
|     model.Project.chdir() | ||||
|     shell.rmrf(const.PROJECT_CK_DIR) | ||||
| 
 | ||||
| 
 | ||||
|  | @ -170,8 +169,7 @@ def grabExtern(extern: dict[str, model.Extern]): | |||
| 
 | ||||
| @cli.command("i", "install", "Install required external packages") | ||||
| def installCmd(args: cli.Args): | ||||
|     project.chdir() | ||||
| 
 | ||||
|     model.Project.chdir() | ||||
|     pj = context.loadProject(".") | ||||
|     grabExtern(pj.extern) | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,22 +4,14 @@ from pathlib import Path | |||
| import os | ||||
| import logging | ||||
| 
 | ||||
| from cutekit.model import ( | ||||
|     Project, | ||||
|     Target, | ||||
|     Component, | ||||
|     Props, | ||||
|     Type, | ||||
|     Tool, | ||||
|     Tools, | ||||
| ) | ||||
| from cutekit import const, shell, jexpr, utils, rules, mixins, project | ||||
| 
 | ||||
| from . import const, shell, jexpr, utils, rules, mixins, project, model | ||||
| 
 | ||||
| _logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| 
 | ||||
| class IContext(Protocol): | ||||
|     target: Target | ||||
|     target: model.Target | ||||
| 
 | ||||
|     def builddir(self) -> str: | ||||
|         ... | ||||
|  | @ -28,7 +20,7 @@ class IContext(Protocol): | |||
| class ComponentInstance: | ||||
|     enabled: bool = True | ||||
|     disableReason = "" | ||||
|     manifest: Component | ||||
|     manifest: model.Component | ||||
|     sources: list[str] = [] | ||||
|     res: list[str] = [] | ||||
|     resolved: list[str] = [] | ||||
|  | @ -38,7 +30,7 @@ class ComponentInstance: | |||
|         self, | ||||
|         enabled: bool, | ||||
|         disableReason: str, | ||||
|         manifest: Component, | ||||
|         manifest: model.Component, | ||||
|         sources: list[str], | ||||
|         res: list[str], | ||||
|         resolved: list[str], | ||||
|  | @ -54,7 +46,7 @@ class ComponentInstance: | |||
|         return self.manifest.id | ||||
| 
 | ||||
|     def isLib(self): | ||||
|         return self.manifest.type == Type.LIB | ||||
|         return self.manifest.type == model.Type.LIB | ||||
| 
 | ||||
|     def objdir(self) -> str: | ||||
|         return os.path.join(self.context.builddir(), f"{self.manifest.id}/obj") | ||||
|  | @ -96,22 +88,25 @@ class ComponentInstance: | |||
|     def cinclude(self) -> str: | ||||
|         if "cpp-root-include" in self.manifest.props: | ||||
|             return self.manifest.dirname() | ||||
|         elif self.manifest.type == Type.LIB: | ||||
|         elif self.manifest.type == model.Type.LIB: | ||||
|             return str(Path(self.manifest.dirname()).parent) | ||||
|         else: | ||||
|             return "" | ||||
| 
 | ||||
| 
 | ||||
| class Context(IContext): | ||||
|     target: Target | ||||
|     target: model.Target | ||||
|     instances: list[ComponentInstance] | ||||
|     tools: Tools | ||||
|     tools: model.Tools | ||||
| 
 | ||||
|     def enabledInstances(self) -> Iterable[ComponentInstance]: | ||||
|         return filter(lambda x: x.enabled, self.instances) | ||||
| 
 | ||||
|     def __init__( | ||||
|         self, target: Target, instances: list[ComponentInstance], tools: Tools | ||||
|         self, | ||||
|         target: model.Target, | ||||
|         instances: list[ComponentInstance], | ||||
|         tools: model.Tools, | ||||
|     ): | ||||
|         self.target = target | ||||
|         self.instances = instances | ||||
|  | @ -143,8 +138,8 @@ class Context(IContext): | |||
|         return os.path.join(const.BUILD_DIR, f"{self.target.id}-{self.hashid()[:8]}") | ||||
| 
 | ||||
| 
 | ||||
| def loadAllTargets() -> list[Target]: | ||||
|     projectRoot = project.root() | ||||
| def loadAllTargets() -> list[model.Target]: | ||||
|     projectRoot = model.Project.root() | ||||
|     if projectRoot is None: | ||||
|         return [] | ||||
| 
 | ||||
|  | @ -159,40 +154,42 @@ def loadAllTargets() -> list[Target]: | |||
|     ret = [] | ||||
|     for entry in paths: | ||||
|         files = shell.find(entry, ["*.json"]) | ||||
|         ret += list(map(lambda path: Target(jexpr.evalRead(path), path), files)) | ||||
|         ret += list(map(lambda path: model.Target(jexpr.evalRead(path), path), files)) | ||||
| 
 | ||||
|     return ret | ||||
| 
 | ||||
| 
 | ||||
| def loadProject(path: str) -> Project: | ||||
| def loadProject(path: str) -> model.Project: | ||||
|     path = os.path.join(path, "project.json") | ||||
|     return Project(jexpr.evalRead(path), path) | ||||
|     return model.Project(jexpr.evalRead(path), path) | ||||
| 
 | ||||
| 
 | ||||
| def loadTarget(id: str) -> Target: | ||||
| def loadTarget(id: str) -> model.Target: | ||||
|     try: | ||||
|         return next(filter(lambda t: t.id == id, loadAllTargets())) | ||||
|     except StopIteration: | ||||
|         raise RuntimeError(f"Target '{id}' not found") | ||||
| 
 | ||||
| 
 | ||||
| def loadAllComponents() -> list[Component]: | ||||
| def loadAllComponents() -> list[model.Component]: | ||||
|     files = shell.find(const.SRC_DIR, ["manifest.json"]) | ||||
|     files += shell.find(const.EXTERN_DIR, ["manifest.json"]) | ||||
| 
 | ||||
|     return list(map(lambda path: Component(jexpr.evalRead(path), path), files)) | ||||
|     return list(map(lambda path: model.Component(jexpr.evalRead(path), path), files)) | ||||
| 
 | ||||
| 
 | ||||
| def filterDisabled( | ||||
|     components: list[Component], target: Target | ||||
| ) -> tuple[list[Component], list[Component]]: | ||||
|     components: list[model.Component], target: model.Target | ||||
| ) -> tuple[list[model.Component], list[model.Component]]: | ||||
|     return list(filter(lambda c: c.isEnabled(target)[0], components)), list( | ||||
|         filter(lambda c: not c.isEnabled(target)[0], components) | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def providerFor(what: str, components: list[Component]) -> tuple[Optional[str], str]: | ||||
|     result: list[Component] = list(filter(lambda c: c.id == what, components)) | ||||
| def providerFor( | ||||
|     what: str, components: list[model.Component] | ||||
| ) -> tuple[Optional[str], str]: | ||||
|     result: list[model.Component] = list(filter(lambda c: c.id == what, components)) | ||||
| 
 | ||||
|     if len(result) == 0: | ||||
|         # Try to find a provider | ||||
|  | @ -211,7 +208,7 @@ def providerFor(what: str, components: list[Component]) -> tuple[Optional[str], | |||
| 
 | ||||
| 
 | ||||
| def resolveDeps( | ||||
|     componentSpec: str, components: list[Component], target: Target | ||||
|     componentSpec: str, components: list[model.Component], target: model.Target | ||||
| ) -> tuple[bool, str, list[str]]: | ||||
|     mapping = dict(map(lambda c: (c.id, c), components)) | ||||
| 
 | ||||
|  | @ -249,7 +246,7 @@ def resolveDeps( | |||
| 
 | ||||
| 
 | ||||
| def instanciate( | ||||
|     componentSpec: str, components: list[Component], target: Target | ||||
|     componentSpec: str, components: list[model.Component], target: model.Target | ||||
| ) -> Optional[ComponentInstance]: | ||||
|     manifest = next(filter(lambda c: c.id == componentSpec, components)) | ||||
|     wildcards = set(chain(*map(lambda rule: rule.fileIn, rules.rules.values()))) | ||||
|  | @ -264,7 +261,9 @@ def instanciate( | |||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def instanciateDisabled(component: Component, target: Target) -> ComponentInstance: | ||||
| def instanciateDisabled( | ||||
|     component: model.Component, target: model.Target | ||||
| ) -> ComponentInstance: | ||||
|     return ComponentInstance( | ||||
|         enabled=False, | ||||
|         disableReason=component.isEnabled(target)[1], | ||||
|  | @ -278,7 +277,7 @@ def instanciateDisabled(component: Component, target: Target) -> ComponentInstan | |||
| context: dict[str, Context] = {} | ||||
| 
 | ||||
| 
 | ||||
| def contextFor(targetSpec: str, props: Props = {}) -> Context: | ||||
| def contextFor(targetSpec: str, props: model.Props = {}) -> Context: | ||||
|     if targetSpec in context: | ||||
|         return context[targetSpec] | ||||
| 
 | ||||
|  | @ -295,12 +294,12 @@ def contextFor(targetSpec: str, props: Props = {}) -> Context: | |||
|     components = loadAllComponents() | ||||
|     components, disabled = filterDisabled(components, target) | ||||
| 
 | ||||
|     tools: Tools = {} | ||||
|     tools: model.Tools = {} | ||||
| 
 | ||||
|     for toolSpec in target.tools: | ||||
|         tool = target.tools[toolSpec] | ||||
| 
 | ||||
|         tools[toolSpec] = Tool( | ||||
|         tools[toolSpec] = model.Tool( | ||||
|             strict=False, cmd=tool.cmd, args=tool.args, files=tool.files | ||||
|         ) | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import os | ||||
| 
 | ||||
| from typing import cast | ||||
| from . import vt100, context, project, cli, shell | ||||
| from . import vt100, context, cli, shell, model | ||||
| 
 | ||||
| 
 | ||||
| def view( | ||||
|  | @ -10,7 +10,7 @@ def view( | |||
|     showExe: bool = True, | ||||
|     showDisabled: bool = False, | ||||
| ): | ||||
|     from graphviz import Digraph | ||||
|     from graphviz import Digraph  # type: ignore | ||||
| 
 | ||||
|     g = Digraph(context.target.id, filename="graph.gv") | ||||
| 
 | ||||
|  | @ -83,7 +83,7 @@ def view( | |||
| 
 | ||||
| @cli.command("g", "graph", "Show the dependency graph") | ||||
| def graphCmd(args: cli.Args): | ||||
|     project.chdir() | ||||
|     model.Project.chdir() | ||||
| 
 | ||||
|     targetSpec = str(args.consumeOpt("target", "host-" + shell.uname().machine)) | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,9 +1,8 @@ | |||
| import os | ||||
| from typing import Any, cast, Callable, Final | ||||
| import json | ||||
| 
 | ||||
| import cutekit.shell as shell | ||||
| from cutekit.compat import ensureSupportedManifest | ||||
| from typing import Any, cast, Callable, Final | ||||
| from . import shell, compat | ||||
| 
 | ||||
| Json = Any | ||||
| Builtin = Callable[..., Json] | ||||
|  | @ -61,5 +60,5 @@ def read(path: str) -> Json: | |||
| def evalRead(path: str, compatibilityCheck: bool = True) -> Json: | ||||
|     data = read(path) | ||||
|     if compatibilityCheck: | ||||
|         ensureSupportedManifest(data, path) | ||||
|         compat.ensureSupportedManifest(data, path) | ||||
|     return eval(data, path) | ||||
|  |  | |||
|  | @ -1,25 +1,26 @@ | |||
| from typing import Callable | ||||
| from cutekit.model import Target, Tools | ||||
| 
 | ||||
| Mixin = Callable[[Target, Tools], Tools] | ||||
| from . import model | ||||
| 
 | ||||
| Mixin = Callable[[model.Target, model.Tools], model.Tools] | ||||
| 
 | ||||
| 
 | ||||
| def patchToolArgs(tools: Tools, toolSpec: str, args: list[str]): | ||||
| def patchToolArgs(tools: model.Tools, toolSpec: str, args: list[str]): | ||||
|     tools[toolSpec].args += args | ||||
| 
 | ||||
| 
 | ||||
| def prefixToolCmd(tools: Tools, toolSpec: str, prefix: str): | ||||
| def prefixToolCmd(tools: model.Tools, toolSpec: str, prefix: str): | ||||
|     tools[toolSpec].cmd = prefix + " " + tools[toolSpec].cmd | ||||
| 
 | ||||
| 
 | ||||
| def mixinCache(target: Target, tools: Tools) -> Tools: | ||||
| def mixinCache(target: model.Target, tools: model.Tools) -> model.Tools: | ||||
|     prefixToolCmd(tools, "cc", "ccache") | ||||
|     prefixToolCmd(tools, "cxx", "ccache") | ||||
|     return tools | ||||
| 
 | ||||
| 
 | ||||
| def makeMixinSan(san: str) -> Mixin: | ||||
|     def mixinSan(target: Target, tools: Tools) -> Tools: | ||||
|     def mixinSan(target: model.Target, tools: model.Tools) -> model.Tools: | ||||
|         patchToolArgs(tools, "cc", [f"-fsanitize={san}"]) | ||||
|         patchToolArgs(tools, "cxx", [f"-fsanitize={san}"]) | ||||
|         patchToolArgs(tools, "ld", [f"-fsanitize={san}"]) | ||||
|  | @ -30,7 +31,7 @@ def makeMixinSan(san: str) -> Mixin: | |||
| 
 | ||||
| 
 | ||||
| def makeMixinOptimize(level: str) -> Mixin: | ||||
|     def mixinOptimize(target: Target, tools: Tools) -> Tools: | ||||
|     def mixinOptimize(target: model.Target, tools: model.Tools) -> model.Tools: | ||||
|         patchToolArgs(tools, "cc", [f"-O{level}"]) | ||||
|         patchToolArgs(tools, "cxx", [f"-O{level}"]) | ||||
| 
 | ||||
|  | @ -39,7 +40,7 @@ def makeMixinOptimize(level: str) -> Mixin: | |||
|     return mixinOptimize | ||||
| 
 | ||||
| 
 | ||||
| def mixinDebug(target: Target, tools: Tools) -> Tools: | ||||
| def mixinDebug(target: model.Target, tools: model.Tools) -> model.Tools: | ||||
|     patchToolArgs(tools, "cc", ["-g", "-gdwarf-4"]) | ||||
|     patchToolArgs(tools, "cxx", ["-g", "-gdwarf-4"]) | ||||
| 
 | ||||
|  | @ -47,7 +48,7 @@ def mixinDebug(target: Target, tools: Tools) -> Tools: | |||
| 
 | ||||
| 
 | ||||
| def makeMixinTune(tune: str) -> Mixin: | ||||
|     def mixinTune(target: Target, tools: Tools) -> Tools: | ||||
|     def mixinTune(target: model.Target, tools: model.Tools) -> model.Tools: | ||||
|         patchToolArgs(tools, "cc", [f"-mtune={tune}"]) | ||||
|         patchToolArgs(tools, "cxx", [f"-mtune={tune}"]) | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,9 +1,10 @@ | |||
| import os | ||||
| from enum import Enum | ||||
| from typing import Any | ||||
| import logging | ||||
| 
 | ||||
| from cutekit.jexpr import Json | ||||
| from enum import Enum | ||||
| from typing import Any | ||||
| from pathlib import Path | ||||
| from . import jexpr | ||||
| 
 | ||||
| 
 | ||||
| _logger = logging.getLogger(__name__) | ||||
|  | @ -25,7 +26,11 @@ class Manifest: | |||
|     path: str = "" | ||||
| 
 | ||||
|     def __init__( | ||||
|         self, json: Json = None, path: str = "", strict: bool = True, **kwargs: Any | ||||
|         self, | ||||
|         json: jexpr.Json = None, | ||||
|         path: str = "", | ||||
|         strict: bool = True, | ||||
|         **kwargs: Any, | ||||
|     ): | ||||
|         if json is not None: | ||||
|             if "id" not in json: | ||||
|  | @ -45,7 +50,7 @@ class Manifest: | |||
|         for key in kwargs: | ||||
|             setattr(self, key, kwargs[key]) | ||||
| 
 | ||||
|     def toJson(self) -> Json: | ||||
|     def toJson(self) -> jexpr.Json: | ||||
|         return {"id": self.id, "type": self.type.value, "path": self.path} | ||||
| 
 | ||||
|     def __str__(self): | ||||
|  | @ -62,7 +67,7 @@ class Extern: | |||
|     git: str = "" | ||||
|     tag: str = "" | ||||
| 
 | ||||
|     def __init__(self, json: Json = None, strict: bool = True, **kwargs: Any): | ||||
|     def __init__(self, json: jexpr.Json = None, strict: bool = True, **kwargs: Any): | ||||
|         if json is not None: | ||||
|             if "git" not in json and strict: | ||||
|                 raise RuntimeError("Missing git") | ||||
|  | @ -79,7 +84,7 @@ class Extern: | |||
|         for key in kwargs: | ||||
|             setattr(self, key, kwargs[key]) | ||||
| 
 | ||||
|     def toJson(self) -> Json: | ||||
|     def toJson(self) -> jexpr.Json: | ||||
|         return {"git": self.git, "tag": self.tag} | ||||
| 
 | ||||
|     def __str__(self): | ||||
|  | @ -94,7 +99,11 @@ class Project(Manifest): | |||
|     extern: dict[str, Extern] = {} | ||||
| 
 | ||||
|     def __init__( | ||||
|         self, json: Json = None, path: str = "", strict: bool = True, **kwargs: Any | ||||
|         self, | ||||
|         json: jexpr.Json = None, | ||||
|         path: str = "", | ||||
|         strict: bool = True, | ||||
|         **kwargs: Any, | ||||
|     ): | ||||
|         if json is not None: | ||||
|             if "description" not in json and strict: | ||||
|  | @ -108,7 +117,7 @@ class Project(Manifest): | |||
| 
 | ||||
|         super().__init__(json, path, strict, **kwargs) | ||||
| 
 | ||||
|     def toJson(self) -> Json: | ||||
|     def toJson(self) -> jexpr.Json: | ||||
|         return { | ||||
|             **super().toJson(), | ||||
|             "description": self.description, | ||||
|  | @ -121,13 +130,31 @@ class Project(Manifest): | |||
|     def __repr__(self): | ||||
|         return f"ProjectManifest({self.id})" | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def root() -> str | None: | ||||
|         cwd = Path.cwd() | ||||
|         while str(cwd) != cwd.root: | ||||
|             if (cwd / "project.json").is_file(): | ||||
|                 return str(cwd) | ||||
|             cwd = cwd.parent | ||||
|         return None | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def chdir() -> None: | ||||
|         path = Project.root() | ||||
|         if path is None: | ||||
|             raise RuntimeError( | ||||
|                 "No project.json found in this directory or any parent directory" | ||||
|             ) | ||||
|         os.chdir(path) | ||||
| 
 | ||||
| 
 | ||||
| class Tool: | ||||
|     cmd: str = "" | ||||
|     args: list[str] = [] | ||||
|     files: list[str] = [] | ||||
| 
 | ||||
|     def __init__(self, json: Json = None, strict: bool = True, **kwargs: Any): | ||||
|     def __init__(self, json: jexpr.Json = None, strict: bool = True, **kwargs: Any): | ||||
|         if json is not None: | ||||
|             if "cmd" not in json and strict: | ||||
|                 raise RuntimeError("Missing cmd") | ||||
|  | @ -146,7 +173,7 @@ class Tool: | |||
|         for key in kwargs: | ||||
|             setattr(self, key, kwargs[key]) | ||||
| 
 | ||||
|     def toJson(self) -> Json: | ||||
|     def toJson(self) -> jexpr.Json: | ||||
|         return {"cmd": self.cmd, "args": self.args, "files": self.files} | ||||
| 
 | ||||
|     def __str__(self): | ||||
|  | @ -165,7 +192,11 @@ class Target(Manifest): | |||
|     routing: dict[str, str] | ||||
| 
 | ||||
|     def __init__( | ||||
|         self, json: Json = None, path: str = "", strict: bool = True, **kwargs: Any | ||||
|         self, | ||||
|         json: jexpr.Json = None, | ||||
|         path: str = "", | ||||
|         strict: bool = True, | ||||
|         **kwargs: Any, | ||||
|     ): | ||||
|         if json is not None: | ||||
|             if "props" not in json and strict: | ||||
|  | @ -182,7 +213,7 @@ class Target(Manifest): | |||
| 
 | ||||
|         super().__init__(json, path, strict, **kwargs) | ||||
| 
 | ||||
|     def toJson(self) -> Json: | ||||
|     def toJson(self) -> jexpr.Json: | ||||
|         return { | ||||
|             **super().toJson(), | ||||
|             "props": self.props, | ||||
|  | @ -229,7 +260,11 @@ class Component(Manifest): | |||
|     subdirs: list[str] = [] | ||||
| 
 | ||||
|     def __init__( | ||||
|         self, json: Json = None, path: str = "", strict: bool = True, **kwargs: Any | ||||
|         self, | ||||
|         json: jexpr.Json = None, | ||||
|         path: str = "", | ||||
|         strict: bool = True, | ||||
|         **kwargs: Any, | ||||
|     ): | ||||
|         if json is not None: | ||||
|             self.decription = json.get("description", self.decription) | ||||
|  | @ -249,7 +284,7 @@ class Component(Manifest): | |||
| 
 | ||||
|         super().__init__(json, path, strict, **kwargs) | ||||
| 
 | ||||
|     def toJson(self) -> Json: | ||||
|     def toJson(self) -> jexpr.Json: | ||||
|         return { | ||||
|             **super().toJson(), | ||||
|             "description": self.decription, | ||||
|  |  | |||
							
								
								
									
										121
									
								
								cutekit/ninja.py
									
										
									
									
									
								
							
							
						
						
									
										121
									
								
								cutekit/ninja.py
									
										
									
									
									
								
							|  | @ -23,13 +23,13 @@ use Python. | |||
| """ | ||||
| 
 | ||||
| import textwrap | ||||
| from typing import TextIO, Union | ||||
| 
 | ||||
| from cutekit.utils import asList | ||||
| from typing import TextIO, Union | ||||
| from . import utils | ||||
| 
 | ||||
| 
 | ||||
| def escapePath(word: str) -> str: | ||||
|     return word.replace('$ ', '$$ ').replace(' ', '$ ').replace(':', '$:') | ||||
|     return word.replace("$ ", "$$ ").replace(" ", "$ ").replace(":", "$:") | ||||
| 
 | ||||
| 
 | ||||
| VarValue = Union[int, str, list[str], None] | ||||
|  | @ -42,29 +42,32 @@ class Writer(object): | |||
|         self.width = width | ||||
| 
 | ||||
|     def newline(self) -> None: | ||||
|         self.output.write('\n') | ||||
|         self.output.write("\n") | ||||
| 
 | ||||
|     def comment(self, text: str) -> None: | ||||
|         for line in textwrap.wrap(text, self.width - 2, break_long_words=False, | ||||
|                                   break_on_hyphens=False): | ||||
|             self.output.write('# ' + line + '\n') | ||||
|         for line in textwrap.wrap( | ||||
|             text, self.width - 2, break_long_words=False, break_on_hyphens=False | ||||
|         ): | ||||
|             self.output.write("# " + line + "\n") | ||||
| 
 | ||||
|     def separator(self, text: str) -> None: | ||||
|         self.output.write(f"# --- {text} ---" + '-' * | ||||
|                           (self.width - 10 - len(text)) + " #\n\n") | ||||
|         self.output.write( | ||||
|             f"# --- {text} ---" + "-" * (self.width - 10 - len(text)) + " #\n\n" | ||||
|         ) | ||||
| 
 | ||||
|     def variable(self, key: str, value: VarValue, indent: int = 0) -> None: | ||||
|         if value is None: | ||||
|             return | ||||
|         if isinstance(value, list): | ||||
|             value = ' '.join(filter(None, value))  # Filter out empty strings. | ||||
|         self._line('%s = %s' % (key, value), indent) | ||||
|             value = " ".join(filter(None, value))  # Filter out empty strings. | ||||
|         self._line("%s = %s" % (key, value), indent) | ||||
| 
 | ||||
|     def pool(self, name: str, depth: int) -> None: | ||||
|         self._line('pool %s' % name) | ||||
|         self.variable('depth', depth, indent=1) | ||||
|         self._line("pool %s" % name) | ||||
|         self.variable("depth", depth, indent=1) | ||||
| 
 | ||||
|     def rule(self, | ||||
|     def rule( | ||||
|         self, | ||||
|         name: str, | ||||
|         command: VarValue, | ||||
|         description: Union[str, None] = None, | ||||
|  | @ -74,27 +77,29 @@ class Writer(object): | |||
|         restat: bool = False, | ||||
|         rspfile: VarValue = None, | ||||
|         rspfile_content: VarValue = None, | ||||
|              deps: VarValue = None) -> None: | ||||
|         self._line('rule %s' % name) | ||||
|         self.variable('command', command, indent=1) | ||||
|         deps: VarValue = None, | ||||
|     ) -> None: | ||||
|         self._line("rule %s" % name) | ||||
|         self.variable("command", command, indent=1) | ||||
|         if description: | ||||
|             self.variable('description', description, indent=1) | ||||
|             self.variable("description", description, indent=1) | ||||
|         if depfile: | ||||
|             self.variable('depfile', depfile, indent=1) | ||||
|             self.variable("depfile", depfile, indent=1) | ||||
|         if generator: | ||||
|             self.variable('generator', '1', indent=1) | ||||
|             self.variable("generator", "1", indent=1) | ||||
|         if pool: | ||||
|             self.variable('pool', pool, indent=1) | ||||
|             self.variable("pool", pool, indent=1) | ||||
|         if restat: | ||||
|             self.variable('restat', '1', indent=1) | ||||
|             self.variable("restat", "1", indent=1) | ||||
|         if rspfile: | ||||
|             self.variable('rspfile', rspfile, indent=1) | ||||
|             self.variable("rspfile", rspfile, indent=1) | ||||
|         if rspfile_content: | ||||
|             self.variable('rspfile_content', rspfile_content, indent=1) | ||||
|             self.variable("rspfile_content", rspfile_content, indent=1) | ||||
|         if deps: | ||||
|             self.variable('deps', deps, indent=1) | ||||
|             self.variable("deps", deps, indent=1) | ||||
| 
 | ||||
|     def build(self, | ||||
|     def build( | ||||
|         self, | ||||
|         outputs: Union[str, list[str]], | ||||
|         rule: str, | ||||
|         inputs: Union[VarPath, None], | ||||
|  | @ -103,31 +108,32 @@ class Writer(object): | |||
|         variables: Union[dict[str, str], None] = None, | ||||
|         implicit_outputs: VarPath = None, | ||||
|         pool: Union[str, None] = None, | ||||
|               dyndep: Union[str, None] = None) -> list[str]: | ||||
|         outputs = asList(outputs) | ||||
|         dyndep: Union[str, None] = None, | ||||
|     ) -> list[str]: | ||||
|         outputs = utils.asList(outputs) | ||||
|         out_outputs = [escapePath(x) for x in outputs] | ||||
|         all_inputs = [escapePath(x) for x in asList(inputs)] | ||||
|         all_inputs = [escapePath(x) for x in utils.asList(inputs)] | ||||
| 
 | ||||
|         if implicit: | ||||
|             implicit = [escapePath(x) for x in asList(implicit)] | ||||
|             all_inputs.append('|') | ||||
|             implicit = [escapePath(x) for x in utils.asList(implicit)] | ||||
|             all_inputs.append("|") | ||||
|             all_inputs.extend(implicit) | ||||
|         if order_only: | ||||
|             order_only = [escapePath(x) for x in asList(order_only)] | ||||
|             all_inputs.append('||') | ||||
|             order_only = [escapePath(x) for x in utils.asList(order_only)] | ||||
|             all_inputs.append("||") | ||||
|             all_inputs.extend(order_only) | ||||
|         if implicit_outputs: | ||||
|             implicit_outputs = [escapePath(x) | ||||
|                                 for x in asList(implicit_outputs)] | ||||
|             out_outputs.append('|') | ||||
|             implicit_outputs = [escapePath(x) for x in utils.asList(implicit_outputs)] | ||||
|             out_outputs.append("|") | ||||
|             out_outputs.extend(implicit_outputs) | ||||
| 
 | ||||
|         self._line('build %s: %s' % (' '.join(out_outputs), | ||||
|                                      ' '.join([rule] + all_inputs))) | ||||
|         self._line( | ||||
|             "build %s: %s" % (" ".join(out_outputs), " ".join([rule] + all_inputs)) | ||||
|         ) | ||||
|         if pool is not None: | ||||
|             self._line('  pool = %s' % pool) | ||||
|             self._line("  pool = %s" % pool) | ||||
|         if dyndep is not None: | ||||
|             self._line('  dyndep = %s' % dyndep) | ||||
|             self._line("  dyndep = %s" % dyndep) | ||||
| 
 | ||||
|         if variables: | ||||
|             iterator = iter(variables.items()) | ||||
|  | @ -138,58 +144,59 @@ class Writer(object): | |||
|         return outputs | ||||
| 
 | ||||
|     def include(self, path: str) -> None: | ||||
|         self._line('include %s' % path) | ||||
|         self._line("include %s" % path) | ||||
| 
 | ||||
|     def subninja(self, path: str) -> None: | ||||
|         self._line('subninja %s' % path) | ||||
|         self._line("subninja %s" % path) | ||||
| 
 | ||||
|     def default(self, paths: VarPath) -> None: | ||||
|         self._line('default %s' % ' '.join(asList(paths))) | ||||
|         self._line("default %s" % " ".join(utils.asList(paths))) | ||||
| 
 | ||||
|     def _count_dollars_before_index(self, s: str, i: int) -> int: | ||||
|         """Returns the number of '$' characters right in front of s[i].""" | ||||
|         dollar_count = 0 | ||||
|         dollar_index = i - 1 | ||||
|         while dollar_index > 0 and s[dollar_index] == '$': | ||||
|         while dollar_index > 0 and s[dollar_index] == "$": | ||||
|             dollar_count += 1 | ||||
|             dollar_index -= 1 | ||||
|         return dollar_count | ||||
| 
 | ||||
|     def _line(self, text: str, indent: int = 0) -> None: | ||||
|         """Write 'text' word-wrapped at self.width characters.""" | ||||
|         leading_space = '  ' * indent | ||||
|         leading_space = "  " * indent | ||||
|         while len(leading_space) + len(text) > self.width: | ||||
|             # The text is too wide; wrap if possible. | ||||
| 
 | ||||
|             # Find the rightmost space that would obey our width constraint and | ||||
|             # that's not an escaped space. | ||||
|             available_space = self.width - len(leading_space) - len(' $') | ||||
|             available_space = self.width - len(leading_space) - len(" $") | ||||
|             space = available_space | ||||
|             while True: | ||||
|                 space = text.rfind(' ', 0, space) | ||||
|                 if (space < 0 or | ||||
|                         self._count_dollars_before_index(text, space) % 2 == 0): | ||||
|                 space = text.rfind(" ", 0, space) | ||||
|                 if space < 0 or self._count_dollars_before_index(text, space) % 2 == 0: | ||||
|                     break | ||||
| 
 | ||||
|             if space < 0: | ||||
|                 # No such space; just use the first unescaped space we can find. | ||||
|                 space = available_space - 1 | ||||
|                 while True: | ||||
|                     space = text.find(' ', space + 1) | ||||
|                     if (space < 0 or | ||||
|                             self._count_dollars_before_index(text, space) % 2 == 0): | ||||
|                     space = text.find(" ", space + 1) | ||||
|                     if ( | ||||
|                         space < 0 | ||||
|                         or self._count_dollars_before_index(text, space) % 2 == 0 | ||||
|                     ): | ||||
|                         break | ||||
|             if space < 0: | ||||
|                 # Give up on breaking. | ||||
|                 break | ||||
| 
 | ||||
|             self.output.write(leading_space + text[0:space] + ' $\n') | ||||
|             self.output.write(leading_space + text[0:space] + " $\n") | ||||
|             text = text[space + 1 :] | ||||
| 
 | ||||
|             # Subsequent lines are continuations, so indent them. | ||||
|             leading_space = '  ' * (indent+2) | ||||
|             leading_space = "  " * (indent + 2) | ||||
| 
 | ||||
|         self.output.write(leading_space + text + '\n') | ||||
|         self.output.write(leading_space + text + "\n") | ||||
| 
 | ||||
|     def close(self) -> None: | ||||
|         self.output.close() | ||||
|  | @ -198,6 +205,6 @@ class Writer(object): | |||
| def escape(string: str) -> str: | ||||
|     """Escape a string such that it can be embedded into a Ninja file without | ||||
|     further interpretation.""" | ||||
|     assert '\n' not in string, 'Ninja syntax does not allow newlines' | ||||
|     assert "\n" not in string, "Ninja syntax does not allow newlines" | ||||
|     # We only have one special metacharacter: '$'. | ||||
|     return string.replace('$', '$$') | ||||
|     return string.replace("$", "$$") | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import os | ||||
| import logging | ||||
| 
 | ||||
| from cutekit import shell, project, const, context | ||||
| from . import shell, model, const, context | ||||
| 
 | ||||
| import importlib.util as importlib | ||||
| 
 | ||||
|  | @ -23,7 +23,7 @@ def load(path: str): | |||
| def loadAll(): | ||||
|     _logger.info("Loading plugins...") | ||||
| 
 | ||||
|     projectRoot = project.root() | ||||
|     projectRoot = model.Project.root() | ||||
| 
 | ||||
|     if projectRoot is None: | ||||
|         _logger.info("Not in project, skipping plugin loading") | ||||
|  |  | |||
|  | @ -1,22 +0,0 @@ | |||
| import os | ||||
| 
 | ||||
| from pathlib import Path | ||||
| 
 | ||||
| 
 | ||||
| def root() -> str | None: | ||||
|     cwd = Path.cwd() | ||||
|     while str(cwd) != cwd.root: | ||||
|         if (cwd / "project.json").is_file(): | ||||
|             return str(cwd) | ||||
|         cwd = cwd.parent | ||||
|     return None | ||||
| 
 | ||||
| 
 | ||||
| def chdir() -> None: | ||||
|     projectRoot = root() | ||||
|     if projectRoot is None: | ||||
|         raise RuntimeError( | ||||
|             "No project.json found in this directory or any parent directory" | ||||
|         ) | ||||
| 
 | ||||
|     os.chdir(projectRoot) | ||||
|  | @ -13,7 +13,7 @@ import tempfile | |||
| 
 | ||||
| 
 | ||||
| from typing import Optional | ||||
| from cutekit import const | ||||
| from . import const | ||||
| 
 | ||||
| _logger = logging.getLogger(__name__) | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,4 +1,3 @@ | |||
| 
 | ||||
| # Extending cutekit | ||||
| 
 | ||||
| By writing custom Python plugins, you can extend Cutekit to do whatever you want. | ||||
|  | @ -9,24 +8,11 @@ Then you can import cutekit and change/add whatever you want. | |||
| For example you can add a new command to the CLI: | ||||
| 
 | ||||
| ```python | ||||
| import os | ||||
| import json | ||||
| import magic | ||||
| import logging | ||||
| from pathlib import Path | ||||
| from cutekit import cli | ||||
| 
 | ||||
| 
 | ||||
| from cutekit import shell, builder, const, project | ||||
| from cutekit.cmds import Cmd, append | ||||
| from cutekit.args import Args | ||||
| from typing import Callable | ||||
| 
 | ||||
| 
 | ||||
| def bootCmd(args: Args) -> None: | ||||
|     project.chdir() | ||||
| @cli.command("h", "hello", "Print hello world") | ||||
| def bootCmd(args: cli.Args) -> None: | ||||
|     print("Hello world!") | ||||
| 
 | ||||
| append(Cmd("h", "hello", "Print hello world", bootCmd)) | ||||
| ``` | ||||
| 
 | ||||
| This feature is used - for example - by [SkiftOS](https://github.com/skift-org/skift/blob/main/meta/plugins/start-cmd.py) to add the `start` command, that build packages and run a virtual machine. | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue