source/command/checkCommand.ml (235 lines of code) (raw):
(*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
open Core
module ExitStatus = struct
type t =
| Ok
| PyreError
| BuckInternalError
| BuckUserError
[@@deriving sexp, compare, hash]
let exit_code = function
| Ok -> 0
| PyreError -> 1
| BuckInternalError -> 2
| BuckUserError -> 3
end
module CheckConfiguration = struct
type t = {
base: CommandStartup.BaseConfiguration.t;
strict: bool;
show_error_traces: bool;
additional_logging_sections: string list;
}
[@@deriving sexp, compare, hash]
let of_yojson json =
let open Yojson.Safe.Util in
let open JsonParsing in
(* Parsing logic *)
try
match CommandStartup.BaseConfiguration.of_yojson json with
| Result.Error _ as error -> error
| Result.Ok base ->
let strict = json |> bool_member "strict" ~default:false in
let show_error_traces = json |> bool_member "show_error_traces" ~default:false in
let additional_logging_sections =
json |> string_list_member "additional_logging_sections" ~default:[]
in
Result.Ok { base; strict; show_error_traces; additional_logging_sections }
with
| Type_error (message, _)
| Undefined (message, _) ->
Result.Error message
| other_exception -> Result.Error (Exn.to_string other_exception)
let analysis_configuration_of
{
base =
{
CommandStartup.BaseConfiguration.source_paths;
search_paths;
excludes;
checked_directory_allowlist;
checked_directory_blocklist;
extensions;
log_path;
global_root;
local_root;
debug;
enable_type_comments;
python_version = { Configuration.PythonVersion.major; minor; micro };
parallel;
number_of_workers;
shared_memory =
{ Configuration.SharedMemory.heap_size; dependency_table_power; hash_table_power };
remote_logging = _;
profiling_output = _;
memory_profiling_output = _;
};
show_error_traces;
strict;
additional_logging_sections = _;
}
=
Configuration.Analysis.create
~parallel
~analyze_external_sources:false
~filter_directories:checked_directory_allowlist
~ignore_all_errors:checked_directory_blocklist
~number_of_workers
~local_root:(Option.value local_root ~default:global_root)
~project_root:global_root
~search_paths:(List.map search_paths ~f:SearchPath.normalize)
~strict
~debug
~show_error_traces
~excludes
~extensions
~incremental_style:Configuration.Analysis.Shallow
~log_directory:(PyrePath.absolute log_path)
~python_major_version:major
~python_minor_version:minor
~python_micro_version:micro
~shared_memory_heap_size:heap_size
~shared_memory_dependency_table_power:dependency_table_power
~shared_memory_hash_table_power:hash_table_power
~enable_type_comments
~source_paths:(Configuration.SourcePaths.to_search_paths source_paths)
()
end
let with_performance_tracking ~debug f =
let timer = Timer.start () in
let result = f () in
let { Caml.Gc.minor_collections; major_collections; compactions; _ } = Caml.Gc.stat () in
Statistics.performance
~name:"check"
~timer
~integers:
[
"gc_minor_collections", minor_collections;
"gc_major_collections", major_collections;
"gc_compactions", compactions;
]
~normals:["request kind", "FullCheck"]
();
if debug then
Memory.report_statistics ();
result
let do_check configuration =
Scheduler.with_scheduler ~configuration ~f:(fun scheduler ->
with_performance_tracking ~debug:configuration.debug (fun () ->
let { Service.Check.errors; environment } =
Service.Check.check
~scheduler
~configuration
~call_graph_builder:(module Analysis.Callgraph.DefaultBuilder)
in
( errors,
Analysis.TypeEnvironment.ast_environment environment
|> Analysis.AstEnvironment.read_only )))
let compute_errors ~configuration ~build_system () =
let errors, ast_environment = do_check configuration in
List.map
(List.sort ~compare:Analysis.AnalysisError.compare errors)
~f:(Server.RequestHandler.instantiate_error ~build_system ~configuration ~ast_environment)
let print_errors errors =
Yojson.Safe.to_string
(`Assoc
[
( "errors",
`List
(List.map ~f:(fun error -> Analysis.AnalysisError.Instantiated.to_yojson error) errors)
);
])
|> Log.print "%s"
let run_check check_configuration =
let { CheckConfiguration.base = { CommandStartup.BaseConfiguration.source_paths; _ }; _ } =
check_configuration
in
Server.BuildSystem.with_build_system source_paths ~f:(fun build_system ->
let errors =
compute_errors
~configuration:(CheckConfiguration.analysis_configuration_of check_configuration)
~build_system
()
in
print_errors errors;
Lwt.return ExitStatus.Ok)
let on_exception = function
| Buck.Raw.BuckError { arguments; description; exit_code; additional_logs } ->
Log.error "Cannot build the project: %s. " description;
(* Avoid spamming the user with repro command if the argument is really long. *)
if Buck.Raw.ArgumentList.length arguments <= 20 then
Log.error
"To reproduce this error, run `%s`."
(Buck.Raw.ArgumentList.to_buck_command arguments);
if not (List.is_empty additional_logs) then (
Log.error "Here are the last few lines of Buck log:";
Log.error " ...";
List.iter additional_logs ~f:(Log.error " %s"));
let exit_status =
match exit_code with
| Some exit_code when exit_code < 10 -> ExitStatus.BuckUserError
| _ -> ExitStatus.BuckInternalError
in
exit_status
| Buck.Interface.JsonError message ->
Log.error "Cannot build the project because Buck returns malformed JSON: %s" message;
ExitStatus.BuckUserError
| Buck.Builder.LinkTreeConstructionError message ->
Log.error
"Cannot build the project because Pyre encounters a fatal error while constructing a link \
tree: %s"
message;
ExitStatus.BuckUserError
| Server.ChecksumMap.LoadError message ->
Log.error "Cannot load external wheel properly. %s" message;
ExitStatus.PyreError
| _ as exn ->
Log.error "Pyre encountered an internal exception: %s" (Exn.to_string exn);
ExitStatus.PyreError
let run_check configuration_file =
let exit_status =
match CommandStartup.read_and_parse_json configuration_file ~f:CheckConfiguration.of_yojson with
| Result.Error message ->
Log.error "%s" message;
ExitStatus.PyreError
| Result.Ok
({
CheckConfiguration.base =
{
CommandStartup.BaseConfiguration.global_root;
local_root;
debug;
remote_logging;
profiling_output;
memory_profiling_output;
_;
};
additional_logging_sections;
_;
} as check_configuration) ->
CommandStartup.setup_global_states
~global_root
~local_root
~debug
~additional_logging_sections
~remote_logging
~profiling_output
~memory_profiling_output
();
Lwt_main.run
(Lwt.catch
(fun () -> run_check check_configuration)
(fun exn -> Lwt.return (on_exception exn)))
in
Statistics.flush ();
exit (ExitStatus.exit_code exit_status)
let command =
let filename_argument = Command.Param.(anon ("filename" %: Filename.arg_type)) in
Command.basic
~summary:"Runs a full check without a server"
(Command.Param.map filename_argument ~f:(fun filename () -> run_check filename))