C build tool of the 21st century
at main 303 lines 8.8 kB view raw
1open Types 2 3type t = { 4 name : string; 5 command : 6 flags:Flags.t -> 7 objects:Object_file.t Seq.t -> 8 output:Object_file.t -> 9 string list; 10 transform_output : Object_file.t -> Object_file.t; 11 parallel : bool; 12 ext : String_set.t; 13 wrap_c_flags : Flags.t -> Flags.t; 14} 15 16let c_like ?(force_color = "-fdiagnostics-color=always") cc = 17 fun ~flags ~objects:_ ~output -> 18 cc 19 @ [ force_color; "-c"; "-o"; Eio.Path.native_exn output.Object_file.path ] 20 @ flags.Flags.compile 21 @ [ Eio.Path.native_exn output.source.path ] 22 23let clang = 24 { 25 name = "clang"; 26 command = c_like [ "clang" ]; 27 ext = String_set.of_list [ "c"; "s"; "ll"; "bc" ]; 28 transform_output = Fun.id; 29 parallel = true; 30 wrap_c_flags = Fun.id; 31 } 32 33let clangxx = 34 { 35 name = "clang++"; 36 command = c_like [ "clang++" ]; 37 ext = String_set.of_list [ "cc"; "cpp"; "cxx" ]; 38 transform_output = Fun.id; 39 parallel = true; 40 wrap_c_flags = Fun.id; 41 } 42 43let ispc = 44 { 45 name = "ispc"; 46 command = 47 (fun ~flags ~objects:_ ~output -> 48 [ 49 "ispc"; 50 "--colored-output"; 51 "--emit-obj"; 52 "-o"; 53 Eio.Path.native_exn output.Object_file.path; 54 ] 55 @ flags.compile 56 @ [ Eio.Path.native_exn output.source.path ]); 57 ext = String_set.of_list [ "ispc" ]; 58 transform_output = Fun.id; 59 parallel = true; 60 wrap_c_flags = Fun.id; 61 } 62 63let ghc = 64 { 65 name = "ghc"; 66 command = 67 (fun ~flags ~objects ~output -> 68 let objects = List.of_seq objects in 69 let output_dir = 70 match Eio.Path.split output.Object_file.path with 71 | None -> [] 72 | Some (p, _) -> [ Eio.Path.native_exn p ] 73 in 74 let dep_dirs = 75 List.filter_map 76 (fun obj -> 77 match Eio.Path.split obj.Object_file.path with 78 | None -> None 79 | Some (p, _) -> Some (Eio.Path.native_exn p)) 80 objects 81 in 82 let hidir_dirs = 83 List.sort_uniq String.compare (output_dir @ dep_dirs) 84 in 85 let include_dirs = List.sort_uniq String.compare dep_dirs in 86 87 let hidir = List.concat_map (fun p -> [ "-hidir"; p ]) hidir_dirs in 88 let include_paths = List.map (fun p -> "-i" ^ p) include_dirs in 89 [ 90 "ghc"; 91 "-fdiagnostics-color=always"; 92 "-v0"; 93 "-package"; 94 "base"; 95 "-package"; 96 "text"; 97 "-c"; 98 "-o"; 99 Eio.Path.native_exn output.Object_file.path; 100 ] 101 @ hidir @ include_paths @ flags.Flags.compile 102 @ [ Eio.Path.native_exn output.source.path ]); 103 ext = String_set.of_list [ "hs"; "lhs" ]; 104 transform_output = Fun.id; 105 parallel = false; 106 wrap_c_flags = 107 (fun flags -> 108 let compile = 109 List.concat_map (fun x -> [ "-optc" ^ x ]) flags.Flags.compile 110 in 111 let link = List.concat_map (fun x -> [ "-optl" ^ x ]) flags.link in 112 Flags.v ~compile ~link ()); 113 } 114 115let ocaml = 116 { 117 name = "ocamlfind"; 118 command = 119 (fun ~flags ~objects ~output -> 120 let o_path = 121 let p, s = output.Object_file.path in 122 let s = Filename.chop_extension s ^ ".o" in 123 (p, s) 124 in 125 let output_dir = 126 match Eio.Path.split output.Object_file.path with 127 | None -> [] 128 | Some (p, _) -> [ Eio.Path.native_exn p ] 129 in 130 let dep_dirs = 131 List.filter_map 132 (fun obj -> 133 match Eio.Path.split obj.Object_file.path with 134 | None -> None 135 | Some (p, _) -> Some (Eio.Path.native_exn p)) 136 (List.of_seq objects) 137 in 138 let dirs = List.sort_uniq String.compare (output_dir @ dep_dirs) in 139 let include_paths = List.concat_map (fun p -> [ "-I"; p ]) dirs in 140 [ 141 "ocamlfind"; 142 "ocamlopt"; 143 "-cc"; 144 "clang"; 145 "-I"; 146 "+unix"; 147 "-color=always"; 148 "-c"; 149 "-o"; 150 Eio.Path.native_exn o_path; 151 ] 152 @ include_paths @ flags.Flags.compile 153 @ [ Eio.Path.native_exn output.source.path ]); 154 ext = String_set.of_list [ "ml" ]; 155 transform_output = 156 (fun obj -> 157 let p, s = obj.path in 158 let dest = 159 (Filename.chop_extension @@ Filename.chop_extension @@ s) ^ ".cmx" 160 in 161 { obj with path = (p, dest) }); 162 parallel = false; 163 wrap_c_flags = 164 (fun flags -> 165 let compile = 166 List.concat_map (fun x -> [ "-ccopt"; x ]) flags.Flags.compile 167 in 168 let link = List.concat_map (fun x -> [ "-cclib"; x ]) flags.link in 169 Flags.v ~compile ~link ()); 170 } 171 172let mlton = 173 { 174 name = "mlton"; 175 command = 176 (fun ~flags ~objects:_ ~output -> 177 let out = 178 Filename.quote (Eio.Path.native_exn output.Object_file.path) 179 in 180 let src = Filename.quote (Eio.Path.native_exn output.source.path) in 181 let args = 182 [ "mlton"; "-cc"; "clang"; "-stop"; "o"; "-output"; out ] 183 @ flags.compile @ [ src ] 184 in 185 let keep_externs = 186 if Util.uname = "Darwin" then "-keep_private_externs" else "" 187 in 188 [ 189 "sh"; 190 "-c"; 191 String.concat " " args 192 ^ Printf.sprintf 193 " && ld -r %s %s.0.o %s.1.o -o %s && rm %s.0.o %s.1.o" 194 keep_externs out out out out out; 195 ]); 196 ext = String_set.of_list [ "sml"; "mlb" ]; 197 transform_output = Fun.id; 198 parallel = true; 199 wrap_c_flags = 200 (fun flags -> 201 let compile = 202 List.concat_map (fun x -> [ "-cc-opt"; x ]) flags.Flags.compile 203 in 204 let link = List.concat_map (fun x -> [ "-link-opt"; x ]) flags.link in 205 Flags.v ~compile ~link ()); 206 } 207 208let ats2 = 209 { 210 name = "patscc"; 211 command = 212 c_like [ "patscc"; "-Wno-unused-command-line-argument"; "-cleanaft" ]; 213 ext = String_set.of_list [ "dats"; "sats" ]; 214 transform_output = Fun.id; 215 parallel = true; 216 wrap_c_flags = Fun.id; 217 } 218 219let flang = 220 { 221 name = "flang-new"; 222 command = c_like [ "flang-new" ]; 223 ext = String_set.of_list [ "f"; "f90"; "f95"; "f03"; "f08"; "F"; "F90" ]; 224 transform_output = Fun.id; 225 parallel = true; 226 wrap_c_flags = Fun.id; 227 } 228 229let default = [ clang; clangxx; ispc; ghc; mlton; ats2; flang; ocaml ] 230let all = ref default 231 232let compile_obj t ~env ~sw ~output ~checker ~log_level ~build_dir ~build_mtime 233 ~objects flags = 234 let st = 235 try 236 Option.some 237 ( Eio.Path.stat ~follow:true output.Object_file.path, 238 Eio.Path.stat ~follow:true output.source.path ) 239 with _ -> None 240 in 241 let src_path = 242 if not (Util.is_verbose log_level) then 243 Util.truncate_path_left output.source.path 244 else Eio.Path.native_exn output.source.path 245 in 246 let obj_path = 247 if not (Util.is_verbose log_level) then 248 Util.truncate_path_left output.Object_file.path 249 else Eio.Path.native_exn output.path 250 in 251 match st with 252 | Some (obj, src) when obj.mtime > src.mtime && obj.mtime > build_mtime -> 253 Util.log_spinner 254 ~verbose:(Util.is_verbose log_level) 255 "CACHE %s -> %s" src_path obj_path; 256 None 257 | _ -> 258 Util.log_spinner 259 ~verbose:(Util.is_verbose log_level) 260 "COMPILE(%s) %s -> %s" t.name src_path obj_path; 261 Util.mkparent output.Object_file.path; 262 let cmd = t.command ~flags ~output ~objects in 263 Command.check_command checker t.name; 264 if log_level = `Debug then Util.log " $ %s" (String.concat " " cmd); 265 Log_file.with_log_file ~keep:true ~build_dir 266 ~name:(Digest.to_hex (Digest.string (String.concat " " cmd))) 267 @@ fun (tmp_path, log_file) -> 268 let proc = 269 Eio.Process.spawn env#process_mgr cmd ~sw ~stdout:log_file 270 ~stderr:log_file 271 in 272 Some (tmp_path, proc) 273 274let register compiler = 275 if not (List.exists (fun c -> c.name = compiler.name) !all) then 276 all := compiler :: !all 277 278let find_by_name ?compilers c = 279 match 280 List.find_opt (fun x -> x.name = c) (Option.value ~default:!all compilers) 281 with 282 | Some x -> Some x 283 | None -> ( 284 match String.lowercase_ascii c with 285 | "c" | "cc" | "clang" -> Some clang 286 | "clang++" | "c++" | "cxx" | "cpp" -> Some clangxx 287 | "ispc" -> Some ispc 288 | "ghc" | "hs" | "lhs" -> Some ghc 289 | "flang-new" | "flang" | "fortran" -> Some flang 290 | "sml" | "mlton" -> Some mlton 291 | "ats" | "ats2" | "pats" | "patscc" -> Some ats2 292 | "ocaml" | "ml" | "ocamlopt" | "ocamlfind" -> Some ocaml 293 | _ -> None) 294 295module Set = struct 296 include Set.Make (struct 297 type nonrec t = t 298 299 let compare a b = String_set.compare a.ext b.ext 300 end) 301 302 let default = of_list default 303end