dune/bootstrap.ml

310 lines
7.3 KiB
OCaml
Raw Normal View History

2016-12-15 16:54:46 +00:00
#warnings "-40";;
2016-12-02 13:54:32 +00:00
#load "unix.cma";;
module Array = ArrayLabels
module List = ListLabels
module String = struct
include StringLabels
let capitalize_ascii = String.capitalize_ascii
let uncapitalize_ascii = String.uncapitalize_ascii
end
2016-11-03 16:44:09 +00:00
open Printf
2016-12-02 13:54:32 +00:00
module String_set = Set.Make(String)
2016-12-15 16:54:46 +00:00
(* Modules overriden to bootstrap faster *)
let overridden =
String_set.of_list
2017-02-21 15:09:58 +00:00
[ "Glob_lexer"
2016-12-15 16:54:46 +00:00
]
2016-11-03 16:44:09 +00:00
let ( ^/ ) = Filename.concat
2016-12-15 16:54:46 +00:00
let protectx x ~finally ~f =
match f x with
| y -> finally x; y
| exception e -> finally x; raise e
let starts_with s ~prefix =
let plen = String.length prefix in
let slen = String.length s in
slen >= plen && String.sub s ~pos:0 ~len:plen = prefix
2016-12-02 13:54:32 +00:00
let exec fmt =
ksprintf (fun cmd ->
print_endline cmd;
Sys.command cmd)
fmt
2016-11-03 16:44:09 +00:00
2016-11-13 12:25:45 +00:00
let path_sep =
if Sys.win32 then
';'
else
':'
let split_path s =
let rec loop i j =
if j = String.length s then
[String.sub s ~pos:i ~len:(j - i)]
else if s.[j] = path_sep then
String.sub s ~pos:i ~len:(j - i) :: loop (j + 1) (j + 1)
else
loop i (j + 1)
in
loop 0 0
2016-11-03 16:44:09 +00:00
let path =
match Sys.getenv "PATH" with
| exception Not_found -> []
2016-11-13 12:25:45 +00:00
| s -> split_path s
2016-11-03 16:44:09 +00:00
let exe = if Sys.win32 then ".exe" else ""
let prog_not_found prog =
eprintf "Program %s not found in PATH" prog;
exit 2
type mode = Native | Byte
let best_prog dir prog =
let fn = dir ^/ prog ^ ".opt" ^ exe in
if Sys.file_exists fn then
Some fn
2016-12-15 16:54:46 +00:00
else
2016-11-03 16:44:09 +00:00
let fn = dir ^/ prog ^ exe in
if Sys.file_exists fn then
Some fn
else
None
let find_prog prog =
let rec search = function
| [] -> None
| dir :: rest ->
match best_prog dir prog with
| None -> search rest
| Some fn -> Some (dir, fn)
in
search path
let get_prog dir prog =
match best_prog dir prog with
| None -> prog_not_found prog
| Some fn -> fn
2016-12-02 13:54:32 +00:00
let bin_dir, mode, compiler =
match find_prog "ocamlopt" with
| Some (bin_dir, prog) -> (bin_dir, Native, prog)
| None ->
match find_prog "ocamlc" with
| Some (bin_dir, prog) -> (bin_dir, Byte, prog)
| None -> prog_not_found "ocamlc"
let ocamllex = get_prog bin_dir "ocamllex"
let ocamldep = get_prog bin_dir "ocamldep"
let run_ocamllex name =
let src = "src" ^/ name ^ ".mll" in
let dst = "src" ^/ name ^ ".ml" in
let x = Sys.file_exists dst in
let n = exec "%s -q %s" ocamllex src in
if n <> 0 then exit n;
if not x then
at_exit (fun () -> try Sys.remove dst with _ -> ())
let modules =
Sys.readdir "src"
|> Array.fold_left ~init:[] ~f:(fun acc fn ->
match String.rindex fn '.' with
| exception Not_found -> acc
| i ->
let ext = String.sub fn ~pos:(i + 1) ~len:(String.length fn - i - 1) in
match ext with
| "ml" | "mll" ->
let base = String.sub fn ~pos:0 ~len:i in
2016-12-15 16:54:46 +00:00
let mod_name = String.capitalize_ascii base in
if String_set.mem mod_name overridden then
acc
else begin
if ext = "mll" then run_ocamllex base;
String.capitalize_ascii base :: acc
end
2016-12-02 13:54:32 +00:00
| _ ->
acc)
|> String_set.of_list
let split_words s =
let rec skip_blanks i =
if i = String.length s then
[]
else
match s.[i] with
| ' ' | '\t' -> skip_blanks (i + 1)
| _ -> parse_word i (i + 1)
and parse_word i j =
if j = String.length s then
[String.sub s ~pos:i ~len:(j - i)]
else
match s.[j] with
| ' ' | '\t' -> String.sub s ~pos:i ~len:(j - i) :: skip_blanks (j + 1)
| _ -> parse_word i (j + 1)
in
skip_blanks 0
let read_deps files =
let ic =
let cmd =
sprintf "%s -modules %s"
ocamldep (String.concat ~sep:" " files)
in
print_endline cmd;
Unix.open_process_in cmd
in
let rec loop acc =
match input_line ic with
| exception End_of_file ->
ignore (Unix.close_process_in ic);
acc
| line ->
let i = String.index line ':' in
let unit =
String.sub line ~pos:0 ~len:i
|> Filename.basename
|> Filename.chop_extension
|> String.capitalize_ascii
in
let deps =
split_words (String.sub line ~pos:(i + 1)
~len:(String.length line - (i + 1)))
|> List.filter ~f:(fun m -> String_set.mem m modules)
in
loop ((unit, deps) :: acc)
in
loop []
let topsort deps =
let n = List.length deps in
let deps_by_module = Hashtbl.create n in
List.iter deps ~f:(fun (m, deps) ->
2016-12-15 16:54:46 +00:00
Hashtbl.add deps_by_module m deps);
2016-12-02 13:54:32 +00:00
let not_seen = ref (List.map deps ~f:fst |> String_set.of_list) in
let res = ref [] in
let rec loop m =
if String_set.mem m !not_seen then begin
not_seen := String_set.remove m !not_seen;
List.iter (Hashtbl.find deps_by_module m) ~f:loop;
res := m :: !res
end
in
while not (String_set.is_empty !not_seen) do
loop (String_set.choose !not_seen)
done;
List.rev !res
let modules =
let files =
List.map (String_set.elements modules) ~f:(fun unit ->
sprintf "src/%s.ml" (String.uncapitalize_ascii unit))
in
topsort (read_deps files)
2016-11-03 16:44:09 +00:00
let count_newlines s =
let newlines = ref 0 in
String.iter s ~f:(function
| '\n' -> incr newlines
| _ -> ());
!newlines
let read_file fn =
let ic = open_in fn in
let data = really_input_string ic (in_channel_length ic) in
close_in ic;
data
2016-12-15 16:54:46 +00:00
let generated_file = "boot.ml"
2016-11-03 16:44:09 +00:00
let generate_file_with_all_the_sources () =
2016-12-02 13:54:32 +00:00
let oc = open_out generated_file in
2016-11-03 16:44:09 +00:00
let pos_in_generated_file = ref 1 in
let pr fmt =
ksprintf (fun s ->
output_string oc s;
output_char oc '\n';
incr pos_in_generated_file)
fmt
in
let dump fn =
let s = read_file fn in
pr "# 1 %S" fn;
output_string oc s;
let newlines = count_newlines s in
let newlines =
if s <> "" && s.[String.length s - 1] <> '\n' then begin
output_char oc '\n';
newlines + 1
end else
newlines
in
pos_in_generated_file := !pos_in_generated_file + newlines;
pr "# %d %S" (!pos_in_generated_file + 1) generated_file
in
2016-12-15 16:54:46 +00:00
let s = {|
2017-02-21 15:09:58 +00:00
module Jbuilder_re = struct
module Re = struct
type t = unit
type re = unit
let compile () = ()
let execp _ _ = false
end
2016-12-15 16:54:46 +00:00
end
module Glob_lexer = struct
let parse_string _ = Error (0, "globs are not available during bootstrap")
end
|}
in
output_string oc s;
pos_in_generated_file := !pos_in_generated_file + count_newlines s;
2016-11-03 16:44:09 +00:00
List.iter modules ~f:(fun m ->
let base = String.uncapitalize m in
let mli = sprintf "src/%s.mli" base in
let ml = sprintf "src/%s.ml" base in
if Sys.file_exists mli then begin
pr "module %s : sig" m;
dump mli;
pr "end = struct";
dump ml;
pr "end"
end else begin
pr "module %s = struct" m;
dump ml;
pr "end"
end);
2017-02-21 15:09:58 +00:00
output_string oc "let () = Main.bootstrap ()\n";
2016-11-03 16:44:09 +00:00
close_out oc
2016-12-02 13:54:32 +00:00
let () = generate_file_with_all_the_sources ()
2016-11-03 16:44:09 +00:00
let () =
let lib_ext =
match mode with
| Native -> "cmxa"
| Byte -> "cma"
in
2016-12-15 16:54:46 +00:00
let n =
protectx ()
~f:(fun () ->
exec "%s -w -40 -o boot.exe unix.%s %s" compiler lib_ext generated_file)
~finally:(fun () ->
try
Array.iter (Sys.readdir ".") ~f:(fun fn ->
if fn <> "boot.exe" && starts_with fn ~prefix:"boot." then
Sys.remove fn)
with _ ->
())
in
if n <> 0 then exit n