C build tool of the 21st century
at main 182 lines 6.9 kB view raw
1open Zenon_build 2open Common 3 4let build ?output ?(ignore = []) ~arg ~cflags ~ldflags ~path ~builds ~file ~run 5 ~pkg ~(linker : string option) ~log_level ~watch () = 6 let path = Unix.realpath path in 7 Eio_posix.run @@ fun env -> 8 let get_config_path () = 9 let eio_path = Eio.Path.(env#fs / Unix.realpath path) in 10 let dir_path = 11 if Eio.Path.is_directory eio_path then eio_path 12 else 13 match Eio.Path.split eio_path with Some (d, _) -> d | None -> eio_path 14 in 15 Config.find_config_in_parents dir_path 16 in 17 18 let get_file_mtimes build_configs = 19 let all_files = ref String_map.empty in 20 (* Add config file mtime *) 21 (match get_config_path () with 22 | Some config_path -> 23 let path_str = Eio.Path.native_exn config_path in 24 let mtime = (Eio.Path.stat ~follow:true config_path).mtime in 25 all_files := String_map.add path_str mtime !all_files 26 | None -> ()); 27 List.iter 28 (fun (build : Build.t) -> 29 let sources = Build.locate_source_files build |> List.of_seq in 30 List.iter 31 (fun (sf : Source_file.t) -> 32 let path = Eio.Path.native_exn sf.path in 33 try 34 let mtime = (Eio.Path.stat ~follow:true sf.path).mtime in 35 all_files := String_map.add path mtime !all_files 36 with _ -> 37 (* File might have been deleted during the build process, 38 which is fine. *) 39 ()) 40 sources) 41 build_configs; 42 !all_files 43 in 44 45 let do_build build_configs = 46 let ignore_patterns = List.map Util.glob ignore in 47 let targets, current_build_configs = 48 match build_configs with 49 | [] -> 50 ( [ "default" ], 51 [ 52 Build.v env ~ignore:ignore_patterns ~pkgconf:pkg 53 ~flags:(Flags.v ()) 54 ~source:Eio.Path.(env#fs / path) 55 ~files:file ~name:"default" 56 ?output:(Option.map (fun x -> Eio.Path.(env#cwd / x)) output) 57 ~log_level 58 ?linker: 59 (Option.map 60 (fun name -> Config.Compiler_config.(linker (named name))) 61 linker); 62 ] ) 63 | configs -> (builds, configs) 64 in 65 let active_targets = filter_builds current_build_configs targets in 66 let build_map = make_build_map current_build_configs in 67 let builds_with_deps_set = builds_with_deps build_map active_targets in 68 let plan = Plan.v () in 69 (* Separate generic and language-specific flags *) 70 let generic_cflags, lang_cflags = 71 List.partition (fun (lang, _) -> lang = "all") cflags 72 in 73 let generic_ldflags, lang_ldflags = 74 List.partition (fun (lang, _) -> lang = "all") ldflags 75 in 76 let generic_cflags = List.map snd generic_cflags in 77 let generic_ldflags = List.map snd generic_ldflags in 78 let cflags_by_lang = Hashtbl.create 8 in 79 List.iter 80 (fun (lang, flag) -> 81 let flags = 82 try Hashtbl.find cflags_by_lang lang with Not_found -> [] 83 in 84 Hashtbl.replace cflags_by_lang lang (flag :: flags)) 85 lang_cflags; 86 let ldflags_by_lang = Hashtbl.create 8 in 87 List.iter 88 (fun (lang, flag) -> 89 let flags = 90 try Hashtbl.find ldflags_by_lang lang with Not_found -> [] 91 in 92 Hashtbl.replace ldflags_by_lang lang (flag :: flags)) 93 lang_ldflags; 94 let reset = not (List.is_empty file) in 95 List.iter 96 (fun build -> 97 if 98 String_set.is_empty builds_with_deps_set 99 || String_set.mem build.Build.name builds_with_deps_set 100 then ( 101 let output = 102 match output with 103 | None -> build.Build.output 104 | Some output -> Some Eio.Path.(env#cwd / output) 105 in 106 (* Add generic flags to build.flags *) 107 Flags.add_compile_flags build.Build.flags generic_cflags; 108 Flags.add_link_flags build.Build.flags generic_ldflags; 109 (* Add language-specific flags to build.compiler_flags *) 110 Hashtbl.iter 111 (fun lang flags -> 112 let existing_flags = 113 try Hashtbl.find build.Build.compiler_flags lang 114 with Not_found -> 115 let new_flags = Flags.v () in 116 Hashtbl.add build.Build.compiler_flags lang new_flags; 117 new_flags 118 in 119 Flags.add_compile_flags existing_flags (List.rev flags)) 120 cflags_by_lang; 121 Hashtbl.iter 122 (fun lang flags -> 123 let existing_flags = 124 try Hashtbl.find build.Build.compiler_flags lang 125 with Not_found -> 126 let new_flags = Flags.v () in 127 Hashtbl.add build.Build.compiler_flags lang new_flags; 128 new_flags 129 in 130 Flags.add_link_flags existing_flags (List.rev flags)) 131 ldflags_by_lang; 132 let () = Build.add_source_files ~reset build file in 133 Plan.build plan 134 { 135 build with 136 log_level; 137 pkgconf = build.Build.pkgconf @ pkg; 138 ignore = build.Build.ignore @ ignore_patterns; 139 output; 140 linker = 141 (match linker with 142 | Some l -> Some Config.Compiler_config.(linker @@ named l) 143 | None -> build.Build.linker); 144 })) 145 current_build_configs; 146 Plan.run_all ~execute:run ~args:arg ~log_level plan 147 (List.filter 148 (fun b -> String_set.mem b.Build.name builds_with_deps_set) 149 current_build_configs); 150 get_file_mtimes current_build_configs 151 in 152 153 let initial_build_configs = load_config ~log_level ~builds env path in 154 155 (* If the user specified targets, check that they are all valid *) 156 if not (List.is_empty builds) then ( 157 let found_build_names = 158 List.map (fun (b : Build.t) -> b.name) initial_build_configs 159 |> String_set.of_list 160 in 161 let requested_build_names = String_set.of_list builds in 162 let missing = String_set.diff requested_build_names found_build_names in 163 if not (String_set.is_empty missing) then 164 Fmt.failwith "Unknown build targets: %s" 165 (String.concat ", " (String_set.to_list missing))); 166 167 let initial_mtimes = do_build initial_build_configs in 168 169 if watch then 170 let rec loop mtimes = 171 Eio.Time.sleep env#clock 2.0; 172 let new_build_configs = load_config ~log_level ~builds env path in 173 let new_mtimes = get_file_mtimes new_build_configs in 174 if not (String_map.equal Float.equal mtimes new_mtimes) then ( 175 Util.log 176 ~verbose:(Util.is_verbose log_level) 177 "+ Change detected, rebuilding..."; 178 let updated_mtimes = do_build new_build_configs in 179 loop updated_mtimes) 180 else loop mtimes 181 in 182 loop initial_mtimes