From 1638968aba5fa0ca5f71669b717ba48f03326f21 Mon Sep 17 00:00:00 2001 From: sneeker Date: Mon, 18 Jul 2022 07:56:15 +0000 Subject: [PATCH] feat(link): drive output names and link flags from the object format --- lib/binfmt.ml | 43 +++++++++++++++++++++++++++++++++++++++++++ lib/build.ml | 33 ++++++++++++++++++++------------- lib/eval.ml | 1 + lib/install.ml | 11 +++++++---- 4 files changed, 71 insertions(+), 17 deletions(-) create mode 100644 lib/binfmt.ml diff --git a/lib/binfmt.ml b/lib/binfmt.ml new file mode 100644 index 0000000..60d22ee --- /dev/null +++ b/lib/binfmt.ml @@ -0,0 +1,43 @@ +(* Output naming and linker flags follow the object file format, not the + operating system: the same triple can name a platform whose format decides + whether a shared library carries a lib prefix, needs an import library, or + has any notion of a soname at all. *) +type t = Elf | Macho | Coff | Wasm + +let of_platform = function + | "darwin" -> Macho + | "windows" -> Coff + | "wasm" -> Wasm + | _ -> Elf + +let name = function Elf -> "elf" | Macho -> "macho" | Coff -> "coff" | Wasm -> "wasm" +let exe_ext = function Coff -> ".exe" | Wasm -> ".wasm" | Elf | Macho -> "" + +(* Archives are written by ar and read back through -l, so they are named the + same way everywhere, including for the gnu ABI on Windows. *) +let static_name n = "lib" ^ n ^ ".a" + +(* COFF keeps the plain name and links through a separate import library, while + the other formats carry the prefix in the shared object name itself. *) +let shared_name fmt n = + match fmt with + | Elf -> "lib" ^ n ^ ".so" + | Macho -> "lib" ^ n ^ ".dylib" + | Coff -> n ^ ".dll" + | Wasm -> "lib" ^ n ^ ".wasm" + +let import_name fmt n = match fmt with Coff -> Some ("lib" ^ n ^ ".dll.a") | _ -> None + +(* COFF and wasm are position independent by construction, and their drivers + warn about a flag that cannot mean anything to them. *) +let pic_flags = function Elf | Macho -> [ "-fPIC" ] | Coff | Wasm -> [] +let shared_flags = function Macho -> [ "-dynamiclib" ] | Elf | Coff | Wasm -> [ "-shared" ] + +let soname_flags fmt s = + match fmt with + | Elf -> [ "-Wl,-soname," ^ s ] + | Macho -> [ "-Wl,-install_name," ^ s ] + | Coff | Wasm -> [] + +let implib_flags fmt path = + match fmt with Coff -> [ "-Wl,--out-implib," ^ path ] | Elf | Macho | Wasm -> [] diff --git a/lib/build.ml b/lib/build.ml index 11e33cb..9ac79e3 100644 --- a/lib/build.ml +++ b/lib/build.ml @@ -4,12 +4,18 @@ let sanitize s = String.concat "/" (List.map (fun p -> if p = ".." then "__" else p) (String.split_on_char '/' 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 = - match p.platform with "darwin" -> ".dylib" | "windows" -> ".dll" | _ -> ".so" +let fmt p = Binfmt.of_platform p.platform +let exe_ext p = Binfmt.exe_ext (fmt p) +let lib_of p (t : target) = p.tc.builddir ^ "/" ^ Binfmt.static_name t.name +let so_of p (t : target) = p.tc.builddir ^ "/" ^ Binfmt.shared_name (fmt p) t.name + +(* Formats that cannot be linked against directly leave an import library + beside the shared one, which is what dependents resolve -l against. *) +let implib_of p (t : target) = + if t.kind <> Shared then None + else + Option.map (fun n -> p.tc.builddir ^ "/" ^ n) (Binfmt.import_name (fmt p) t.name) -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 ^ exe_ext p let test_of p (t : target) = p.tc.builddir ^ "/test/" ^ t.name ^ exe_ext p @@ -49,7 +55,7 @@ let compile_cmd p ~pic (t : target) src obj = in let flags = base @ inherited_flags - @ (if Hashtbl.mem pic t.name then [ "-fPIC" ] else []) + @ (if Hashtbl.mem pic t.name then Binfmt.pic_flags (fmt p) else []) @ List.concat_map (fun d -> [ "-D"; d ]) t.defines @ List.concat_map (fun d -> [ "-I"; d ]) (includes_of p t) in @@ -72,14 +78,14 @@ let link_cmd p (t : target) objs = | Lib -> Array.of_list ((p.tc.ar :: [ "rcs"; out ]) @ objs) | Shared -> let soname = - match t.soname with - | None -> [] - | Some s -> - if p.platform = "darwin" then [ "-Wl,-install_name," ^ s ] else [ "-Wl,-soname," ^ s ] + match t.soname with None -> [] | Some s -> Binfmt.soname_flags (fmt p) s + in + let implib = + match implib_of p t with None -> [] | Some f -> Binfmt.implib_flags (fmt p) f in Array.of_list - (((driver :: [ "-shared" ]) @ objs @ [ "-o"; out ]) @ soname @ link_libs p t @ t.ldflags - @ p.tc.ldflags @ p.tc.xflags) + (((driver :: Binfmt.shared_flags (fmt p)) @ objs @ [ "-o"; out ]) @ soname @ implib + @ link_libs p t @ t.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 @ p.tc.xflags) @@ -125,7 +131,8 @@ let of_project p = let tag, _ = tag_of t.kind in let ins = objs @ List.map (fun (d : target) -> out_of p d) (deps_of p t) in let label = match t.kind with Lib | Shared -> Filename.basename out | _ -> t.name in - add (Graph.make ~id:!n ~tag ~label ~cmd:(link_cmd p t objs) ~outs:[ out ] ~ins ()); + let outs = out :: Option.to_list (implib_of p t) in + add (Graph.make ~id:!n ~tag ~label ~cmd:(link_cmd p t objs) ~outs ~ins ()); Hashtbl.replace outputs t.name (!n - 1)) p.targets; { g = Graph.build (List.rev !nodes); outputs; p } diff --git a/lib/eval.ml b/lib/eval.ml index c7b7c08..907a4a4 100644 --- a/lib/eval.ml +++ b/lib/eval.ml @@ -96,6 +96,7 @@ let platform_vars env plat arch = [ ("platform", plat); ("arch", arch); + ("format", Binfmt.name (Binfmt.of_platform plat)); ("linux", if plat = "linux" then "true" else "false"); ("darwin", if plat = "darwin" then "true" else "false"); ("bsd", if plat = "bsd" then "true" else "false"); diff --git a/lib/install.ml b/lib/install.ml index 0906c31..6e33c40 100644 --- a/lib/install.ml +++ b/lib/install.ml @@ -8,13 +8,16 @@ let dest_dir p = function let plan p = let from_targets = - List.filter_map + List.concat_map (fun (t : target) -> match t.install with - | None -> None + | None -> [] | Some where -> - let src = Build.out_of p t in - Some (src, Filename.concat (dest_dir p where) (Filename.basename src))) + (* An import library is half of the shared library it belongs to, + so it travels with it. *) + List.map + (fun src -> (src, Filename.concat (dest_dir p where) (Filename.basename src))) + (Build.out_of p t :: Option.to_list (Build.implib_of p t))) p.targets in let from_blocks =