diff --git a/bin/meowc.ml b/bin/meowc.ml index 497d6a8..af961cc 100644 --- a/bin/meowc.ml +++ b/bin/meowc.ml @@ -27,6 +27,8 @@ let usage = " -C DIR change to DIR first"; " -D name=value set an option declared with 'option'"; " --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"; " -n dry run, print what would happen"; " -v print each command as it finishes"; @@ -73,12 +75,14 @@ type flags = { mutable dot : bool; mutable targets_only : bool; mutable interval : float; + mutable target : string; + mutable sysroot : string; } let fl = { file = "build.meow"; jobs = 0; chdir = ""; defs = []; prefix = None; keep = 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 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)) ]); 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 | "--dot" :: r -> fl.dot <- 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 | "--no-color" :: r -> Style.enabled := false; go r | ("-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" -> 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); @@ -144,7 +151,11 @@ let configure () = Diag.error ~hint:"run 'meowc init ' to start one" "no %s here" fl.file; let overrides = Hashtbl.create 8 in 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 -> ()); let stmts = Parse.file fl.file in Eval.run env (Filename.dirname fl.file) stmts; diff --git a/lib/build.ml b/lib/build.ml index 240f108..11e33cb 100644 --- a/lib/build.ml +++ b/lib/build.ml @@ -5,10 +5,13 @@ let sanitize s = 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 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 bin_of p (t : target) = p.tc.builddir ^ "/bin/" ^ t.name -let test_of p (t : target) = p.tc.builddir ^ "/test/" ^ 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 ^ exe_ext p let out_of p (t : target) = match t.kind with @@ -37,7 +40,7 @@ let includes_of p (t : target) = let compile_cmd p ~pic (t : target) src obj = let lang = lang_of src 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 = List.concat_map (fun (d : target) -> match lang with Cxx -> d.cxxflags | _ -> d.cflags) @@ -76,9 +79,9 @@ let link_cmd p (t : target) objs = in Array.of_list (((driver :: [ "-shared" ]) @ objs @ [ "-o"; out ]) @ soname @ link_libs p t @ t.ldflags - @ p.tc.ldflags) + @ p.tc.ldflags @ p.tc.xflags) | 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 | Lib -> ("ar", Style.magenta) diff --git a/lib/eval.ml b/lib/eval.ml index a2503cf..c7b7c08 100644 --- a/lib/eval.ml +++ b/lib/eval.ml @@ -60,20 +60,82 @@ let compiler_id cc = else if has "tcc" then "tcc" 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 = { cc = "cc"; cxx = "c++"; ar = "ar"; ranlib = "ranlib"; + target = ""; + sysroot = ""; + xflags = []; cflags = []; cxxflags = []; ldflags = []; builddir = "build"; } -let create ~overrides ~quiet ~builddir = - let tc = { default_tc with builddir } in +let platform_vars env plat arch = + 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 = { vars = Hashtbl.create 64; @@ -92,23 +154,21 @@ let create ~overrides ~quiet ~builddir = probes_shown = 0; } in - let os = uname () in - let plat = - match os with + let host_plat = + match uname () with | "linux" -> "linux" | "darwin" -> "darwin" | "freebsd" | "openbsd" | "netbsd" -> "bsd" | s -> s 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 ]) [ - ("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" then "false" else "true"); + ("host", host_plat); + ("target", target); + ("sysroot", ""); + ("cross", if target = "" then "false" else "true"); ("builddir", builddir); ("cc", tc.cc); ("cxx", tc.cxx); @@ -116,6 +176,7 @@ let create ~overrides ~quiet ~builddir = ("project", env.pname); ("prefix", "/usr/local"); ]; + if target <> "" then derive_cross env (Hashtbl.create 1); env let lookup env name span = @@ -261,6 +322,8 @@ let rec flatten_items env items = items let apply_toolchain env fields = + let explicit = Hashtbl.create 8 in + List.iter (fun (f : field) -> Hashtbl.replace explicit f.key ()) fields; List.iter (fun (f : field) -> let vs = expand_all env f.values in @@ -276,20 +339,27 @@ let apply_toolchain env fields = | "cxxflags" -> { tc with cxxflags = tc.cxxflags @ vs } | "ldflags" -> { tc with ldflags = tc.ldflags @ vs } | "builddir" -> { tc with builddir = one () } + | "target" -> { tc with target = one () } + | "sysroot" -> { tc with sysroot = one () } | k -> Diag.error ~span:f.kspan ~hint: (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)) fields; + if env.tc.target <> "" then derive_cross env explicit; setvar env "cc" [ env.tc.cc ]; 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 "cc_id" [ compiler_id env.tc.cc ]; env.probe.Probe.cc <- env.tc.cc; - env.probe.Probe.cflags <- env.tc.cflags; - env.probe.Probe.ldflags <- env.tc.ldflags + env.probe.Probe.cflags <- env.tc.xflags @ env.tc.cflags; + env.probe.Probe.ldflags <- env.tc.xflags @ env.tc.ldflags let rec predeclare env stmts = let name (v : value) = v.text in diff --git a/lib/exec.ml b/lib/exec.ml index bea0041..f2af1c3 100644 --- a/lib/exec.ml +++ b/lib/exec.ml @@ -73,3 +73,12 @@ let capture cmd = (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)) diff --git a/lib/types.ml b/lib/types.ml index 4046d0d..ce4124c 100644 --- a/lib/types.ml +++ b/lib/types.ml @@ -37,6 +37,9 @@ type toolchain = { cxx : string; ar : string; ranlib : string; + target : string; + sysroot : string; + xflags : string list; cflags : string list; cxxflags : string list; ldflags : string list;