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.
This commit is contained in:
5 files changed
+120
-24
No files matched your search
+14
-3
@@ -27,6 +27,8 @@ let usage =
|
|||||||
" -C DIR change to DIR first";
|
" -C DIR change to DIR first";
|
||||||
" -D name=value set an option declared with 'option'";
|
" -D name=value set an option declared with 'option'";
|
||||||
" --prefix DIR install prefix (default /usr/local)";
|
" --prefix DIR install prefix (default /usr/local)";
|
||||||
|
" --target TRIPLE cross compile for TRIPLE (e.g. aarch64-linux-gnu)";
|
||||||
|
" --sysroot DIR sysroot to compile and link against";
|
||||||
" -k keep going after a failure";
|
" -k keep going after a failure";
|
||||||
" -n dry run, print what would happen";
|
" -n dry run, print what would happen";
|
||||||
" -v print each command as it finishes";
|
" -v print each command as it finishes";
|
||||||
@@ -73,12 +75,14 @@ type flags = {
|
|||||||
mutable dot : bool;
|
mutable dot : bool;
|
||||||
mutable targets_only : bool;
|
mutable targets_only : bool;
|
||||||
mutable interval : float;
|
mutable interval : float;
|
||||||
|
mutable target : string;
|
||||||
|
mutable sysroot : string;
|
||||||
}
|
}
|
||||||
|
|
||||||
let fl =
|
let fl =
|
||||||
{ file = "build.meow"; jobs = 0; chdir = ""; defs = []; prefix = None; keep = false;
|
{ file = "build.meow"; jobs = 0; chdir = ""; defs = []; prefix = None; keep = false;
|
||||||
dry = false; verbose = false; quiet = false; dot = false; targets_only = false;
|
dry = false; verbose = false; quiet = false; dot = false; targets_only = false;
|
||||||
interval = 0.25 }
|
interval = 0.25; target = ""; sysroot = "" }
|
||||||
|
|
||||||
let parse_args argv =
|
let parse_args argv =
|
||||||
let rest = ref [] in
|
let rest = ref [] in
|
||||||
@@ -102,6 +106,8 @@ let parse_args argv =
|
|||||||
fl.defs <- fl.defs @ [ (String.sub v 0 i, String.sub v (i + 1) (String.length v - i - 1)) ]);
|
fl.defs <- fl.defs @ [ (String.sub v 0 i, String.sub v (i + 1) (String.length v - i - 1)) ]);
|
||||||
go r
|
go r
|
||||||
| "--prefix" :: v :: r -> fl.prefix <- Some v; go r
|
| "--prefix" :: v :: r -> fl.prefix <- Some v; go r
|
||||||
|
| "--target" :: v :: r -> fl.target <- v; go r
|
||||||
|
| "--sysroot" :: v :: r -> fl.sysroot <- v; go r
|
||||||
| "--interval" :: v :: r -> fl.interval <- (try float_of_string v with _ -> 0.25); go r
|
| "--interval" :: v :: r -> fl.interval <- (try float_of_string v with _ -> 0.25); go r
|
||||||
| "--dot" :: r -> fl.dot <- true; go r
|
| "--dot" :: r -> fl.dot <- true; go r
|
||||||
| "--targets" :: r -> fl.targets_only <- true; go r
|
| "--targets" :: r -> fl.targets_only <- true; go r
|
||||||
@@ -111,7 +117,8 @@ let parse_args argv =
|
|||||||
| "-q" :: r -> fl.quiet <- true; go r
|
| "-q" :: r -> fl.quiet <- true; go r
|
||||||
| "--no-color" :: r -> Style.enabled := false; go r
|
| "--no-color" :: r -> Style.enabled := false; go r
|
||||||
| ("-h" | "--help") :: _ -> print_endline usage; exit 0
|
| ("-h" | "--help") :: _ -> print_endline usage; exit 0
|
||||||
| (("-f" | "-j" | "-C" | "-D" | "--prefix") as o) :: [] -> Diag.error "%s needs a value" o
|
| (("-f" | "-j" | "-C" | "-D" | "--prefix" | "--target" | "--sysroot") as o) :: [] ->
|
||||||
|
Diag.error "%s needs a value" o
|
||||||
| a :: r when String.length a > 2 && String.sub a 0 2 = "-j" ->
|
| a :: r when String.length a > 2 && String.sub a 0 2 = "-j" ->
|
||||||
let v = String.sub a 2 (String.length a - 2) in
|
let v = String.sub a 2 (String.length a - 2) in
|
||||||
fl.jobs <- (try int_of_string v with _ -> Diag.error "-j wants a number, got %S" v);
|
fl.jobs <- (try int_of_string v with _ -> Diag.error "-j wants a number, got %S" v);
|
||||||
@@ -144,7 +151,11 @@ let configure () =
|
|||||||
Diag.error ~hint:"run 'meowc init <name>' to start one" "no %s here" fl.file;
|
Diag.error ~hint:"run 'meowc init <name>' to start one" "no %s here" fl.file;
|
||||||
let overrides = Hashtbl.create 8 in
|
let overrides = Hashtbl.create 8 in
|
||||||
List.iter (fun (k, v) -> Hashtbl.replace overrides k v) fl.defs;
|
List.iter (fun (k, v) -> Hashtbl.replace overrides k v) fl.defs;
|
||||||
let env = Eval.create ~overrides ~quiet:fl.quiet ~builddir:"build" in
|
let env = Eval.create ~overrides ~quiet:fl.quiet ~builddir:"build" ~target:fl.target in
|
||||||
|
if fl.sysroot <> "" then
|
||||||
|
Eval.apply_toolchain env
|
||||||
|
[ { Ast.key = "sysroot"; kspan = Span.none;
|
||||||
|
values = [ { Ast.text = fl.sysroot; span = Span.none; quoted = false } ] } ];
|
||||||
(match fl.prefix with Some p -> Eval.setvar env "prefix" [ p ] | None -> ());
|
(match fl.prefix with Some p -> Eval.setvar env "prefix" [ p ] | None -> ());
|
||||||
let stmts = Parse.file fl.file in
|
let stmts = Parse.file fl.file in
|
||||||
Eval.run env (Filename.dirname fl.file) stmts;
|
Eval.run env (Filename.dirname fl.file) stmts;
|
||||||
|
|||||||
+9
-6
@@ -5,10 +5,13 @@ let sanitize s =
|
|||||||
|
|
||||||
let obj_of p (t : target) src = p.tc.builddir ^ "/obj/" ^ t.name ^ "/" ^ sanitize src ^ ".o"
|
let obj_of p (t : target) src = p.tc.builddir ^ "/obj/" ^ t.name ^ "/" ^ sanitize src ^ ".o"
|
||||||
let lib_of p (t : target) = p.tc.builddir ^ "/lib" ^ t.name ^ ".a"
|
let lib_of p (t : target) = p.tc.builddir ^ "/lib" ^ t.name ^ ".a"
|
||||||
let dylib_ext p = if p.platform = "darwin" then ".dylib" else ".so"
|
let dylib_ext p =
|
||||||
|
match p.platform with "darwin" -> ".dylib" | "windows" -> ".dll" | _ -> ".so"
|
||||||
|
|
||||||
|
let exe_ext p = if p.platform = "windows" then ".exe" else ""
|
||||||
let so_of p (t : target) = p.tc.builddir ^ "/lib" ^ t.name ^ dylib_ext p
|
let so_of p (t : target) = p.tc.builddir ^ "/lib" ^ t.name ^ dylib_ext p
|
||||||
let bin_of p (t : target) = p.tc.builddir ^ "/bin/" ^ t.name
|
let bin_of p (t : target) = p.tc.builddir ^ "/bin/" ^ t.name ^ exe_ext p
|
||||||
let test_of p (t : target) = p.tc.builddir ^ "/test/" ^ t.name
|
let test_of p (t : target) = p.tc.builddir ^ "/test/" ^ t.name ^ exe_ext p
|
||||||
|
|
||||||
let out_of p (t : target) =
|
let out_of p (t : target) =
|
||||||
match t.kind with
|
match t.kind with
|
||||||
@@ -37,7 +40,7 @@ let includes_of p (t : target) =
|
|||||||
let compile_cmd p ~pic (t : target) src obj =
|
let compile_cmd p ~pic (t : target) src obj =
|
||||||
let lang = lang_of src in
|
let lang = lang_of src in
|
||||||
let driver = match lang with Cxx -> p.tc.cxx | _ -> p.tc.cc in
|
let driver = match lang with Cxx -> p.tc.cxx | _ -> p.tc.cc in
|
||||||
let base = match lang with Cxx -> p.tc.cxxflags @ t.cxxflags | _ -> p.tc.cflags @ t.cflags in
|
let base = p.tc.xflags @ (match lang with Cxx -> p.tc.cxxflags @ t.cxxflags | _ -> p.tc.cflags @ t.cflags) in
|
||||||
let inherited_flags =
|
let inherited_flags =
|
||||||
List.concat_map
|
List.concat_map
|
||||||
(fun (d : target) -> match lang with Cxx -> d.cxxflags | _ -> d.cflags)
|
(fun (d : target) -> match lang with Cxx -> d.cxxflags | _ -> d.cflags)
|
||||||
@@ -76,9 +79,9 @@ let link_cmd p (t : target) objs =
|
|||||||
in
|
in
|
||||||
Array.of_list
|
Array.of_list
|
||||||
(((driver :: [ "-shared" ]) @ objs @ [ "-o"; out ]) @ soname @ link_libs p t @ t.ldflags
|
(((driver :: [ "-shared" ]) @ objs @ [ "-o"; out ]) @ soname @ link_libs p t @ t.ldflags
|
||||||
@ p.tc.ldflags)
|
@ p.tc.ldflags @ p.tc.xflags)
|
||||||
| Bin | Test ->
|
| Bin | Test ->
|
||||||
Array.of_list (((driver :: objs) @ [ "-o"; out ]) @ link_libs p t @ t.ldflags @ p.tc.ldflags)
|
Array.of_list (((driver :: objs) @ [ "-o"; out ]) @ link_libs p t @ t.ldflags @ p.tc.ldflags @ p.tc.xflags)
|
||||||
|
|
||||||
let tag_of = function
|
let tag_of = function
|
||||||
| Lib -> ("ar", Style.magenta)
|
| Lib -> ("ar", Style.magenta)
|
||||||
|
|||||||
+85
-15
@@ -60,20 +60,82 @@ let compiler_id cc =
|
|||||||
else if has "tcc" then "tcc"
|
else if has "tcc" then "tcc"
|
||||||
else "unknown"
|
else "unknown"
|
||||||
|
|
||||||
|
let triple_arch t = match String.index_opt t '-' with Some i -> String.sub t 0 i | None -> t
|
||||||
|
|
||||||
|
let triple_platform t =
|
||||||
|
let has sub =
|
||||||
|
let n = String.length sub and m = String.length t in
|
||||||
|
let rec go i = i + n <= m && (String.sub t i n = sub || go (i + 1)) in
|
||||||
|
go 0
|
||||||
|
in
|
||||||
|
if has "linux" then "linux"
|
||||||
|
else if has "darwin" || has "apple" || has "macos" then "darwin"
|
||||||
|
else if has "mingw" || has "windows" || has "cygwin" then "windows"
|
||||||
|
else if has "freebsd" || has "openbsd" || has "netbsd" || has "dragonfly" then "bsd"
|
||||||
|
else if has "wasi" || has "emscripten" then "wasm"
|
||||||
|
else if has "none" || has "eabi" then "bare"
|
||||||
|
else "unknown"
|
||||||
|
|
||||||
let default_tc : Types.toolchain =
|
let default_tc : Types.toolchain =
|
||||||
{
|
{
|
||||||
cc = "cc";
|
cc = "cc";
|
||||||
cxx = "c++";
|
cxx = "c++";
|
||||||
ar = "ar";
|
ar = "ar";
|
||||||
ranlib = "ranlib";
|
ranlib = "ranlib";
|
||||||
|
target = "";
|
||||||
|
sysroot = "";
|
||||||
|
xflags = [];
|
||||||
cflags = [];
|
cflags = [];
|
||||||
cxxflags = [];
|
cxxflags = [];
|
||||||
ldflags = [];
|
ldflags = [];
|
||||||
builddir = "build";
|
builddir = "build";
|
||||||
}
|
}
|
||||||
|
|
||||||
let create ~overrides ~quiet ~builddir =
|
let platform_vars env plat arch =
|
||||||
let tc = { default_tc with builddir } in
|
List.iter (fun (k, v) -> setvar env k [ v ])
|
||||||
|
[
|
||||||
|
("platform", plat);
|
||||||
|
("arch", arch);
|
||||||
|
("linux", if plat = "linux" then "true" else "false");
|
||||||
|
("darwin", if plat = "darwin" then "true" else "false");
|
||||||
|
("bsd", if plat = "bsd" then "true" else "false");
|
||||||
|
("windows", if plat = "windows" then "true" else "false");
|
||||||
|
("unix", if plat = "windows" || plat = "bare" || plat = "wasm" then "false" else "true");
|
||||||
|
]
|
||||||
|
|
||||||
|
let derive_cross env explicit =
|
||||||
|
let tc = env.tc in
|
||||||
|
let t = tc.target in
|
||||||
|
let set k = Hashtbl.mem explicit k in
|
||||||
|
let prefixed n = t ^ "-" ^ n in
|
||||||
|
let have_prefixed = Exec.which (prefixed "gcc") || Exec.which (prefixed "cc") in
|
||||||
|
let tc =
|
||||||
|
if not have_prefixed then tc
|
||||||
|
else
|
||||||
|
let pick n alt = if Exec.which (prefixed n) then prefixed n else prefixed alt in
|
||||||
|
{
|
||||||
|
tc with
|
||||||
|
cc = (if set "cc" then tc.cc else pick "gcc" "cc");
|
||||||
|
cxx = (if set "cxx" then tc.cxx else pick "g++" "c++");
|
||||||
|
ar = (if set "ar" then tc.ar else prefixed "ar");
|
||||||
|
ranlib = (if set "ranlib" then tc.ranlib else prefixed "ranlib");
|
||||||
|
}
|
||||||
|
in
|
||||||
|
let xflags =
|
||||||
|
(if have_prefixed then [] else [ "--target=" ^ t ])
|
||||||
|
@ (if tc.sysroot = "" then [] else [ "--sysroot=" ^ tc.sysroot ])
|
||||||
|
in
|
||||||
|
let tc = { tc with xflags } in
|
||||||
|
let tc =
|
||||||
|
if set "builddir" || Filename.basename tc.builddir = t then tc
|
||||||
|
else { tc with builddir = Filename.concat tc.builddir t }
|
||||||
|
in
|
||||||
|
env.tc <- tc;
|
||||||
|
platform_vars env (triple_platform t) (triple_arch t)
|
||||||
|
|
||||||
|
let create ~overrides ~quiet ~builddir ~target =
|
||||||
|
let builddir = if target = "" then builddir else Filename.concat builddir target in
|
||||||
|
let tc = { default_tc with builddir; target } in
|
||||||
let env =
|
let env =
|
||||||
{
|
{
|
||||||
vars = Hashtbl.create 64;
|
vars = Hashtbl.create 64;
|
||||||
@@ -92,23 +154,21 @@ let create ~overrides ~quiet ~builddir =
|
|||||||
probes_shown = 0;
|
probes_shown = 0;
|
||||||
}
|
}
|
||||||
in
|
in
|
||||||
let os = uname () in
|
let host_plat =
|
||||||
let plat =
|
match uname () with
|
||||||
match os with
|
|
||||||
| "linux" -> "linux"
|
| "linux" -> "linux"
|
||||||
| "darwin" -> "darwin"
|
| "darwin" -> "darwin"
|
||||||
| "freebsd" | "openbsd" | "netbsd" -> "bsd"
|
| "freebsd" | "openbsd" | "netbsd" -> "bsd"
|
||||||
| s -> s
|
| s -> s
|
||||||
in
|
in
|
||||||
|
if target = "" then platform_vars env host_plat (arch ())
|
||||||
|
else platform_vars env (triple_platform target) (triple_arch target);
|
||||||
List.iter (fun (k, v) -> setvar env k [ v ])
|
List.iter (fun (k, v) -> setvar env k [ v ])
|
||||||
[
|
[
|
||||||
("platform", plat);
|
("host", host_plat);
|
||||||
("arch", arch ());
|
("target", target);
|
||||||
("linux", if plat = "linux" then "true" else "false");
|
("sysroot", "");
|
||||||
("darwin", if plat = "darwin" then "true" else "false");
|
("cross", if target = "" then "false" else "true");
|
||||||
("bsd", if plat = "bsd" then "true" else "false");
|
|
||||||
("windows", if plat = "windows" then "true" else "false");
|
|
||||||
("unix", if plat = "windows" then "false" else "true");
|
|
||||||
("builddir", builddir);
|
("builddir", builddir);
|
||||||
("cc", tc.cc);
|
("cc", tc.cc);
|
||||||
("cxx", tc.cxx);
|
("cxx", tc.cxx);
|
||||||
@@ -116,6 +176,7 @@ let create ~overrides ~quiet ~builddir =
|
|||||||
("project", env.pname);
|
("project", env.pname);
|
||||||
("prefix", "/usr/local");
|
("prefix", "/usr/local");
|
||||||
];
|
];
|
||||||
|
if target <> "" then derive_cross env (Hashtbl.create 1);
|
||||||
env
|
env
|
||||||
|
|
||||||
let lookup env name span =
|
let lookup env name span =
|
||||||
@@ -261,6 +322,8 @@ let rec flatten_items env items =
|
|||||||
items
|
items
|
||||||
|
|
||||||
let apply_toolchain env fields =
|
let apply_toolchain env fields =
|
||||||
|
let explicit = Hashtbl.create 8 in
|
||||||
|
List.iter (fun (f : field) -> Hashtbl.replace explicit f.key ()) fields;
|
||||||
List.iter
|
List.iter
|
||||||
(fun (f : field) ->
|
(fun (f : field) ->
|
||||||
let vs = expand_all env f.values in
|
let vs = expand_all env f.values in
|
||||||
@@ -276,20 +339,27 @@ let apply_toolchain env fields =
|
|||||||
| "cxxflags" -> { tc with cxxflags = tc.cxxflags @ vs }
|
| "cxxflags" -> { tc with cxxflags = tc.cxxflags @ vs }
|
||||||
| "ldflags" -> { tc with ldflags = tc.ldflags @ vs }
|
| "ldflags" -> { tc with ldflags = tc.ldflags @ vs }
|
||||||
| "builddir" -> { tc with builddir = one () }
|
| "builddir" -> { tc with builddir = one () }
|
||||||
|
| "target" -> { tc with target = one () }
|
||||||
|
| "sysroot" -> { tc with sysroot = one () }
|
||||||
| k ->
|
| k ->
|
||||||
Diag.error ~span:f.kspan
|
Diag.error ~span:f.kspan
|
||||||
~hint:
|
~hint:
|
||||||
(Suggest.hint k
|
(Suggest.hint k
|
||||||
[ "cc"; "cxx"; "ar"; "ranlib"; "cflags"; "cxxflags"; "ldflags"; "builddir" ])
|
[ "cc"; "cxx"; "ar"; "ranlib"; "cflags"; "cxxflags"; "ldflags"; "builddir";
|
||||||
|
"target"; "sysroot" ])
|
||||||
"toolchain has no field %S" k))
|
"toolchain has no field %S" k))
|
||||||
fields;
|
fields;
|
||||||
|
if env.tc.target <> "" then derive_cross env explicit;
|
||||||
setvar env "cc" [ env.tc.cc ];
|
setvar env "cc" [ env.tc.cc ];
|
||||||
setvar env "cxx" [ env.tc.cxx ];
|
setvar env "cxx" [ env.tc.cxx ];
|
||||||
|
setvar env "target" [ env.tc.target ];
|
||||||
|
setvar env "sysroot" [ env.tc.sysroot ];
|
||||||
|
setvar env "cross" [ (if env.tc.target = "" then "false" else "true") ];
|
||||||
setvar env "builddir" [ env.tc.builddir ];
|
setvar env "builddir" [ env.tc.builddir ];
|
||||||
setvar env "cc_id" [ compiler_id env.tc.cc ];
|
setvar env "cc_id" [ compiler_id env.tc.cc ];
|
||||||
env.probe.Probe.cc <- env.tc.cc;
|
env.probe.Probe.cc <- env.tc.cc;
|
||||||
env.probe.Probe.cflags <- env.tc.cflags;
|
env.probe.Probe.cflags <- env.tc.xflags @ env.tc.cflags;
|
||||||
env.probe.Probe.ldflags <- env.tc.ldflags
|
env.probe.Probe.ldflags <- env.tc.xflags @ env.tc.ldflags
|
||||||
|
|
||||||
let rec predeclare env stmts =
|
let rec predeclare env stmts =
|
||||||
let name (v : value) = v.text in
|
let name (v : value) = v.text in
|
||||||
|
|||||||
@@ -73,3 +73,12 @@ let capture cmd =
|
|||||||
(code, out)
|
(code, out)
|
||||||
|
|
||||||
let ok cmd = fst (capture cmd) = 0
|
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))
|
||||||
@@ -37,6 +37,9 @@ type toolchain = {
|
|||||||
cxx : string;
|
cxx : string;
|
||||||
ar : string;
|
ar : string;
|
||||||
ranlib : string;
|
ranlib : string;
|
||||||
|
target : string;
|
||||||
|
sysroot : string;
|
||||||
|
xflags : string list;
|
||||||
cflags : string list;
|
cflags : string list;
|
||||||
cxxflags : string list;
|
cxxflags : string list;
|
||||||
ldflags : string list;
|
ldflags : string list;
|
||||||
|
|||||||
Reference in new issue
Block a user