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 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,
|
||||||
|
|
159
cutekit/ninja.py
159
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,92 +42,98 @@ 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(
|
||||||
name: str,
|
self,
|
||||||
command: VarValue,
|
name: str,
|
||||||
description: Union[str, None] = None,
|
command: VarValue,
|
||||||
depfile: VarValue = None,
|
description: Union[str, None] = None,
|
||||||
generator: VarValue = False,
|
depfile: VarValue = None,
|
||||||
pool: VarValue = None,
|
generator: VarValue = False,
|
||||||
restat: bool = False,
|
pool: VarValue = None,
|
||||||
rspfile: VarValue = None,
|
restat: bool = False,
|
||||||
rspfile_content: VarValue = None,
|
rspfile: VarValue = None,
|
||||||
deps: VarValue = None) -> None:
|
rspfile_content: VarValue = None,
|
||||||
self._line('rule %s' % name)
|
deps: VarValue = None,
|
||||||
self.variable('command', command, indent=1)
|
) -> None:
|
||||||
|
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(
|
||||||
outputs: Union[str, list[str]],
|
self,
|
||||||
rule: str,
|
outputs: Union[str, list[str]],
|
||||||
inputs: Union[VarPath, None],
|
rule: str,
|
||||||
implicit: VarPath = None,
|
inputs: Union[VarPath, None],
|
||||||
order_only: VarPath = None,
|
implicit: VarPath = None,
|
||||||
variables: Union[dict[str, str], None] = None,
|
order_only: VarPath = None,
|
||||||
implicit_outputs: VarPath = None,
|
variables: Union[dict[str, str], None] = None,
|
||||||
pool: Union[str, None] = None,
|
implicit_outputs: VarPath = None,
|
||||||
dyndep: Union[str, None] = None) -> list[str]:
|
pool: Union[str, None] = None,
|
||||||
outputs = asList(outputs)
|
dyndep: Union[str, None] = None,
|
||||||
|
) -> 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,5 +1,4 @@
|
||||||
|
# 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…
Reference in a new issue