feat(windows): the ludic CLI and ludic bundle on Windows
The CLI's shell commands go through shell(), which is run() on POSIX and a scratch script handed to Git for Windows' bash on Windows (exit codes read directly there). compile_app links through `ludicc -o` on Windows, ludic run starts the .exe, and ludic-dev build and ensure_ludicc assemble selfhost/ludicc.win.seed.ll, which ludic-dev reseed now writes beside the macOS seed. ludic bundle makes build/<name>/ with a GUI-subsystem exe carrying the .ico beside `app icon` as an llvm-rc resource, game.lpak and packs.index; ludicc gains --gui and --link, and quotes its whole link line for cmd.exe. Verified: ludic-dev test 135/135, selfhost-test 32/32; on the PC a checkout bootstraps from the Windows seed, ludic-dev build makes the toolchain, and `ludic bundle` makes build/Maroon Lake/, which runs from its own folder. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
63d9f61bf1
commit
55b0e2ebf6
22 changed files with 112450 additions and 818 deletions
|
|
@ -26,6 +26,14 @@ type: feat
|
|||
clip, loops, volume, rate and a balance pan through the output matrix, RIFF WAVE in PCM or
|
||||
float. It reads a clip through the asset pack, so a bundled game's first `Audio.load`
|
||||
succeeds - AVAudioPlayer takes a filesystem path, which is why macOS cannot.
|
||||
- **The CLI on Windows** — `ludic build`, `ludic run` and `ludic-dev build` work from a Windows
|
||||
checkout. Every shell command the CLI issues runs through Git for Windows' bash
|
||||
(`$LUDIC_BASH` names another), `compile_app` links through `ludicc -o`, and a checkout
|
||||
bootstraps from the new `selfhost/ludicc.win.seed.ll`, which `ludic-dev reseed` now
|
||||
writes beside the macOS seed.
|
||||
- **`ludic bundle` on Windows** — `build/<name>/` holding a GUI-subsystem `<name>.exe`, the
|
||||
`game.lpak` and `packs.index`; an `.ico` beside `app icon` is compiled with `llvm-rc` and
|
||||
linked in. `ludicc` gains `--gui` (no console behind the window) and `--link <file>`.
|
||||
- **Not yet** — the splash and `App.set_icon` do nothing, and `Http.*` is still a macOS
|
||||
platform layer; a Windows build that uses it stops with a message saying so.
|
||||
- **`Gl.*` on Windows** — `gl_win.ll` makes a WGL 4.1 core context on a hidden window's DC,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
111260
selfhost/ludicc.win.seed.ll
Normal file
111260
selfhost/ludicc.win.seed.ll
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -135,6 +135,8 @@ entry {
|
|||
var save = false # --save-temps: keep the intermediate .ll
|
||||
var run = false # compile then execute the result
|
||||
var target = null # --target <triple>: null means the host
|
||||
var gui = false # --gui: a Windows GUI-subsystem program (no console behind the window)
|
||||
var links = new []pointer # --link <file>: an extra input to the link (a compiled resource)
|
||||
g_coverage = false # --coverage: instrument each statement with a per-line hit counter
|
||||
|
||||
var ai = 1
|
||||
|
|
@ -150,6 +152,8 @@ entry {
|
|||
else if a == "--run" { run = true }
|
||||
else if a == "--coverage" { g_coverage = true }
|
||||
else if a == "--target" { ai += 1; if ai < arg_count() { target = arg(ai) } }
|
||||
else if a == "--gui" { gui = true }
|
||||
else if a == "--link" { ai += 1; if ai < arg_count() { push(links, arg(ai)) } }
|
||||
else if a == "-o" { ai += 1; if ai < arg_count() { out = arg(ai) } }
|
||||
else if a[0] == '-' {
|
||||
# an unknown flag is ignored (with a note) rather than mistaken for the input file
|
||||
|
|
@ -286,9 +290,20 @@ entry {
|
|||
cmd = `{cmd} {gl} {glt} -framework OpenGL`
|
||||
}
|
||||
}
|
||||
# A shipped Windows game is a GUI-subsystem program: started from Explorer, a
|
||||
# console-subsystem one opens a console window behind the game. mainCRTStartup keeps
|
||||
# the C runtime's ordinary main() entry rather than WinMain.
|
||||
if g_target_win and gui { cmd = `{cmd} -Wl,/subsystem:windows -Wl,/entry:mainCRTStartup` }
|
||||
var li = 0
|
||||
while li < len(links) { cmd = `{cmd} "{links[li]}"`; li += 1 }
|
||||
cmd = `{cmd} -o {out}`
|
||||
|
||||
let rc = run(cmd)
|
||||
# cmd.exe takes the first and last quote off a line that starts with one and holds more
|
||||
# than two - which a quoted clang path plus a quoted --link file is - so on Windows the
|
||||
# whole line wears one more pair for it to remove
|
||||
var line = cmd
|
||||
if host_is_windows() { line = `"{cmd}"` }
|
||||
let rc = run(line)
|
||||
if not save { remove_file(ll) }
|
||||
if rc != 0 { die("ludicc: link failed\n") }
|
||||
|
||||
|
|
|
|||
|
|
@ -58,10 +58,10 @@ function cmd_fetch_assets() -> int {
|
|||
continue
|
||||
}
|
||||
print(`fetch {rel}`)
|
||||
run(`mkdir -p "$(dirname {dest})"`)
|
||||
shell(`mkdir -p "$(dirname {dest})"`)
|
||||
if not shq(`curl -sSL --retry 3 -o {dest} {url}`) {
|
||||
err(`fetch-assets: cannot fetch {rel}\n`)
|
||||
run(`rm -f {dest}`)
|
||||
shell(`rm -f {dest}`)
|
||||
failed += 1
|
||||
} else {
|
||||
got += 1
|
||||
|
|
|
|||
|
|
@ -10,10 +10,12 @@
|
|||
# IR seed with clang alone — no C compiler is ever involved.
|
||||
function ensure_ludicc() -> void {
|
||||
if not in_toolchain_repo() { return }
|
||||
run("mkdir -p bin build")
|
||||
if (not is_exec("bin/ludicc")) or newer("selfhost/ludicc.seed.ll", "bin/ludicc") {
|
||||
print("cc: selfhost/ludicc.seed.ll -> bin/ludicc (from the IR seed, no C compiler)")
|
||||
if not shq(`{cc()} selfhost/ludicc.seed.ll -o bin/ludicc`) {
|
||||
shell("mkdir -p bin build")
|
||||
let seed = seed_file()
|
||||
let bin = `bin/{exe_name("ludicc")}`
|
||||
if (not is_exec(bin)) or newer(seed, bin) {
|
||||
print(`cc: {seed} -> {bin} (from the IR seed, no C compiler)`)
|
||||
if not shq(`{cc()} {seed} -o {bin}`) {
|
||||
err("ludic: could not assemble the seed\n"); exit(1)
|
||||
}
|
||||
}
|
||||
|
|
@ -30,14 +32,24 @@ function compile_app(src: pointer, out: pointer, mode: int, save: bool) -> bool
|
|||
let home = ludic_home()
|
||||
let pbf = prebuilt_link_flags() # #64: link resolved prebuilt module dylibs, if any
|
||||
let odir = dir_of_path(out)
|
||||
if len(odir) > 0 { run(`mkdir -p {odir}`) }
|
||||
run("mkdir -p build")
|
||||
if len(odir) > 0 { shell(`mkdir -p {odir}`) }
|
||||
shell("mkdir -p build")
|
||||
let ll = `{out}.ll`
|
||||
|
||||
# On Windows the compiler links too: ludicc knows the Windows runtime's files and
|
||||
# libraries (win32.ll, xaudio2, opengl32 ...), so saying them again here would be a
|
||||
# second copy to keep in step. It writes {out}.exe.
|
||||
if host_windows() {
|
||||
var flags = "--windowed"
|
||||
if mode == 2 { flags = "--headless" }
|
||||
if save { flags = flags + " --save-temps" }
|
||||
return shq(`{ludicc()} {flags} {src} -o {out}`)
|
||||
}
|
||||
|
||||
if mode == 2 {
|
||||
if not shq(`{ludicc()} --headless {src} --emit-llvm -o {ll}`) { return false }
|
||||
if not shq(`{cc()} -O2 {ll}{gl_link_flags(ll)}{pbf} -o {out}`) { return false }
|
||||
if not save { run(`rm -f {ll}`) }
|
||||
if not save { shell(`rm -f {ll}`) }
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +59,7 @@ function compile_app(src: pointer, out: pointer, mode: int, save: bool) -> bool
|
|||
let cocoa = `{home}runtime/native/cocoa.ll`
|
||||
let audio = `{home}runtime/native/audio.ll`
|
||||
if not shq(`{cc()} -O2 {ll} {cocoa} {audio} -framework Cocoa -Wl,-needed_framework,GameController -Wl,-needed_framework,AVFoundation -Wl,-rpath,@loader_path{gl_link_flags(ll)}{pbf} -o {out}`) { return false }
|
||||
if not save { run(`rm -f {ll}`) }
|
||||
if not save { shell(`rm -f {ll}`) }
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -75,8 +87,8 @@ function dir_of_path(p: pointer) -> pointer {
|
|||
# bin/ are kept so the running CLI survives; in the toolchain repo, rebuild them
|
||||
# any time with `ludic-dev build`.
|
||||
function cmd_clean() -> int {
|
||||
run("rm -rf build")
|
||||
run("rm -f out.ppm bin/*.tmp")
|
||||
shell("rm -rf build")
|
||||
shell("rm -f out.ppm bin/*.tmp")
|
||||
print("cleaned: build/ (incl. build/out.ppm) and stray artifacts (bin/ is left alone)")
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ function build_icon(src: pointer, out_icns: pointer) -> bool {
|
|||
return false
|
||||
}
|
||||
let set = `{tmp_dir()}/AppIcon.iconset`
|
||||
run(`rm -rf {set} && mkdir -p {set}`)
|
||||
shell(`rm -rf {set} && mkdir -p {set}`)
|
||||
if not icon_size(src, set, 16, "16x16") { return false }
|
||||
if not icon_size(src, set, 32, "16x16@2x") { return false }
|
||||
if not icon_size(src, set, 32, "32x32") { return false }
|
||||
|
|
@ -229,9 +229,108 @@ function xml_escape(s: pointer) -> pointer {
|
|||
|
||||
# ---- the command ------------------------------------------------------------
|
||||
|
||||
# ---- Windows: a folder --------------------------------------------------------
|
||||
#
|
||||
# A Windows game ships as a directory a player unzips anywhere and double-clicks
|
||||
# inside:
|
||||
#
|
||||
# build/<name>/<name>.exe windowed, GUI subsystem, the icon linked in as a resource
|
||||
# build/<name>/game.lpak the same pack a .app carries
|
||||
# build/<name>/packs.index pack / home / splash, read beside the executable
|
||||
#
|
||||
# `home <name>` points the game at %APPDATA%\<name>, so saves live where a Windows
|
||||
# game's saves live and the folder itself stays read-only, as a .app does.
|
||||
#
|
||||
# The icon is `app icon` with its extension changed to .ico, when that file exists:
|
||||
# Windows reads icons from a resource inside the executable, and the resource
|
||||
# compiler takes .ico only. A project makes one from the same artwork as its .icns.
|
||||
|
||||
# an LLVM tool beside the clang this toolchain uses (llvm-rc, ...)
|
||||
function llvm_tool(name: pointer) -> pointer {
|
||||
let env = getenv("LUDIC_CC")
|
||||
if env == null and Fs.exists(`C:/Program Files/LLVM/bin/{name}.exe`) { return `"C:/Program Files/LLVM/bin/{name}.exe"` }
|
||||
return name
|
||||
}
|
||||
|
||||
# the icon resource for the executable, or "" when there is no .ico to make it from
|
||||
function windows_icon_res(icon: pointer) -> pointer {
|
||||
if icon == "" { return "" }
|
||||
var ico = icon
|
||||
let n = len(icon)
|
||||
if n > 4 and icon[n - 4..n] == ".png" { ico = icon[0..n - 4] + ".ico" }
|
||||
if not Fs.exists(ico) { return "" }
|
||||
let rc = tmp_path("app.rc")
|
||||
let res = tmp_path("app.res")
|
||||
# the resource compiler resolves a relative name against its own directory, not ours
|
||||
let abs = win_slashes(capture_line(`cygpath -m "$(pwd)/{ico}"`))
|
||||
if not write_file(rc, `1 ICON "{abs}"` + nl()) { return "" }
|
||||
# -fo rather than /FO: Git Bash rewrites an argument that starts with "/" into a path
|
||||
if not shq(`{llvm_tool("llvm-rc")} -fo "{res}" "{rc}"`) {
|
||||
err("ludic bundle: warning: the icon resource did not compile; the game has no icon\n")
|
||||
return ""
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function cmd_bundle_windows() -> int {
|
||||
let entry = parse_build_args(2)
|
||||
if g_argerr { return 1 }
|
||||
if entry == "" { return no_entry() }
|
||||
|
||||
let m = read_root_manifest()
|
||||
let name = app_name(m)
|
||||
let root = `build/{name}`
|
||||
shell(`rm -rf "{root}"`)
|
||||
shell(`mkdir -p "{root}"`)
|
||||
|
||||
# 1. the executable - staged under a name with no spaces, as on macOS
|
||||
let res = windows_icon_res(manifest_app(m, "icon"))
|
||||
let staged = tmp_path("bundle_exe")
|
||||
var flags = "--windowed --gui"
|
||||
if res != "" { flags = flags + ` --link "{res}"` }
|
||||
if not shq(`{ludicc()} {flags} {entry} -o {staged}`) { return 1 }
|
||||
if not shq(`cp "{staged}.exe" "{root}/{name}.exe"`) {
|
||||
err("ludic bundle: could not place the executable\n")
|
||||
return 1
|
||||
}
|
||||
|
||||
# 2. the assets, exactly as the .app packs them
|
||||
let roots = pack_roots(m, 999)
|
||||
var packed = false
|
||||
let files = pack_gather_all(roots)
|
||||
pack_add_packages(files)
|
||||
pack_sort(files)
|
||||
if len(files.names) > 0 {
|
||||
let staged_pak = tmp_path("game.lpak")
|
||||
if not pack_write(staged_pak, files) { return 1 }
|
||||
if not shq(`cp "{staged_pak}" "{root}/game.lpak"`) {
|
||||
err("ludic bundle: could not place the asset pack\n")
|
||||
return 1
|
||||
}
|
||||
packed = true
|
||||
}
|
||||
|
||||
# 3. what to mount and where the game writes
|
||||
var idx = ""
|
||||
if packed { idx = idx + "pack game.lpak" + nl() }
|
||||
idx = idx + `home {name}` + nl()
|
||||
let splash = manifest_app(m, "splash")
|
||||
if splash != "" and packed {
|
||||
idx = idx + `splash {splash}` + nl()
|
||||
idx = idx + `splashbg {string(hex_color(manifest_app(m, "splash_bg")))}` + nl()
|
||||
}
|
||||
write_file(`{root}/packs.index`, idx)
|
||||
|
||||
print(`bundled {root}`)
|
||||
if res == "" { print(" no icon: put an .ico beside `app icon` in package.ludic (assets/app/icon.ico)") }
|
||||
if not packed { print(" no assets packed: nothing under assets/ and no `pack` line in package.ludic") }
|
||||
return 0
|
||||
}
|
||||
|
||||
function cmd_bundle() -> int {
|
||||
if host_windows() { return cmd_bundle_windows() }
|
||||
if not is_darwin() {
|
||||
err("ludic bundle: a .app is a macOS bundle; this host is not macOS\n")
|
||||
err("ludic bundle: this host is neither macOS (a .app) nor Windows (a folder)\n")
|
||||
return 1
|
||||
}
|
||||
let entry = parse_build_args(2)
|
||||
|
|
@ -247,8 +346,8 @@ function cmd_bundle() -> int {
|
|||
# and in `ps`, so it takes the display name rather than the file name - spaces
|
||||
# and all, which is what every shipped Mac application does.
|
||||
let contents = `{root}/Contents`
|
||||
run(`rm -rf "{root}"`)
|
||||
run(`mkdir -p "{contents}/MacOS" "{contents}/Resources"`)
|
||||
shell(`rm -rf "{root}"`)
|
||||
shell(`mkdir -p "{contents}/MacOS" "{contents}/Resources"`)
|
||||
|
||||
# 1. the binary, built windowed - a bundled game never wants the headless path.
|
||||
# It is compiled to a scratch path and copied in, because a display name is
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ function table_set(h: pointer, name: pointer) -> []pointer {
|
|||
|
||||
# concatenate every selfhost/*.ludic into one buffer (via cat, then read)
|
||||
function read_all_selfhost() -> pointer {
|
||||
run(`find selfhost -name '*.ludic' | sort | xargs cat > {tmp_dir()}/allsh.txt 2>/dev/null`)
|
||||
shell(`find selfhost -name '*.ludic' | sort | xargs cat > {tmp_dir()}/allsh.txt 2>/dev/null`)
|
||||
let s = read_file(`{tmp_dir()}/allsh.txt`)
|
||||
if s == null { return "" }
|
||||
return s
|
||||
|
|
|
|||
|
|
@ -959,7 +959,7 @@ function cmd_docs_gen() -> int {
|
|||
}
|
||||
i += 1
|
||||
}
|
||||
run(`mkdir -p {out}`)
|
||||
shell(`mkdir -p {out}`)
|
||||
palette_init()
|
||||
let sections = load_sections()
|
||||
let syms = build_symbols(sections)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ var forgejo_hdr: pointer = null
|
|||
function forgejo_headers() -> pointer {
|
||||
if forgejo_hdr == null {
|
||||
forgejo_hdr = tmp_path("forgejo_headers.txt")
|
||||
run(`umask 077 && printf 'Authorization: token %s\n' "$FORGEJO_TOKEN" > {forgejo_hdr}`)
|
||||
shell(`umask 077 && printf 'Authorization: token %s\n' "$FORGEJO_TOKEN" > {forgejo_hdr}`)
|
||||
}
|
||||
return forgejo_hdr
|
||||
}
|
||||
|
|
|
|||
|
|
@ -296,7 +296,7 @@ function cmd_test_lsp() -> int {
|
|||
let req_path = tmp_path("lsp_req.bin")
|
||||
let res_path = tmp_path("lsp_res.bin")
|
||||
write_file(req_path, sb_str(lsp_req))
|
||||
run(`{server} --stdio < {req_path} > {res_path} 2>{tmp_path("lsp_err.txt")}`)
|
||||
shell(`{server} --stdio < {req_path} > {res_path} 2>{tmp_path("lsp_err.txt")}`)
|
||||
let stream = read_file(res_path)
|
||||
if stream == null { bad("ludic-lsp produced no output"); return report() }
|
||||
lsp_parse_stream(stream)
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ function pack_write(out_path: pointer, l: PackList) -> bool {
|
|||
i += 1
|
||||
}
|
||||
|
||||
run(`mkdir -p "$(dirname {out_path})"`)
|
||||
shell(`mkdir -p "$(dirname {out_path})"`)
|
||||
let f = file_open(out_path, "wb")
|
||||
if f == null { err(`ludic pack: cannot write {out_path}\n`); return false }
|
||||
|
||||
|
|
|
|||
|
|
@ -242,10 +242,10 @@ function repo_url(module: pointer) -> pointer {
|
|||
function ensure_clone(module: pointer) -> pointer {
|
||||
let cache = `{store_root()}cache/{flat(module)}`
|
||||
if file_exists(cache) {
|
||||
run(`git -C {cache} fetch -q --tags 2>/dev/null`)
|
||||
shell(`git -C {cache} fetch -q --tags 2>/dev/null`)
|
||||
return cache
|
||||
}
|
||||
run(`mkdir -p {store_root()}cache`)
|
||||
shell(`mkdir -p {store_root()}cache`)
|
||||
if not shq(`git clone -q {repo_url(module)} {cache} 2>/dev/null`) {
|
||||
err(`ludic: cannot fetch {module} (git clone {repo_url(module)} failed)\n`)
|
||||
return ""
|
||||
|
|
@ -320,10 +320,10 @@ function snapshot(module: pointer, ver: pointer) -> pointer {
|
|||
if slen(h) == 0 { return "" }
|
||||
let dest = `{store_root()}{h}`
|
||||
if not file_exists(dest) {
|
||||
run(`rm -rf {dest}.tmp`)
|
||||
run(`cp -R {cache} {dest}.tmp`)
|
||||
run(`rm -rf {dest}.tmp/.git`)
|
||||
run(`mv {dest}.tmp {dest}`)
|
||||
shell(`rm -rf {dest}.tmp`)
|
||||
shell(`cp -R {cache} {dest}.tmp`)
|
||||
shell(`rm -rf {dest}.tmp/.git`)
|
||||
shell(`mv {dest}.tmp {dest}`)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
|
@ -332,9 +332,9 @@ function snapshot(module: pointer, ver: pointer) -> pointer {
|
|||
function link_module(module: pointer, hash: pointer) -> void {
|
||||
let dest = `{store_root()}{hash}`
|
||||
let link = `ludic_modules/{module}`
|
||||
run(`mkdir -p "$(dirname {link})"`)
|
||||
run(`rm -rf {link}`)
|
||||
run(`ln -s {dest} {link}`)
|
||||
shell(`mkdir -p "$(dirname {link})"`)
|
||||
shell(`rm -rf {link}`)
|
||||
shell(`ln -s {dest} {link}`)
|
||||
}
|
||||
|
||||
# ---- MVS resolution ----------------------------------------------------------
|
||||
|
|
@ -645,7 +645,7 @@ function cmd_pkg_vendor() -> int {
|
|||
let txt = read_file("package.lock.ludic")
|
||||
if txt == null { err("ludic: no package.lock.ludic (run ludic get first)\n"); return 1 }
|
||||
let locked = parse_lock(txt)
|
||||
run("rm -rf vendor")
|
||||
shell("rm -rf vendor")
|
||||
var i = 0
|
||||
while i < len(locked) {
|
||||
let m = locked[i]
|
||||
|
|
@ -653,8 +653,8 @@ function cmd_pkg_vendor() -> int {
|
|||
let dest = `{store_root()}{raw}`
|
||||
if not file_exists(dest) { err(`ludic: {m.module}@{m.ver} not in the store — run ludic get\n`); return 1 }
|
||||
let vdir = `vendor/{m.module}`
|
||||
run(`mkdir -p "$(dirname {vdir})"`)
|
||||
run(`cp -R {dest} {vdir}`)
|
||||
shell(`mkdir -p "$(dirname {vdir})"`)
|
||||
shell(`cp -R {dest} {vdir}`)
|
||||
print(` vendored {m.module}@{m.ver}`)
|
||||
i += 1
|
||||
}
|
||||
|
|
@ -680,7 +680,7 @@ function cmd_pkg_build_lib() -> int {
|
|||
if man.module != "" { name = capture_line(`basename {man.module}`) }
|
||||
if name == "" { name = capture_line(`basename {src} .ludic`) }
|
||||
let t = target_id()
|
||||
run(`mkdir -p lib/{t}`)
|
||||
shell(`mkdir -p lib/{t}`)
|
||||
let ludicc = getenv_or("LUDICC", "bin/ludicc")
|
||||
let ll = `{tmp_dir()}/buildlib.ll`
|
||||
let blerr = tmp_path("bl.err")
|
||||
|
|
@ -694,7 +694,7 @@ function cmd_pkg_build_lib() -> int {
|
|||
let why2 = capture_line(`tail -1 {blerr}`)
|
||||
err(`ludic build-lib: link failed — {why2}\n`); return 1
|
||||
}
|
||||
run(`rm -f {ll}`)
|
||||
shell(`rm -f {ll}`)
|
||||
print(`built {out} (target {t})`)
|
||||
print(`add to package.ludic: kind prebuilt / targets "{t}" — then commit lib/ and git tag`)
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@
|
|||
|
||||
# write `body` to `path`, creating parent directories
|
||||
function pt_write(path: pointer, body: pointer) -> void {
|
||||
run(`mkdir -p "$(dirname {path})"`)
|
||||
shell(`mkdir -p "$(dirname {path})"`)
|
||||
write_file(path, body)
|
||||
}
|
||||
|
||||
# commit the current tree of `dir` and tag it `tag`
|
||||
function pt_commit_tag(dir: pointer, tag: pointer) -> void {
|
||||
run(`git -C {dir} add -A`)
|
||||
run(`git -C {dir} -c user.email=t@t.test -c user.name=tester commit -q -m {tag}`)
|
||||
run(`git -C {dir} tag {tag}`)
|
||||
shell(`git -C {dir} add -A`)
|
||||
shell(`git -C {dir} -c user.email=t@t.test -c user.name=tester commit -q -m {tag}`)
|
||||
shell(`git -C {dir} tag {tag}`)
|
||||
}
|
||||
|
||||
function cmd_test_pkg() -> int {
|
||||
|
|
@ -34,13 +34,13 @@ function cmd_test_pkg() -> int {
|
|||
let proxy = `{work}/proxy`
|
||||
let store = `{work}/store`
|
||||
let proj = `{work}/proj`
|
||||
run(`rm -rf {work}`)
|
||||
run(`mkdir -p {proxy} {store} {proj}`)
|
||||
shell(`rm -rf {work}`)
|
||||
shell(`mkdir -p {proxy} {store} {proj}`)
|
||||
|
||||
# ---- fixture packages -----------------------------------------------------
|
||||
# example.test/util: a leaf source package, two published versions.
|
||||
let util = `{proxy}/example.test/util`
|
||||
run(`git -C {util} init -q 2>/dev/null || ( mkdir -p {util} && git -C {util} init -q )`)
|
||||
shell(`git -C {util} init -q 2>/dev/null || ( mkdir -p {util} && git -C {util} init -q )`)
|
||||
pt_write(`{util}/package.ludic`, `package "example.test/util"` + nl() + `version "1.0.0"` + nl() + "kind source" + nl() + `provides "Util"` + nl())
|
||||
pt_write(`{util}/util.ludic`, "function util_tag() -> pointer { return \"u1\" }\n")
|
||||
pt_commit_tag(util, "v1.0.0")
|
||||
|
|
@ -50,7 +50,7 @@ function cmd_test_pkg() -> int {
|
|||
|
||||
# example.test/greeter: requires util >=1.0.0, provides Greet.
|
||||
let greeter = `{proxy}/example.test/greeter`
|
||||
run(`mkdir -p {greeter} && git -C {greeter} init -q`)
|
||||
shell(`mkdir -p {greeter} && git -C {greeter} init -q`)
|
||||
pt_write(`{greeter}/package.ludic`, `package "example.test/greeter"` + nl() + `version "1.0.0"` + nl() + "kind source" + nl() + `provides "Greet"` + nl() + `require "example.test/util" "1.0.0"` + nl())
|
||||
pt_write(`{greeter}/greet.ludic`, "function greet_hello() -> pointer { return \"hi\" }\n")
|
||||
pt_commit_tag(greeter, "v1.0.0")
|
||||
|
|
@ -105,14 +105,14 @@ function cmd_test_pkg() -> int {
|
|||
|
||||
# tamper with a stored file — verify must now flag it
|
||||
let uhash = capture_line(`grep 'example.test/util' {proj}/package.lock.ludic | sed 's/.*sha256://; s/\".*//'`)
|
||||
run(`echo tampered >> {store}/{uhash}/util.ludic`)
|
||||
shell(`echo tampered >> {store}/{uhash}/util.ludic`)
|
||||
if not shq(`( cd {proj} && {envp} {root}/bin/ludic verify > {work}/verify2.out 2>&1 )`) {
|
||||
ok("ludic verify detects a tampered store entry")
|
||||
} else { bad("ludic verify missed a tampered store entry") }
|
||||
# heal the store: a content-addressed entry is keyed by its hash-named dir, so
|
||||
# `ludic get` trusts an existing one — drop it first, then re-fetch a clean copy.
|
||||
run(`rm -rf {store}/{uhash}`)
|
||||
run(`( cd {proj} && {envp} {root}/bin/ludic get > /dev/null 2>&1 )`)
|
||||
shell(`rm -rf {store}/{uhash}`)
|
||||
shell(`( cd {proj} && {envp} {root}/bin/ludic get > /dev/null 2>&1 )`)
|
||||
if shq(`( cd {proj} && {envp} {root}/bin/ludic verify > {work}/verify3.out 2>&1 )`) {
|
||||
ok("re-fetching heals a tampered store entry (ludic verify green again)")
|
||||
} else { bad2("store did not heal after re-fetch", capture_line(`tail -1 {work}/verify3.out`)) }
|
||||
|
|
@ -125,7 +125,7 @@ function cmd_test_pkg() -> int {
|
|||
# relative to the compile CWD, so — exactly like `ludic build` — the game is compiled
|
||||
# from the repo root with the package linked in via an absolute LUDIC_MODULES.
|
||||
let epkg = `{proxy}/example.test/enginepkg`
|
||||
run(`mkdir -p {epkg} && git -C {epkg} init -q`)
|
||||
shell(`mkdir -p {epkg} && git -C {epkg} init -q`)
|
||||
pt_write(`{epkg}/package.ludic`, `package "example.test/enginepkg"` + nl() + `version "1.0.0"` + nl() + "kind source" + nl() + `provides "EnginePkg"` + nl())
|
||||
pt_write(`{epkg}/lib.ludic`, "property Health { hp: int = 0, max: int = 0 }\nmodel Unit { Health }\n@Public @OnSpawn(Unit) handler Init { Health.hp = Health.max }\nfunction bonus() -> int { return 7 }\n")
|
||||
pt_commit_tag(epkg, "v1.0.0")
|
||||
|
|
@ -143,7 +143,7 @@ function cmd_test_pkg() -> int {
|
|||
|
||||
# ---- namespace collision policy (v1: hard error) --------------------------
|
||||
let dupe = `{proxy}/example.test/dupe`
|
||||
run(`mkdir -p {dupe} && git -C {dupe} init -q`)
|
||||
shell(`mkdir -p {dupe} && git -C {dupe} init -q`)
|
||||
pt_write(`{dupe}/package.ludic`, `package "example.test/dupe"` + nl() + `version "1.0.0"` + nl() + "kind source" + nl() + `provides "Util"` + nl())
|
||||
pt_write(`{dupe}/dupe.ludic`, "function dupe_tag() -> pointer { return \"d\" }\n")
|
||||
pt_commit_tag(dupe, "v1.0.0")
|
||||
|
|
@ -155,7 +155,7 @@ function cmd_test_pkg() -> int {
|
|||
|
||||
# ---- prebuilt target matrix ----------------------------------------------
|
||||
let pb = `{proxy}/example.test/prebuilt`
|
||||
run(`mkdir -p {pb} && git -C {pb} init -q`)
|
||||
shell(`mkdir -p {pb} && git -C {pb} init -q`)
|
||||
pt_write(`{pb}/package.ludic`, `package "example.test/prebuilt"` + nl() + `version "1.0.0"` + nl() + "kind prebuilt" + nl() + `provides "Blob"` + nl() + `targets "made-up-target"` + nl())
|
||||
pt_write(`{pb}/README`, "a prebuilt lib shipping no artifact for this host\n")
|
||||
pt_commit_tag(pb, "v1.0.0")
|
||||
|
|
@ -165,7 +165,7 @@ function cmd_test_pkg() -> int {
|
|||
pt_write(`{proj}/package.ludic`, `package "app"` + nl() + `version "0.0.0"` + nl() + `require "example.test/greeter" "1.0.0"` + nl() + `require "example.test/util" "1.2.0"` + nl())
|
||||
|
||||
# ---- ludic vendor: hermetic copy ---------------------------------------------
|
||||
run(`( cd {proj} && {envp} {root}/bin/ludic get > /dev/null 2>&1 )`)
|
||||
shell(`( cd {proj} && {envp} {root}/bin/ludic get > /dev/null 2>&1 )`)
|
||||
if shq(`( cd {proj} && {envp} {root}/bin/ludic vendor > {work}/vendor.out 2>&1 )`) and file_exists(`{proj}/vendor/example.test/util/util.ludic`) {
|
||||
ok("ludic vendor copies the resolved packages into ./vendor")
|
||||
} else { bad2("ludic vendor failed", capture_line(`tail -1 {work}/vendor.out`)) }
|
||||
|
|
@ -175,7 +175,7 @@ function cmd_test_pkg() -> int {
|
|||
# Foo.* namespace (@Namespace) with no compiler edit; a consumer game imports it
|
||||
# and both take effect. Source path → runs everywhere (not dylib/macOS-gated).
|
||||
let cp = `{proxy}/example.test/coachpack`
|
||||
run(`mkdir -p {cp} && git -C {cp} init -q`)
|
||||
shell(`mkdir -p {cp} && git -C {cp} init -q`)
|
||||
pt_write(`{cp}/package.ludic`, `package "example.test/coachpack"` + nl() + `version "1.0.0"` + nl() + "kind source" + nl() + `provides "Coach"` + nl())
|
||||
pt_write(`{cp}/coach.ludic`, "property Score { value: int = 0 }\n@EngineSystem(Score, Update) function esys_score() -> void {\n let P = world_prop_id(\"Score\")\n let e = world_query_next(P, 0)\n if e >= 0 { world_set(e, P, 0, world_get(e, P, 0) + 1) }\n}\n@Namespace(Coach) function coach_bonus() -> int { return 99 }\n")
|
||||
pt_commit_tag(cp, "v1.0.0")
|
||||
|
|
@ -183,7 +183,7 @@ function cmd_test_pkg() -> int {
|
|||
pt_write(`{proj4}/package.ludic`, `package "game"` + nl() + `version "0.0.0"` + nl() + `require "example.test/coachpack" "1.0.0"` + nl())
|
||||
pt_write(`{proj4}/game.ludic`, "program Game {\n import \"example.test/coachpack/coach.ludic\"\n model Player { Score }\n @OnSpawn(Player) handler Init { }\n var frames: int = 0\n handler Boot phase Start { let e = world_spawn(world_model_id(\"Player\")) }\n handler Run phase Update {\n frames = frames + 1\n if frames >= 5 {\n let P = world_prop_id(\"Score\")\n let e = world_query_next(P, 0)\n print(world_get(e, P, 0))\n print(Coach.bonus())\n quit()\n }\n }\n}\n")
|
||||
if shq(`( cd {proj4} && {envp} {root}/bin/ludic get > {work}/get4.out 2>&1 )`) {
|
||||
run(`LUDIC_HOME={root} LUDIC_MODULES={proj4}/ludic_modules {root}/bin/ludicc --headless {proj4}/game.ludic -o {proj4}/game 2>{work}/gc4.err`)
|
||||
shell(`LUDIC_HOME={root} LUDIC_MODULES={proj4}/ludic_modules {root}/bin/ludicc --headless {proj4}/game.ludic -o {proj4}/game 2>{work}/gc4.err`)
|
||||
if file_exists(`{proj4}/game`) {
|
||||
let got = capture_line(`printf 'aaaaa' | {proj4}/game`)
|
||||
if got == "4 99" {
|
||||
|
|
@ -203,7 +203,7 @@ function cmd_test_pkg() -> int {
|
|||
}
|
||||
let tgt = `native-{capture_line("uname -m")}`
|
||||
let mp = `{proxy}/example.test/manapack`
|
||||
run(`mkdir -p {mp} && git -C {mp} init -q`)
|
||||
shell(`mkdir -p {mp} && git -C {mp} init -q`)
|
||||
pt_write(`{mp}/package.ludic`, `package "example.test/manapack"` + nl() + `version "1.0.0"` + nl() + "kind prebuilt" + nl() + `provides "Mana"` + nl() + `targets "{tgt}"` + nl())
|
||||
pt_write(`{mp}/manapack.ludic`, "program ManaPack {\n function module_init() -> void { world_register_prop(\"Mana\", 2) }\n @System(Update) function mana_regen() -> void {\n let M = world_prop_id(\"Mana\")\n let e = world_query_next(M, 0)\n if e >= 0 { let v = world_get(e, M, 0); world_set(e, M, 0, v + 1) }\n }\n function mana_bonus() -> int { return 42 }\n}\n")
|
||||
if shq(`( cd {mp} && LUDICC={root}/bin/ludicc {root}/bin/ludic build-lib manapack.ludic > {work}/bl.out 2>&1 )`) and file_exists(`{mp}/lib/{tgt}/libmanapack.dylib`) {
|
||||
|
|
@ -219,7 +219,7 @@ function cmd_test_pkg() -> int {
|
|||
} else { bad2("prebuilt ludic get failed", capture_line(`tail -1 {work}/get3.out`)) }
|
||||
# compile the ECS consumer from the repo root (so runtime/native resolves), then
|
||||
# link the module dylib via `ludic link-flags` (a real external build's link step).
|
||||
run(`LUDIC_HOME={root} {root}/bin/ludicc --headless {proj3}/game.ludic --emit-llvm -o {proj3}/game.ll 2>{work}/gc.err`)
|
||||
shell(`LUDIC_HOME={root} {root}/bin/ludicc --headless {proj3}/game.ludic --emit-llvm -o {proj3}/game.ll 2>{work}/gc.err`)
|
||||
let flags = capture_line(`( cd {proj3} && LUDIC_STORE={store} {root}/bin/ludic link-flags )`)
|
||||
if shq(`{cc()} -O2 -Wno-override-module {proj3}/game.ll {flags} -o {proj3}/game 2>{work}/gl.err`) {
|
||||
let got = capture_line(`printf 'aaaaaa' | {proj3}/game`)
|
||||
|
|
|
|||
|
|
@ -57,13 +57,53 @@ function is_exec(path: pointer) -> bool { return shq(`test -x {path}`) }
|
|||
function newer(a: pointer, b: pointer) -> bool { return shq(`test {a} -nt {b}`) }
|
||||
|
||||
# ---- process control --------------------------------------------------------
|
||||
#
|
||||
# Every command this tool issues is POSIX shell: mkdir -p, rm -rf, test -x, pipes
|
||||
# through tr and sed. On macOS and Linux the C library's system() is /bin/sh, so
|
||||
# `shell` is `run`. On Windows system() is cmd.exe, which speaks none of it, so each
|
||||
# command is written to a scratch script and handed to Git for Windows' bash - the
|
||||
# Windows toolchain's one requirement beyond LLVM; $LUDIC_BASH names another bash.
|
||||
# A script rather than `bash -c "..."`: a command line goes through cmd.exe's quoting
|
||||
# and then bash's startup parser, and a command with quotes of its own does not
|
||||
# survive both.
|
||||
|
||||
# `run` returns the raw wait status; the program's exit code is the high byte.
|
||||
function exit_code(st: int) -> int { return (st >> 8) & 255 }
|
||||
function host_windows() -> bool {
|
||||
let os = getenv("OS")
|
||||
if os == null { return false }
|
||||
return os == "Windows_NT"
|
||||
}
|
||||
|
||||
var g_bash: pointer = null
|
||||
function win_bash() -> pointer {
|
||||
if g_bash == null { g_bash = getenv_or("LUDIC_BASH", "C:/Program Files/Git/bin/bash.exe") }
|
||||
return g_bash
|
||||
}
|
||||
|
||||
var g_shell_n: int = 0
|
||||
function shell(cmd: pointer) -> int {
|
||||
if not host_windows() { return run(cmd) }
|
||||
# numbered, so a command that is itself running this tool's script cannot have it
|
||||
# rewritten underneath it
|
||||
g_shell_n += 1
|
||||
let script = tmp_path(`sh_{string(g_shell_n)}.sh`)
|
||||
if not write_file(script, cmd + "\n") { return 1 }
|
||||
# cmd.exe takes the first and last quote off a line that starts with one, so the
|
||||
# whole line wears one more pair for it to remove
|
||||
let st = run(`""{win_bash()}" "{script}""`)
|
||||
Fs.remove(script)
|
||||
return st
|
||||
}
|
||||
|
||||
# `run` returns the raw wait status on POSIX, where the exit code is the high byte;
|
||||
# Windows' system() returns the exit code itself.
|
||||
function exit_code(st: int) -> int {
|
||||
if host_windows() { return st }
|
||||
return (st >> 8) & 255
|
||||
}
|
||||
# run a command, returning its exit code (0 = success)
|
||||
function sh(cmd: pointer) -> int { return exit_code(run(cmd)) }
|
||||
function sh(cmd: pointer) -> int { return exit_code(shell(cmd)) }
|
||||
# run a command, true when it succeeded
|
||||
function shq(cmd: pointer) -> bool { return exit_code(run(cmd)) == 0 }
|
||||
function shq(cmd: pointer) -> bool { return exit_code(shell(cmd)) == 0 }
|
||||
|
||||
# ---- scratch files ------------------------------------------------------------
|
||||
# Every scratch file the runner writes lives under one per-process directory
|
||||
|
|
@ -76,7 +116,7 @@ function tmp_dir() -> pointer {
|
|||
var base = Os.temp_dir()
|
||||
if len(base) > 1 and base[len(base) - 1] == '/' { base = base[0..len(base) - 1] } # macOS: TMPDIR ends in '/'
|
||||
x_tmp = `{base}/ludic_{Os.pid()}`
|
||||
run(`mkdir -p {x_tmp}`)
|
||||
Fs.mkdir(x_tmp) # not the shell: on Windows it keeps its scripts here
|
||||
}
|
||||
return x_tmp
|
||||
}
|
||||
|
|
@ -89,6 +129,9 @@ function tmp_path(name: pointer) -> pointer { return `{tmp_dir()}/{name}` }
|
|||
function tmp_cleanup() -> void {
|
||||
if x_tmp == null { return }
|
||||
if getenv_or("LUDIC_KEEP_TMP", "0") == "1" { print(`scratch kept: {x_tmp}`) }
|
||||
# on Windows the shell's own script is open in this directory while it runs, so it is
|
||||
# removed by cmd.exe instead
|
||||
else if host_windows() { run(`rmdir /s /q "{win_backslashes(x_tmp)}" 2>nul`) }
|
||||
else { run(`rm -rf {x_tmp}`) }
|
||||
x_tmp = null
|
||||
}
|
||||
|
|
@ -96,7 +139,7 @@ function tmp_cleanup() -> void {
|
|||
# run `cmd` and return its stdout (stderr discarded). Never null.
|
||||
function capture(cmd: pointer) -> pointer {
|
||||
let tmp = tmp_path("capture.out")
|
||||
run(`{cmd} > {tmp} 2>/dev/null`)
|
||||
shell(`{cmd} > {tmp} 2>/dev/null`)
|
||||
let s = read_file(tmp)
|
||||
if (s == null) { return "" }
|
||||
return s
|
||||
|
|
@ -214,17 +257,19 @@ function self_dir() -> pointer {
|
|||
var a0 = arg(0)
|
||||
var slash = -1
|
||||
var i = 0
|
||||
while a0[i] != 0 { if a0[i] == '/' { slash = i }; i += 1 }
|
||||
while a0[i] != 0 { if a0[i] == '/' or a0[i] == 92 { slash = i }; i += 1 }
|
||||
if slash < 0 {
|
||||
let found = capture_line(`command -v {a0}`)
|
||||
var lookup = `command -v {a0}`
|
||||
if host_windows() { lookup = `cygpath -m "$(command -v {a0})"` }
|
||||
let found = capture_line(lookup)
|
||||
if found == "" { return "" }
|
||||
a0 = found
|
||||
slash = -1
|
||||
i = 0
|
||||
while a0[i] != 0 { if a0[i] == '/' { slash = i }; i += 1 }
|
||||
while a0[i] != 0 { if a0[i] == '/' or a0[i] == 92 { slash = i }; i += 1 }
|
||||
if slash < 0 { return "" }
|
||||
}
|
||||
return a0[0..slash + 1]
|
||||
return win_slashes(a0[0..slash + 1])
|
||||
}
|
||||
|
||||
# The toolchain install root, with a trailing '/', or "" for the current
|
||||
|
|
@ -242,7 +287,7 @@ function ludic_home() -> pointer {
|
|||
var d = self_dir()
|
||||
if len(d) >= 4 and d[len(d) - 4..len(d)] == "bin/" { d = d[0..len(d) - 4] }
|
||||
# a source checkout the CLI was run from elsewhere in: prefer a ./bin here
|
||||
if not is_exec(`{d}bin/ludicc`) and is_exec("bin/ludicc") { d = "" }
|
||||
if not is_exec(`{d}bin/{exe_name("ludicc")}`) and is_exec(`bin/{exe_name("ludicc")}`) { d = "" }
|
||||
g_home = d
|
||||
return g_home
|
||||
}
|
||||
|
|
@ -250,7 +295,7 @@ function ludic_home() -> pointer {
|
|||
# the path to a toolchain binary (ludicc, ludic-fmt, ludic-lsp). Falls back to
|
||||
# the bare name — on $PATH — when the install root has no bin/ of its own.
|
||||
function tool(name: pointer) -> pointer {
|
||||
let p = `{ludic_home()}bin/{name}`
|
||||
let p = `{ludic_home()}bin/{exe_name(name)}`
|
||||
if is_exec(p) { return p }
|
||||
return name
|
||||
}
|
||||
|
|
@ -259,8 +304,37 @@ function ludicc() -> pointer { return tool("ludicc") }
|
|||
|
||||
# true in a checkout of the toolchain repo itself, where a seed is present to
|
||||
# assemble the compiler from.
|
||||
# a program's file name on this host: `name` everywhere but Windows, `name.exe` there
|
||||
function exe_name(name: pointer) -> pointer {
|
||||
if host_windows() { return name + ".exe" }
|
||||
return name
|
||||
}
|
||||
|
||||
# the compiler's IR seed for this host. The IR is not portable between the two: each
|
||||
# names its own C library (see selfhost/backend/stdlib/emit_win.ludic).
|
||||
function seed_file() -> pointer {
|
||||
if host_windows() { return "selfhost/ludicc.win.seed.ll" }
|
||||
return "selfhost/ludicc.seed.ll"
|
||||
}
|
||||
|
||||
# a copy of a path with every '\' as '/', and one with every '/' as '\' (for cmd.exe)
|
||||
function win_slashes(p: pointer) -> pointer {
|
||||
let n = len(p)
|
||||
let b = bytes(n + 1)
|
||||
for i in 0 .. n { if p[i] == 92 { b[i] = '/' } else { b[i] = p[i] } }
|
||||
b[n] = 0
|
||||
return b
|
||||
}
|
||||
function win_backslashes(p: pointer) -> pointer {
|
||||
let n = len(p)
|
||||
let b = bytes(n + 1)
|
||||
for i in 0 .. n { if p[i] == '/' { b[i] = 92 } else { b[i] = p[i] } }
|
||||
b[n] = 0
|
||||
return b
|
||||
}
|
||||
|
||||
function in_toolchain_repo() -> bool {
|
||||
return file_exists("selfhost/ludicc.seed.ll") and file_exists("tools/ludic-cli/main.ludic")
|
||||
return file_exists(seed_file()) and file_exists("tools/ludic-cli/main.ludic")
|
||||
}
|
||||
|
||||
# ---- strings ----------------------------------------------------------------
|
||||
|
|
@ -374,4 +448,13 @@ function title_case(s: pointer) -> pointer {
|
|||
# clang assembles the emitted IR and drives the linker. -Wno-override-module for
|
||||
# the same reason the compiler itself passes it (see selfhost/main.ludic): the IR
|
||||
# names no target triple, so clang substitutes the host's and warns every time.
|
||||
function cc() -> pointer { return getenv_or("LUDIC_CC", "clang") + " -Wno-override-module" }
|
||||
# On Windows the LLVM installer leaves clang off %PATH% unless asked, so its own
|
||||
# location is the default there.
|
||||
function cc() -> pointer {
|
||||
let env = getenv("LUDIC_CC")
|
||||
if env != null { return env + " -Wno-override-module" }
|
||||
if host_windows() and Fs.exists("C:/Program Files/LLVM/bin/clang.exe") {
|
||||
return "\"C:/Program Files/LLVM/bin/clang.exe\" -Wno-override-module"
|
||||
}
|
||||
return "clang -Wno-override-module"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ function cmd_new() -> int {
|
|||
err(`ludic new: {name} already exists\n`)
|
||||
return 1
|
||||
}
|
||||
run(`mkdir -p {name}/src {name}/tests {name}/assets`)
|
||||
shell(`mkdir -p {name}/src {name}/tests {name}/assets`)
|
||||
if not write_file(`{name}/package.ludic`, template_manifest(name)) {
|
||||
err(`ludic new: cannot write {name}/package.ludic\n`)
|
||||
return 1
|
||||
|
|
@ -312,7 +312,7 @@ function cmd_run() -> int {
|
|||
if entry == "" { return no_entry() }
|
||||
let out = output_path(entry)
|
||||
if not compile_app(entry, out, g_mode, g_save) { return 1 }
|
||||
return sh(`./{out}`)
|
||||
return sh(`./{exe_name(out)}`)
|
||||
}
|
||||
|
||||
# `ludic mygame.ludic` — the file is argv[1], so the scan starts there.
|
||||
|
|
@ -322,7 +322,7 @@ function cmd_run_file() -> int {
|
|||
if entry == "" { return no_entry() }
|
||||
let out = output_path(entry)
|
||||
if not compile_app(entry, out, g_mode, g_save) { return 1 }
|
||||
return sh(`./{out}`)
|
||||
return sh(`./{exe_name(out)}`)
|
||||
}
|
||||
|
||||
# ---- ludic test -------------------------------------------------------------
|
||||
|
|
@ -354,7 +354,7 @@ function cmd_test() -> int {
|
|||
if not file_exists(files[mi]) { err(`ludic test: no such file: {files[mi]}\n`); return 1 }
|
||||
mi += 1
|
||||
}
|
||||
run("mkdir -p build")
|
||||
shell("mkdir -p build")
|
||||
var failed = 0
|
||||
var i = 0
|
||||
while i < len(files) {
|
||||
|
|
@ -508,7 +508,7 @@ function cmd_doctor() -> int {
|
|||
function cmd_version() -> int {
|
||||
let cc = ludicc()
|
||||
if is_exec(cc) {
|
||||
run(`{cc} --version`)
|
||||
shell(`{cc} --version`)
|
||||
return 0
|
||||
}
|
||||
let v = read_file(`{ludic_home()}VERSION`)
|
||||
|
|
|
|||
|
|
@ -240,17 +240,17 @@ function cmd_changelog_render() -> int {
|
|||
# release section (or at the end of the header if this is the first release).
|
||||
function prepend_changelog() -> void {
|
||||
if not file_exists("CHANGELOG.md") {
|
||||
run("printf '# Changelog\n\nAll notable changes to the Ludic toolchain, newest first. Generated from the\nchangesets under changes/ by ludic-dev release; do not edit released sections by hand.\n\n' > CHANGELOG.md")
|
||||
shell("printf '# Changelog\n\nAll notable changes to the Ludic toolchain, newest first. Generated from the\nchangesets under changes/ by ludic-dev release; do not edit released sections by hand.\n\n' > CHANGELOG.md")
|
||||
}
|
||||
let ln = capture_line("grep -n '^## ' CHANGELOG.md | head -1 | cut -d: -f1")
|
||||
if (ln == "") {
|
||||
run(`cat CHANGELOG.md {tmp_dir()}/rel_section.md > {tmp_dir()}/rel_new.md`)
|
||||
shell(`cat CHANGELOG.md {tmp_dir()}/rel_section.md > {tmp_dir()}/rel_new.md`)
|
||||
} else {
|
||||
run(`head -n $(expr {ln} - 1) CHANGELOG.md > {tmp_dir()}/rel_new.md`)
|
||||
run(`cat {tmp_dir()}/rel_section.md >> {tmp_dir()}/rel_new.md`)
|
||||
run(`tail -n +{ln} CHANGELOG.md >> {tmp_dir()}/rel_new.md`)
|
||||
shell(`head -n $(expr {ln} - 1) CHANGELOG.md > {tmp_dir()}/rel_new.md`)
|
||||
shell(`cat {tmp_dir()}/rel_section.md >> {tmp_dir()}/rel_new.md`)
|
||||
shell(`tail -n +{ln} CHANGELOG.md >> {tmp_dir()}/rel_new.md`)
|
||||
}
|
||||
run(`cp {tmp_dir()}/rel_new.md CHANGELOG.md`)
|
||||
shell(`cp {tmp_dir()}/rel_new.md CHANGELOG.md`)
|
||||
}
|
||||
|
||||
# ---- release artifacts + publishing -----------------------------------------
|
||||
|
|
@ -313,7 +313,7 @@ function sha256_cmd() -> pointer {
|
|||
#
|
||||
# shasum -a 256 -c ludic-X.Y.Z-src.tar.gz.sha256
|
||||
function build_artifacts(ver: pointer) -> bool {
|
||||
run("rm -rf dist && mkdir -p dist")
|
||||
shell("rm -rf dist && mkdir -p dist")
|
||||
if not shq(`git archive --format=tar.gz --prefix=ludic-{ver}/ -o dist/ludic-{ver}-src.tar.gz v{ver}`) {
|
||||
err(`release: git archive of v{ver} failed (is the tag present?)\n`)
|
||||
return false
|
||||
|
|
@ -323,28 +323,28 @@ function build_artifacts(ver: pointer) -> bool {
|
|||
let arch = capture_line("uname -m")
|
||||
let name = `ludic-{ver}-{plat}-{arch}`
|
||||
let stage = `{tmp_dir()}/{name}`
|
||||
run(`rm -rf {stage} && mkdir -p {stage}/bin`)
|
||||
shell(`rm -rf {stage} && mkdir -p {stage}/bin`)
|
||||
if not shq(`cp bin/ludic bin/ludicc bin/ludic-fmt bin/ludic-lsp {stage}/bin/`) {
|
||||
err("release: the toolchain is not built (run: ludic-dev build)\n")
|
||||
return false
|
||||
}
|
||||
run(`cp -R runtime {stage}/runtime`)
|
||||
run(`cp -R packages {stage}/packages`)
|
||||
run(`cp VERSION LICENSE README.md {stage}/`)
|
||||
shell(`cp -R runtime {stage}/runtime`)
|
||||
shell(`cp -R packages {stage}/packages`)
|
||||
shell(`cp VERSION LICENSE README.md {stage}/`)
|
||||
# the Linux link needs the Darwin-stdio shim injected through $LUDIC_CC; it
|
||||
# travels with the toolchain that needs it (see tools/ci/linux_stdio_shim.ll).
|
||||
if plat == "linux" { run(`mkdir -p {stage}/lib && cp tools/ci/linux_stdio_shim.ll {stage}/lib/`) }
|
||||
if plat == "linux" { shell(`mkdir -p {stage}/lib && cp tools/ci/linux_stdio_shim.ll {stage}/lib/`) }
|
||||
if not shq(`tar -czf dist/{name}.tar.gz -C {tmp_dir()} {name}`) {
|
||||
err("release: building the toolchain tarball failed\n")
|
||||
return false
|
||||
}
|
||||
run(`rm -rf {stage}`)
|
||||
shell(`rm -rf {stage}`)
|
||||
}
|
||||
if not shq(`cd dist && for f in *.tar.gz; do {sha256_cmd()} "$f" > "$f.sha256" || exit 1; done`) {
|
||||
err("release: writing the per-artifact .sha256 files failed\n")
|
||||
return false
|
||||
}
|
||||
run("ls -l dist")
|
||||
shell("ls -l dist")
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -424,7 +424,7 @@ function cmd_release() -> int {
|
|||
}
|
||||
prepend_changelog()
|
||||
if not write_file("VERSION", `{ver}\n`) { err("release: cannot write VERSION\n"); return 1 }
|
||||
run("rm -f $(ls changes/*.md | grep -v '/README.md')")
|
||||
shell("rm -f $(ls changes/*.md | grep -v '/README.md')")
|
||||
|
||||
# stage only the release artifacts — never a blanket `git add -A`, which would
|
||||
# sweep unrelated in-flight edits (this tree is worked on concurrently).
|
||||
|
|
|
|||
|
|
@ -97,13 +97,13 @@ function line_count(path: pointer) -> pointer {
|
|||
# ---- selfhost-build: assemble + compile the self-host compiler ---------------
|
||||
# usage: ludic-dev selfhost-build [ludicc] [outbin] (defaults: bin/ludicc, build/selfhost)
|
||||
function cmd_selfhost_build(lc: pointer, outbin: pointer) -> int {
|
||||
run("mkdir -p build")
|
||||
shell("mkdir -p build")
|
||||
let src = "build/selfhost.ludic"
|
||||
if not write_selfhost_src(src) { err("ludic-dev: cannot write build/selfhost.ludic\n"); return 1 }
|
||||
let ll = `{outbin}.ll`
|
||||
if not shq(`{lc} {src} > {ll} 2>/dev/null`) { run(`rm -f {ll}`); return 1 }
|
||||
if not shq(`{lc} {src} > {ll} 2>/dev/null`) { shell(`rm -f {ll}`); return 1 }
|
||||
let rc = shq(`{cc()} {ll} -o {outbin} 2>/dev/null`)
|
||||
run(`rm -f {ll}`)
|
||||
shell(`rm -f {ll}`)
|
||||
if rc { return 0 }
|
||||
return 1
|
||||
}
|
||||
|
|
@ -126,7 +126,7 @@ function game_build_ok(shbin: pointer, game: pointer, outbin: pointer) -> bool {
|
|||
let ll = `{outbin}.ll`
|
||||
if not shq(`{shbin} {game} > {ll} 2>{tmp_dir()}/gb.err`) { return false }
|
||||
if not shq(`{cc()} -O2 {ll} -o {outbin} 2>{tmp_dir()}/gb.err`) { return false }
|
||||
run(`rm -f {ll}`)
|
||||
shell(`rm -f {ll}`)
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -144,7 +144,7 @@ function cmd_game_build(shbin: pointer, game: pointer, outbin: pointer) -> int {
|
|||
# gen2.ll == gen3.ll is the fixpoint that proves the compiler reproduces itself.
|
||||
function cmd_bootstrap() -> int {
|
||||
ensure_ludicc()
|
||||
run("mkdir -p build/boot")
|
||||
shell("mkdir -p build/boot")
|
||||
if cmd_selfhost_build("bin/ludicc", "build/boot/gen1") != 0 { print("FAIL: stage0 build"); return 1 }
|
||||
print(" stage0: bin/ludicc -> gen1 (self-host compiler)")
|
||||
|
||||
|
|
@ -156,7 +156,7 @@ function cmd_bootstrap() -> int {
|
|||
print(` stage2: gen2 -> gen3.ll ({line_count("build/boot/gen3.ll")} lines)`)
|
||||
|
||||
if shq("cmp -s build/boot/gen2.ll build/boot/gen3.ll") {
|
||||
run("rm -f build/selfhost.ludic")
|
||||
shell("rm -f build/selfhost.ludic")
|
||||
print(" FIXPOINT: gen2.ll == gen3.ll — the self-hosted compiler reproduces itself")
|
||||
return 0
|
||||
}
|
||||
|
|
@ -170,7 +170,7 @@ function cmd_bootstrap() -> int {
|
|||
# the seed-built compiler compiles its own source, and the result must equal the
|
||||
# seed byte for byte.
|
||||
function cmd_bootstrap_cfree() -> int {
|
||||
run("mkdir -p build/cfree")
|
||||
shell("mkdir -p build/cfree")
|
||||
if not shq(`{cc()} selfhost/ludicc.seed.ll -o build/cfree/sh_seed 2>/dev/null`) { print("FAIL: assemble seed"); return 1 }
|
||||
print(" seed.ll --clang--> sh_seed (no C compiler used)")
|
||||
if not write_selfhost_src("build/cfree/selfhost.ludic") { print("FAIL: write source"); return 1 }
|
||||
|
|
@ -191,17 +191,20 @@ function cmd_bootstrap_cfree() -> int {
|
|||
# bin/ludicc only if the seed itself no longer assembles. Verify with
|
||||
# `ludic-dev bootstrap-cfree` afterwards.
|
||||
function cmd_reseed() -> int {
|
||||
run("mkdir -p build/cfree")
|
||||
shell("mkdir -p build/cfree")
|
||||
if not write_selfhost_src("build/cfree/selfhost.ludic") { print("FAIL: write source"); return 1 }
|
||||
if shq(`{cc()} selfhost/ludicc.seed.ll -o build/cfree/sh_old 2>/dev/null`) {
|
||||
if not shq("build/cfree/sh_old build/cfree/selfhost.ludic > build/cfree/step1.ll") { print("FAIL: step1"); return 1 }
|
||||
if not shq(`{cc()} build/cfree/step1.ll -o build/cfree/sh_new`) { print("FAIL: assemble sh_new"); return 1 }
|
||||
if not shq("build/cfree/sh_new build/cfree/selfhost.ludic > selfhost/ludicc.seed.ll") { print("FAIL: reseed"); return 1 }
|
||||
# and the Windows seed, from the same compiler: a Windows checkout bootstraps from it
|
||||
if not shq("build/cfree/sh_new --target x86_64-pc-windows-msvc build/cfree/selfhost.ludic > selfhost/ludicc.win.seed.ll") { print("FAIL: reseed (windows)"); return 1 }
|
||||
} else {
|
||||
print("seed does not build; reseeding from bin/ludicc")
|
||||
ensure_ludicc()
|
||||
if cmd_selfhost_build("bin/ludicc", "build/cfree/sh_c") != 0 { print("FAIL: build from bin/ludicc"); return 1 }
|
||||
if not shq("build/cfree/sh_c build/cfree/selfhost.ludic > selfhost/ludicc.seed.ll") { print("FAIL: reseed"); return 1 }
|
||||
if not shq("build/cfree/sh_c --target x86_64-pc-windows-msvc build/cfree/selfhost.ludic > selfhost/ludicc.win.seed.ll") { print("FAIL: reseed (windows)"); return 1 }
|
||||
}
|
||||
print(`reseeded: {line_count("selfhost/ludicc.seed.ll")} lines`)
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ function sh_case(name: pointer, exp: pointer) -> void {
|
|||
function game_case(path: pointer, keys: pointer) -> void {
|
||||
let nm = flat(path)
|
||||
if not game_build_ok("bin/ludicc", `examples/{path}.ludic`, `{tmp_dir()}/g_{nm}`) { bad(`{path}: build`); return }
|
||||
run("mkdir -p build")
|
||||
run(`printf '%s' '{keys}' | {tmp_dir()}/g_{nm} >/dev/null 2>&1`)
|
||||
shell("mkdir -p build")
|
||||
shell(`printf '%s' '{keys}' | {tmp_dir()}/g_{nm} >/dev/null 2>&1`)
|
||||
# The build + headless run above guard against a compile or runtime break on
|
||||
# every host. The pixel-exact golden comparison, though, is against a render
|
||||
# blessed on macOS; text rasterization differs by a hair on other hosts, so
|
||||
|
|
@ -44,8 +44,8 @@ function bless_render(path: pointer, keys: pointer) -> pointer {
|
|||
if not game_build_ok("bin/ludicc", `examples/{path}.ludic`, `{tmp_dir()}/bg_{nm}`) {
|
||||
err(`golden: {path} failed to build\n`); return ""
|
||||
}
|
||||
run("mkdir -p build")
|
||||
run(`printf '%s' '{keys}' | {tmp_dir()}/bg_{nm} >/dev/null 2>&1`)
|
||||
shell("mkdir -p build")
|
||||
shell(`printf '%s' '{keys}' | {tmp_dir()}/bg_{nm} >/dev/null 2>&1`)
|
||||
let h = capture_line("shasum -a 256 build/out.ppm | cut -d' ' -f1")
|
||||
return `{path} {h}\n`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,11 +68,11 @@ function install_layout_case() -> void {
|
|||
let root = capture_line("pwd")
|
||||
let home = `{tmp_dir()}/install`
|
||||
let lbl = "an installed layout (bin/ + runtime/ + packages/, found on PATH) builds a new project"
|
||||
run(`rm -rf {home} && mkdir -p {home}/bin`)
|
||||
run(`cp bin/ludic bin/ludicc bin/ludic-fmt {home}/bin/`)
|
||||
run(`cp -R runtime {home}/runtime && cp -R packages {home}/packages && cp VERSION {home}/VERSION`)
|
||||
shell(`rm -rf {home} && mkdir -p {home}/bin`)
|
||||
shell(`cp bin/ludic bin/ludicc bin/ludic-fmt {home}/bin/`)
|
||||
shell(`cp -R runtime {home}/runtime && cp -R packages {home}/packages && cp VERSION {home}/VERSION`)
|
||||
let work = `{tmp_dir()}/installwork`
|
||||
run(`rm -rf {work} && mkdir -p {work}`)
|
||||
shell(`rm -rf {work} && mkdir -p {work}`)
|
||||
# PATH only — no LUDIC_HOME, and the current directory has no bin/ of its own.
|
||||
let envp = `env -u LUDIC_HOME PATH={home}/bin:/usr/bin:/bin:/usr/sbin:/sbin`
|
||||
if not shq(`cd {work} && {envp} ludic new demo > {work}/new.out 2>&1`) {
|
||||
|
|
@ -116,7 +116,7 @@ function pack_roundtrip_case() -> void {
|
|||
let lbl = "ludic pack -> a game reads its assets with none of them on disk"
|
||||
let work = `{tmp_dir()}/packrt`
|
||||
let root = capture_line("pwd")
|
||||
run(`rm -rf {work} && mkdir -p {work}/assets/sub {work}/ship`)
|
||||
shell(`rm -rf {work} && mkdir -p {work}/assets/sub {work}/ship`)
|
||||
write_file(`{work}/assets/a.txt`, "alpha")
|
||||
write_file(`{work}/assets/sub/b.txt`, "beta")
|
||||
write_file(`{work}/game.ludic`, pack_probe_src())
|
||||
|
|
@ -133,7 +133,7 @@ function pack_roundtrip_case() -> void {
|
|||
write_file(`{work}/ship/packs.index`, "pack game.lpak" + nl())
|
||||
|
||||
# the assets exist only inside the pack from here on
|
||||
run(`rm -rf {work}/assets`)
|
||||
shell(`rm -rf {work}/assets`)
|
||||
# run from somewhere with no relation to the project at all
|
||||
let got = s_trim(capture(`cd / && {work}/ship/probe 2>&1`))
|
||||
if got != "alpha|beta|1|5|0" {
|
||||
|
|
@ -167,9 +167,9 @@ function app_icon_case() -> void {
|
|||
if not is_darwin() { skip(lbl); return }
|
||||
let work = `{tmp_dir()}/appicon`
|
||||
let root = capture_line("pwd")
|
||||
run(`rm -rf {work} && mkdir -p {work}`)
|
||||
shell(`rm -rf {work} && mkdir -p {work}`)
|
||||
# a real PNG to set, made the same way the bundle test makes its icon
|
||||
run(`sips -s format png --resampleHeightWidth 64 64 /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericApplicationIcon.icns --out {work}/icon.png > /dev/null 2>&1`)
|
||||
shell(`sips -s format png --resampleHeightWidth 64 64 /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericApplicationIcon.icns --out {work}/icon.png > /dev/null 2>&1`)
|
||||
var src = "program IconSet {" + nl()
|
||||
src = src + " entry {" + nl()
|
||||
src = src + " App.set_icon(\"icon.png\")" + nl()
|
||||
|
|
@ -206,7 +206,7 @@ function os_dirs_case() -> void {
|
|||
let lbl = "Os.save_dir / config_dir / cache_dir follow the platform's own convention"
|
||||
let work = `{tmp_dir()}/osdirs`
|
||||
let root = capture_line("pwd")
|
||||
run(`rm -rf {work} && mkdir -p {work}`)
|
||||
shell(`rm -rf {work} && mkdir -p {work}`)
|
||||
var src = "program OsDirs {" + nl()
|
||||
src = src + " entry {" + nl()
|
||||
src = src + " print(`{Os.save_dir(\"App X\")}|{Os.config_dir(\"App X\")}|{Os.cache_dir(\"App X\")}`)" + nl()
|
||||
|
|
@ -249,8 +249,8 @@ function packignore_case() -> void {
|
|||
let lbl = "ludic pack -> .packignore excludes, with gitignore's rules"
|
||||
let work = `{tmp_dir()}/packign`
|
||||
let root = capture_line("pwd")
|
||||
run(`rm -rf {work}`)
|
||||
run(`mkdir -p {work}/assets/kit/preview {work}/assets/deep/a/tmp {work}/assets/scratch {work}/assets/sub/scratch {work}/assets/keep`)
|
||||
shell(`rm -rf {work}`)
|
||||
shell(`mkdir -p {work}/assets/kit/preview {work}/assets/deep/a/tmp {work}/assets/scratch {work}/assets/sub/scratch {work}/assets/keep`)
|
||||
write_file(`{work}/assets/kit/preview/x.png`, "x") # preview/ - a directory, any depth
|
||||
write_file(`{work}/assets/kit/model.blend`, "x") # *.blend - a name, any depth
|
||||
write_file(`{work}/assets/kit/hiker.png`, "x") # kept
|
||||
|
|
@ -311,10 +311,10 @@ function bundle_case() -> void {
|
|||
if not is_darwin() { skip(lbl); return }
|
||||
let work = `{tmp_dir()}/bundle`
|
||||
let root = capture_line("pwd")
|
||||
run(`rm -rf {work} && mkdir -p {work}/src {work}/assets`)
|
||||
shell(`rm -rf {work} && mkdir -p {work}/src {work}/assets`)
|
||||
write_file(`{work}/assets/hello.txt`, "packed")
|
||||
# a 64x64 source icon: small, but sips will scale it and iconutil accept it
|
||||
run(`sips -s format png --resampleHeightWidth 64 64 /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericApplicationIcon.icns --out {work}/assets/icon.png > /dev/null 2>&1`)
|
||||
shell(`sips -s format png --resampleHeightWidth 64 64 /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/GenericApplicationIcon.icns --out {work}/assets/icon.png > /dev/null 2>&1`)
|
||||
write_file(`{work}/package.ludic`, bundle_manifest_src())
|
||||
write_file(`{work}/src/main.ludic`, bundle_probe_src())
|
||||
|
||||
|
|
@ -371,7 +371,7 @@ function bundle_probe_src() -> pointer {
|
|||
function scaffold_names_case() -> void {
|
||||
let lbl = "ludic new scaffolds a project that compiles (hyphens, digits, dots)"
|
||||
let work = `{tmp_dir()}/scaffold`
|
||||
run(`rm -rf {work} && mkdir -p {work}`)
|
||||
shell(`rm -rf {work} && mkdir -p {work}`)
|
||||
let root = capture_line("pwd")
|
||||
let names = new []pointer
|
||||
push(names, "my-game")
|
||||
|
|
@ -482,7 +482,7 @@ function cov_one(path: pointer, dir: pointer) -> int {
|
|||
if not shq(`{cc()} -O2 {ll} -o {bin} 2>{tmp_dir()}/cov.err`) {
|
||||
bad2(path, capture_line(`tail -1 {tmp_dir()}/cov.err`)); return 1
|
||||
}
|
||||
run(`rm -f {ll}`)
|
||||
shell(`rm -f {ll}`)
|
||||
# run the spec; its atexit hook writes the dump to $LUDIC_COVERAGE
|
||||
if not shq(`LUDIC_COVERAGE={cov} {bin} > /dev/null 2>&1`) {
|
||||
bad2(path, "spec exited non-zero"); return 1
|
||||
|
|
@ -511,7 +511,7 @@ var COV_TOTAL: int = 0
|
|||
function cmd_test_coverage() -> int {
|
||||
# the suite normally rebuilds bin/ludicc from the seed; do the same here so the
|
||||
# instrumentation path is exactly what a clean checkout ships.
|
||||
run(`mkdir -p bin build {tmp_dir()}/cov_out`)
|
||||
shell(`mkdir -p bin build {tmp_dir()}/cov_out`)
|
||||
if (not is_exec("bin/ludicc")) or newer("selfhost/ludicc.seed.ll", "bin/ludicc") {
|
||||
if not shq(`{cc()} selfhost/ludicc.seed.ll -o bin/ludicc`) { err("ludic-dev: cannot build bin/ludicc from the seed\n"); return 1 }
|
||||
}
|
||||
|
|
@ -539,7 +539,7 @@ function cmd_dev_test() -> int {
|
|||
ok(`the only C in the tree is any editor tooling ({cleft} .c files)`)
|
||||
|
||||
print("== the compiler builds from the IR seed (no C compiler) ==")
|
||||
run("rm -f bin/ludicc")
|
||||
shell("rm -f bin/ludicc")
|
||||
if shq("bin/ludic build examples/games/snake.ludic --headless >/dev/null 2>&1") and is_exec("bin/ludicc") {
|
||||
ok("clang selfhost/ludicc.seed.ll -> bin/ludicc")
|
||||
} else { bad("compiler did not build from the seed") }
|
||||
|
|
@ -550,7 +550,7 @@ function cmd_dev_test() -> int {
|
|||
game_case("games/chronorift", "ddddwwwwaassK")
|
||||
|
||||
print("== self-hosting and the bootstrap fixpoints ==")
|
||||
run(`bin/ludic-dev selfhost-test > {tmp_dir()}/sh.out 2>&1`)
|
||||
shell(`bin/ludic-dev selfhost-test > {tmp_dir()}/sh.out 2>&1`)
|
||||
if shq(`grep -q 'gen2.ll == gen3.ll' {tmp_dir()}/sh.out`) { ok("the compiler reproduces itself (fixpoint)") } else { bad("fixpoint") }
|
||||
if shq(`grep -q 'no C compiler' {tmp_dir()}/sh.out`) { ok("the compiler rebuilds itself from the seed, C-free") } else { bad("C-free bootstrap") }
|
||||
let corr = capture_line(`grep -c PASS {tmp_dir()}/sh.out`)
|
||||
|
|
@ -770,7 +770,7 @@ function cmd_dev_test() -> int {
|
|||
# stands up throwaway git repos, so like selfhost-test it runs as a sub-process
|
||||
# and this stage checks its footer).
|
||||
print("== package manager (ludic-dev test-pkg) ==")
|
||||
run(`bin/ludic-dev test-pkg > {tmp_dir()}/pkg.out 2>&1`)
|
||||
shell(`bin/ludic-dev test-pkg > {tmp_dir()}/pkg.out 2>&1`)
|
||||
if shq(`grep -q '0 failed' {tmp_dir()}/pkg.out`) {
|
||||
let pc = capture_line(`grep -c PASS {tmp_dir()}/pkg.out`)
|
||||
ok(`package-manager suite: {pc} checks passed (see: ludic-dev test-pkg)`)
|
||||
|
|
|
|||
|
|
@ -5,10 +5,14 @@
|
|||
# tools — every binary the toolchain builds of itself.
|
||||
function build_tool(name: pointer, src: pointer) -> bool {
|
||||
let ll = `build/{name}.ll`
|
||||
if not shq(`bin/ludicc {src} --emit-llvm -o {ll} 2>/dev/null`) { print(`build failed: {name} (compile)`); return false }
|
||||
let out = `bin/{exe_name(name)}`
|
||||
if not shq(`bin/{exe_name("ludicc")} {src} --emit-llvm -o {ll} 2>/dev/null`) { print(`build failed: {name} (compile)`); return false }
|
||||
# write to a temp then move, so a running bin/ludic can rebuild itself in place
|
||||
if not shq(`{cc()} -O2 {ll} -o bin/{name}.tmp`) { print(`build failed: {name} (link)`); return false }
|
||||
run(`mv -f bin/{name}.tmp bin/{name} && rm -f {ll}`)
|
||||
# (Windows lets a running executable be renamed, never replaced, so the old one
|
||||
# steps aside first)
|
||||
if not shq(`{cc()} -O2 {ll} -o {out}.tmp`) { print(`build failed: {name} (link)`); return false }
|
||||
if host_windows() { shell(`rm -f {out}.old; mv -f {out} {out}.old 2>/dev/null; mv -f {out}.tmp {out} && rm -f {ll} {out}.old 2>/dev/null`) }
|
||||
else { shell(`mv -f {out}.tmp {out} && rm -f {ll}`) }
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -17,11 +21,13 @@ function build_tool(name: pointer, src: pointer) -> bool {
|
|||
# with clang alone; invoked as `ludic` it compiles-and-runs, as `ludicc` it just
|
||||
# compiles.
|
||||
function cmd_dev_build_cli() -> int {
|
||||
run("mkdir -p bin build")
|
||||
print("cc: selfhost/ludicc.seed.ll -> bin/ludicc (from the IR seed, no C compiler)")
|
||||
if not shq(`{cc()} selfhost/ludicc.seed.ll -o bin/ludicc`) { err("ludic: the seed did not assemble\n"); return 1 }
|
||||
run("chmod +x bin/ludicc")
|
||||
print("done. built bin/ludicc")
|
||||
shell("mkdir -p bin build")
|
||||
let seed = seed_file()
|
||||
let bin = `bin/{exe_name("ludicc")}`
|
||||
print(`cc: {seed} -> {bin} (from the IR seed, no C compiler)`)
|
||||
if not shq(`{cc()} {seed} -o {bin}`) { err("ludic: the seed did not assemble\n"); return 1 }
|
||||
shell(`chmod +x {bin}`)
|
||||
print(`done. built {bin}`)
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
# refresh one editor-copy of a shared grammar file if it has drifted
|
||||
function sync_one(shared: pointer, dst: pointer) -> void {
|
||||
if not shq(`cmp -s {shared} {dst}`) {
|
||||
run(`cp {shared} {dst}`)
|
||||
shell(`cp {shared} {dst}`)
|
||||
print(`sync: {shared} -> {dst}`)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ function sync_one(shared: pointer, dst: pointer) -> void {
|
|||
# copy that build refreshes rather than trusts (test-tools asserts they match).
|
||||
function sync_vscode_grammar() -> void {
|
||||
if not shq("test -d tools/editors/vscode") { return }
|
||||
run("mkdir -p tools/editors/vscode/syntaxes")
|
||||
shell("mkdir -p tools/editors/vscode/syntaxes")
|
||||
sync_one("tools/editors/shared/ludic.tmLanguage.json", "tools/editors/vscode/syntaxes/ludic.tmLanguage.json")
|
||||
sync_one("tools/editors/shared/ludic.markdown-injection.json", "tools/editors/vscode/syntaxes/ludic.markdown-injection.json")
|
||||
sync_one("tools/editors/shared/language-configuration.json", "tools/editors/vscode/language-configuration.json")
|
||||
|
|
@ -42,9 +42,9 @@ function cmd_tools() -> int {
|
|||
sync_vscode_grammar()
|
||||
|
||||
if has_flag("--install") {
|
||||
run("mkdir -p \"$HOME/.local/bin\"")
|
||||
run("ln -sf \"$PWD/bin/ludic-fmt\" \"$HOME/.local/bin/ludic-fmt\"")
|
||||
run("ln -sf \"$PWD/bin/ludic-lsp\" \"$HOME/.local/bin/ludic-lsp\"")
|
||||
shell("mkdir -p \"$HOME/.local/bin\"")
|
||||
shell("ln -sf \"$PWD/bin/ludic-fmt\" \"$HOME/.local/bin/ludic-fmt\"")
|
||||
shell("ln -sf \"$PWD/bin/ludic-lsp\" \"$HOME/.local/bin/ludic-lsp\"")
|
||||
print("linked into ~/.local/bin (make sure it is on your PATH)")
|
||||
}
|
||||
if has_flag("--test") { return cmd_test_tools() }
|
||||
|
|
@ -97,7 +97,7 @@ function cmd_test_tools() -> int {
|
|||
|
||||
# --- messy input is normalised ---------------------------------------------
|
||||
write_file(`{tmp_dir()}/m.ludic`, "program M{\nproperty P{x:int=0}\nhandler S phase Update{let a=1+2\nif a== -1 {a=-a}}\n}\n")
|
||||
run(`{fmt} {tmp_dir()}/m.ludic > {tmp_dir()}/m.out 2>/dev/null`)
|
||||
shell(`{fmt} {tmp_dir()}/m.ludic > {tmp_dir()}/m.out 2>/dev/null`)
|
||||
if shq(`grep -q '^ property P {{ x: int = 0 }}$' {tmp_dir()}/m.out`) { ok("reindents and respaces") } else { bad("reindents and respaces") }
|
||||
if shq(`grep -q 'a = -a' {tmp_dir()}/m.out`) { ok("unary minus not spaced") } else { bad("unary minus not spaced") }
|
||||
|
||||
|
|
@ -112,13 +112,13 @@ function cmd_test_tools() -> int {
|
|||
# --- --check exit status, for CI and pre-commit hooks ----------------------
|
||||
let chk1 = sh(`{fmt} --check -q {tmp_dir()}/m.ludic`)
|
||||
check("--check exits 1 on unformatted input", string(chk1), "1")
|
||||
run(`{fmt} -w {tmp_dir()}/m.ludic`)
|
||||
shell(`{fmt} -w {tmp_dir()}/m.ludic`)
|
||||
let chk2 = sh(`{fmt} --check -q {tmp_dir()}/m.ludic`)
|
||||
check("--check exits 0 after -w", string(chk2), "0")
|
||||
|
||||
# --- markdown: fences formatted, prose untouched ---------------------------
|
||||
write_file(`{tmp_dir()}/d.md`, "# T\n\nprose with spaces\n\n```ludic\nprogram D{property P{x:int=0}}\n```\n\n```python\nz = 1\n```\n")
|
||||
run(`{fmt} {tmp_dir()}/d.md > {tmp_dir()}/d.out 2>/dev/null`)
|
||||
shell(`{fmt} {tmp_dir()}/d.md > {tmp_dir()}/d.out 2>/dev/null`)
|
||||
if shq(`grep -q 'prose with spaces' {tmp_dir()}/d.out`) { ok("markdown prose untouched") } else { bad("markdown prose untouched") }
|
||||
if shq(`grep -q 'z = 1' {tmp_dir()}/d.out`) { ok("non-ludic fences untouched") } else { bad("non-ludic fences untouched") }
|
||||
if shq(`grep -q 'program D {{ property P {{ x: int = 0 }} }}' {tmp_dir()}/d.out`) { ok("ludic fences formatted") } else { bad("ludic fences formatted") }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue