365 lines
10 KiB
Python
365 lines
10 KiB
Python
from typing import Callable, cast
|
|
import os
|
|
import json
|
|
|
|
from osdk.args import Args
|
|
from osdk.context import contextFor
|
|
from osdk import context, shell, const, vt100, builder, graph
|
|
|
|
Callback = Callable[[Args], None]
|
|
|
|
|
|
class Cmd:
|
|
shortName: str | None
|
|
longName: str
|
|
helpText: str
|
|
callback: Callable[[Args], None]
|
|
isPlugin: bool = False
|
|
|
|
def __init__(self, shortName: str | None, longName: str, helpText: str, callback: Callable[[Args], None]):
|
|
self.shortName = shortName
|
|
self.longName = longName
|
|
self.helpText = helpText
|
|
self.callback = callback
|
|
|
|
|
|
cmds: list[Cmd] = []
|
|
|
|
|
|
def append(cmd: Cmd):
|
|
cmd.isPlugin = True
|
|
cmds.append(cmd)
|
|
cmds.sort(key=lambda c: c.shortName or c.longName)
|
|
|
|
|
|
def runCmd(args: Args):
|
|
targetSpec = cast(str, args.consumeOpt(
|
|
"target", "host-" + shell.uname().machine))
|
|
|
|
componentSpec = args.consumeArg()
|
|
|
|
if componentSpec is None:
|
|
raise Exception("Component not specified")
|
|
|
|
component = builder.build(componentSpec, targetSpec)
|
|
|
|
os.environ["OSDK_TARGET"] = component.context.target.id
|
|
os.environ["OSDK_COMPONENT"] = component.id()
|
|
os.environ["OSDK_BUILDDIR"] = component.context.builddir()
|
|
|
|
shell.exec(component.outfile(), *args.args)
|
|
|
|
|
|
cmds += [Cmd("r", "run", "Run the target", runCmd)]
|
|
|
|
|
|
def testCmd(args: Args):
|
|
targetSpec = cast(str, args.consumeOpt(
|
|
"target", "host-" + shell.uname().machine))
|
|
builder.testAll(targetSpec)
|
|
|
|
|
|
cmds += [Cmd("t", "test", "Run all test targets", testCmd)]
|
|
|
|
|
|
def debugCmd(args: Args):
|
|
targetSpec = cast(str, args.consumeOpt(
|
|
"target", "host-" + shell.uname().machine))
|
|
|
|
componentSpec = args.consumeArg()
|
|
|
|
if componentSpec is None:
|
|
raise Exception("Component not specified")
|
|
|
|
component = builder.build(componentSpec, targetSpec)
|
|
|
|
os.environ["OSDK_TARGET"] = component.context.target.id
|
|
os.environ["OSDK_COMPONENT"] = component.id()
|
|
os.environ["OSDK_BUILDDIR"] = component.context.builddir()
|
|
|
|
shell.exec("lldb", "-o", "run", component.outfile(), *args.args)
|
|
|
|
|
|
cmds += [Cmd("d", "debug", "Debug the target", debugCmd)]
|
|
|
|
|
|
def buildCmd(args: Args):
|
|
targetSpec = cast(str, args.consumeOpt(
|
|
"target", "host-" + shell.uname().machine))
|
|
|
|
componentSpec = args.consumeArg()
|
|
|
|
if componentSpec is None:
|
|
builder.buildAll(targetSpec)
|
|
else:
|
|
builder.build(componentSpec, targetSpec)
|
|
|
|
|
|
cmds += [Cmd("b", "build", "Build the target", buildCmd)]
|
|
|
|
|
|
def listCmd(args: Args):
|
|
components = context.loadAllComponents()
|
|
targets = context.loadAllTargets()
|
|
|
|
vt100.title("Components")
|
|
if len(components) == 0:
|
|
print(f" (No components available)")
|
|
else:
|
|
print(vt100.indent(vt100.wordwrap(
|
|
", ".join(map(lambda m: m.id, components)))))
|
|
print()
|
|
|
|
vt100.title("Targets")
|
|
|
|
if len(targets) == 0:
|
|
print(f" (No targets available)")
|
|
else:
|
|
print(vt100.indent(vt100.wordwrap(", ".join(map(lambda m: m.id, targets)))))
|
|
|
|
print()
|
|
|
|
|
|
cmds += [Cmd("l", "list", "List the targets", listCmd)]
|
|
|
|
|
|
def cleanCmd(args: Args):
|
|
shell.rmrf(const.BUILD_DIR)
|
|
|
|
|
|
cmds += [Cmd("c", "clean", "Clean the build directory", cleanCmd)]
|
|
|
|
|
|
def nukeCmd(args: Args):
|
|
shell.rmrf(const.OSDK_DIR)
|
|
|
|
|
|
cmds += [Cmd("n", "nuke", "Clean the build directory and cache", nukeCmd)]
|
|
|
|
|
|
def helpCmd(args: Args):
|
|
usage()
|
|
|
|
print()
|
|
|
|
vt100.title("Description")
|
|
print(" Operating System Development Kit.")
|
|
|
|
print()
|
|
vt100.title("Commands")
|
|
for cmd in cmds:
|
|
pluginText = ""
|
|
if cmd.isPlugin:
|
|
pluginText = f"{vt100.CYAN}(plugin){vt100.RESET}"
|
|
|
|
print(
|
|
f" {vt100.GREEN}{cmd.shortName or ' '}{vt100.RESET} {cmd.longName} - {cmd.helpText} {pluginText}")
|
|
|
|
print()
|
|
|
|
|
|
cmds += [Cmd("h", "help", "Show this help message", helpCmd)]
|
|
|
|
|
|
def versionCmd(args: Args):
|
|
print(f"OSDK v{const.VERSION}\n")
|
|
|
|
|
|
cmds += [Cmd("v", "version", "Show current version", versionCmd)]
|
|
|
|
|
|
def graphCmd(args: Args):
|
|
targetSpec = cast(str, args.consumeOpt(
|
|
"target", "host-" + shell.uname().machine))
|
|
|
|
scope: str | None = cast(str | None, args.tryConsumeOpt("scope"))
|
|
onlyLibs: bool = args.consumeOpt("only-libs", False) == True
|
|
showDisabled: bool = args.consumeOpt("show-disabled", False) == True
|
|
|
|
context = contextFor(targetSpec)
|
|
|
|
graph.view(context, scope=scope, showExe=not onlyLibs,
|
|
showDisabled=showDisabled)
|
|
|
|
|
|
cmds += [Cmd("g", "graph", "Show dependency graph", graphCmd)]
|
|
|
|
|
|
def installCmd(args: Args):
|
|
project = context.loadProject(".")
|
|
|
|
for extSpec in project.extern:
|
|
ext = project.extern[extSpec]
|
|
|
|
extPath = os.path.join(const.EXTERN_DIR, extSpec)
|
|
|
|
if os.path.exists(extPath):
|
|
print(f"Skipping {extSpec}, already installed")
|
|
continue
|
|
|
|
print(f"Installing {extSpec}-{ext.tag} from {ext.git}...")
|
|
shell.popen("git", "clone", "--depth", "1", "--branch",
|
|
ext.tag, ext.git, extPath)
|
|
|
|
|
|
cmds += [Cmd("i", "install", "Install all the external packages", installCmd)]
|
|
|
|
|
|
def initCmd(args: Args):
|
|
"""
|
|
|
|
|
| - project.json
|
|
| - src/
|
|
| | - project_name/
|
|
| | - main.c
|
|
| | - manifest.json
|
|
| - meta/
|
|
| | - targets/
|
|
| | | - host-*.json
|
|
| | - plugins/
|
|
| | | - run.py
|
|
| - .gitignore
|
|
| - README.md
|
|
|
|
|
"""
|
|
|
|
print("This utility will walk you through creating a new project.")
|
|
print("This only covers the most common items, and tries to give sensible defaults.")
|
|
print()
|
|
print("First, let's create a project.json file.")
|
|
|
|
project_name = input("Project name: ")
|
|
description = input("Description: ")
|
|
|
|
to_create = ["src", "meta", os.path.join(
|
|
"meta", "targets"), os.path.join("meta", "plugins")]
|
|
|
|
os.mkdir(project_name.lower())
|
|
for directory in to_create:
|
|
os.mkdir(os.path.join(project_name.lower(), directory))
|
|
|
|
with open(os.path.join(project_name.lower(), "project.json"), "w") as f:
|
|
f.write(json.dumps({
|
|
"$schema": "https://schemas.cute.engineering/latest/osdk.manifest.component",
|
|
"name": project_name,
|
|
"type": "project",
|
|
"description": description,
|
|
"extern": {},
|
|
}, indent=4))
|
|
|
|
with open(os.path.join(project_name.lower(), ".gitignore"), "w") as f:
|
|
f.write(".osdk\n.ninja_log\n__pycache__\n")
|
|
|
|
with open(os.path.join(project_name.lower(), "README.md"), "w") as f:
|
|
f.write(f"# {project_name}\n")
|
|
f.write("I was created using the OSDK!\n")
|
|
f.write(
|
|
"You can find more information about the OSDK in its [Repo](https://github.com/cute-engineering/osdk)."
|
|
)
|
|
|
|
with open(os.path.join(project_name.lower(), "src", "main.c"), "w") as f:
|
|
f.write("#include <stdio.h>\n\n")
|
|
f.write("int main(void)\n{\n")
|
|
f.write(" printf(\"Hello, World!\\n\");\n")
|
|
f.write(" return 0;\n")
|
|
f.write("}")
|
|
|
|
with open(os.path.join(project_name.lower(), "src", "manifest.json"), "w") as f:
|
|
f.write(json.dumps({
|
|
"$schema": "https://schemas.cute.engineering/latest/osdk.manifest.component",
|
|
"id": project_name.lower(),
|
|
"type": "exe",
|
|
"description": description,
|
|
}, indent=4))
|
|
|
|
with open(os.path.join(project_name.lower(), "meta", "plugins", "run.py"), "w") as f:
|
|
f.write("from osdk import builder, shell\n")
|
|
f.write("from osdk.args import Args\n")
|
|
f.write("from osdk.cmds import Cmd, append\n\n")
|
|
f.write("def runCmd(args: Args) -> None:\n")
|
|
f.write(
|
|
f" {project_name.lower()} = builder.build(\"{project_name.lower()}\", \"host-{shell.uname().machine}\").outfile()\n"
|
|
)
|
|
f.write(f" shell.exec(*[{project_name.lower()}])")
|
|
f.write("\n\nappend(Cmd(\"s\", \"start\", \"Run the project\", runCmd))")
|
|
|
|
with open(os.path.join(project_name.lower(), "meta", "targets", f"host-{shell.uname().machine}.json"), "w") as f:
|
|
f.write(json.dumps({
|
|
"$schema": "https://schemas.cute.engineering/latest/osdk.manifest.component",
|
|
"id": f"host-{shell.uname().machine}",
|
|
"type": "target",
|
|
"props": {
|
|
"arch": shell.uname().machine,
|
|
"toolchain": "clang",
|
|
"sys": [
|
|
"@uname",
|
|
"sysname"
|
|
],
|
|
"abi": "unknown",
|
|
"freestanding": False,
|
|
"host": True,
|
|
},
|
|
"tools": {
|
|
"cc": {
|
|
"cmd": [
|
|
"@latest",
|
|
"clang"
|
|
],
|
|
"args": []
|
|
},
|
|
"cxx": {
|
|
"cmd": [
|
|
"@latest",
|
|
"clang++"
|
|
],
|
|
"args": []
|
|
},
|
|
"ld": {
|
|
"cmd": [
|
|
"@latest",
|
|
"clang++"
|
|
],
|
|
"args": [
|
|
]
|
|
},
|
|
"ar": {
|
|
"cmd": [
|
|
"@latest",
|
|
"llvm-ar"
|
|
],
|
|
"args": [
|
|
"rcs"
|
|
]
|
|
},
|
|
"as": {
|
|
"cmd": "clang",
|
|
"args": [
|
|
"-c"
|
|
]
|
|
}
|
|
}
|
|
}, indent=4))
|
|
|
|
shell.exec(*["git", "init", project_name.lower()])
|
|
print("Done! Don't forget to add a LICENSE ;)")
|
|
|
|
|
|
cmds += [Cmd("I", "init", "Start a new project", initCmd)]
|
|
|
|
|
|
def usage():
|
|
print(f"Usage: {const.ARGV0} <command> [args...]")
|
|
|
|
|
|
def exec(args: Args):
|
|
cmd = args.consumeArg()
|
|
|
|
if cmd is None:
|
|
raise Exception("No command specified")
|
|
|
|
for c in cmds:
|
|
if c.shortName == cmd or c.longName == cmd:
|
|
c.callback(args)
|
|
return
|
|
|
|
raise Exception(f"Unknown command {cmd}")
|