2 * Copyright (c) 2016, Facebook, Inc.
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the "hack" directory of this source tree. An additional grant
7 * of patent rights can be found in the PATENTS file in the same directory.
12 open IndexBuilderTypes
16 (* Keep track of all references yet to scan *)
17 let files_scanned = ref 0
19 let error_count = ref 0
21 (* Extract kind, abstract, and final flags *)
22 let get_details_from_info (info_opt
: Facts.type_facts
option) :
23 si_kind
* bool * bool =
26 | None
-> (SI_Unknown
, false, false)
31 | TKInterface
-> SI_Interface
35 | TKRecord
-> SI_Unknown
36 | TKTypeAlias
-> SI_Typedef
39 let is_abstract = info
.flags
land flags_abstract
> 0 in
40 let is_final = info
.flags
land flags_final
> 0 in
41 (k, is_abstract, is_final))
43 let convert_facts ~
(path
: Relative_path.t
) ~
(facts
: Facts.facts
) : si_capture
45 let relative_path_str = Relative_path.suffix path
in
46 (* Identify all classes in the file *)
47 let class_keys = InvSMap.keys facts
.types
in
49 List.map
class_keys ~f
:(fun key
->
50 let info_opt = InvSMap.find_opt key facts
.types
in
51 let (kind
, is_abstract, is_final) = get_details_from_info info_opt in
53 (* We need to strip away the preceding backslash for hack classes
54 * but leave intact the : for xhp classes. The preceding : symbol
55 * is needed to distinguish which type of a class you want. *)
56 sif_name
= Utils.strip_ns key
;
58 sif_filepath
= relative_path_str;
59 sif_is_abstract
= is_abstract;
60 sif_is_final
= is_final;
63 (* Identify all functions in the file *)
64 let functions_mapped =
65 List.map facts
.functions ~f
:(fun funcname
->
68 sif_kind
= SI_Function
;
69 sif_filepath
= relative_path_str;
70 sif_is_abstract
= false;
74 (* Handle constants *)
75 let constants_mapped =
76 List.map facts
.constants ~f
:(fun constantname
->
78 sif_name
= constantname
;
79 sif_kind
= SI_GlobalConstant
;
80 sif_filepath
= relative_path_str;
81 sif_is_abstract
= false;
85 (* Return unified results *)
86 List.append
classes_mapped functions_mapped |> List.append
constants_mapped
88 (* Parse one single file and capture information about it *)
90 ~
(namespace_map
: (string * string) list
) ~
(path
: Relative_path.t
) :
92 let filename = Relative_path.to_absolute path
in
93 let text = In_channel.read_all
filename in
94 (* Just the facts ma'am *)
95 Facts_parser.mangle_xhp_mode
:= false;
97 Facts_parser.from_text
98 ~php5_compat_mode
:false
99 ~hhvm_compat_mode
:true
100 ~disable_legacy_soft_typehints
:false
101 ~allow_new_attribute_syntax
:false
102 ~disable_legacy_attribute_syntax
:false
103 ~enable_xhp_class_modifier
:false
104 ~disable_xhp_element_mangling
:false
105 ~auto_namespace_map
:namespace_map
109 (* Iterate through facts and print them out *)
112 | Some facts
-> convert_facts ~path ~facts
115 files_scanned := !files_scanned + 1;
118 (* Parse the file using the existing context*)
119 let parse_file (ctxt
: index_builder_context
) (path
: Relative_path.t
) :
121 parse_one_file ~path ~namespace_map
:ctxt
.namespace_map
123 (* Parse a batch of files *)
125 (ctxt
: index_builder_context
)
127 (files
: Relative_path.t list
) : si_capture
=
128 let repo_path = Path.make ctxt
.repo_folder
in
129 if ctxt
.set_paths_for_worker
then (
130 Relative_path.set_path_prefix
Relative_path.Root
repo_path;
131 Relative_path.set_path_prefix
133 (Option.value_exn ctxt
.hhi_root_folder
)
135 List.fold files ~init
:acc ~f
:(fun acc file
->
137 let res = (parse_file ctxt
) file
in
141 error_count := !error_count + 1;
143 "IndexBuilder exception: %s. Failed to parse [%s]"
144 (Caml.Printexc.to_string exn
)
145 (Relative_path.to_absolute file
);
149 ~
(workers
: MultiWorker.worker list
option)
150 (files
: Relative_path.t list
)
151 (ctxt
: index_builder_context
) : si_capture
=
154 ~job
:(parse_batch ctxt
)
157 ~next
:(MultiWorker.next workers files
)
160 WorkerControllerEntryPoint.register ~restore
:(fun () ~
(worker_id
: int) ->
161 Hh_logger.set_id
(Printf.sprintf
"indexBuilder %d" worker_id
))
163 (* Create one worker per cpu *)
164 let init_workers () =
165 let nbr_procs = Sys_utils.nbr_procs in
166 let gc_control = GlobalConfig.gc_control in
167 let config = SharedMem.default_config
in
168 let heap_handle = SharedMem.init
config ~num_workers
:nbr_procs in
171 ~longlived_workers
:false
178 let gather_file_list (path
: string) : Relative_path.t list
=
179 Find.find ~file_only
:true ~filter
:FindUtils.file_filter
[Path.make path
]
180 |> List.map ~f
:(fun path
-> Relative_path.create_detect_prefix path
)
182 (* Run something and measure its duration *)
183 let measure_time ~
(silent
: bool) ~f ~
(name
: string) =
184 let start_time = Unix.gettimeofday
() in
186 let end_time = Unix.gettimeofday
() in
188 Hh_logger.log
"%s [%0.1f secs]" name
(end_time -. start_time);
191 (* All data is ready. Identify unique namespaces and filepaths *)
192 let convert_capture (incoming
: si_capture
) : si_scan_result
=
195 sisr_capture
= incoming
;
196 sisr_namespaces
= Caml.Hashtbl.create
0;
197 sisr_filepaths
= Caml.Hashtbl.create
0;
201 List.iter incoming ~f
:(fun s
->
202 (* Find / add namespace *)
203 let (namespace
, _name
) = Utils.split_ns_from_name s
.sif_name
in
204 if not
(Caml.Hashtbl.mem
result.sisr_namespaces namespace
) then (
205 Caml.Hashtbl.add
result.sisr_namespaces namespace
!ns_id;
209 (* Find / add filepath hashes *)
210 if not
(Caml.Hashtbl.mem
result.sisr_filepaths s
.sif_filepath
) then
211 let path_hash = SharedMemHash.hash_string s
.sif_filepath
in
212 Caml.Hashtbl.add
result.sisr_filepaths s
.sif_filepath
path_hash);
215 let export_to_custom_writer
216 (json_exported_files
: string list
) (ctxt
: index_builder_context
) : unit =
217 match (ctxt
.custom_service
, ctxt
.custom_repo_name
) with
220 print_endline
"API export requires both a service and a repo name."
222 | (Some service
, Some repo_name
) ->
225 "Exported to custom symbol index writer [%s] [%s] in "
232 CustomJsonUploader.send_to_custom_writer
234 ~print_file_status
:true
235 ~files
:json_exported_files
238 ~repo_folder
:ctxt
.repo_folder
)
241 (* Run the index builder project *)
242 let go (ctxt
: index_builder_context
) (workers
: MultiWorker.worker list
option)
244 if Option.is_some ctxt
.json_repo_name
then
245 (* if json repo is specified, just export to custom writer directly *)
246 let json_exported_files =
247 match ctxt
.json_repo_name
with
250 Sys_utils.collect_paths
253 Str.string_match
(Str.regexp
"[./a-zA-Z0-9_]+.json") filename 0
257 export_to_custom_writer json_exported_files ctxt
259 (* Gather list of files *)
261 Printf.sprintf
"Scanned repository folder [%s] in " ctxt
.repo_folder
263 let hhconfig_path = Path.concat
(Path.make ctxt
.repo_folder
) ".hhconfig" in
265 (* Sanity test. If the folder does not have an .hhconfig file, this is probably
266 * an integration test that's using a fake repository. Don't do anything! *)
267 if Disk.file_exists
(Path.to_string
hhconfig_path) then
268 let options = ServerArgs.default_options ~root
:ctxt
.repo_folder
in
272 (Relative_path.create
274 (Path.to_string
hhconfig_path))
277 let popt = ServerConfig.parser_options hhconfig
in
279 { ctxt with namespace_map
= ParserOptions.auto_namespace_map
popt }
283 ~f
:(fun () -> gather_file_list ctxt.repo_folder
)
286 if not
ctxt.silent
then
288 "The repository [%s] lacks an .hhconfig file. Skipping index of repository."
293 (* If desired, get the HHI root folder and add all HHI files from there *)
295 if Option.is_some
ctxt.hhi_root_folder
then
296 let hhi_root_folder_path =
297 Path.to_string
(Option.value_exn
ctxt.hhi_root_folder
)
300 Printf.sprintf
"Scanned HHI folder [%s] in " hhi_root_folder_path
305 ~f
:(fun () -> gather_file_list hhi_root_folder_path)
309 List.append
files hhi_files
313 (* Spawn the parallel parser *)
314 let name = Printf.sprintf
"Parsed %d files in " (List.length
files) in
318 ~f
:(fun () -> parallel_parse ~workers
files ctxt)
321 (* Convert the raw capture into results *)
322 let results = convert_capture capture in
323 (* Are we exporting a sqlite file? *)
325 match ctxt.sqlite_filename
with
330 "Wrote %d symbols to sqlite in "
331 (List.length
results.sisr_capture
)
335 ~f
:(fun () -> SqliteSymbolIndexWriter.record_in_db
filename results)
339 (* Are we exporting a text file? *)
341 match ctxt.text_filename
with
346 "Wrote %d symbols to text in "
347 (List.length
results.sisr_capture
)
352 TextSymbolIndexWriter.record_in_textfile
filename results)
356 (* Are we exporting a json file? *)
357 let json_exported_files =
358 match ctxt.json_filename
with
363 "Wrote %d symbols to json in "
364 (List.length
results.sisr_capture
)
369 JsonSymbolIndexWriter.record_in_jsonfiles
375 (* Are we exporting to a custom writer? *)
376 export_to_custom_writer json_exported_files ctxt