(* This file is distributed under the terms and conditions of the GNU GPL (General Public License), as detailed in the file LICENSE. Copyright (C) 2016 by Gerd Stolpmann. *) (* Bootstrap build driver for omake *) (* You can override the commands ocaml, ocamlc, ocamlopt on the command-line, e.g. ocaml build.ml OCAML=myocaml *) #warnings "-3";; #load "str.cma";; #load "unix.cma";; open Printf module StrMap = Map.Make(String) module StrSet = Set.Make(String) let ocaml vars = try StrMap.find "OCAML" vars with Not_found -> "ocaml" let ocamlc vars = try StrMap.find "OCAMLC" vars with Not_found -> "ocamlc" let ocamlopt vars = try StrMap.find "OCAMLOPT" vars with Not_found -> "ocamlopt" let rec restart f arg = try f arg with | Unix.Unix_error(Unix.EINTR,_,_) -> restart f arg let rec fold_lines f number acc fh = match try let line = input_line fh in let n = String.length line in let line = if n > 0 && line.[n-1] = '\r' then String.sub line 0 (n-1) else line in Some line with End_of_file -> None with | Some line -> let acc' = f number line acc in fold_lines f (number+1) acc' fh | None -> acc let with_command cmd parse = let ch = Unix.open_process_in cmd in try let data = parse ch in let status = Unix.close_process_in ch in match status with | Unix.WEXITED 0 -> data | Unix.WEXITED n -> failwith ("Command exited with error: " ^ cmd) | _ -> failwith ("Command exited with signal: " ^ cmd) with | Unix.Unix_error(code,_,name) -> ignore(Unix.close_process_in ch); let prefix = if name = "" then "" else name ^ ": " in raise (Sys_error(prefix ^ Unix.error_message code)) let bytecomp_c_compiler_re = Str.regexp "^bytecomp_c_compiler: \\(.*\\)$" let ws_re = Str.regexp "[ \t]+" let ocaml_cc vars = try StrMap.find "OCAML_CC" vars with Not_found -> with_command "ocamlc -config" (fold_lines (fun i line acc -> if Str.string_match bytecomp_c_compiler_re line 0 then let full = Str.matched_group 1 line in let words = Str.split ws_re full in List.hd words else acc ) 1 "cc" ) let ocaml_cflags vars = try StrMap.find "OCAML_CFLAGS" vars with Not_found -> with_command "ocamlc -config" (fold_lines (fun i line acc -> if Str.string_match bytecomp_c_compiler_re line 0 then let full = Str.matched_group 1 line in let words = Str.split ws_re full in String.concat " " (List.tl words) else acc ) 1 "" ) let exec_command env prog args = try let cmd = String.concat " " (prog :: List.map Filename.quote (Array.to_list args)) in printf "%s\n%!" cmd; let env = Array.of_list (List.map (fun (n,v) -> n ^ "=" ^ v) env) in let env = Array.append env (Unix.environment()) in let args = Array.append [| prog |] args in let pid = Unix.create_process_env prog args env Unix.stdin Unix.stdout Unix.stderr in let _, st = Unix.waitpid [] pid in match st with | Unix.WEXITED 0 -> () | Unix.WEXITED n -> failwith ("Command exited with error: " ^ cmd) | _ -> failwith ("Command exited with signal: " ^ cmd) with | Unix.Unix_error(code,_,name) -> let prefix = if name = "" then "" else name ^ ": " in raise (Sys_error(prefix ^ Unix.error_message code)) let copy src dest = (* only for small files... *) let f_src = open_in_bin src in let n = in_channel_length f_src in let data = Bytes.create n in really_input f_src data 0 n; close_in f_src; let f_dest = open_out_bin dest in output f_dest data 0 n; flush f_dest; close_out f_dest let copy_executable src dest = let need_exe = Sys.file_exists (src ^ ".exe") in let with_exe file = if need_exe then file ^ ".exe" else file in let src = with_exe src in let dest = with_exe dest in copy src dest; Unix.chmod dest 0o755 let touch file = let f = open_out_gen [ Open_wronly; Open_append; Open_creat ] 0o666 file in close_out f let safe_mkdir file = try Unix.mkdir file 0o777 with | Unix.Unix_error(Unix.EEXIST,_,_) -> () let is_dir dir = (* Sys.file_exists (Filename.concat dir ".") - doesn't work under Windows *) try let st = Unix.stat dir in Unix.(st.st_kind = S_DIR) with | Unix.Unix_error _ -> false let rec find dir pattern = (* pattern: regexp of the files to match in this directory *) let re = Str.regexp pattern in let allfiles = Array.to_list (Sys.readdir dir) in let allpaths = List.map (Filename.concat dir) allfiles in let files = List.filter (fun n -> Str.string_match re n 0 ) allfiles in let paths = List.map (Filename.concat dir) files in let subdirs = List.filter is_dir allpaths in let subpaths = List.flatten (List.map (fun dir -> find dir pattern) subdirs) in paths @ subpaths let safe_remove path = try Sys.remove path with Sys_error _ -> () let safe_remove_list l = List.iter safe_remove l let system_re = Str.regexp "^system: \\(.*\\)$" let get_system vars = let cmd = sprintf "%s -config" (ocamlc vars) in with_command cmd (fold_lines (fun i line acc -> if Str.string_match system_re line 0 then Str.matched_group 1 line else acc ) 1 "unknown" ) let have_ocamlopt vars = let cmd = ocamlopt vars in Sys.command cmd = 0 let configure_bootstrap vars = safe_mkdir "boot"; copy "src/Makefile" "boot/Makefile"; touch "boot/Makefile.dep"; ( match get_system vars with | "mingw" | "mingw64" -> copy "mk/osconfig_mingw.mk" "boot/osconfig.mk" | "win32" | "win64" -> copy "mk/osconfig_msvc.mk" "boot/osconfig.mk" | _ -> copy "mk/osconfig_unix.mk" "boot/osconfig.mk" ); let ocaml_cc_val = ocaml_cc vars in let ocaml_cflags_val = ocaml_cflags vars in let f = open_out_gen [Open_wronly; Open_append; Open_binary] 0 "boot/osconfig.mk" in fprintf f "\n\n# additions from build.ml:\n"; fprintf f "OCAML_CC = %s\n" ocaml_cc_val; fprintf f "OCAML_CFLAGS = %s\n" ocaml_cflags_val; close_out f let make self makedir vars target = let dir = Filename.dirname self in let args = StrMap.fold (fun n v acc -> (n ^ "=" ^ v) :: acc) vars [] in let args_a = Array.of_list (Filename.concat dir "make.ml" :: "-C" :: makedir :: target :: args) in exec_command [] (ocaml vars) args_a let run_bootstrap self vars = configure_bootstrap vars; let vars = if have_ocamlopt vars then StrMap.add "PREFERRED" ".opt" (StrMap.add "OCAMLSUFFIX" ".opt" vars) else vars in make self "boot" vars "Makefile.dep"; make self "boot" vars "omake" let do_bootstrap self vars ty = match ty with | `Force -> run_bootstrap self vars; ("boot/omake", "src/main/prelim_omake") | `Disable -> ("omake", "omake") | `Auto -> if not (Sys.file_exists "boot/omake") && not (Sys.file_exists "boot/omake.exe") then run_bootstrap self vars; ("boot/omake", "src/main/prelim_omake") let run_omake omake env vars args = let args1 = StrMap.fold (fun n v acc -> (n ^ "=" ^ v) :: acc) vars [] in exec_command env omake (Array.of_list (args @ args1)) let do_action self vars action omake1 omake2 = let env = [ "OMAKEFLAGS", ""; "OMAKEPATH", "lib" ] in match action with | `Build -> if not (Sys.file_exists ".preconfig") then failwith "Unconfigured. Run the configure script first!"; touch ".config"; run_omake omake1 env vars [ "--dotomake"; ".omake"; "--force-dotomake"; "main" ]; (* Windows cannot replace running executables. Create a copy. *) if omake1 <> omake2 then copy_executable "src/main/omake" "src/main/prelim_omake"; run_omake omake2 env vars [ "--dotomake"; ".omake"; "--force-dotomake"; "all" ] | `Install -> run_omake omake2 env vars [ "--dotomake"; ".omake"; "--force-dotomake"; "install" ] | `Clean -> safe_remove ".config"; if Sys.file_exists "boot" then find "boot" ".*" |> safe_remove_list; find "." ".*\\.omc" |> safe_remove_list; find "src" ".*\\.cmi" |> safe_remove_list; find "src" ".*\\.cmo" |> safe_remove_list; find "src" ".*\\.cmx" |> safe_remove_list; find "src" ".*\\.o" |> safe_remove_list; find "src" ".*\\.cmxa" |> safe_remove_list; find "src" ".*\\.a" |> safe_remove_list; find "src" ".*\\.so" |> safe_remove_list; find "src" ".*\\.dll" |> safe_remove_list; find "src" ".*\\.exe" |> safe_remove_list; safe_remove "src/env/omake_ast_parse.mly"; safe_remove "src/libmojave/lm_thread_core.ml"; safe_remove "src/libmojave/lm_thread_pool.ml"; safe_remove "src/magic/omake_magic.ml"; safe_remove "src/shell/omake_shell_sys.ml"; safe_remove "src/main/omake"; safe_remove "src/main/prelim_omake"; safe_remove "src/main/osh"; safe_remove ".omakedb"; safe_remove ".omakedb.lock" let set_re = Str.regexp "^\\([^=]+\\)=\\(.*\\)$" let main() = let self = Sys.argv.(0) in let self = if Filename.is_relative self then Filename.concat (Sys.getcwd()) self else self in let bootstrap = ref `Auto in let action = ref `Build in let vars = ref StrMap.empty in let setvar name value = vars := StrMap.add name value !vars in Arg.parse [ "-force-bootstrap", Arg.Unit (fun () -> bootstrap := `Force), " do the bootstrap again"; "-no-bootstrap", Arg.Unit (fun () -> bootstrap := `Disable), " no bootstrap, instead use the omake from $PATH"; "-auto-bootstrap", Arg.Unit (fun () -> bootstrap := `Auto), " do the bootstrap if needed - this is the default"; "-build", Arg.Unit (fun () -> action := `Build), " action: build omake - this is the default"; "-install", Arg.Unit (fun () -> action := `Install), " action: install omake"; "-clean", Arg.Unit (fun () -> action := `Clean), " action: clean up"; ] (fun s -> if Str.string_match set_re s 0 then let name = Str.matched_group 1 s in let value = Str.matched_group 2 s in setvar name value else raise(Arg.Bad ("Don't know what to do with: " ^ s)) ) "ocaml build.ml [options] (var=value) ..."; let boot_omake, made_omake = if !action = `Clean then "", "" else do_bootstrap self !vars !bootstrap in do_action self !vars !action boot_omake made_omake let () = try main() with | Failure msg | Arg.Bad msg | Sys_error msg -> flush stdout; prerr_endline msg; flush stderr; exit 1