diff --git a/changes/ludic-build-http.md b/changes/ludic-build-http.md new file mode 100644 index 00000000..da86e5ad --- /dev/null +++ b/changes/ludic-build-http.md @@ -0,0 +1,8 @@ +bump: patch +type: fix +**`ludic build` links `Http.*`** — a program that uses the HTTP client builds through the CLI. + +`compile_app` linked the OpenGL backend when a program named `@lgl_*` but never +`runtime/native/http.ll` and Foundation for `@hs_*`, so a `Http.*` program failed to link +under `ludic build` / `ludic run` while `ludicc -o` built it. It now links them the same way, +windowed and headless. diff --git a/tools/ludic-cli/build.ludic b/tools/ludic-cli/build.ludic index 3ecedcb9..381bb954 100644 --- a/tools/ludic-cli/build.ludic +++ b/tools/ludic-cli/build.ludic @@ -36,7 +36,7 @@ function compile_app(src: pointer, out: pointer, mode: int, save: bool) -> bool 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 shq(`{cc()} -O2 {ll}{gl_link_flags(ll)}{http_link_flags(ll)}{pbf} -o {out}`) { return false } if not save { run(`rm -f {ll}`) } return true } @@ -46,7 +46,7 @@ function compile_app(src: pointer, out: pointer, mode: int, save: bool) -> bool # canonical `ludicc -o` path links it only when Audio.* is used. 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 shq(`{cc()} -O2 {ll} {cocoa} {audio} -framework Cocoa -Wl,-needed_framework,GameController -Wl,-needed_framework,AVFoundation -Wl,-rpath,@loader_path{gl_link_flags(ll)}{http_link_flags(ll)}{pbf} -o {out}`) { return false } if not save { run(`rm -f {ll}`) } return true } @@ -59,6 +59,14 @@ function gl_link_flags(ll: pointer) -> pointer { return ` {home}runtime/native/gl.ll {home}runtime/native/gl_thunks.ll -framework OpenGL` } +# A program that uses Http.* calls the @hs_* transport; link http.ll and Foundation only +# then, in both modes, as `ludicc -o` does (selfhost/main.ludic). Without it a Http.* +# program built through `ludic build` failed to link on every hs_* symbol. +function http_link_flags(ll: pointer) -> pointer { + if not shq(`grep -q "@hs_" {ll}`) { return "" } + return ` {ludic_home()}runtime/native/http.ll -Wl,-needed_framework,Foundation` +} + # the directory part of a path, without the trailing '/' ("" when there is none) function dir_of_path(p: pointer) -> pointer { var last = -1 diff --git a/tools/ludic-cli/test.ludic b/tools/ludic-cli/test.ludic index 62713b86..cc0ac8fb 100644 --- a/tools/ludic-cli/test.ludic +++ b/tools/ludic-cli/test.ludic @@ -679,6 +679,7 @@ function cmd_dev_test() -> int { feat_case("library/input_auto", "llq", "2", "input_auto.ludic (#83 the frame loop auto-commits the device layer: Input.active fires with no manual Input.poll)") feat_case("library/input_edge", "xkkxq", "0 11 1 100 0", "input_edge.ludic (#87 Input.key_pressed/key_released edges fire on the transition frame; no double-commit under the frame loop)") feat_case("library/input_movement", "", "1 0 -1 -1 0", "input_movement.ludic (#79 Input.axis_i turns WASD into a -1/0/1 movement intent, no bool->int glue)") + feat_case("library/input_arrows", "", "128 129 130 131 1 0 -1 1 0", "input_arrows.ludic (Key.Up/Down/Left/Right are the held set's 128-131: key_down and move_i take the arrows)") feat_case("library/audio", "", "0 0 0 0 1", "audio.ludic (#22 Audio.* load/play/music/volume/pitch/stop/is_playing — headless no-op)") # #6 Http.* — the client links Foundation (macOS-only), so build it through the # canonical `ludicc -o` path (which wires the framework) and gate on Darwin. The @@ -689,6 +690,13 @@ function cmd_dev_test() -> int { if (got == "200 1 5 hello 42 text/plain 404 0") { ok("http.ludic (#6 Http.parse/status/ok/text/header — pure response parser)") } else { bad2("http.ludic", `got [{got}]`) } } else { bad2("http.ludic build", capture_line(`tail -1 {tmp_dir()}/http.out`)) } + # and through `ludic build`, which is how a project builds: compile_app used to link + # gl.ll when a program named @lgl_* but never http.ll for @hs_*, so this failed to link + if shq(`LUDIC_HOME=. bin/ludic build examples/library/http.ludic --headless -o {tmp_dir()}/http_lb > {tmp_dir()}/http_lb.out 2>&1`) { + let got2 = capture_line(`{tmp_dir()}/http_lb < /dev/null`) + if (got2 == "200 1 5 hello 42 text/plain 404 0") { ok("ludic build links http.ll for a program that uses Http.*") } + else { bad2("http.ludic via ludic build", `got [{got2}]`) } + } else { bad2("ludic build of an Http.* program", capture_line(`tail -1 {tmp_dir()}/http_lb.out`)) } } feat_case("events/recurse", "", "16", "recurse.ludic (EV6: re-entrant emit is depth-bounded, no runaway cycle)")