open Import let with_metrics ~common f = let start_time = Unix.gettimeofday () in Fiber.finalize f ~finally:(fun () -> let duration = Unix.gettimeofday () -. start_time in if Common.print_metrics common then ( let gc_stat = Gc.quick_stat () in (* We reset Memo counters below, unconditionally. *) let memo_counters_report = Memo.Metrics.report ~reset_after_reporting:false in Console.print_user_message (User_message.make ([ Pp.textf "%s" memo_counters_report ; Pp.textf "(%.2fs total, %.1fM heap words)" duration (float_of_int gc_stat.heap_words /. 1_000_000.) ; Pp.text "Timers:" ] @ List.map ~f:(fun (timer, { Metrics.Timer.Measure.cumulative_time; count }) -> Pp.textf "%s - time spent = %.2fs, count = %d" timer cumulative_time count) (String.Map.to_list (Metrics.Timer.aggregated_timers ()))))); Memo.Metrics.reset (); Fiber.return ()) ;; let run_build_system ~common ~request = let run ~(toplevel : unit Memo.Lazy.t) = with_metrics ~common (fun () -> build (fun () -> Memo.Lazy.force toplevel)) in let open Fiber.O in Fiber.finalize (fun () -> (* CR-someday amokhov: Currently we invalidate cached timestamps on every incremental rebuild. This conservative approach helps us to work around some [mtime] resolution problems (e.g. on Mac OS). It would be nice to find a way to avoid doing this. In fact, this may be unnecessary even for the initial build if we assume that the user does not modify files in the [_build] directory. For now, it's unclear if optimising this is worth the effort. *) Cached_digest.invalidate_cached_timestamps (); let* setup = Import.Main.setup () in let request = Action_builder.bind (Action_builder.of_memo setup) ~f:(fun setup -> request setup) in (* CR-someday cmoseley: Can we avoid creating a new lazy memo node every time the build system is rerun? *) (* This top-level node is used for traversing the whole Memo graph. *) let toplevel_cell, toplevel = Memo.Lazy.Expert.create ~name:"toplevel" (fun () -> let open Memo.O in let+ (), (_ : Dep.Fact.t Dep.Map.t) = Action_builder.evaluate_and_collect_facts request in ()) in let* res = run ~toplevel in let+ () = match Common.dump_memo_graph_file common with | None -> Fiber.return () | Some file -> let path = Path.external_ file in let+ graph = Memo.dump_cached_graph ~time_nodes:(Common.dump_memo_graph_with_timing common) toplevel_cell in Graph.serialize graph ~path ~format:(Common.dump_memo_graph_format common) (* CR-someday cmoseley: It would be nice to use Persistent to dump a copy of the graph's internal representation here, so it could be used without needing to re-run the build*) in res) ~finally:(fun () -> Hooks.End_of_build.run (); Fiber.return ()) ;; let poll_handling_rpc_build_requests ~(common : Common.t) ~config = let open Fiber.O in let rpc = match Common.rpc common with | `Allow server -> server | `Forbid_builds -> Code_error.raise "rpc server must be allowed in passive mode" [] in Scheduler.Run.poll_passive ~get_build_request: (let+ (Build (targets, ivar)) = Dune_rpc_impl.Server.pending_build_action rpc in let request setup = Target.interpret_targets (Common.root common) config setup targets in run_build_system ~common ~request, ivar) ;; let run_build_command_poll_eager ~(common : Common.t) ~config ~request : unit = Scheduler.go_with_rpc_server_and_console_status_reporting ~common ~config (fun () -> let open Fiber.O in (* Run two fibers concurrently. One is responible for rebuilding targets named on the command line in reaction to file system changes. The other is responsible for building targets named in RPC build requests. *) let+ () = Scheduler.Run.poll (run_build_system ~common ~request) and+ () = poll_handling_rpc_build_requests ~common ~config in ()) ;; let run_build_command_poll_passive ~common ~config ~request:_ : unit = (* CR-someday aalekseyev: It would've been better to complain if [request] is non-empty, but we can't check that here because [request] is a function.*) Scheduler.go_with_rpc_server_and_console_status_reporting ~common ~config (fun () -> poll_handling_rpc_build_requests ~common ~config) ;; let run_build_command_once ~(common : Common.t) ~config ~request = let open Fiber.O in let once () = let+ res = run_build_system ~common ~request in match res with | Error `Already_reported -> raise Dune_util.Report_error.Already_reported | Ok () -> () in Scheduler.go_with_rpc_server ~common ~config once ;; let run_build_command ~(common : Common.t) ~config ~request = (match Common.watch common with | Yes Eager -> run_build_command_poll_eager | Yes Passive -> run_build_command_poll_passive | No -> run_build_command_once) ~common ~config ~request ;; let build_via_rpc_server ~print_on_success ~targets = Rpc_common.wrap_build_outcome_exn ~print_on_success (Rpc.Build.build ~wait:true) targets () ;; let build = let doc = "Build the given targets, or the default ones if none are given." in let man = [ `S "DESCRIPTION" ; `P {|Targets starting with a $(b,@) are interpreted as aliases.|} ; `Blocks Common.help_secs ; Common.examples [ "Build all targets in the current source tree", "dune build" ; "Build targets in the `./foo/bar' directory", "dune build ./foo/bar" ; ( "Build the minimal set of targets required for tooling such as Merlin \ (useful for quickly detecting errors)" , "dune build @check" ) ; "Run all code formatting tools in-place", "dune build --auto-promote @fmt" ] ] in let name_ = Arg.info [] ~docv:"TARGET" in let term = let+ builder = Common.Builder.term and+ targets = Arg.(value & pos_all dep [] name_) and+ aliases_rec = Arg.(value & opt_all Dep.alias_rec_arg [] & info [ "alias-rec" ]) and+ aliases = Arg.(value & opt_all Dep.alias_arg [] & info [ "alias" ]) in let targets = List.concat [ targets; aliases; aliases_rec ] in let targets = match targets with | [] -> [ Common.Builder.default_target builder ] | _ :: _ -> targets in let common, config = Common.init builder in (* Here we need to find out whether another instance of dune already holds the global build lock, as this will determine whether the current instance of dune will perform the build itself or send a build request to the RPC server in an already-running dune process. The method of checking whether another dune instance holds the lock is to simply try to take the lock. If taking the lock succeeds then the current process will perform the build itself, and future attempts by this process to take the lock are guaranteed to succeed. If taking the lock fails then we know that another instance of dune must have it, and the current process will send a build RPC request to that dune instance. Checking the status of the lock by taking prevents a race condition where the state of the lock could otherwise change between checking it and taking it. *) match Dune_util.Global_lock.lock ~timeout:None with | Error lock_held_by -> (* This case is reached if dune detects that another instance of dune is already running. Rather than performing the build itself, the current instance of dune will instruct the already-running instance to perform the build by sending an RPC message. As only one RPC server can run at a time we need to use a fiber scheduler which does not run an RPC server in the background to schedule the fiber which will perform the RPC call. *) Rpc_common.run_via_rpc ~builder ~common ~config lock_held_by (Rpc.Build.build ~wait:true) targets | Ok () -> let request setup = Target.interpret_targets (Common.root common) config setup targets in run_build_command ~common ~config ~request in Cmd.v (Cmd.info "build" ~doc ~man ~envs:Common.envs) term ;;