1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
|
open Dune_config
open Import
open Pkg_common
module Package_version = Dune_pkg.Package_version
module Opam_repo = Dune_pkg.Opam_repo
module Lock_dir = Dune_pkg.Lock_dir
module Pin_stanza = Dune_lang.Pin_stanza
module Pin = Dune_pkg.Pin
module Progress_indicator = struct
module Per_lockdir = struct
module State = struct
module Repository = Dune_pkg.Pkg_workspace.Repository
type t =
| Updating_repos of Repository.Name.t list
| Solving
let pp = function
| Updating_repos repo_names ->
Pp.textf
"Updating package repos %s..."
(List.map repo_names ~f:(fun repo_name ->
Repository.Name.to_string repo_name |> String.quoted)
|> String.enumerate_and)
| Solving -> Pp.text "Solving..."
;;
end
type t =
{ lockdir_path : Path.Source.t
; state : State.t option ref
}
let create lockdir_path = { lockdir_path; state = ref None }
end
(* The progress indicator for the entire lock operation, which may
involve generating multiple lockdirs *)
type t = Per_lockdir.t list
let pp (t : t) =
(* Only display the first non-done lockdir state, since the status
line can only consist of a single line. *)
List.find_map t ~f:(fun { Per_lockdir.lockdir_path; state } ->
Option.map !state ~f:(fun state ->
Pp.concat
[ Pp.textf "Locking %s: " (Path.Source.to_string_maybe_quoted lockdir_path)
; Per_lockdir.State.pp state
]))
|> Option.value ~default:Pp.nop
;;
let add_overlay (t : t) = Console.Status_line.add_overlay (Live (fun () -> pp t))
end
let project_and_package_pins project =
let dir = Dune_project.root project in
let pins = Dune_project.pins project in
let packages = Dune_project.packages project in
Pin.DB.add_opam_pins (Pin.DB.of_stanza ~dir pins) packages
;;
(* For recursive pins, we must traverse the pinned sources. The [project_pins]
are the initial pins that we have in our project. *)
let resolve_project_pins project_pins =
let scan_project ~read ~files =
let read file = Memo.of_reproducible_fiber (read file) in
let open Memo.O in
(* Opam files may never contain recursive pins, so don't both reading them *)
Dune_project.gen_load
~read
~files
~dir:Path.Source.root
~infer_from_opam_files:false
~load_opam_file_with_contents:Dune_pkg.Opam_file.load_opam_file_with_contents
>>| Option.map ~f:(fun project ->
let packages = Dune_project.packages project in
let pins = project_and_package_pins project in
pins, packages)
|> Memo.run
in
Pin.resolve project_pins ~scan_project
;;
let solve_multiple_platforms
base_solver_env
version_preference
repos
~pins
~local_packages
~constraints
~selected_depopts
~solve_for_platforms
~portable_lock_dir
=
let open Fiber.O in
let solve_for_env env =
Dune_pkg.Opam_solver.solve_lock_dir
env
version_preference
repos
~pins
~local_packages
~constraints
~selected_depopts
~portable_lock_dir
in
let portable_solver_env =
Dune_pkg.Solver_env.unset_multi
base_solver_env
Dune_lang.Package_variable_name.platform_specific
in
let+ results =
Fiber.parallel_map solve_for_platforms ~f:(fun platform_env ->
let solver_env = Dune_pkg.Solver_env.extend portable_solver_env platform_env in
solve_for_env solver_env)
in
let solver_results, errors =
List.partition_map results ~f:(function
| Ok result -> Left result
| Error (`Diagnostic_message message) -> Right message)
in
match solver_results, errors with
| [], [] -> Code_error.raise "Solver did not run for any platforms." []
| [], errors -> `All_error errors
| x :: xs, errors ->
let merged_solver_result =
List.fold_left xs ~init:x ~f:Dune_pkg.Opam_solver.Solver_result.merge
in
if List.is_empty errors
then `All_ok merged_solver_result
else `Partial (merged_solver_result, errors)
;;
let solve_lock_dir
workspace
~local_packages
~project_pins
~print_perf_stats
~portable_lock_dir
version_preference
solver_env_from_current_system
lock_dir_path
progress_state
=
let open Fiber.O in
let lock_dir = Workspace.find_lock_dir workspace lock_dir_path in
let project_pins, solve_for_platforms =
match lock_dir with
| None -> project_pins, Dune_pkg.Solver_env.popular_platform_envs
| Some lock_dir ->
let workspace =
Pin.DB.Workspace.of_stanza workspace.pins
|> Pin.DB.Workspace.extract ~names:lock_dir.pins
in
Pin.DB.combine_exn workspace project_pins, lock_dir.solve_for_platforms
in
let solver_env_from_context =
Option.bind lock_dir ~f:(fun lock_dir -> lock_dir.solver_env)
in
let solver_env =
solver_env
~solver_env_from_context
~solver_env_from_current_system
~unset_solver_vars_from_context:
(unset_solver_vars_of_workspace workspace ~lock_dir_path)
in
let solve_for_platforms =
match portable_lock_dir with
| true ->
(match solver_env_from_context with
| Some solver_env_from_context ->
List.map solve_for_platforms ~f:(fun platform_env ->
Dune_pkg.Solver_env.extend solver_env_from_context platform_env)
| None -> solve_for_platforms)
| false -> [ solver_env ]
in
let time_start = Unix.gettimeofday () in
let* repos =
let repo_map = repositories_of_workspace workspace in
let repo_names =
Dune_pkg.Pkg_workspace.Repository.Name.Map.keys repo_map
|> List.sort ~compare:Dune_pkg.Pkg_workspace.Repository.Name.compare
in
progress_state
:= Some (Progress_indicator.Per_lockdir.State.Updating_repos repo_names);
get_repos repo_map ~repositories:(repositories_of_lock_dir workspace ~lock_dir_path)
in
let* pins = resolve_project_pins project_pins in
let time_solve_start = Unix.gettimeofday () in
progress_state := Some Progress_indicator.Per_lockdir.State.Solving;
let* result =
solve_multiple_platforms
solver_env
(Pkg_common.Version_preference.choose
~from_arg:version_preference
~from_context:
(Option.bind lock_dir ~f:(fun lock_dir -> lock_dir.version_preference)))
repos
~pins
~local_packages:
(Package_name.Map.map local_packages ~f:Dune_pkg.Local_package.for_solver)
~constraints:(constraints_of_workspace workspace ~lock_dir_path)
~selected_depopts:(depopts_of_workspace workspace ~lock_dir_path)
~solve_for_platforms
~portable_lock_dir
in
let solver_result =
match result with
| `All_error messages -> Error messages
| `All_ok solver_result -> Ok (solver_result, [])
| `Partial (solver_result, errors) ->
Log.info errors;
Ok
( solver_result
, [ Pp.nop
; Pp.text
"No solution was found for some platforms. See the log or run with \
--verbose for more details."
|> Pp.tag User_message.Style.Warning
] )
in
match solver_result with
| Error messages -> Fiber.return (Error (lock_dir_path, messages))
| Ok (solver_result, maybe_unsolved_platforms_message) ->
let { Dune_pkg.Opam_solver.Solver_result.lock_dir
; files
; pinned_packages
; num_expanded_packages
}
=
solver_result
in
let time_end = Unix.gettimeofday () in
let maybe_perf_stats =
if print_perf_stats
then
[ Pp.nop
; Pp.textf "Expanded packages: %d" num_expanded_packages
; Pp.textf "Updated repos in: %.2fs" (time_solve_start -. time_start)
; Pp.textf "Solved dependencies in: %.2fs" (time_end -. time_solve_start)
]
else []
in
let summary_message =
User_message.make
((Pp.tag
User_message.Style.Success
(Pp.textf
"Solution for %s:"
(Path.Source.to_string_maybe_quoted lock_dir_path))
:: (match Lock_dir.Packages.to_pkg_list lock_dir.packages with
| [] ->
Pp.tag User_message.Style.Warning @@ Pp.text "(no dependencies to lock)"
| packages -> pp_packages packages)
:: maybe_perf_stats)
@ maybe_unsolved_platforms_message)
in
progress_state := None;
let+ lock_dir = Lock_dir.compute_missing_checksums ~pinned_packages lock_dir in
Ok
( Lock_dir.Write_disk.prepare ~portable_lock_dir ~lock_dir_path ~files lock_dir
, summary_message )
;;
let solve
workspace
~local_packages
~project_pins
~solver_env_from_current_system
~version_preference
~lock_dirs
~print_perf_stats
~portable_lock_dir
=
let open Fiber.O in
(* a list of thunks that will perform all the file IO side
effects after performing validation so that if materializing any
lockdir would fail then no side effect takes place. *)
(let+ errors, solutions =
let progress_indicator =
List.map lock_dirs ~f:Progress_indicator.Per_lockdir.create
in
let overlay = Progress_indicator.add_overlay progress_indicator in
let+ result =
Fiber.finalize
~finally:(fun () ->
Console.Status_line.remove_overlay overlay;
Fiber.return ())
(fun () ->
Fiber.parallel_map progress_indicator ~f:(fun { lockdir_path; state } ->
solve_lock_dir
workspace
~local_packages
~project_pins
~print_perf_stats
~portable_lock_dir
version_preference
solver_env_from_current_system
lockdir_path
state))
in
List.partition_map result ~f:Result.to_either
in
match errors with
| [] -> Ok solutions
| _ -> Error errors)
>>| function
| Error errors ->
User_error.raise
([ Pp.text "Unable to solve dependencies for the following lock directories:" ]
@ List.concat_map errors ~f:(fun (path, messages) ->
[ Pp.textf "Lock directory %s:" (Path.Source.to_string_maybe_quoted path)
; Pp.hovbox (Pp.concat ~sep:Pp.newline messages)
]))
| Ok write_disks_with_summaries ->
let write_disk_list, summary_messages = List.split write_disks_with_summaries in
List.iter summary_messages ~f:Console.print_user_message;
(* All the file IO side effects happen here: *)
List.iter write_disk_list ~f:Lock_dir.Write_disk.commit
;;
let project_pins =
let open Memo.O in
Dune_rules.Dune_load.projects ()
>>| List.fold_left ~init:Pin.DB.empty ~f:(fun acc project ->
let pins = project_and_package_pins project in
Pin.DB.combine_exn acc pins)
;;
let lock ~version_preference ~lock_dirs_arg ~print_perf_stats ~portable_lock_dir =
let open Fiber.O in
let* solver_env_from_current_system =
poll_solver_env_from_current_system () >>| Option.some
and* workspace, local_packages, project_pins =
Memo.run
@@
let open Memo.O in
let+ workspace = Workspace.workspace ()
and+ local_packages = find_local_packages
and+ project_pins = project_pins in
workspace, local_packages, project_pins
in
let lock_dirs =
Pkg_common.Lock_dirs_arg.lock_dirs_of_workspace lock_dirs_arg workspace
in
solve
workspace
~local_packages
~project_pins
~solver_env_from_current_system
~version_preference
~lock_dirs
~print_perf_stats
~portable_lock_dir
;;
let term =
let+ builder = Common.Builder.term
and+ version_preference = Version_preference.term
and+ lock_dirs_arg = Pkg_common.Lock_dirs_arg.term
and+ print_perf_stats = Arg.(value & flag & info [ "print-perf-stats" ]) in
let builder = Common.Builder.forbid_builds builder in
let common, config = Common.init builder in
Scheduler.go_with_rpc_server ~common ~config (fun () ->
let portable_lock_dir =
match Config.get Dune_rules.Compile_time.portable_lock_dir with
| `Enabled -> true
| `Disabled -> false
in
lock ~version_preference ~lock_dirs_arg ~print_perf_stats ~portable_lock_dir)
;;
let info =
let doc = "Create a lockfile" in
Cmd.info "lock" ~doc
;;
let command = Cmd.v info term
|