(* * Copyright (c) 2016 - present Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. *) open! Utils module CLOpt = CommandLineOption module F = Format let capture_text = if Config.analyzer = Config.Linters then "linting" else "translating" (** Read the files to compile from the changed files index. *) let should_capture_file_from_index () = match SourceFile.changed_files_set with | None -> (match Config.changed_files_index with | Some index -> Process.print_error_and_exit "Error reading the changed files index %s.\n%!" index | None -> function _ -> true) | Some files_set -> function source_file -> SourceFile.Set.mem source_file files_set (** The buck targets are assumed to start with //, aliases are not supported. *) let check_args_for_targets args = if not (IList.exists (String.is_prefix ~prefix:"//") args) then let args_s = String.concat ~sep:" " args in Process.print_error_and_exit "Error reading buck command %s. Please, pass buck targets, aliases are not allowed.\n%!" args_s let add_flavor_to_targets args = let flavor = match Config.use_compilation_database with | Some `Deps -> "#uber-compilation-database" | Some `NoDeps -> "#compilation-database" | _ -> assert false (* cannot happen *) in let process_arg arg = (* Targets are assumed to start with //, aliases are not allowed *) if String.is_prefix ~prefix:"//" arg then arg ^ flavor else arg in IList.map process_arg args let create_files_stack compilation_database should_capture_file = let stack = Stack.create () in let add_to_stack file _ = if should_capture_file file then Stack.push file stack in CompilationDatabase.iter compilation_database add_to_stack; stack let swap_command cmd = let plusplus = "++" in let clang = "clang" in let clangplusplus = "clang++" in if String.is_suffix ~suffix:plusplus cmd then Config.wrappers_dir // clangplusplus else Config.wrappers_dir // clang let run_compilation_file compilation_database file = try let compilation_data = CompilationDatabase.find compilation_database file in let wrapper_cmd = swap_command compilation_data.command in let arg_file = ClangQuotes.mk_arg_file "cdb_clang_args_" ClangQuotes.EscapedNoQuotes [compilation_data.args] in let args = ["@" ^ arg_file] in let env = let env0 = Unix.environment () in let found = ref false in Array.iteri (fun i key_val -> match String.rsplit2 key_val ~on:'=' with | Some (var, args) when String.equal var CLOpt.args_env_var -> found := true ; env0.(i) <- F.sprintf "%s=%s%c--fcp-syntax-only" CLOpt.args_env_var args CLOpt.env_var_sep | _ -> () ) env0 ; if !found then env0 else Array.append env0 [|CLOpt.args_env_var ^ "=--fcp-syntax-only"|] in (Some compilation_data.dir, wrapper_cmd, args, env) with Not_found -> Process.print_error_and_exit "Failed to find compilation data for %a \n%!" SourceFile.pp file let run_compilation_database compilation_database should_capture_file = let number_of_files = CompilationDatabase.get_size compilation_database in Logging.out "Starting %s %d files \n%!" capture_text number_of_files; Logging.stdout "Starting %s %d files \n%!" capture_text number_of_files; let jobs_stack = create_files_stack compilation_database should_capture_file in let capture_text_upper = String.capitalize capture_text in let job_to_string = fun file -> Format.asprintf "%s %a" capture_text_upper SourceFile.pp file in Process.run_jobs_in_parallel jobs_stack (run_compilation_file compilation_database) job_to_string (** Computes the compilation database files. *) let get_compilation_database_files_buck () = let cmd = IList.rev_append Config.rest (IList.rev Config.buck_build_args) in match cmd with | buck :: build :: args -> (check_args_for_targets args; let args_with_flavor = add_flavor_to_targets args in let args = build :: "--config" :: "*//cxx.pch_enabled=false" :: args_with_flavor in Process.create_process_and_wait ~prog:buck ~args; let buck_targets_list = buck :: "targets" :: "--show-output" :: args_with_flavor in let buck_targets = String.concat ~sep:" " buck_targets_list in try match fst @@ Utils.with_process_in buck_targets Std.input_list with | [] -> Logging.stdout "There are no files to process, exiting."; exit 0 | lines -> Logging.out "Reading compilation database from:@\n%s@\n" (String.concat ~sep:"\n" lines); let scan_output compilation_database_files chan = Scanf.sscanf chan "%s %s" (fun target file -> StringMap.add target file compilation_database_files) in (* Map from targets to json output *) let compilation_database_files = IList.fold_left scan_output StringMap.empty lines in IList.map (snd) (StringMap.bindings compilation_database_files) with Unix.Unix_error (err, _, _) -> Process.print_error_and_exit "Cannot execute %s\n%!" (buck_targets ^ " " ^ (Unix.error_message err))) | _ -> let cmd = String.concat ~sep:" " cmd in Process.print_error_and_exit "Incorrect buck command: %s. Please use buck build " cmd (** Compute the compilation database files. *) let get_compilation_database_files_xcodebuild () = let prog_args = IList.rev Config.rest in let temp_dir = Config.results_dir // "clang" in create_dir temp_dir; let tmp_file = Filename.temp_file ~in_dir:temp_dir "cdb" ".json" in let xcodebuild_prog, xcodebuild_args = match prog_args with | prog :: args -> (prog, args) | [] -> failwith("Build command cannot be empty") in let xcpretty_prog = "xcpretty" in let xcpretty_args = ["--report"; "json-compilation-database"; "--output"; tmp_file] in 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 () -> [tmp_file] | _ -> Logging.stderr "There was an error executing the build command"; exit 1 let capture_files_in_database compilation_database = run_compilation_database compilation_database (should_capture_file_from_index ())