You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

377 lines
12 KiB

(*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
[make] s/ocamlbuild/jbuilder/g Summary: Use jbuilder to build infer instead of ocamlbuild. This is mainly to get faster builds: ``` times in 10ms, ±differences measured in speedups, 4 cores | | ocb total | jb | ±total | ocb user | jb | ±user | ocb cpu | jb | ±cpu | ocb sys | jb | ±sys | |-----------------------------------+-----------+------+--------+----------+------+-------+---------+-----+------+---------+------+------| | byte from scratch | 6428 | 2456 | 2.62 | 7743 | 6662 | 1.16 | 138 | 331 | 2.40 | 1184 | 1477 | 0.80 | | native from scratch | 9841 | 4289 | 2.29 | 9530 | 8834 | 1.08 | 110 | 245 | 2.23 | 1373 | 1712 | 0.80 | | byte after native | 29578 | 1602 | 18.46 | 4514 | 4640 | 0.97 | 170 | 325 | 1.91 | 543 | 576 | 0.94 | | change infer.ml byte | 344 | 282 | 1.22 | 292 | 215 | 1.36 | 96 | 99 | 1.03 | 040 | 066 | 0.61 | | change infer.ml native | 837 | 223 | 3.75 | 789 | 174 | 4.53 | 98 | 99 | 1.01 | 036 | 47 | 0.77 | | change Config.ml byte | 451 | 339 | 1.33 | 382 | 336 | 1.14 | 97 | 122 | 1.26 | 056 | 80 | 0.70 | | change Config.ml native | 4024 | 1760 | 2.29 | 4585 | 4225 | 1.09 | 127 | 276 | 2.17 | 559 | 644 | 0.87 | | change cFrontend_config.ml byte | 348 | 643 | 0.54 | 297 | 330 | 0.90 | 96 | 67 | 0.70 | 038 | 102 | 0.37 | | change cFrontend_config.ml native | 1480 | 584 | 2.53 | 1435 | 906 | 1.58 | 106 | 185 | 1.75 | 136 | 178 | 0.76 | #+TBLFM: $4=$2/$3;f2::$7=$5/$6;f2::$10=$9/$8;f2::$13=$11/$12;f2 50 cores | | ocb total | jb | ±total | ocb user | jb | ±user | ocb cpu | jb | ±cpu | ocb sys | jb | ±sys | |---------------------+-----------+------+--------+----------+------+-------+---------+----+------+---------+------+------| | byte from scratch | 9114 | 2061 | 4.42 | 9334 | 5133 | 1.82 | | | 0/0 | 2566 | 1726 | 1.49 | | native from scratch | 13481 | 3967 | 3.40 | 12291 | 7608 | 1.62 | | | 0/0 | 3003 | 2100 | 1.43 | | byte after native | 3467 | 1476 | 2.35 | 5067 | 3912 | 1.30 | | | 0/0 | 971 | 801 | 1.21 | #+TBLFM: $4=$2/$3;f2::$7=$5/$6;f2::$10=$9/$8;f2::$13=$11/$12;f2 ``` Menu: 1. Write a jbuild file, autogenerated from jbuild.in because we need to fill in some information at build-time (really, at configure time, but TODO), such as whether or not clang is enabled. 2. Nuke lots of stuff from infer/src/Makefile that is now in the jbuild file 3. The jbuild file lives in infer/src/ so it can see all the sources. If we put it somewhere else, eg, infer/, then `jbuilder` scans too many files (all irrelevant) and takes 2.5s to start instead of .8s. Adding irrelevant directories to jbuild-ignore does not help. 4. jbuilder does not support subdirectories, so resort to listing all the source files in the generated jbuild (only source directories need to be manually listed in jbuild.in though). Still, the generated .merlin is wrong and makes merlin find source files in _build, so manually tune it to get good merlin support. We also lose some of merlin for unit tests as it cannot see their build artefacts anymore. 5. checkCopyright gets its own jbuild because it's standalone. Also, remove some deprecation warnings in checkCopyright due to the new version of Core from a while ago. 6. Drop less-used Makefile features (they had regressed anyway) such as building individual modules. Also, building mod_dep.pdf now takes all the source files available so they better build (before, it would only take the source files from the config, eg with or without clang) (that's pretty minor). 7. The toplevel is now built as a custom toplevel because that was easier. It should soon be even easier: https://github.com/janestreet/jbuilder/issues/210 8. Move BUILTINS.mli to BUILTINS.ml because jbuilder is not happy about interface files without implementations. In particular, I did not try to migrate too much of the Makefile logic to jbuilder, more can be done in the future. Reviewed By: jberdine Differential Revision: D5573661 fbshipit-source-id: 4ca6d8f
8 years ago
open! Core
module F = Format
type copyright_event = CopyrightMalformed | CopyrightModified
exception CopyrightEvent of copyright_event
let exit_code_of_event = function CopyrightModified -> 1 | CopyrightMalformed -> 3
type comment_style =
| Line of string * bool
(** line comments, eg "#" for shell, and whether there should be a
newline before the copyright notice *)
| Block of string * string * string * bool (** block comments, eg ("(*", "*", "*)") for ocaml *)
[@@deriving compare]
let equal_comment_style = [%compare.equal: comment_style]
let comment_style_al = Line ("//", false)
let comment_style_c = Block ("/*", "*", "*/", false)
let comment_style_llvm = Line (";", true)
let comment_style_m4 = Line ("dnl", false)
let comment_style_make = Line ("#", false)
let comment_style_ocaml = Block ("(*", "*", "*)", false)
let comment_style_php = Line ("//", true)
let comment_style_python = Line ("#", false)
let comment_style_shell = Line ("#", true)
let comment_styles_lang =
[ (comment_style_al, "AL")
; (comment_style_c, "C")
; (comment_style_llvm, "LLVM")
; (comment_style_m4, "M4")
; (comment_style_make, "Makefile")
; (comment_style_ocaml, "OCaml")
; (comment_style_php, "PHP")
; (comment_style_python, "python")
; (comment_style_shell, "shell") ]
let lang_of_comment_style style =
List.Assoc.find_exn ~equal:equal_comment_style comment_styles_lang style
let comment_styles = List.map comment_styles_lang ~f:fst
let starts_with_newline = function
| Line (_, starts_with_newline) | Block (_, _, _, starts_with_newline) ->
starts_with_newline
let default_start_line_of_com_style style = if starts_with_newline style then 1 else 0
let indent_of_comment_style = function
| Line _ ->
""
| Block (_, inter, _, _) ->
String.make (String.length inter) ' '
let keep_going = ref false
(** If true, update the copyright message of the files. *)
let update_files = ref false
let show_diff = ref false
let line_contains_copyright line = String.is_substring ~substring:"opyright " line
let find_copyright_line lines =
List.findi lines ~f:(fun _ line -> line_contains_copyright line) |> Option.map ~f:fst
let array_rev_find_mapi_from array ~from ~f =
let i = ref from in
let found = ref None in
while !i >= 0 && Option.is_none !found do
found := f !i array.(!i) ;
decr i
done ;
!found
let find_comment_start_and_style lines n =
(* are we in a line comment? *)
let cur_line_comment =
List.find comment_styles ~f:(function
| Line (s, starts_with_newline) when String.is_prefix ~prefix:s lines.(n) ->
if starts_with_newline then n <> 0 else true
| _ ->
false )
in
let is_start i line =
match cur_line_comment with
| Some (Line _) ->
cur_line_comment
| Some (Block _) | None ->
List.find comment_styles ~f:(function
| Block (s, _, _, starts_with_newline) when String.is_substring ~substring:s line ->
if starts_with_newline then i <> 0 else true
| _ ->
false )
in
let find_in_line i line = is_start i line |> Option.map ~f:(fun style -> (i, style)) in
array_rev_find_mapi_from lines ~from:n ~f:find_in_line
let find_comment_end lines n com_style =
let is_end line =
match com_style with
| Line (s, _) ->
(not (String.is_prefix ~prefix:s line), `After)
| Block (_, _, s, _) ->
(String.is_substring ~substring:s line, `OnIt)
in
let i = ref (n + 1) in
let len = Array.length lines in
let found = ref (len - 1) in
while !i < len && !found = len - 1 do
( match is_end lines.(!i) with
| true, `OnIt ->
found := !i
| true, `After ->
found := !i - 1
| false, _ ->
() ) ;
incr i
done ;
match com_style with Line _ -> !found | Block _ -> !found
(** Heuristic to check if this looks like a copyright message. *)
let looks_like_copyright_message cstart cend lines =
let max_len = 100 in
let check_len () =
let ok = ref true in
for i = cstart to cend do
if String.length lines.(i) > max_len then ok := false
done ;
!ok
in
cstart >= 0 && cend - cstart <= 10 && check_len ()
let contains_string ~substring cstart cend lines =
let found = ref false in
for i = cstart to cend do
if String.is_substring ~substring lines.(i) then found := true
done ;
!found
let contains_monoidics cstart cend lines = contains_string ~substring:"Monoidics" cstart cend lines
let contains_ropas cstart cend lines = contains_string ~substring:"ROPAS" cstart cend lines
let pp_copyright ~monoidics ~ropas com_style fmt =
let running_comment = match com_style with Line (s, _) | Block (_, s, _, _) -> s in
let indent = indent_of_comment_style com_style in
let pp_line str =
F.kfprintf (fun fmt -> F.fprintf fmt "@\n") fmt ("%s%s" ^^ str) indent running_comment
in
let pp_start () =
match com_style with Line _ -> () | Block (start, _, _, _) -> F.fprintf fmt "%s@\n" start
in
let pp_end () =
match com_style with
| Line _ ->
()
| Block (_, _, finish, _) ->
F.fprintf fmt "%s%s@\n" indent finish
in
pp_start () ;
if ropas then (
pp_line " Copyright (c) 2016-present, Programming Research Laboratory (ROPAS)" ;
pp_line " Seoul National University, Korea" )
else if monoidics then pp_line " Copyright (c) 2009-2013, Monoidics ltd." ;
pp_line " Copyright (c) Facebook, Inc. and its affiliates." ;
pp_line "" ;
pp_line " This source code is licensed under the MIT license found in the" ;
pp_line " LICENSE file in the root directory of this source tree." ;
pp_end ()
let copyright_has_changed fname lines ~notice_range:(cstart, cend) ~monoidics ~ropas com_style =
let old_copyright =
let r = ref "" in
for i = cstart to cend do
r := !r ^ lines.(i) ^ "\n"
done ;
!r
in
let new_copyright = Format.asprintf "%t" (pp_copyright ~monoidics ~ropas com_style) in
let changed = not (String.equal old_copyright new_copyright) in
if !show_diff && changed then (
let with_suffix fname suff = Filename.basename fname ^ suff in
let orig_fname = with_suffix fname ".orig" in
let new_fname = with_suffix fname ".new" in
Out_channel.write_lines orig_fname [old_copyright] ;
Out_channel.write_lines new_fname [new_copyright] ;
() ) ;
changed
let com_style_of_lang =
[ (".ac", comment_style_m4)
; (".al", comment_style_al)
; (".atd", comment_style_ocaml)
; (".c", comment_style_c)
; (".cpp", comment_style_c)
; (".h", comment_style_c)
; (".inc", comment_style_c)
; (".java", comment_style_c)
; (".ll", comment_style_llvm)
; (".m", comment_style_c)
; (".m4", comment_style_m4)
; (".make", comment_style_make)
; (".mk", comment_style_make)
; (".ml", comment_style_ocaml)
; (".mli", comment_style_ocaml)
; (".mll", comment_style_ocaml)
; (".mly", comment_style_c)
; (".mm", comment_style_c)
; (".php", comment_style_php)
; (".py", comment_style_python)
; (".re", comment_style_c)
; (".rei", comment_style_c)
; (".sh", comment_style_shell)
; ("dune.in", comment_style_ocaml)
; ("dune.common.in", comment_style_ocaml)
; ("dune-common.in", comment_style_ocaml)
; ("dune-workspace.in", comment_style_llvm)
; ("Makefile", comment_style_make) ]
let comment_style_of_filename fname =
List.Assoc.find com_style_of_lang ~equal:Filename.check_suffix fname
let output_diff ~fname lines ?notice_range ?(monoidics = false) ?(ropas = false) com_style =
let lang = lang_of_comment_style com_style in
let pp_range_opt fmt = function
| None ->
F.pp_print_string fmt "none"
| Some (s, e) ->
F.fprintf fmt "%d-%d" s e
in
F.eprintf "%s (lang:%s notice:%a monoidics:%b ropas:%b)\n%!" fname lang pp_range_opt notice_range
monoidics ropas ;
let pp_newfile fmt =
let copy_lines_before, copy_lines_after =
match notice_range with
| Some (s, e) ->
(s - 1, e + 1)
| None ->
let insert_notice_at = default_start_line_of_com_style com_style in
(insert_notice_at - 1, insert_notice_at)
in
for i = 0 to copy_lines_before do
F.fprintf fmt "%s\n" lines.(i)
done ;
if
starts_with_newline com_style && copy_lines_before > 0 && lines.(copy_lines_before - 1) <> ""
then F.fprintf fmt "@\n" ;
pp_copyright ~monoidics ~ropas com_style fmt ;
for i = copy_lines_after to Array.length lines - 1 do
F.fprintf fmt "%s\n" lines.(i)
done ;
F.fprintf fmt "%!"
in
if !update_files then
Out_channel.with_file fname ~f:(fun cout ->
let fmt = F.formatter_of_out_channel cout in
pp_newfile fmt )
else pp_newfile F.std_formatter
let check_copyright fname =
let lines_list = In_channel.read_lines fname in
let lines = Array.of_list lines_list in
match (find_copyright_line lines_list, comment_style_of_filename fname) with
| None, None ->
()
| None, Some com_style ->
output_diff ~fname lines com_style ;
raise (CopyrightEvent CopyrightModified)
| Some n, fname_com_style ->
let cstart, contents_com_style =
find_comment_start_and_style lines n |> Option.value ~default:(0, Line ("#", false))
in
let com_style =
match fname_com_style with
| None ->
contents_com_style
| Some fname_com_style ->
let inferred_styles_agree =
match (fname_com_style, contents_com_style) with
| Line (fs, _), Line (cs, _) ->
String.equal fs cs
| Block (fstart, fbody, fend, _), Block (cstart, cbody, cend, _) ->
String.equal fstart cstart && String.equal fbody cbody && String.equal fend cend
| _ ->
false
in
if not inferred_styles_agree then (
F.eprintf
"Inferred comment style doesn't match the filename '%s':@\n\
From the filename I was expecting the %s comment style, but looking inside the \
file I found %s instead.@."
fname
(lang_of_comment_style fname_com_style)
(lang_of_comment_style contents_com_style) ;
raise (CopyrightEvent CopyrightMalformed) ) ;
fname_com_style
in
(* hack to detect shebangs regardless of the inferred style *)
let com_style =
match com_style with
| Line ("#", false) when String.is_prefix ~prefix:"#!" lines.(0) ->
comment_style_shell
| _ ->
com_style
in
let cend = find_comment_end lines n com_style in
if not (looks_like_copyright_message cstart cend lines) then (
F.eprintf "Copyright not recognized: %s@." fname ;
raise (CopyrightEvent CopyrightMalformed) ) ;
let monoidics = contains_monoidics cstart cend lines in
let ropas = contains_ropas cstart cend lines in
if copyright_has_changed fname lines ~notice_range:(cstart, cend) ~monoidics ~ropas com_style
then (
output_diff ~fname lines ~notice_range:(cstart, cend) ~monoidics ~ropas com_style ;
raise (CopyrightEvent CopyrightModified) )
let speclist =
[ ("-i", Arg.Set update_files, "Update copyright notice in-place")
; ("--inplace", Arg.Set update_files, "Update copyright notice in-place")
; ("-k", Arg.Set keep_going, "Exit with code 0 no matter what")
; ("--keep-going", Arg.Set keep_going, "Exit with code 0 no matter what")
; ( "--show-diff"
, Arg.Set show_diff
, "Write file.orig and file.new files to inspect the differences found" ) ]
let usage_msg = "checkCopyright [-i] [-k] [--show-diff] file1 ..."
let () =
let to_check = ref [] in
let add_file_to_check fname =
(* hack: LICENSE looks copyrightable but is not... *)
if not (String.is_prefix ~prefix:"LICENSE" (Filename.basename fname)) then
to_check := fname :: !to_check
in
Arg.parse (Arg.align speclist) add_file_to_check usage_msg ;
let to_check = List.rev !to_check in
let exit_code = ref 0 in
List.iter to_check ~f:(fun file ->
try check_copyright file
with CopyrightEvent event -> if not !keep_going then exit_code := exit_code_of_event event ) ;
exit !exit_code