Cleanups imports and got ride of cutekit.project
This commit is contained in:
parent
3a78537dff
commit
8d1ca3095a
|
@ -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,
|
||||
|
|
125
cutekit/ninja.py
125
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")
|
||||
def separator(self, text: str) -> None:
|
||||
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')
|
||||
text = text[space+1:]
|
||||
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…
Reference in a new issue