Files
meowc/lib/exec.ml
T
sneeker 38889de6e6 feat(toolchain): cross compile through target triples and sysroots
A triple is set with --target or a target field in the toolchain block, and
everything derived from it follows: platform, arch and the conditionals built
on them come from the triple rather than uname, so a build file can branch on
the platform it is building for instead of the one it is running on.

Tools are taken from the triple prefix when a matching toolchain is installed
(x86_64-w64-mingw32-gcc, -ar, -ranlib) and otherwise the host compiler is
invoked with --target, which covers clang without a prefixed toolchain. An
explicit cc, cxx, ar or ranlib in the build file always wins over the derived
one. --sysroot adds --sysroot to both the compile and link lines.

Each triple gets its own build directory and probe cache under builddir, so a
host tree and a cross tree do not invalidate each other and neither reruns the
other's checks. Probes never execute what they compile, sizeof included, so the
configure stage needed no changes to work against a foreign target.

Executables and shared libraries now take the extension the target platform
uses. Without that the graph expected bin/app while mingw wrote bin/app.exe,
and the link step repeated on every build.

Verified against x86_64-w64-mingw32, which produces a PE32+ binary, and
aarch64-linux-gnu through clang, which produces AArch64 objects.
2022-07-16 01:16:10 +00:00

85 lines
2.8 KiB
OCaml

type job = { desc : string; cmd : string array; after : unit -> unit }
type report = { ran : int; failures : int }
let quote s =
if s <> "" && String.for_all (fun c -> not (List.mem c [ ' '; '\t'; '"'; '\''; '$'; '&'; ';' ])) s
then s
else "'" ^ String.concat "'\\''" (String.split_on_char '\'' s) ^ "'"
let show cmd = String.concat " " (List.map quote (Array.to_list cmd))
let run ~jobs ~verbose ~on_done js =
let queue = ref js in
let running = Hashtbl.create 8 in
let failures = ref 0 in
let ran = ref 0 in
let spawn j =
let tmp = Filename.temp_file "meowc" ".log" in
let fd = Unix.openfile tmp [ Unix.O_WRONLY; Unix.O_CREAT; Unix.O_TRUNC ] 0o600 in
let pid =
try Unix.create_process j.cmd.(0) j.cmd Unix.stdin fd fd
with Unix.Unix_error (e, _, _) ->
Unix.close fd;
Sys.remove tmp;
Diag.error "cannot run %s: %s" j.cmd.(0) (Unix.error_message e)
in
Unix.close fd;
Hashtbl.replace running pid (j, tmp)
in
let reap () =
let pid, status = Unix.wait () in
match Hashtbl.find_opt running pid with
| None -> ()
| Some (j, tmp) ->
Hashtbl.remove running pid;
incr ran;
let ok = status = Unix.WEXITED 0 in
if ok then j.after () else incr failures;
let log = try Fs.read tmp with Sys_error _ -> "" in
(try Sys.remove tmp with Sys_error _ -> ());
on_done ~ok j;
if verbose then print_endline (" " ^ Style.dim (show j.cmd));
if String.trim log <> "" then (print_string log; flush stdout)
in
while !queue <> [] || Hashtbl.length running > 0 do
while !queue <> [] && Hashtbl.length running < jobs && !failures = 0 do
match !queue with
| j :: rest -> queue := rest; spawn j
| [] -> ()
done;
if Hashtbl.length running > 0 then reap ()
else if !failures > 0 then queue := []
done;
{ ran = !ran; failures = !failures }
let devnull () = Unix.openfile "/dev/null" [ Unix.O_RDONLY ] 0
let capture cmd =
let tmp = Filename.temp_file "meowc" ".out" in
let fd = Unix.openfile tmp [ Unix.O_WRONLY; Unix.O_CREAT; Unix.O_TRUNC ] 0o600 in
let nul = devnull () in
let code =
match Unix.create_process cmd.(0) cmd nul fd fd with
| pid ->
let _, st = Unix.waitpid [] pid in
(match st with Unix.WEXITED c -> c | _ -> 128)
| exception Unix.Unix_error _ -> 127
in
Unix.close fd;
Unix.close nul;
let out = try Fs.read tmp with Sys_error _ -> "" in
(try Sys.remove tmp with Sys_error _ -> ());
(code, out)
let ok cmd = fst (capture cmd) = 0
let which name =
if String.contains name '/' then Sys.file_exists name
else
match Sys.getenv_opt "PATH" with
| None -> false
| Some path ->
String.split_on_char ':' path
|> List.exists (fun dir -> dir <> "" && Sys.file_exists (Filename.concat dir name))