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.

162 lines
6.6 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.
*)
open! IStd
module F = Format
module L = Logging
let create_cmd (source_file, (compilation_data : CompilationDatabase.compilation_data)) =
let swap_executable cmd =
if String.is_suffix ~suffix:"++" cmd then Config.wrappers_dir ^/ "clang++"
else Config.wrappers_dir ^/ "clang"
in
let arg_file =
ClangQuotes.mk_arg_file "cdb_clang_args" ClangQuotes.EscapedNoQuotes
compilation_data.escaped_arguments
in
( source_file
, { CompilationDatabase.directory= compilation_data.directory
; executable= swap_executable compilation_data.executable
; escaped_arguments= ["@" ^ arg_file; "-fsyntax-only"] @ List.rev Config.clang_extra_flags } )
let invoke_cmd (source_file, (cmd : CompilationDatabase.compilation_data)) =
let argv = cmd.executable :: cmd.escaped_arguments in
( ( match Spawn.spawn ~cwd:(Path cmd.directory) ~prog:cmd.executable ~argv () with
| pid ->
!ProcessPoolState.update_status (Mtime_clock.now ()) (SourceFile.to_string source_file) ;
Unix.waitpid (Pid.of_int pid)
|> Result.map_error ~f:(fun unix_error ->
Unix.Exit_or_signal.to_string_hum (Error unix_error) )
| exception Unix.Unix_error (err, f, arg) ->
Error (F.asprintf "%s(%s): %s@." f arg (Unix.Error.message err)) )
|> function
| Ok () ->
()
| Error error ->
let log_or_die fmt =
if Config.linters_ignore_clang_failures || Config.keep_going then L.debug Capture Quiet fmt
else L.die ExternalError fmt
in
log_or_die "Error running compilation for '%a': %a:@\n%s@." SourceFile.pp source_file
Pp.cli_args argv error ) ;
None
let run_compilation_database compilation_database should_capture_file =
let compilation_data =
CompilationDatabase.filter_compilation_data compilation_database ~f:should_capture_file
in
let number_of_jobs = List.length compilation_data in
L.(debug Capture Quiet)
"Starting %s %d files@\n%!" Config.clang_frontend_action_string number_of_jobs ;
L.progress "Starting %s %d files@\n%!" Config.clang_frontend_action_string number_of_jobs ;
let compilation_commands = List.map ~f:create_cmd compilation_data in
let tasks () = ProcessPool.TaskGenerator.of_list compilation_commands in
(* no stats to record so [child_epilogue] does nothing and we ignore the return
{!Tasks.Runner.run} *)
let runner =
Tasks.Runner.create ~jobs:Config.jobs ~f:invoke_cmd ~child_epilogue:(fun () -> ()) ~tasks
in
Tasks.Runner.run runner |> ignore ;
L.progress "@." ;
L.(debug Analysis Medium) "Ran %d jobs" number_of_jobs
(** Computes the compilation database files. *)
let get_compilation_database_files_buck db_deps ~prog ~args =
match
Buck.add_flavors_to_buck_arguments (ClangCompilationDB db_deps) ~filter_kind:`Yes
~extra_flavors:Config.append_buck_flavors args
with
| {targets} when List.is_empty targets ->
L.external_warning "WARNING: found no buck targets to analyze.@." ;
[]
| {command= "build" as command; rev_not_targets; targets} ->
let targets_args = Buck.store_args_in_file targets in
let build_args =
(command :: List.rev_append rev_not_targets (List.rev Config.buck_build_args_no_inline))
@ (* Infer doesn't support C++ modules nor precompiled headers yet (T35656509) *)
"--config" :: "*//cxx.pch_enabled=false" :: "--config" :: "*//cxx.modules_default=false"
:: "--config" :: "*//cxx.modules=False" :: targets_args
in
Logging.(debug Linters Quiet)
"Processed buck command is: 'buck %a'@\n" (Pp.seq F.pp_print_string) build_args ;
Process.create_process_and_wait ~prog ~args:build_args ;
let buck_targets_shell =
prog :: "targets"
:: List.rev_append
(Buck.filter_compatible `Targets rev_not_targets)
(List.rev Config.buck_build_args_no_inline)
@ ("--show-output" :: targets_args)
in
let on_target_lines = function
| [] ->
L.(die ExternalError) "There are no files to process, exiting"
| lines ->
L.(debug Capture Quiet)
"Reading compilation database from:@\n%a@\n"
(Pp.seq ~sep:"\n" F.pp_print_string)
lines ;
(* this assumes that flavors do not contain spaces *)
let split_regex = Str.regexp "#[^ ]* " in
let scan_output compilation_database_files line =
match Str.bounded_split split_regex line 2 with
| [_; filename] ->
`Raw filename :: compilation_database_files
| _ ->
L.internal_error
"Failed to parse `buck targets --show-output ...` line of output:@\n%s" line ;
compilation_database_files
in
List.fold ~f:scan_output ~init:[] lines
in
Utils.with_process_lines
~debug:L.(debug Capture Quiet)
~cmd:buck_targets_shell ~tmp_prefix:"buck_targets_" ~f:on_target_lines
| _ ->
Process.print_error_and_exit "Incorrect buck command: %s %a. Please use buck build <targets>"
prog (Pp.seq F.pp_print_string) args
(** Compute the compilation database files. *)
let get_compilation_database_files_xcodebuild ~prog ~args =
let tmp_file = Filename.temp_file ~in_dir:(ResultsDir.get_path Temporary) "cdb" ".json" in
let xcodebuild_prog, xcodebuild_args = (prog, prog :: args) in
let xcpretty_prog = "xcpretty" in
let xcpretty_args =
[xcpretty_prog; "--report"; "json-compilation-database"; "--output"; tmp_file]
in
L.(debug Capture Quiet)
"Running %s | %s@\n@."
(List.to_string ~f:Fn.id xcodebuild_args)
(List.to_string ~f:Fn.id xcpretty_args) ;
let producer_status, consumer_status =
Process.pipeline ~producer_prog:xcodebuild_prog ~producer_args:xcodebuild_args
~consumer_prog:xcpretty_prog ~consumer_args:xcpretty_args
in
match (producer_status, consumer_status) with
| Ok (), Ok () ->
[`Escaped tmp_file]
| _ ->
L.(die ExternalError) "There was an error executing the build command"
let capture_files_in_database ~changed_files compilation_database =
let filter_changed =
match changed_files with
| None ->
fun _ -> true
| Some changed_files_set ->
fun source_file -> SourceFile.Set.mem source_file changed_files_set
in
run_compilation_database compilation_database filter_changed
let capture_file_in_database compilation_database source_file =
run_compilation_database compilation_database (SourceFile.equal source_file)