flan build and flan run of a file with no main say so and show one, rather than failing at the link

This commit is contained in:
Joseph Ferano 2026-09-25 11:26:13 +07:00
parent 8b4c6f81df
commit 0b9ae9318e
4 changed files with 34 additions and 11 deletions

View File

@ -736,6 +736,8 @@ let () =
if List.mem warn_memory_flag rest then
print_memory_warnings ~file:path p)
in
Flan.Build.need_main ~file:path ~doing:"flan build has nothing to link"
f.program;
ignore (Flan.Build.executable
~opts:{ Flan.Build.default with checks; dev; debug; sanitize;
target; x86;
@ -915,6 +917,8 @@ let () =
(Printf.sprintf "flan-run-%d" (Unix.getpid ()))
in
let f = Flan.Front.linked ~all:true path in
Flan.Build.need_main ~file:path ~doing:"flan run has nothing to run"
f.program;
ignore (Flan.Build.executable
~opts:{ Flan.Build.default with checks; debug; sanitize;
x86;

View File

@ -744,6 +744,22 @@ let compile_c ~opts ?tflags ?(warn = []) ~src ~name () =
end;
obj
(* A program with no [main] builds every function and then fails at the link,
as an undefined reference from the C startup code — a message about crt1.o
for a mistake in the .flan file. Asked here, by the commands that make an
executable, rather than inside [executable], whose callers include hosts
and tests that supply their own [main]. *)
let need_main ~file ~doing (p : Tast.program) =
if not (List.exists (fun (f : Tast.fn) -> f.Tast.name = "main") p.Tast.fns)
then
failwith
(Printf.sprintf
"%s has no main, so %s. A program starts at a function named main, \
for example:\n\n\
\ (defn main [] i32\n\
\ 0)"
file doing)
(* [csrcs] and [lflags] come from the imported packages (see [Load]): the C
shim a package binds through, and the arguments needed to link the library
it binds to.

View File

@ -4520,17 +4520,8 @@ let make_session_dir ~file dir =
would find that out at the link — as a missing symbol, or as the merged
build's rename finding nothing to rename. *)
let need_main ~file (session : Session.t) =
if not
(List.exists (fun (f : Tast.fn) -> f.Tast.name = "main")
session.Session.host.Tast.fns)
then
failwith
(Printf.sprintf
"%s has no main, so flan dev has nothing to run. A program starts at \
a function named main, for example:\n\n\
\ (defn main [] i32\n\
\ 0)"
file)
Build.need_main ~file ~doing:"flan dev has nothing to run"
session.Session.host
(* [debug] is off by default, which keeps [flan dev] exactly what it was: a
-O2 host and -O2 modules. It is opt-in rather than always-on because a debug

View File

@ -6472,6 +6472,18 @@ level "1"
cli_case "--debug and an explicit -O are refused together"
"build ../calc-me.flan --debug -O2 -o /dev/null" ~code:2
~says:[ "--debug"; "-O2"; "Drop one of the two" ];
(* A file with no main is refused by name before the link, which would
otherwise report an undefined reference from crt1.o. *)
let nomain = Filename.concat scratch "no-main.flan" in
Out_channel.with_open_bin nomain (fun oc ->
output_string oc "(defn f [] i32 0)\n");
cli_case "build of a file with no main names main"
(Printf.sprintf "build %s -o /dev/null" (Filename.quote nomain)) ~code:1
~says:[ "has no main"; "(defn main [] i32" ];
cli_case "run of a file with no main names main"
(Printf.sprintf "run %s" (Filename.quote nomain)) ~code:1
~says:[ "has no main"; "(defn main [] i32" ];
Sys.remove nomain;
(* Every row above that went through the pool has been forked; nothing
after this point may look at [failures] until every one of them has