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:
Orkun ÇAKILKAYA 2026-09-13 03:01:33 +03:00
parent 63d9f61bf1
commit 55b0e2ebf6
22 changed files with 112450 additions and 818 deletions

File diff suppressed because it is too large Load diff

111260
selfhost/ludicc.win.seed.ll Normal file

File diff suppressed because it is too large Load diff

View file

@ -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") }