refactor(cli)!: split the contributor tool out of the ludic CLI
`ludic help` ended with a section titled "contributing to the toolchain itself", listing bootstrap, reseed, docs-gen and release tasks. None of that is available to someone who installed the language — those tasks need the repository — so the shipped tool was advertising work its user cannot do, in a namespace they have to read past to find `new` and `run`. The tasks move to a second program, dev.ludic -> bin/ludic-dev, built from a checkout and excluded from every release artifact. `ludic` keeps the project and package commands and nothing else; `ludic dev …` now explains where the tasks went instead of failing as an unknown command. What this shook out: the two programs share prelude/build/project/pkg, so the helpers each had accreted in whichever file first needed them — cc(), ensure_ludicc, the string functions, title_case, cmd_version — moved to where both can see them. The argument-shift indirection added for the `dev` namespace is gone with the namespace, so commands read argv directly again. `ludic-dev test` asserts the split rather than trusting it: the staged install must build a project, and `ludic dev build` there must fail while naming ludic-dev. install.sh keeps building older tags, whose bootstrap goes through main.ludic. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
f369fbd227
commit
e175619543
46 changed files with 630 additions and 558 deletions
|
|
@ -1,13 +1,13 @@
|
|||
# ludic — the command-line interface to the Ludic toolchain. One native binary
|
||||
# ludic — the command-line interface to the Ludic language. One native binary
|
||||
# (bin/ludic) written in Ludic and compiled by Ludic; the unix tools, clang and
|
||||
# the compiler it drives are invoked through the `run` intrinsic.
|
||||
#
|
||||
# Two audiences, one binary. A user of the language sees a small surface —
|
||||
# new, build, run, test, add, fmt — that works from any directory against an
|
||||
# installed toolchain. A contributor to the toolchain itself gets everything
|
||||
# else under `ludic dev`: bootstrapping the compiler from its IR seed, the
|
||||
# regression suites, the docs site, releases. Splitting them by namespace keeps
|
||||
# `ludic help` about the language rather than about the repository.
|
||||
# This is everything a user of the language does: create a project, build it, run
|
||||
# it, test it, add a package, format, upgrade. Nothing here is about the
|
||||
# toolchain's own repository — building the compiler, reseeding it, generating
|
||||
# the docs site, cutting a release live in a separate tool (dev.ludic ->
|
||||
# bin/ludic-dev) that only exists in a checkout and is never installed. Someone
|
||||
# who installs Ludic should not have to read past commands they cannot use.
|
||||
#
|
||||
# Installing (macOS, Linux):
|
||||
#
|
||||
|
|
@ -17,29 +17,13 @@
|
|||
# compiling Ludic needs a compiler:
|
||||
#
|
||||
# mkdir -p bin && clang selfhost/ludicc.seed.ll -o bin/ludicc
|
||||
# bin/ludicc tools/ludic-cli/main.ludic -o bin/ludic
|
||||
# bin/ludic dev build
|
||||
#
|
||||
# Thereafter `ludic dev build` rebuilds the whole toolchain — including the CLI
|
||||
# itself. The `dev` tasks run from the repository root.
|
||||
# bin/ludicc tools/ludic-cli/dev.ludic -o bin/ludic-dev
|
||||
# bin/ludic-dev build
|
||||
program Ludic {
|
||||
import "prelude.ludic"
|
||||
import "selfhost.ludic"
|
||||
import "build.ludic"
|
||||
import "project.ludic"
|
||||
import "tools.ludic"
|
||||
import "selfhost_test.ludic"
|
||||
import "test.ludic"
|
||||
import "json.ludic"
|
||||
import "lsp_test.ludic"
|
||||
import "forgejo.ludic"
|
||||
import "checks.ludic"
|
||||
import "docgen.ludic"
|
||||
import "docgen_gen.ludic"
|
||||
import "docgen_check.ludic"
|
||||
import "release.ludic"
|
||||
import "pkg.ludic"
|
||||
import "pkg_test.ludic"
|
||||
|
||||
function usage() -> void {
|
||||
print("ludic — the toolchain for the Ludic language")
|
||||
|
|
@ -71,61 +55,9 @@ program Ludic {
|
|||
print(" lsp run the language server on stdio (what editors spawn)")
|
||||
print(" help this message")
|
||||
print("")
|
||||
print("contributing to the toolchain itself:")
|
||||
print(" dev <task> build, test, bootstrap, docs, release (ludic dev help)")
|
||||
print("")
|
||||
print("A file argument runs it: ludic mygame.ludic")
|
||||
}
|
||||
|
||||
function dev_usage() -> void {
|
||||
print("ludic dev — tasks for a checkout of the Ludic toolchain (run from the repository root)")
|
||||
print("")
|
||||
print("build:")
|
||||
print(" dev build build the whole toolchain into bin/ (ludicc, ludic, ludic-fmt, ludic-lsp)")
|
||||
print(" dev build-cli build just bin/ludicc from the IR seed")
|
||||
print(" dev tools [--install] [--test] build the editor toolchain (ludic-fmt, ludic-lsp)")
|
||||
print("")
|
||||
print("test:")
|
||||
print(" dev test the full regression suite")
|
||||
print(" dev test --coverage per-file line coverage over the test specs")
|
||||
print(" dev test-pkg the package-manager suite (hermetic git repos, offline)")
|
||||
print(" dev selfhost-test the self-hosting suite (correctness + bootstrap fixpoints)")
|
||||
print(" dev test-tools the editor-toolchain suite")
|
||||
print(" dev test-lsp drive bin/ludic-lsp over real LSP traffic (exit 2 = no server)")
|
||||
print(" dev golden regenerate selfhost/golden/renders.sha256 (review with git diff)")
|
||||
print("")
|
||||
print("doc / lint checks (Ludic, no Python):")
|
||||
print(" dev check-docs every ```ludic doc fence parses (or is marked skip/expect-error)")
|
||||
print(" dev check-impl every implemented feature has a docs/language page")
|
||||
print(" dev check-vocabulary the vocabulary is in sync across grammar / lexer / header / parser")
|
||||
print(" dev lint-asset <file> validate one editor .json / .xml asset")
|
||||
print(" dev docs-gen [--out DIR] generate the documentation site (default build/pages)")
|
||||
print(" dev docs-check [DIR] coverage/integrity guard over a generated docs site")
|
||||
print(" dev docs-palette [--check] regenerate emit_color.ludic + palette.json from the palette table")
|
||||
print("")
|
||||
print("release:")
|
||||
print(" dev release [major|minor|patch] [--dry-run] [--publish]")
|
||||
print(" cut a release: CHANGELOG + VERSION bump + tag (+ Forgejo release)")
|
||||
print(" dev publish [vX.Y.Z] publish an already-tagged release; what CI runs on a tag push")
|
||||
print(" dev changelog-section <ver> print that release's CHANGELOG.md section")
|
||||
print(" dev changelog-render <ver> <date> <dir>")
|
||||
print(" render a CHANGELOG section from a directory of changesets")
|
||||
print("")
|
||||
print("self-host internals:")
|
||||
print(" dev selfhost-build [ludicc] [out] assemble + compile the self-host compiler")
|
||||
print(" dev bootstrap the self-hosting fixpoint proof (seeded from bin/ludicc)")
|
||||
print(" dev bootstrap-cfree rebuild the compiler from the seed with no C compiler")
|
||||
print(" dev reseed regenerate selfhost/ludicc.seed.ll after a compiler change")
|
||||
print(" dev game-build <ludicc> <game.ludic> <out> compile a Ludic game (headless)")
|
||||
print(" dev sh-compile <ludicc> <in.ludic> <out> compile + link one .ludic file")
|
||||
}
|
||||
|
||||
# a positional argument, or a default when absent
|
||||
function argn(i: int, dflt: pointer) -> pointer {
|
||||
if (i < arg_count()) { return arg(i) }
|
||||
return dflt
|
||||
}
|
||||
|
||||
# does `s` end in ".ludic"? — `ludic mygame.ludic` runs that file, the shape
|
||||
# the compiler-runner had before the CLI grew subcommands.
|
||||
function is_ludic_file(s: pointer) -> bool {
|
||||
|
|
@ -134,55 +66,6 @@ program Ludic {
|
|||
return sslice(s, n - 6, n) == ".ludic"
|
||||
}
|
||||
|
||||
# ---- ludic dev <task> — the toolchain repo's own tasks ---------------------
|
||||
# Every task here reads its arguments one position further along than a
|
||||
# top-level command, so the shift is set once, here.
|
||||
function dispatch_dev(cmd: pointer) -> int {
|
||||
g_shift = 1
|
||||
if (cmd == "build") { return cmd_dev_build() }
|
||||
if (cmd == "build-cli") { return cmd_dev_build_cli() }
|
||||
if (cmd == "tools") { return cmd_tools() }
|
||||
if (cmd == "clean") { return cmd_clean() }
|
||||
if (cmd == "test") {
|
||||
if (argn(3, "") == "--coverage") { return cmd_test_coverage() }
|
||||
return cmd_dev_test()
|
||||
}
|
||||
if (cmd == "selfhost-test") { return cmd_selfhost_test() }
|
||||
if (cmd == "test-tools") { return cmd_test_tools() }
|
||||
if (cmd == "test-lsp") { return cmd_test_lsp() }
|
||||
if (cmd == "test-pkg") { return cmd_test_pkg() }
|
||||
if (cmd == "check-docs") { return cmd_check_docs() }
|
||||
if (cmd == "check-impl") { return cmd_check_impl() }
|
||||
if (cmd == "check-vocabulary") { return cmd_check_vocab() }
|
||||
if (cmd == "lint-asset") { return cmd_lint_asset() }
|
||||
if (cmd == "docs-palette") { return cmd_docs_palette() }
|
||||
if (cmd == "docs-gen") { return cmd_docs_gen() }
|
||||
if (cmd == "docs-check") { return cmd_docs_check() }
|
||||
if (cmd == "golden") { return cmd_golden() }
|
||||
if (cmd == "bootstrap") { return cmd_bootstrap() }
|
||||
if (cmd == "bootstrap-cfree") { return cmd_bootstrap_cfree() }
|
||||
if (cmd == "reseed") { return cmd_reseed() }
|
||||
if (cmd == "selfhost-build") { return cmd_selfhost_build(argn(3, "bin/ludicc"), argn(4, "build/selfhost")) }
|
||||
if (cmd == "sh-compile") {
|
||||
if (arg_count() < 6) { err("usage: ludic dev sh-compile <ludicc> <in.ludic> <out>\n"); exit(1) }
|
||||
return cmd_sh_compile(arg(3), arg(4), arg(5))
|
||||
}
|
||||
if (cmd == "game-build") {
|
||||
if (arg_count() < 6) { err("usage: ludic dev game-build <ludicc> <game.ludic> <out>\n"); exit(1) }
|
||||
return cmd_game_build(arg(3), arg(4), arg(5))
|
||||
}
|
||||
if (cmd == "release") { return cmd_release() }
|
||||
if (cmd == "changelog-render") { return cmd_changelog_render() }
|
||||
if (cmd == "changelog-section") { return cmd_changelog_section() }
|
||||
if (cmd == "publish") { return cmd_publish() }
|
||||
if (cmd == "help") or (cmd == "--help") or (cmd == "-h") or (cmd == "") { dev_usage(); return 0 }
|
||||
# reported here rather than by entry, so the message names the dev task the
|
||||
# user actually typed and the usage shown is the dev one
|
||||
err(`ludic dev: unknown task '{cmd}'\n`)
|
||||
dev_usage()
|
||||
return 1
|
||||
}
|
||||
|
||||
# route one subcommand to its implementation and return its exit code; -1 when
|
||||
# the name is unknown. Returning (rather than exiting in place) lets entry tear
|
||||
# down the per-process scratch directory before the process ends.
|
||||
|
|
@ -206,13 +89,23 @@ program Ludic {
|
|||
if (cmd == "link-flags") { return cmd_pkg_link_flags() }
|
||||
|
||||
if (cmd == "version") or (cmd == "--version") or (cmd == "-v") { return cmd_version() }
|
||||
if (cmd == "help") or (cmd == "--help") or (cmd == "-h") { usage(); return 0 }
|
||||
if (cmd == "dev") { return dispatch_dev(argn(2, "")) }
|
||||
if (cmd == "help") or (cmd == "--help") or (cmd == "-h") { usage(); return 0 }
|
||||
|
||||
# The toolchain's own tasks used to live here under `ludic dev`. Say where
|
||||
# they went rather than printing "unknown command" at someone who read an
|
||||
# older README or a commit message.
|
||||
if (cmd == "dev") {
|
||||
err("ludic: the toolchain's own tasks live in `ludic-dev`, which is built from a\n")
|
||||
err(" checkout of the Ludic repository and is not part of an install:\n")
|
||||
err(" clang selfhost/ludicc.seed.ll -o bin/ludicc\n")
|
||||
err(" bin/ludicc tools/ludic-cli/dev.ludic -o bin/ludic-dev && bin/ludic-dev help\n")
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
entry {
|
||||
if (arg_count() < 2) { usage(); exit(1) }
|
||||
if (arg_count() < 2) { usage(); exit(1) }
|
||||
let cmd = arg(1)
|
||||
# `ludic mygame.ludic` — build and run that file
|
||||
if is_ludic_file(cmd) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue