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