• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ocaml / dune / 28709

19 Oct 2024 12:00PM UTC coverage: 6.919% (-0.003%) from 6.922%
28709

push

github

web-flow
refactor: add a bunch of memo stacktraces (#11023)

To improve error message for cycles

Signed-off-by: Rudi Grinberg <me@rgrinberg.com>

0 of 61 new or added lines in 2 files covered. (0.0%)

1 existing line in 1 file now uncovered.

2932 of 42379 relevant lines covered (6.92%)

26880.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.69
/src/dune_rules/pkg_rules.ml
1
open Import
2
open Memo.O
3

4
include struct
5
  open Dune_pkg
6
  module Sys_poll = Sys_poll
7
  module Package_variable = Package_variable
8
  module Substs = Substs
9
  module Checksum = Checksum
10
  module Source = Source
11
  module Build_command = Lock_dir.Build_command
12
  module Display = Dune_engine.Display
13
  module Pkg_info = Lock_dir.Pkg_info
14
end
15

16
module Variable = struct
17
  type value = OpamVariable.variable_contents =
18
    | B of bool
19
    | S of string
20
    | L of string list
21

22
  type t = Package_variable_name.t * value
23

24
  let dyn_of_value : value -> Dyn.t =
25
    let open Dyn in
26
    function
27
    | B b -> variant "Bool" [ bool b ]
×
28
    | S s -> variant "String" [ string s ]
×
29
    | L xs -> variant "Strings" [ list string xs ]
×
30
  ;;
31

32
  let dune_value : value -> Value.t list = function
33
    | B b -> [ String (Bool.to_string b) ]
×
34
    | S s -> [ String s ]
×
35
    | L s -> List.map s ~f:(fun x -> Value.String x)
×
36
  ;;
37

38
  let of_values : dir:Path.t -> Value.t list -> value =
39
    fun ~dir xs ->
40
    match List.map xs ~f:(Value.to_string ~dir) with
×
41
    | [ x ] -> S x
×
42
    | xs -> L xs
×
43
  ;;
44

45
  let to_dyn (name, value) =
46
    Dyn.(pair Package_variable_name.to_dyn dyn_of_value (name, value))
×
47
  ;;
48
end
49

50
module Package_universe = struct
51
  (* A type of group of packages that are co-installed. Different
52
     package universes are unaware of each other. For example the
53
     dependencies of the project and the dependencies of one of the dev
54
     tools don't need to be mutually co-installable as they are in
55
     different universes. *)
56
  type t =
57
    | Project_dependencies of Context_name.t
58
    | Dev_tool of Dune_pkg.Dev_tool.t
59

60
  let equal a b =
61
    match a, b with
×
62
    | Project_dependencies a, Project_dependencies b -> Context_name.equal a b
×
63
    | Dev_tool a, Dev_tool b -> Dune_pkg.Dev_tool.equal a b
×
64
    | _ -> false
×
65
  ;;
66

67
  let context_name = function
68
    | Project_dependencies context_name -> context_name
×
69
    | Dev_tool _ ->
×
70
      (* Dev tools can only be built in the default context. *)
71
      Context_name.default
72
  ;;
73

74
  let lock_dir t =
75
    match t with
×
76
    | Project_dependencies ctx -> Lock_dir.get_exn ctx
×
77
    | Dev_tool dev_tool -> Lock_dir.of_dev_tool dev_tool
×
78
  ;;
79

80
  let lock_dir_path t =
81
    match t with
×
82
    | Project_dependencies ctx -> Lock_dir.get_path ctx
×
83
    | Dev_tool dev_tool ->
×
84
      Memo.return (Some (Dune_pkg.Lock_dir.dev_tool_lock_dir_path dev_tool))
×
85
  ;;
86
end
87

88
module Paths = struct
89
  (* The [paths] of a package are the information about the artifacts
90
     that we know {e without} executing any commands. *)
91
  type 'a t =
92
    { source_dir : 'a
93
    ; target_dir : 'a
94
    ; extra_sources : 'a
95
    ; name : Package.Name.t
96
    ; install_roots : 'a Install.Roots.t Lazy.t
97
    ; install_paths : 'a Install.Paths.t Lazy.t
98
    ; prefix : 'a
99
    }
100

101
  let map_path t ~f =
102
    { t with
×
103
      source_dir = f t.source_dir
×
104
    ; target_dir = f t.target_dir
×
105
    ; extra_sources = f t.extra_sources
×
106
    ; install_roots = Lazy.map ~f:(Install.Roots.map ~f) t.install_roots
×
107
    ; install_paths = Lazy.map ~f:(Install.Paths.map ~f) t.install_paths
×
108
    ; prefix = f t.prefix
×
109
    }
110
  ;;
111

112
  let install_roots ~target_dir ~relative =
113
    Install.Roots.opam_from_prefix ~relative target_dir
×
114
  ;;
115

116
  let install_paths roots package ~relative = Install.Paths.make ~relative ~package ~roots
×
117

118
  let of_root name ~root ~relative =
119
    let source_dir = relative root "source" in
×
120
    let target_dir = relative root "target" in
×
121
    let extra_sources = relative root "extra_source" in
×
122
    let install_roots = lazy (install_roots ~target_dir ~relative) in
×
123
    let install_paths = lazy (install_paths (Lazy.force install_roots) name ~relative) in
×
124
    { source_dir
125
    ; target_dir
126
    ; extra_sources
127
    ; name
128
    ; install_paths
129
    ; install_roots
130
    ; prefix = target_dir
131
    }
132
  ;;
133

134
  let extra_source t extra_source = Path.append_local t.extra_sources extra_source
×
135

136
  let extra_source_build t extra_source =
137
    Path.Build.append_local t.extra_sources extra_source
×
138
  ;;
139

140
  let make package_universe name =
141
    let universe_root =
×
142
      match (package_universe : Package_universe.t) with
143
      | Dev_tool dev_tool -> Pkg_dev_tool.universe_install_path dev_tool
×
144
      | Project_dependencies _ ->
×
145
        let build_dir =
146
          Path.Build.relative
147
            Private_context.t.build_dir
148
            (Context_name.to_string (Package_universe.context_name package_universe))
×
149
        in
150
        Path.Build.relative build_dir ".pkg"
×
151
    in
152
    let root = Path.Build.relative universe_root (Package.Name.to_string name) in
×
153
    of_root name ~root
×
154
  ;;
155

156
  let make_install_cookie target_dir ~relative = relative target_dir "cookie"
×
157

158
  let install_cookie' target_dir =
159
    make_install_cookie target_dir ~relative:Path.Build.relative
×
160
  ;;
161

162
  let install_cookie t = make_install_cookie t.target_dir ~relative:Path.relative
×
163

164
  let install_file t =
165
    Path.Build.relative
×
166
      t.source_dir
167
      (sprintf "%s.install" (Package.Name.to_string t.name))
×
168
  ;;
169

170
  let config_file t =
171
    Path.Build.relative t.source_dir (sprintf "%s.config" (Package.Name.to_string t.name))
×
172
  ;;
173

174
  let install_paths t = Lazy.force t.install_paths
×
175
  let install_roots t = Lazy.force t.install_roots
×
176
end
177

178
module Install_cookie = struct
179
  (* The install cookie represents a serialized representation of all the
180
     installed artifacts and variables.
181

182
     The install cookie of a package is the source of all data we must refer to
183
     address a package's artifacts.
184

185
     It is constructed after we've built and installed the packages. In this
186
     sense, it is the "installation trace" that we must refer to so that we
187
     don't have to know anything about the installation procedure.
188
  *)
189

190
  type t =
191
    { files : Path.t list Section.Map.t
192
    ; variables : Variable.t list
193
    }
194

195
  let to_dyn { files; variables } =
196
    let open Dyn in
×
197
    record
198
      [ "files", Section.Map.to_dyn (list Path.to_dyn) files
×
199
      ; "variables", list Variable.to_dyn variables
×
200
      ]
201
  ;;
202

203
  include Dune_util.Persistent.Make (struct
204
      type nonrec t = t
205

206
      let name = "INSTALL-COOKIE"
207
      let version = 1
208
      let to_dyn = to_dyn
209
      let test_example () = { files = Section.Map.empty; variables = [] }
×
210
    end)
211

212
  let load_exn f =
213
    match load f with
×
214
    | Some f -> f
×
215
    | None -> User_error.raise ~loc:(Loc.in_file f) [ Pp.text "unable to load" ]
×
216
  ;;
217
end
218

219
module Value_list_env = struct
220
  (* A representation of an environment where each variable can hold a
221
     list of [Value.t]. Each variable will be encoded into a delimited
222
     string (in the style of the PATH variable). *)
223
  type t = Value.t list Env.Map.t
224

225
  let parse_strings s = Bin.parse s |> List.map ~f:(fun s -> Value.String s)
×
226
  let of_env env : t = Env.to_map env |> Env.Map.map ~f:parse_strings
×
227

228
  (* Concatenate a list of values in the style of lists found in
229
     environment variables, such as PATH *)
230
  let string_of_env_values values =
231
    List.map values ~f:(function
×
232
      | Value.String s -> s
×
233
      | Dir s | Path s -> Path.to_absolute_filename s)
×
234
    |> Bin.encode_strings
×
235
  ;;
236

237
  let to_env (t : t) = Env.Map.map t ~f:string_of_env_values |> Env.of_map
×
238
  let get_path t = Env.Map.find t Env_path.var
×
239

240
  (* [extend_concat_path a b] adds all variables from [b] to [a]
241
     overwriting any existing values of those variables in [a] except for PATH
242
     which is set to the concatenation of the PATH variables from [a] and [b]
243
     with the PATH entries from [b] preceding the PATH entries from
244
     [a]. If only one of the arguments contains a PATH variable then
245
     its value will be the value of PATH in the result, however if
246
     neither argument contains a PATH variable then PATH will be unset
247
     in the result. *)
248
  let extend_concat_path a b =
249
    let extended = Env.Map.superpose b a in
×
250
    let concated_path =
×
251
      match get_path a, get_path b with
×
252
      | None, None -> None
×
253
      | Some x, None | None, Some x -> Some x
×
254
      | Some a, Some b -> Some (b @ a)
×
255
    in
256
    match concated_path with
257
    | None -> extended
×
258
    | Some concated_path -> Env.Map.set extended Env_path.var concated_path
×
259
  ;;
260

261
  (* Adds a path to an env where variables are associated with lists
262
     of paths. The path is prepended to the list associated with the
263
     given variable and a new binding is added to the env if the
264
     variable is not yet part of the env. *)
265
  let add_path (t : t) var path : t =
266
    Env.Map.update t var ~f:(fun paths ->
×
267
      let paths = Option.value paths ~default:[] in
×
268
      Some (Value.Dir path :: paths))
×
269
  ;;
270
end
271

272
module Env_update = struct
273
  include Dune_lang.Action.Env_update
274

275
  (* Handle the :=, +=, =:, and =+ opam environment update operators.
276

277
     The operators with colon character update a variable, adding a
278
     leading/trailing separator (e.g. the ':' chars in PATH on unix)
279
     if the variable was initially unset or empty, while the operators
280
     with a plus character add no leading/trailing separator in such a
281
     case.
282

283
     Updates where the newly added value is the empty string are
284
     ignored since opam refuses to add empty strings to list
285
     variables.*)
286
  let update kind ~new_v ~old_v ~f =
287
    if new_v = ""
×
288
    then old_v
×
289
    else (
×
290
      match kind with
291
      | `Colon ->
×
292
        let old_v = Option.value ~default:[] old_v in
293
        Some (f ~old_v ~new_v)
×
294
      | `Plus ->
×
295
        (match old_v with
296
         | None | Some [] -> Some [ Value.String new_v ]
×
297
         | Some old_v -> Some (f ~old_v ~new_v)))
×
298
  ;;
299

300
  let append = update ~f:(fun ~old_v ~new_v -> old_v @ [ Value.String new_v ])
×
301
  let prepend = update ~f:(fun ~old_v ~new_v -> Value.String new_v :: old_v)
×
302

303
  let set env { op; var = k; value = new_v } =
304
    Env.Map.update env k ~f:(fun old_v ->
×
305
      let append = append ~new_v ~old_v in
×
306
      let prepend = prepend ~new_v ~old_v in
307
      match op with
308
      | Eq ->
×
309
        if new_v = ""
310
        then if Sys.win32 then None else Some [ String "" ]
×
311
        else Some [ Value.String new_v ]
×
312
      | PlusEq -> prepend `Plus
×
313
      | ColonEq -> prepend `Colon
×
314
      | EqPlus -> append `Plus
×
315
      | EqColon -> append `Colon
×
316
      | EqPlusEq ->
317
        (* TODO nobody uses this AFAIK *)
318
        assert false)
319
  ;;
320
end
321

322
module Pkg = struct
323
  module Id = Id.Make ()
324

325
  type t =
326
    { id : Id.t
327
    ; build_command : Build_command.t option
328
    ; install_command : Dune_lang.Action.t option
329
    ; depends : t list
330
    ; depexts : string list
331
    ; info : Pkg_info.t
332
    ; paths : Path.t Paths.t
333
    ; write_paths : Path.Build.t Paths.t
334
    ; files_dir : Path.Build.t
335
    ; mutable exported_env : string Env_update.t list
336
    }
337

338
  module Top_closure = Top_closure.Make (Id.Set) (Monad.Id)
339

340
  let top_closure depends =
341
    match
×
342
      Top_closure.top_closure depends ~key:(fun t -> t.id) ~deps:(fun t -> t.depends)
×
343
    with
344
    | Ok s -> s
×
345
    | Error cycle ->
×
346
      User_error.raise
347
        [ Pp.text "the following packages form a cycle:"
×
348
        ; Pp.chain cycle ~f:(fun pkg ->
×
349
            Pp.verbatim (Package.Name.to_string pkg.info.name))
×
350
        ]
351
  ;;
352

353
  let deps_closure t = top_closure t.depends
×
354

355
  let source_files t ~loc =
356
    let skip_dir = function
×
357
      | ".hg" | ".git" | "_darcs" | "_opam" | "_build" | "_esy" -> true
×
358
      | _ -> false
×
359
    in
360
    let skip_file = String.is_prefix ~prefix:".#" in
361
    let rec loop root acc path =
362
      let full_path = Path.External.append_local root path in
×
363
      Fs_memo.dir_contents (External full_path)
×
364
      >>= function
365
      | Error e ->
×
366
        User_error.raise
367
          ~loc
368
          [ Pp.textf "Unable to read %s" (Path.External.to_string_maybe_quoted full_path)
×
369
          ; Unix_error.Detailed.pp e
×
370
          ]
371
      | Ok contents ->
×
372
        let files, dirs =
373
          let contents = Fs_cache.Dir_contents.to_list contents in
374
          List.rev_filter_partition_map contents ~f:(fun (name, kind) ->
×
375
            (* TODO handle links and cycles correctly *)
376
            match kind with
×
377
            | S_DIR -> if skip_dir name then Skip else Right name
×
378
            | _ -> if skip_file name then Skip else Left name)
×
379
        in
380
        let acc =
381
          Path.Local.Set.of_list_map files ~f:(Path.Local.relative path)
×
382
          |> Path.Local.Set.union acc
×
383
        in
384
        let+ dirs =
×
385
          Memo.parallel_map dirs ~f:(fun dir ->
×
386
            let dir = Path.Local.relative path dir in
×
387
            loop root Path.Local.Set.empty dir)
×
388
        in
389
        Path.Local.Set.union_all (acc :: dirs)
×
390
    in
391
    (match t.info.source with
392
     | None -> Memo.return None
×
393
     | Some source ->
×
394
       Lock_dir.source_kind source
×
395
       >>| (function
×
396
        | `Local (`File, _) | `Fetch -> None
×
397
        | `Local (`Directory, root) -> Some root))
×
398
    >>= function
399
    | None -> Memo.return Path.Local.Set.empty
×
400
    | Some root -> loop root Path.Local.Set.empty Path.Local.root
×
401
  ;;
402

403
  let dep t = Dep.file t.paths.target_dir
×
404

405
  let package_deps t =
406
    deps_closure t
×
407
    |> List.fold_left ~init:Dep.Set.empty ~f:(fun acc t -> dep t |> Dep.Set.add acc)
×
408
  ;;
409

410
  (* Given a list of packages, construct an env containing variables
411
     set by each package. Variables containing delimited lists of
412
     paths (e.g. PATH) which appear in multiple package's envs are
413
     concatenated in the reverse order of their associated packages in
414
     the input list. Environment updates via the `exported_env` field
415
     (equivalent to opam's `setenv` field) are applied for each
416
     package in the same order as the argument list. *)
417
  let build_env_of_deps ts =
418
    List.fold_left ts ~init:Env.Map.empty ~f:(fun env t ->
×
419
      let env =
×
420
        let roots = Paths.install_roots t.paths in
421
        let init = Value_list_env.add_path env Env_path.var roots.bin in
×
422
        let vars = Install.Roots.to_env_without_path roots ~relative:Path.relative in
×
423
        List.fold_left vars ~init ~f:(fun acc (var, path) ->
×
424
          Value_list_env.add_path acc var path)
×
425
      in
426
      List.fold_left t.exported_env ~init:env ~f:Env_update.set)
427
  ;;
428

429
  (* [build_env t] returns an env containing paths containing all the
430
     tools and libraries required to build the package [t] inside the
431
     faux opam directory contained in the _build dir. *)
432
  let build_env t = build_env_of_deps @@ deps_closure t
×
433

434
  let base_env t =
435
    Env.Map.of_list_exn
×
436
      [ Opam_switch.opam_switch_prefix_var_name, [ Value.Path t.paths.target_dir ]
437
      ; "CDPATH", [ Value.String "" ]
438
      ; "MAKELEVEL", [ Value.String "" ]
439
      ; "OPAM_PACKAGE_NAME", [ Value.String (Package.Name.to_string t.info.name) ]
×
440
      ; ( "OPAM_PACKAGE_VERSION"
441
        , [ Value.String (Package_version.to_string t.info.version) ] )
×
442
      ; "OPAMCLI", [ Value.String "2.0" ]
443
      ]
444
  ;;
445

446
  (* [exported_value_env t] returns the complete env that will be used
447
     to build the package [t] *)
448
  let exported_value_env t =
449
    let package_env = build_env t |> Env.Map.superpose (base_env t) in
×
450
    (* TODO: Run actions in a constrained environment. [Global.env ()] is the
451
       environment from which dune was executed, and some of the environment
452
       variables may affect builds in unintended ways and make builds less
453
       reproducible. However other environment variables must be set in order
454
       for build actions to run successfully, such as $PATH on systems where the
455
       shell's default $PATH variable doesn't include the location of standard
456
       programs or build tools (e.g. NixOS). *)
457
    Value_list_env.extend_concat_path (Value_list_env.of_env (Global.env ())) package_env
×
458
  ;;
459

460
  let exported_env t = Value_list_env.to_env @@ exported_value_env t
×
461
end
462

463
module Pkg_installed = struct
464
  type t = { cookie : Install_cookie.t Action_builder.t }
465

466
  let of_paths (paths : _ Paths.t) =
467
    let cookie =
×
468
      let open Action_builder.O in
469
      let path = Paths.install_cookie paths in
470
      let+ () = path |> Dep.file |> Action_builder.dep in
×
471
      Install_cookie.load_exn path
×
472
    in
473
    { cookie }
474
  ;;
475
end
476

477
module Expander0 = struct
478
  include Expander0
479

480
  type t =
481
    { name : Dune_pkg.Package_name.t
482
    ; paths : Path.t Paths.t
483
    ; artifacts : Path.t Filename.Map.t
484
    ; depends :
485
        (Variable.value Package_variable_name.Map.t * Path.t Paths.t) Package.Name.Map.t
486
    ; depexts : string list
487
    ; context : Context_name.t
488
    ; version : Package_version.t
489
    ; env : Value.t list Env.Map.t
490
    }
491

492
  let expand_pform_fdecl
493
    : (t
494
       -> source:Dune_sexp.Template.Pform.t
495
       -> Pform.t
496
       -> (Value.t list, [ `Undefined_pkg_var of Package_variable_name.t ]) result Memo.t)
497
        Fdecl.t
498
    =
499
    Fdecl.create Dyn.opaque
39✔
500
  ;;
501
end
502

503
module Substitute = struct
504
  include Substs.Make (Memo)
505
  module Expander = Expander0
506

507
  module Spec = struct
508
    type ('src, 'dst) t =
509
      { (* XXX it's not good to serialize the substitution map like this. We're
510
           essentially implementing the same substitution procedure but in two
511
           different places: action geeneration, and action execution.
512

513
           The two implementations are bound to drift. Better would be to
514
           reconstruct everything that is needed to call our one and only
515
           substitution function. *)
516
        expander : Expander.t
517
      ; src : 'src
518
      ; dst : 'dst
519
      }
520

521
    let name = "substitute"
522
    let version = 2
523
    let bimap t f g = { t with src = f t.src; dst = g t.dst }
×
524
    let is_useful_to ~memoize = memoize
×
525

526
    let encode { expander; src; dst } input output : Sexp.t =
527
      let e =
×
528
        let paths (p : Path.t Paths.t) = p.source_dir, p.target_dir, p.name in
×
529
        ( paths expander.paths
×
530
        , expander.artifacts
531
        , Package.Name.Map.to_list_map expander.depends ~f:(fun _ (m, p) -> m, paths p)
×
532
        , expander.version
533
        , expander.context
534
        , expander.env )
535
        |> Digest.generic
536
        |> Digest.to_string_raw
×
537
      in
538
      List [ Atom e; input src; output dst ]
×
539
    ;;
540

541
    let action { expander; src; dst } ~ectx:_ ~eenv:_ =
542
      let open Fiber.O in
×
543
      let* () = Fiber.return () in
×
544
      let env (var : Substs.Variable.t) =
×
545
        let open Memo.O in
×
546
        ((* TODO loc *)
547
         let loc = Loc.none in
548
         let source =
549
           (* TODO it's rather ugly that we're going through the pform machinery
550
              to do this *)
551
           { Dune_sexp.Template.Pform.loc; name = ""; payload = None }
552
         in
553
         match
554
           match var with
555
           | Package var -> Some (Package_variable.to_pform var)
×
556
           | Global n ->
×
557
             Package_variable_name.to_string n
558
             |> Pform.Var.of_opam_global_variable_name
×
559
             |> Option.map ~f:(fun v -> Pform.Var v)
×
560
         with
561
         | None -> Memo.return @@ Variable.S ""
×
562
         | Some pform ->
×
563
           (Fdecl.get Expander.expand_pform_fdecl) expander ~source pform
×
564
           >>| (function
×
565
            | Error (`Undefined_pkg_var _) ->
×
566
              (* these are opam's semantics as far as I understand. *)
567
              Variable.S ""
568
            | Ok v ->
×
569
              let dir = Path.parent_exn src |> Path.drop_optional_sandbox_root in
×
570
              Variable.of_values v ~dir))
×
571
        >>| Option.some
572
      in
573
      subst env expander.paths.name ~src ~dst |> Memo.run
×
574
    ;;
575
  end
576

577
  module A = Action_ext.Make (Spec)
578

579
  let action expander ~src ~dst = A.action { Spec.expander; src; dst }
×
580
end
581

582
let depexts_hint = function
583
  | [] -> None
×
584
  | depexts ->
×
585
    [ Pp.textf "You may want to verify the following depexts are installed:"
×
586
    ; Pp.enumerate ~f:Pp.verbatim depexts
×
587
    ]
588
    |> Pp.concat_map ~sep:Pp.cut ~f:(fun pp -> Pp.box pp)
×
589
    |> Option.some
×
590
;;
591

592
module Run_with_path = struct
593
  module Output : sig
594
    type error
595

596
    val io : error -> Process.Io.output Process.Io.t
597

598
    val with_error
599
      :  accepted_exit_codes:int Predicate.t
600
      -> pkg:Dune_pkg.Package_name.t * Loc.t
601
      -> depexts:string list
602
      -> display:Display.t
603
      -> (error -> 'a)
604
      -> 'a
605

606
    val prerr : rc:int -> error -> unit
607
  end = struct
608
    type error =
609
      { pkg : Dune_pkg.Package_name.t * Loc.t
610
      ; depexts : string list
611
      ; filename : Dpath.t
612
      ; io : Process.Io.output Process.Io.t
613
      ; accepted_exit_codes : int Predicate.t
614
      ; display : Display.t
615
      }
616

617
    let io t = t.io
×
618

619
    let with_error ~accepted_exit_codes ~pkg ~depexts ~display f =
620
      let filename = Temp.create File ~prefix:"dune-pkg" ~suffix:"stderr" in
×
621
      let io = Process.Io.(file filename Out) in
×
622
      let t = { filename; io; accepted_exit_codes; display; pkg; depexts } in
623
      let result = f t in
624
      Temp.destroy File filename;
×
625
      result
×
626
    ;;
627

628
    let to_paragraphs t error =
629
      let pp_pkg = Pp.textf "Logs for package %s" (Package.Name.to_string (fst t.pkg)) in
×
630
      [ pp_pkg; Pp.verbatim error ]
×
631
    ;;
632

633
    let prerr ~rc error =
634
      let hints =
×
635
        lazy
636
          (match depexts_hint error.depexts with
×
637
           | None -> []
×
638
           | Some h -> [ h ])
×
639
      in
640
      let loc = snd error.pkg in
641
      match Predicate.test error.accepted_exit_codes rc, error.display with
×
642
      | false, _ ->
×
643
        let paragraphs = Stdune.Io.read_file error.filename |> to_paragraphs error in
×
644
        User_warning.emit ~hints:(Lazy.force hints) ~loc ~is_error:true paragraphs
×
645
      | true, Display.Verbose ->
×
646
        let content = Stdune.Io.read_file error.filename in
647
        if not (String.is_empty content)
×
648
        then (
×
649
          let paragraphs = to_paragraphs error content in
650
          User_warning.emit ~hints:(Lazy.force hints) ~loc paragraphs)
×
651
      | true, _ -> ()
×
652
    ;;
653
  end
654

655
  module Spec = struct
656
    type 'path chunk =
657
      | String of string
658
      | Path of 'path
659

660
    type 'path arg = 'path chunk Array.Immutable.t
661

662
    type ('path, 'target) t =
663
      { prog : Action.Prog.t
664
      ; args : 'path arg Array.Immutable.t
665
      ; ocamlfind_destdir : 'path
666
      ; pkg : Dune_pkg.Package_name.t * Loc.t
667
      ; depexts : string list
668
      }
669

670
    let name = "run-with-path"
671
    let version = 2
672

673
    let map_arg arg ~f =
674
      Array.Immutable.map arg ~f:(function
×
675
        | String _ as s -> s
×
676
        | Path p -> Path (f p))
×
677
    ;;
678

679
    let bimap t f _g =
680
      { t with
×
681
        args = Array.Immutable.map t.args ~f:(map_arg ~f)
×
682
      ; ocamlfind_destdir = f t.ocamlfind_destdir
×
683
      }
684
    ;;
685

686
    let is_useful_to ~memoize:_ = true
×
687

688
    let encode { prog; args; ocamlfind_destdir; pkg = _; depexts = _ } path _ : Sexp.t =
689
      let prog : Sexp.t =
×
690
        Atom
691
          (match prog with
692
           | Ok p -> Path.reach p ~from:Path.root
×
693
           | Error e -> e.program)
×
694
      in
695
      let args =
696
        Array.Immutable.to_list_map args ~f:(fun x ->
697
          Sexp.List
×
698
            (Array.Immutable.to_list_map x ~f:(function
×
699
              | String s -> Sexp.Atom s
×
700
              | Path p -> path p)))
×
701
      in
702
      List [ List ([ prog ] @ args); path ocamlfind_destdir ]
×
703
    ;;
704

705
    let action
706
      { prog; args; ocamlfind_destdir; pkg; depexts }
707
      ~(ectx : Action.context)
708
      ~(eenv : Action.env)
709
      =
710
      let open Fiber.O in
×
711
      let display = !Clflags.display in
712
      match prog with
713
      | Error e -> Action.Prog.Not_found.raise e
×
714
      | Ok prog ->
×
715
        let args =
716
          Array.Immutable.to_list_map args ~f:(fun arg ->
717
            Array.Immutable.to_list_map arg ~f:(function
×
718
              | String s -> s
×
719
              | Path p -> Path.to_absolute_filename p)
×
720
            |> String.concat ~sep:"")
×
721
        in
722
        let metadata = Process.create_metadata ~purpose:ectx.metadata.purpose () in
×
723
        let env =
×
724
          Env.add
725
            eenv.env
726
            ~var:"OCAMLFIND_DESTDIR"
727
            ~value:(Path.to_absolute_filename ocamlfind_destdir)
×
728
        in
729
        Output.with_error
×
730
          ~accepted_exit_codes:eenv.exit_codes
731
          ~pkg
732
          ~depexts
733
          ~display
734
          (fun error ->
735
             let stdout_to =
×
736
               match !Clflags.debug_package_logs, display with
737
               | true, _ | false, Display.Verbose -> eenv.stdout_to
×
738
               | _ -> Process.Io.(null Out)
×
739
             in
740
             Process.run
×
741
               Return
742
               prog
743
               args
744
               ~display
745
               ~metadata
746
               ~stdout_to
747
               ~stderr_to:(Output.io error)
×
748
               ~stdin_from:eenv.stdin_from
749
               ~dir:eenv.working_dir
750
               ~env
751
             >>= fun (_, rc) ->
752
             Output.prerr ~rc error;
×
753
             Fiber.return ())
×
754
    ;;
755
  end
756

757
  module A = Action_ext.Make (Spec)
758

759
  let action ~pkg ~depexts prog args ~ocamlfind_destdir =
760
    A.action { Spec.prog; args; ocamlfind_destdir; pkg; depexts }
×
761
  ;;
762
end
763

764
module Action_expander = struct
765
  module Expander = struct
766
    include Expander0
767

768
    let map_exe _ x =
769
      (* TODO *)
770
      x
×
771
    ;;
772

773
    let dune_section_of_pform : Pform.Var.Pkg.Section.t -> Section.t = function
774
      | Lib -> Lib
×
775
      | Libexec -> Libexec
×
776
      | Bin -> Bin
×
777
      | Sbin -> Sbin
×
778
      | Toplevel -> Toplevel
×
779
      | Share -> Share
×
780
      | Etc -> Etc
×
781
      | Doc -> Doc
×
782
      | Stublibs -> Stublibs
×
783
      | Man -> Man
×
784
    ;;
785

786
    let section_dir_of_root
787
      (roots : _ Install.Roots.t)
788
      (section : Pform.Var.Pkg.Section.t)
789
      =
790
      match section with
×
791
      | Lib -> roots.lib_root
×
792
      | Libexec -> roots.libexec_root
×
793
      | Bin -> roots.bin
×
794
      | Sbin -> roots.sbin
×
795
      | Share -> roots.share_root
×
796
      | Etc -> roots.etc_root
×
797
      | Doc -> roots.doc_root
×
798
      | Man -> roots.man
×
799
      | Toplevel -> Path.relative roots.lib_root "toplevel"
×
800
      | Stublibs -> Path.relative roots.lib_root "stublibs"
×
801
    ;;
802

803
    let sys_poll_var accessor =
804
      accessor Lock_dir.Sys_vars.poll
×
805
      |> Memo.Lazy.force
×
806
      >>| function
807
      | Some v -> [ Value.String v ]
×
808
      | None ->
×
809
        (* TODO: in OPAM an unset variable evaluates to false, but we
810
           can't represent that in a string so it evaluates to an empty
811
           string instead *)
812
        [ Value.String "" ]
813
    ;;
814

815
    let expand_pkg (paths : Path.t Paths.t) (pform : Pform.Var.Pkg.t) =
816
      match pform with
×
817
      | Switch -> Memo.return [ Value.String "dune" ]
×
818
      | Os -> sys_poll_var (fun { os; _ } -> os)
×
819
      | Os_version -> sys_poll_var (fun { os_version; _ } -> os_version)
×
820
      | Os_distribution -> sys_poll_var (fun { os_distribution; _ } -> os_distribution)
×
821
      | Os_family -> sys_poll_var (fun { os_family; _ } -> os_family)
×
822
      | Sys_ocaml_version ->
×
823
        sys_poll_var (fun { sys_ocaml_version; _ } -> sys_ocaml_version)
×
824
      | Build -> Memo.return [ Value.Dir paths.source_dir ]
×
825
      | Prefix -> Memo.return [ Value.Dir paths.prefix ]
×
826
      | User -> Memo.return [ Value.String (Unix.getlogin ()) ]
×
827
      | Jobs -> Memo.return [ Value.String (Int.to_string !Clflags.concurrency) ]
×
828
      | Arch -> sys_poll_var (fun { arch; _ } -> arch)
×
829
      | Group ->
×
830
        let group = Unix.getgid () |> Unix.getgrgid in
×
831
        Memo.return [ Value.String group.gr_name ]
×
832
      | Section_dir section ->
×
833
        let roots = Paths.install_roots paths in
834
        let dir = section_dir_of_root roots section in
×
835
        Memo.return [ Value.Dir dir ]
×
836
    ;;
837

838
    let expand_pkg_macro ~loc (paths : _ Paths.t) deps macro_invocation =
839
      let { Package_variable.name = variable_name; scope } =
×
840
        match Package_variable.of_macro_invocation ~loc macro_invocation with
841
        | Ok package_variable -> package_variable
×
842
        | Error `Unexpected_macro ->
×
843
          Code_error.raise
×
844
            "Attempted to treat an unexpected macro invocation as a package variable \
845
             encoding"
846
            []
847
      in
848
      let variables, paths =
849
        let package_name =
850
          match scope with
851
          | Self -> paths.name
×
852
          | Package package_name -> package_name
×
853
        in
854
        match Package.Name.Map.find deps package_name with
855
        | None -> Package_variable_name.Map.empty, None
×
856
        | Some (var, paths) -> var, Some paths
×
857
      in
858
      match Package_variable_name.Map.find variables variable_name with
859
      | Some v -> Memo.return @@ Ok (Variable.dune_value v)
×
860
      | None ->
×
861
        let present = Option.is_some paths in
862
        (* TODO we should be looking it up in all packages now *)
863
        (match Package_variable_name.to_string variable_name with
×
864
         | "pinned" -> Memo.return @@ Ok [ Value.false_ ]
×
865
         | "enable" ->
×
866
           Memo.return @@ Ok [ Value.String (if present then "enable" else "disable") ]
×
867
         | "installed" -> Memo.return @@ Ok [ Value.String (Bool.to_string present) ]
×
868
         | _ ->
×
869
           (match paths with
870
            | None -> Memo.return (Error (`Undefined_pkg_var variable_name))
×
871
            | Some paths ->
×
872
              (match
873
                 Pform.Var.Pkg.Section.of_string
874
                   (Package_variable_name.to_string variable_name)
×
875
               with
876
               | None -> Memo.return (Error (`Undefined_pkg_var variable_name))
×
877
               | Some section ->
×
878
                 let section = dune_section_of_pform section in
879
                 let install_paths = Paths.install_paths paths in
×
880
                 Memo.return @@ Ok [ Value.Dir (Install.Paths.get install_paths section) ])))
×
881
    ;;
882

883
    let expand_pform
884
      { name = _
885
      ; env = _
886
      ; paths
887
      ; artifacts = _
888
      ; context
889
      ; depends
890
      ; version = _
891
      ; depexts = _
892
      }
893
      ~source
894
      (pform : Pform.t)
895
      : (Value.t list, [ `Undefined_pkg_var of Package_variable_name.t ]) result Memo.t
896
      =
897
      let loc = Dune_sexp.Template.Pform.loc source in
×
898
      match pform with
×
899
      | Var (Pkg var) -> expand_pkg paths var >>| Result.ok
×
900
      | Var Context_name ->
×
901
        Memo.return (Ok [ Value.String (Context_name.to_string context) ])
×
902
      | Var Make ->
×
903
        let+ make =
904
          let path = Env_path.path (Global.env ()) in
×
905
          Make_prog.which loc context ~path
×
906
        in
907
        Ok [ Value.Path make ]
×
908
      | Macro ({ macro = Pkg | Pkg_self; _ } as macro_invocation) ->
×
909
        expand_pkg_macro ~loc paths depends macro_invocation
910
      | _ -> Expander0.isn't_allowed_in_this_position ~source
×
911
    ;;
912

913
    let () = Fdecl.set expand_pform_fdecl expand_pform
39✔
914

915
    let expand_pform_gen t =
916
      String_expander.Memo.expand ~dir:t.paths.source_dir ~f:(fun ~source pform ->
×
917
        expand_pform t ~source pform
×
918
        >>| function
919
        | Ok x -> x
×
920
        | Error (`Undefined_pkg_var variable_name) ->
×
921
          User_error.raise
922
            ~loc:(Dune_sexp.Template.Pform.loc source)
×
923
            [ Pp.textf
×
924
                "Undefined package variable: %s"
925
                (Package_variable_name.to_string variable_name)
×
926
            ])
927
    ;;
928

929
    let expand_exe_value t value ~loc =
930
      let+ prog =
×
931
        match value with
932
        | Value.Dir p ->
×
933
          User_error.raise
×
934
            ~loc
935
            [ Pp.textf
×
936
                "%s is a directory and cannot be used as an executable"
937
                (Path.to_string_maybe_quoted p)
×
938
            ]
939
        | Path p -> Memo.return @@ Ok p
×
940
        | String program ->
×
941
          (match Filename.analyze_program_name program with
942
           | Relative_to_current_dir | Absolute ->
×
943
             let dir = t.paths.source_dir in
944
             Memo.return @@ Ok (Path.relative dir program)
×
945
           | In_path ->
×
946
             (match Filename.Map.find t.artifacts program with
947
              | Some s -> Memo.return @@ Ok s
×
948
              | None ->
×
949
                (let path = Global.env () |> Env_path.path in
×
950
                 Which.which ~path program)
×
951
                >>| (function
×
952
                 | Some p -> Ok p
×
953
                 | None ->
×
954
                   let hint =
955
                     depexts_hint t.depexts
956
                     |> Option.map ~f:(fun pp -> Format.asprintf "%a" Pp.to_fmt pp)
×
957
                   in
958
                   Error
×
959
                     (Action.Prog.Not_found.create
×
960
                        ?hint
961
                        ~program
962
                        ~context:t.context
963
                        ~loc:(Some loc)
964
                        ()))))
965
      in
966
      Result.map prog ~f:(map_exe t)
×
967
    ;;
968

969
    let slang_expander t sw =
970
      String_expander.Memo.expand_result_deferred_concat sw ~mode:Many ~f:(expand_pform t)
×
971
    ;;
972

973
    let eval_blang t blang =
974
      Slang_expand.eval_blang blang ~dir:t.paths.source_dir ~f:(slang_expander t)
×
975
    ;;
976

977
    let eval_slangs_located t slangs =
978
      Slang_expand.eval_multi_located slangs ~dir:t.paths.source_dir ~f:(slang_expander t)
×
979
    ;;
980
  end
981

982
  let rec expand (action : Dune_lang.Action.t) ~(expander : Expander.t) =
983
    let dir = expander.paths.source_dir in
×
984
    match action with
985
    | Run args ->
×
986
      Expander.eval_slangs_located expander args
×
987
      >>= (function
988
       | [] ->
×
989
         let loc =
990
           let loc = function
991
             | Slang.Nil -> None
×
992
             | Literal sw -> Some (String_with_vars.loc sw)
×
993
             | Form (loc, _) -> Some loc
×
994
           in
995
           let start = List.find_map args ~f:loc in
996
           let stop =
×
997
             List.fold_left args ~init:None ~f:(fun last a ->
998
               match loc a with
×
999
               | None -> last
×
1000
               | Some _ as s -> s)
×
1001
           in
1002
           Option.both start stop
×
1003
           |> Option.map ~f:(fun (start, stop) -> Loc.span start stop)
×
1004
         in
1005
         User_error.raise
1006
           ?loc
1007
           [ Pp.text "\"run\" action must have at least one argument" ]
×
1008
       | (prog_loc, prog) :: args ->
×
1009
         let+ exe =
1010
           let prog = Value.Deferred_concat.force prog ~dir in
1011
           Expander.expand_exe_value expander prog ~loc:prog_loc
×
1012
         in
1013
         let args =
×
1014
           Array.Immutable.of_list_map args ~f:(fun (_loc, arg) ->
1015
             Value.Deferred_concat.parts arg
×
1016
             |> Array.Immutable.of_list_map ~f:(fun (arg : Value.t) ->
×
1017
               match arg with
×
1018
               | String s -> Run_with_path.Spec.String s
×
1019
               | Path p | Dir p -> Path p))
×
1020
         in
1021
         let ocamlfind_destdir = (Lazy.force expander.paths.install_roots).lib_root in
×
1022
         Run_with_path.action
1023
           ~depexts:expander.depexts
1024
           ~pkg:(expander.name, prog_loc)
1025
           exe
1026
           args
1027
           ~ocamlfind_destdir)
1028
    | Progn t ->
×
1029
      let+ args = Memo.parallel_map t ~f:(expand ~expander) in
×
1030
      Action.Progn args
×
1031
    | System arg ->
×
1032
      Expander.expand_pform_gen ~mode:Single expander arg
×
1033
      >>| Value.to_string ~dir
×
1034
      >>| System.action
1035
    | Patch p ->
×
1036
      let+ patch =
1037
        Expander.expand_pform_gen ~mode:Single expander p >>| Value.to_path ~dir
×
1038
      in
1039
      Dune_patch.action ~patch
×
1040
    | Substitute (src, dst) ->
×
1041
      let+ src =
1042
        Expander.expand_pform_gen ~mode:Single expander src >>| Value.to_path ~dir
×
1043
      and+ dst =
1044
        Expander.expand_pform_gen ~mode:Single expander dst
×
1045
        >>| Value.to_path ~dir
×
1046
        >>| Expander0.as_in_build_dir ~what:"substitute" ~loc:(String_with_vars.loc dst)
×
1047
      in
1048
      Substitute.action expander ~src ~dst
×
1049
    | Withenv (updates, action) -> expand_withenv expander updates action
×
1050
    | When (condition, action) ->
×
1051
      Expander.eval_blang expander condition
×
1052
      >>= (function
1053
       | true -> expand action ~expander
×
1054
       | false -> Memo.return (Action.progn []))
×
1055
    | Write_file (path_sw, perm, contents_sw) ->
×
1056
      let+ path =
1057
        Expander.expand_pform_gen ~mode:Single expander path_sw
×
1058
        >>| Value.to_path ~dir
×
1059
        >>| Expander0.as_in_build_dir
×
1060
              ~what:"write-file"
1061
              ~loc:(String_with_vars.loc path_sw)
×
1062
      and+ contents =
1063
        Expander.expand_pform_gen ~mode:Single expander contents_sw
×
1064
        >>| Value.to_string ~dir
×
1065
      in
1066
      Action.Write_file (path, perm, contents)
×
1067
    | _ ->
×
1068
      Code_error.raise
1069
        "Pkg_rules.action_expander.expand: unsupported action"
1070
        [ "action", Dune_lang.Action.to_dyn action ]
×
1071

1072
  and expand_withenv (expander : Expander.t) updates action =
1073
    let* env, updates =
×
1074
      let dir = expander.paths.source_dir in
1075
      Memo.List.fold_left
×
1076
        ~init:(expander.env, [])
1077
        updates
1078
        ~f:(fun (env, updates) ({ Env_update.op = _; var; value } as update) ->
1079
          let+ value =
×
1080
            let+ value =
1081
              let expander = { expander with env } in
1082
              Expander.expand_pform_gen expander value ~mode:Single
×
1083
            in
1084
            Value.to_string ~dir value
×
1085
          in
1086
          let env = Env_update.set env { update with value } in
×
1087
          let update =
×
1088
            let value =
1089
              match Env.Map.find env var with
1090
              | Some v -> Value_list_env.string_of_env_values v
×
1091
              | None ->
×
1092
                (* TODO *)
1093
                ""
1094
            in
1095
            var, value
1096
          in
1097
          env, update :: updates)
1098
    in
1099
    let+ action =
×
1100
      let expander = { expander with env } in
1101
      expand action ~expander
×
1102
    in
1103
    List.fold_left updates ~init:action ~f:(fun action (k, v) ->
×
1104
      Action.Setenv (k, v, action))
×
1105
  ;;
1106

1107
  module Artifacts_and_deps = struct
1108
    type artifacts_and_deps =
1109
      { binaries : Path.t Filename.Map.t
1110
      ; dep_info :
1111
          (OpamVariable.variable_contents Package_variable_name.Map.t * Path.t Paths.t)
1112
            Package.Name.Map.t
1113
      }
1114

1115
    let empty = { binaries = Filename.Map.empty; dep_info = Package.Name.Map.empty }
1116

1117
    let of_closure closure =
1118
      Memo.parallel_map closure ~f:(fun (pkg : Pkg.t) ->
×
1119
        let cookie = (Pkg_installed.of_paths pkg.paths).cookie in
×
1120
        Action_builder.evaluate_and_collect_facts cookie
1121
        |> Memo.map ~f:(fun ((cookie : Install_cookie.t), _) -> pkg, cookie))
×
1122
      |> Memo.map ~f:(fun cookies ->
×
1123
        List.fold_left
×
1124
          cookies
1125
          ~init:empty
1126
          ~f:(fun { binaries; dep_info } ((pkg : Pkg.t), (cookie : Install_cookie.t)) ->
1127
            let binaries =
×
1128
              Section.Map.Multi.find cookie.files Bin
1129
              |> List.fold_left ~init:binaries ~f:(fun acc bin ->
×
1130
                Filename.Map.set acc (Path.basename bin) bin)
×
1131
            in
1132
            let dep_info =
×
1133
              let variables =
1134
                Package_variable_name.Map.superpose
1135
                  (Package_variable_name.Map.of_list_exn cookie.variables)
×
1136
                  (Pkg_info.variables pkg.info)
×
1137
              in
1138
              Package.Name.Map.add_exn dep_info pkg.info.name (variables, pkg.paths)
×
1139
            in
1140
            { binaries; dep_info }))
1141
    ;;
1142
  end
1143

1144
  let expander context (pkg : Pkg.t) =
1145
    let+ { Artifacts_and_deps.binaries; dep_info } =
×
NEW
1146
      Memo.push_stack_frame
×
1147
        ~human_readable_description:(fun () ->
NEW
1148
          Pp.textf
×
1149
            "Computing closure for package %S"
NEW
1150
            (Package.Name.to_string pkg.info.name))
×
NEW
1151
        (fun () -> Pkg.deps_closure pkg |> Artifacts_and_deps.of_closure)
×
1152
    in
1153
    let env = Pkg.exported_value_env pkg in
×
1154
    let depends =
×
1155
      Package.Name.Map.add_exn
1156
        dep_info
1157
        pkg.info.name
1158
        (Pkg_info.variables pkg.info, pkg.paths)
×
1159
    in
1160
    { Expander.paths = pkg.paths
×
1161
    ; name = pkg.info.name
1162
    ; artifacts = binaries
1163
    ; context
1164
    ; depends
1165
    ; depexts = pkg.depexts
1166
    ; version = pkg.info.version
1167
    ; env
1168
    }
1169
  ;;
1170

1171
  let sandbox = Sandbox_mode.Set.singleton Sandbox_mode.copy
39✔
1172

1173
  let expand context (pkg : Pkg.t) action =
1174
    let+ action =
×
1175
      let* expander = expander context pkg in
×
1176
      expand action ~expander >>| Action.chdir pkg.paths.source_dir
×
1177
    in
1178
    (* TODO copying is needed for build systems that aren't dune and those
1179
       with an explicit install step *)
1180
    Action.Full.make ~sandbox action
×
1181
    |> Action_builder.return
×
1182
    |> Action_builder.with_no_targets
×
1183
  ;;
1184

1185
  let dune_exe context =
1186
    Which.which ~path:(Env_path.path Env.initial) "dune"
×
1187
    >>| function
1188
    | Some s -> Ok s
×
1189
    | None -> Error (Action.Prog.Not_found.create ~loc:None ~context ~program:"dune" ())
×
1190
  ;;
1191

1192
  let build_command context (pkg : Pkg.t) =
1193
    Option.map pkg.build_command ~f:(function
×
1194
      | Action action -> expand context pkg action
×
1195
      | Dune ->
×
1196
        (* CR-rgrinberg: respect [dune subst] settings. *)
1197
        Command.run_dyn_prog
1198
          (Action_builder.of_memo (dune_exe context))
×
1199
          ~dir:pkg.paths.source_dir
1200
          [ A "build"; A "-p"; A (Package.Name.to_string pkg.info.name) ]
×
1201
        |> Memo.return)
×
1202
  ;;
1203

1204
  let install_command context (pkg : Pkg.t) =
1205
    Option.map pkg.install_command ~f:(fun action -> expand context pkg action)
×
1206
  ;;
1207

1208
  let exported_env (expander : Expander.t) (env : _ Env_update.t) =
1209
    let+ value =
×
1210
      let+ value = Expander.expand_pform_gen expander env.value ~mode:Single in
×
1211
      value |> Value.to_string ~dir:expander.paths.source_dir
×
1212
    in
1213
    { env with value }
×
1214
  ;;
1215
end
1216

1217
module DB = struct
1218
  type t =
1219
    { all : Lock_dir.Pkg.t Package.Name.Map.t
1220
    ; system_provided : Package.Name.Set.t
1221
    }
1222

1223
  let equal t { all; system_provided } =
1224
    Package.Name.Map.equal ~equal:Lock_dir.Pkg.equal t.all all
×
1225
    && Package.Name.Set.equal t.system_provided system_provided
×
1226
  ;;
1227

1228
  let get package_universe =
1229
    let dune = Package.Name.Set.singleton (Package.Name.of_string "dune") in
×
1230
    let+ all = Package_universe.lock_dir package_universe in
×
1231
    { all = all.packages; system_provided = dune }
×
1232
  ;;
1233
end
1234

1235
module rec Resolve : sig
1236
  val resolve
1237
    :  DB.t
1238
    -> Loc.t * Package.Name.t
1239
    -> Package_universe.t
1240
    -> [ `Inside_lock_dir of Pkg.t | `System_provided ] Memo.t
1241
end = struct
1242
  open Resolve
1243

1244
  module Input = struct
1245
    type t =
1246
      { db : DB.t
1247
      ; package : Package.Name.t
1248
      ; universe : Package_universe.t
1249
      }
1250

1251
    let equal { db; package; universe } t =
1252
      DB.equal db t.db
×
1253
      && Package.Name.equal package t.package
×
1254
      && Package_universe.equal universe t.universe
×
1255
    ;;
1256

1257
    let hash { db; package; universe } =
1258
      Poly.hash (Poly.hash db, Package.Name.hash package, Poly.hash universe)
×
1259
    ;;
1260

1261
    let to_dyn = Dyn.opaque
1262
  end
1263

1264
  let resolve_impl { Input.db; package = name; universe = package_universe } =
1265
    match Package.Name.Map.find db.all name with
×
1266
    | None -> Memo.return None
×
1267
    | Some
×
1268
        ({ Lock_dir.Pkg.build_command
1269
         ; install_command
1270
         ; depends
1271
         ; info
1272
         ; exported_env
1273
         ; depexts
1274
         } as pkg) ->
1275
      assert (Package.Name.equal name info.name);
×
1276
      let* depends =
1277
        Memo.parallel_map depends ~f:(fun name ->
×
1278
          resolve db name package_universe
×
1279
          >>| function
1280
          | `Inside_lock_dir pkg -> Some pkg
×
1281
          | `System_provided -> None)
×
1282
        >>| List.filter_opt
×
1283
      and+ files_dir =
1284
        let+ lock_dir =
1285
          Package_universe.lock_dir_path package_universe >>| Option.value_exn
×
1286
        in
1287
        Path.Build.append_source
×
1288
          (Context_name.build_dir (Package_universe.context_name package_universe))
×
1289
          (Dune_pkg.Lock_dir.Pkg.files_dir info.name ~lock_dir)
×
1290
      in
1291
      let id = Pkg.Id.gen () in
×
1292
      let write_paths = Paths.make package_universe name ~relative:Path.Build.relative in
×
1293
      let* paths, build_command, install_command =
×
1294
        let paths = Paths.map_path write_paths ~f:Path.build in
1295
        match Pkg_toolchain.is_compiler_and_toolchains_enabled info.name with
×
1296
        | false -> Memo.return (paths, build_command, install_command)
×
1297
        | true ->
×
1298
          let pkg_dir = Pkg_toolchain.pkg_dir pkg in
1299
          let suffix = Path.basename (Path.outside_build_dir pkg_dir) in
×
1300
          let prefix = Pkg_toolchain.installation_prefix ~pkg_dir in
×
1301
          let install_roots =
1302
            Pkg_toolchain.install_roots ~prefix
1303
            |> Install.Roots.map ~f:Path.outside_build_dir
1304
          in
1305
          let* build_command =
×
1306
            match build_command with
1307
            | None | Some Dune -> Memo.return build_command
×
1308
            | Some (Action action) ->
×
1309
              let+ action = Pkg_toolchain.modify_build_action ~prefix action in
×
1310
              Some (Build_command.Action action)
×
1311
          in
1312
          let+ install_command =
×
1313
            match install_command with
1314
            | None -> Memo.return None
×
1315
            | Some install_command ->
×
1316
              Pkg_toolchain.modify_install_action ~prefix ~suffix install_command
×
1317
              >>| Option.some
×
1318
          in
1319
          ( { paths with
×
1320
              prefix = Path.outside_build_dir prefix
×
1321
            ; install_roots = Lazy.from_val install_roots
×
1322
            }
1323
          , build_command
1324
          , install_command )
1325
      in
1326
      let t =
×
1327
        { Pkg.id
1328
        ; build_command
1329
        ; install_command
1330
        ; depends
1331
        ; depexts
1332
        ; paths
1333
        ; write_paths
1334
        ; info
1335
        ; files_dir
1336
        ; exported_env = []
1337
        }
1338
      in
1339
      let+ exported_env =
1340
        let* expander =
1341
          Action_expander.expander (Package_universe.context_name package_universe) t
×
1342
        in
1343
        Memo.parallel_map exported_env ~f:(Action_expander.exported_env expander)
×
1344
      in
1345
      t.exported_env <- exported_env;
×
1346
      Some t
1347
  ;;
1348

1349
  let resolve =
1350
    let memo =
1351
      Memo.create
1352
        "pkg-resolve"
1353
        ~input:(module Input)
1354
        ~human_readable_description:(fun t ->
1355
          Pp.textf "- package %s" (Package.Name.to_string t.package))
×
1356
        resolve_impl
1357
    in
1358
    fun (db : DB.t) (loc, name) package_universe ->
39✔
1359
      if Package.Name.Set.mem db.system_provided name
×
1360
      then Memo.return `System_provided
×
1361
      else
1362
        Memo.exec memo { db; package = name; universe = package_universe }
×
1363
        >>| function
1364
        | Some s -> `Inside_lock_dir s
×
1365
        | None ->
×
1366
          User_error.raise
1367
            ~loc
1368
            [ Pp.textf "Unknown package %S" (Package.Name.to_string name) ]
×
1369
  ;;
1370
end
1371

1372
module Install_action = struct
1373
  (* The install action does the following:
1374

1375
     1. Runs the install action in the lock file (if exists)
1376
     2. Reads the .install file produced by the build command
1377
     3. Discoves all the files produced by 1.
1378
     4. Combines the set of files in 2. and 3. to produce a "cookie" file
1379
  *)
1380

1381
  let installable_sections =
1382
    Section.(Set.diff all (Set.of_list [ Misc; Libexec; Libexec_root ]))
39✔
1383
    |> Section.Set.to_list
39✔
1384
  ;;
1385

1386
  module Spec = struct
1387
    type ('path, 'target) t =
1388
      { (* location of the install file we must read (if produced) *)
1389
        install_file : 'path
1390
      ; (* location of the variables we must read (if produced) *)
1391
        config_file : 'path
1392
      ; (* where we are supposed to put the installed artifacts *)
1393
        target_dir : 'target
1394
      ; (* if the package's installation prefix is outside the build
1395
           dir, it's stored here and will be used instead of [target_dir]
1396
           as the location of insntalled artifacts *)
1397
        prefix_outside_build_dir : Path.Outside_build_dir.t option
1398
      ; (* does the package have its own install command? *)
1399
        install_action : [ `Has_install_action | `No_install_action ]
1400
      ; package : Package.Name.t
1401
      }
1402

1403
    let name = "install-file-run"
1404
    let version = 1
1405

1406
    let bimap
1407
      ({ install_file
1408
       ; config_file
1409
       ; target_dir
1410
       ; prefix_outside_build_dir = _
1411
       ; install_action = _
1412
       ; package = _
1413
       } as t)
1414
      f
1415
      g
1416
      =
1417
      { t with
×
1418
        install_file = f install_file
×
1419
      ; config_file = f config_file
×
1420
      ; target_dir = g target_dir
×
1421
      }
1422
    ;;
1423

1424
    let is_useful_to ~memoize = memoize
×
1425

1426
    let encode
1427
      { install_file
1428
      ; config_file
1429
      ; target_dir
1430
      ; prefix_outside_build_dir
1431
      ; install_action
1432
      ; package
1433
      }
1434
      path
1435
      target
1436
      : Sexp.t
1437
      =
1438
      List
×
1439
        [ path install_file
×
1440
        ; path config_file
×
1441
        ; target target_dir
×
1442
        ; (match
1443
             Option.map
1444
               prefix_outside_build_dir
1445
               ~f:Path.Outside_build_dir.to_string_maybe_quoted
1446
           with
1447
           | None -> List []
×
1448
           | Some s -> List [ Atom s ])
×
1449
        ; Atom (Package.Name.to_string package)
×
1450
        ; Atom
1451
            (match install_action with
1452
             | `Has_install_action -> "has_install_action"
×
1453
             | `No_install_action -> "no_install_action")
×
1454
        ]
1455
    ;;
1456

1457
    let prepare_copy ~install_file ~target_dir entry =
1458
      let dst =
×
1459
        let paths =
1460
          let package =
1461
            Path.basename install_file
1462
            |> Filename.remove_extension
×
1463
            |> Package.Name.of_string
×
1464
          in
1465
          let roots =
×
1466
            Path.build target_dir
1467
            |> Install.Roots.opam_from_prefix ~relative:Path.relative
×
1468
          in
1469
          Install.Paths.make ~relative:Path.relative ~package ~roots
×
1470
        in
1471
        Install.Entry.relative_installed_path entry ~paths
×
1472
      in
1473
      Path.mkdir_p (Path.parent_exn dst);
×
1474
      dst
×
1475
    ;;
1476

1477
    let readdir path =
1478
      match Path.Untracked.readdir_unsorted_with_kinds path with
×
1479
      | Error _ -> [], []
×
1480
      | Ok listing ->
×
1481
        List.partition_map listing ~f:(fun (basename, kind) ->
1482
          let path = Path.relative path basename in
×
1483
          match kind with
×
1484
          | S_DIR -> Right path
×
1485
          | _ -> Left path)
×
1486
    ;;
1487

1488
    let rec collect paths acc =
1489
      match paths with
×
1490
      | [] -> acc
×
1491
      | path :: paths ->
×
1492
        let files, dirs = readdir path in
1493
        let acc = List.rev_append files acc in
×
1494
        collect (List.rev_append dirs paths) acc
×
1495
    ;;
1496

1497
    let skip path skip =
1498
      List.iter skip ~f:(fun s -> assert (Path.equal path (Path.parent_exn s)));
×
1499
      let files, dirs = readdir path in
×
1500
      let dirs =
×
1501
        List.filter_map dirs ~f:(fun path ->
1502
          if List.mem skip path ~equal:Path.equal then None else Some path)
×
1503
      in
1504
      files, dirs
×
1505
    ;;
1506

1507
    let maybe_drop_sandbox_dir path =
1508
      match Path.extract_build_context_dir_maybe_sandboxed path with
×
1509
      | None -> path
×
1510
      | Some (sandbox, source) ->
×
1511
        let ctx =
1512
          let name = Path.basename sandbox in
1513
          Path.relative (Path.build Path.Build.root) name
×
1514
        in
1515
        Path.append_source ctx source
1516
    ;;
1517

1518
    let section_map_of_dir install_paths =
1519
      (* reverse engineer the installed artifacts from running the install
1520
         action by looking at the file system post running the action and
1521
         taking educated guesses about which section each file belongs to *)
1522
      let get = Install.Paths.get install_paths in
×
1523
      List.concat_map installable_sections ~f:(fun section ->
×
1524
        let path = get section in
×
1525
        let acc, dirs =
×
1526
          match section with
1527
          | Lib_root -> skip path [ get Toplevel; get Stublibs; get Lib ]
×
1528
          | Share_root -> skip path [ get Share ]
×
1529
          | _ -> [], [ path ]
×
1530
        in
1531
        collect dirs acc
1532
        |> List.rev_map ~f:(fun file ->
×
1533
          let section =
×
1534
            match
1535
              match section with
1536
              | Lib_root -> Some Section.Libexec_root
×
1537
              | Lib -> Some Libexec
×
1538
              | _ -> None
×
1539
            with
1540
            | None -> section
×
1541
            | Some section' ->
×
1542
              let perm = (Path.Untracked.stat_exn file).st_perm in
×
1543
              if Path.Permissions.(test execute perm) then section' else section
×
1544
          in
1545
          section, maybe_drop_sandbox_dir file))
×
1546
      |> Section.Map.of_list_multi
×
1547
    ;;
1548

1549
    let maybe_set_executable section dst =
1550
      match Section.should_set_executable_bit section with
×
1551
      | false -> ()
×
1552
      | true ->
×
1553
        let permission =
1554
          let perm = (Path.Untracked.stat_exn dst).st_perm in
×
1555
          Path.Permissions.(add execute) perm
×
1556
        in
1557
        Path.chmod dst ~mode:permission
1558
    ;;
1559

1560
    let read_variables config_file =
1561
      match Path.Untracked.exists config_file with
×
1562
      | false -> []
×
1563
      | true ->
×
1564
        let config =
1565
          let filename = Path.to_string config_file in
1566
          match
×
1567
            Io.read_file config_file
1568
            |> OpamFile.Dot_config.read_from_string
×
1569
                 ~filename:(OpamFile.make (OpamFilename.of_string filename))
×
1570
          with
1571
          | s -> s
×
1572
          | exception OpamPp.Bad_format (pos, message) ->
×
1573
            let loc =
1574
              Option.map
1575
                pos
1576
                ~f:(fun { OpamParserTypes.FullPos.filename = _; start; stop } ->
1577
                  let file_contents = Io.read_file config_file in
×
1578
                  let bols = ref [ 0 ] in
×
1579
                  String.iteri file_contents ~f:(fun i ch ->
1580
                    if ch = '\n' then bols := (i + 1) :: !bols);
×
1581
                  let bols = Array.of_list (List.rev !bols) in
×
1582
                  let make_pos (line, column) =
×
1583
                    let pos_bol = bols.(line - 1) in
×
1584
                    { Lexing.pos_fname = filename
×
1585
                    ; pos_lnum = line
1586
                    ; pos_bol
1587
                    ; pos_cnum = pos_bol + column
1588
                    }
1589
                  in
1590
                  let start = make_pos start in
1591
                  let stop = make_pos stop in
×
1592
                  Loc.create ~start ~stop)
×
1593
            in
1594
            let message_with_loc =
×
1595
              (* The location is inlined b/c the original config file is going
1596
                 to be deleted, so we don't be able to fetch the part of the
1597
                 file that's bad *)
1598
              let open Pp.O in
1599
              let error = Pp.textf "Error parsing %s" (Path.basename config_file) in
×
1600
              match loc with
×
1601
              | None -> error
×
1602
              | Some loc ->
×
1603
                (Loc.pp loc |> Pp.map_tags ~f:(fun Loc.Loc -> User_message.Style.Loc))
×
1604
                ++ error
×
1605
            in
1606
            User_error.raise
×
1607
              [ message_with_loc; Pp.seq (Pp.text "Reason: ") (Pp.text message) ]
×
1608
        in
1609
        OpamFile.Dot_config.bindings config
1610
        |> List.map ~f:(fun (name, value) -> Package_variable_name.of_opam name, value)
×
1611
    ;;
1612

1613
    let install_entry ~src ~install_file ~target_dir (entry : Path.t Install.Entry.t) =
1614
      match Path.Untracked.exists src, entry.optional with
×
1615
      | false, true -> None
×
1616
      | false, false ->
×
1617
        User_error.raise
1618
          (* TODO loc *)
1619
          [ Pp.textf
×
1620
              "entry %s in %s does not exist"
1621
              (Path.to_string_maybe_quoted src)
×
1622
              (Path.to_string install_file)
×
1623
          ]
1624
      | true, _ ->
×
1625
        let dst = prepare_copy ~install_file ~target_dir entry in
1626
        (let src =
×
1627
           match Path.to_string src |> Unix.readlink with
×
1628
           | exception Unix.Unix_error (_, _, _) -> src
×
1629
           | link ->
×
1630
             Path.external_
×
1631
               (let base = Path.parent_exn src in
1632
                Filename.concat (Path.to_absolute_filename base) link
×
1633
                |> Path.External.of_string)
×
1634
         in
1635
         Io.portable_hardlink ~src ~dst);
1636
        maybe_set_executable entry.section dst;
1637
        Some (entry.section, dst)
×
1638
    ;;
1639

1640
    let action
1641
      { package
1642
      ; install_file
1643
      ; config_file
1644
      ; target_dir
1645
      ; prefix_outside_build_dir
1646
      ; install_action
1647
      }
1648
      ~ectx:_
1649
      ~eenv:_
1650
      =
1651
      let open Fiber.O in
×
1652
      let* () = Fiber.return () in
×
1653
      let* files =
×
1654
        let from_install_action =
1655
          let target_dir =
1656
            (* If the package used a prefix that was outside the build
1657
               directory (as is the case with toolchains), parse the
1658
               installed sections from that location. Otherwise parse the
1659
               installed sections from the package's location within the
1660
               build directory. *)
1661
            match prefix_outside_build_dir with
1662
            | Some prefix_outside_build_dir ->
×
1663
              Path.outside_build_dir prefix_outside_build_dir
×
1664
            | None -> Path.build target_dir
×
1665
          in
1666
          match install_action with
1667
          | `No_install_action -> Section.Map.empty
×
1668
          | `Has_install_action ->
×
1669
            let install_paths =
1670
              Paths.of_root
1671
                package
1672
                ~root:(Path.parent_exn target_dir)
×
1673
                ~relative:Path.relative
1674
              |> Paths.install_paths
×
1675
            in
1676
            section_map_of_dir install_paths
×
1677
        in
1678
        let+ from_install_file =
1679
          (* Read all the artifacts from the .install file produced by
1680
             the build command. This is the happy path where we don't guess
1681
             anything. *)
1682
          Async.async (fun () -> Path.Untracked.exists install_file)
×
1683
          >>= function
×
1684
          | false -> Fiber.return Section.Map.empty
×
1685
          | true ->
×
1686
            let* map =
1687
              let install_entries =
1688
                let dir = Path.parent_exn install_file in
1689
                Install.Entry.load_install_file install_file (fun local ->
×
1690
                  Path.append_local dir local)
×
1691
              in
1692
              let by_src =
1693
                List.rev_map install_entries ~f:(fun (entry : _ Install.Entry.t) ->
1694
                  entry.src, entry)
×
1695
                |> Path.Map.of_list_multi
×
1696
              in
1697
              let+ install_entries =
×
1698
                Path.Map.to_list_map by_src ~f:(fun src entries ->
1699
                  List.map entries ~f:(fun entry -> src, entry))
×
1700
                |> List.concat
×
1701
                |> Fiber.parallel_map ~f:(fun (src, entry) ->
×
1702
                  Async.async (fun () ->
×
1703
                    install_entry ~src ~install_file ~target_dir entry))
×
1704
                >>| List.filter_opt
×
1705
              in
1706
              List.rev_map install_entries ~f:(fun (section, file) ->
×
1707
                let file = maybe_drop_sandbox_dir file in
×
1708
                section, file)
×
1709
              |> Section.Map.of_list_multi
×
1710
            in
1711
            let+ () = Async.async (fun () -> Path.unlink_exn install_file) in
×
1712
            map
×
1713
        in
1714
        (* Combine the artifacts declared in the .install, and the ones we discovered
1715
           by runing the install action *)
1716
        (* TODO we should make sure that overwrites aren't allowed *)
1717
        Section.Map.union from_install_action from_install_file ~f:(fun _ x y ->
×
1718
          Some (x @ y))
×
1719
      in
1720
      let* cookies =
×
1721
        let+ variables = Async.async (fun () -> read_variables config_file) in
×
1722
        { Install_cookie.files; variables }
×
1723
      in
1724
      (* Produce the cookie file in the standard path *)
1725
      let cookie_file = Path.build @@ Paths.install_cookie' target_dir in
×
1726
      Async.async (fun () ->
×
1727
        cookie_file |> Path.parent_exn |> Path.mkdir_p;
×
1728
        Install_cookie.dump cookie_file cookies)
×
1729
    ;;
1730
  end
1731

1732
  module A = Action_ext.Make (Spec)
1733

1734
  let action (p : Path.Build.t Paths.t) install_action ~prefix_outside_build_dir =
1735
    A.action
×
1736
      { Spec.install_file = Path.build @@ Paths.install_file p
×
1737
      ; config_file = Path.build @@ Paths.config_file p
×
1738
      ; target_dir = p.target_dir
1739
      ; prefix_outside_build_dir
1740
      ; install_action
1741
      ; package = p.name
1742
      }
1743
  ;;
1744
end
1745

1746
let add_env env action =
1747
  Action_builder.With_targets.map action ~f:(Action.Full.add_env env)
×
1748
;;
1749

1750
let rule ?loc { Action_builder.With_targets.build; targets } =
1751
  (* TODO this ignores the workspace file *)
1752
  Rule.make ~info:(Rule.Info.of_loc_opt loc) ~targets build |> Rules.Produce.rule
×
1753
;;
1754

1755
let source_rules (pkg : Pkg.t) =
1756
  let+ source_deps, copy_rules =
×
1757
    match pkg.info.source with
1758
    | None -> Memo.return (Dep.Set.empty, [])
×
1759
    | Some source ->
×
1760
      let loc = fst source.url in
1761
      Lock_dir.source_kind source
×
1762
      >>= (function
×
1763
       | `Local (`File, _) | `Fetch ->
×
1764
         let fetch =
1765
           Fetch_rules.fetch ~target:pkg.write_paths.source_dir `Directory source
1766
           |> With_targets.map
×
1767
                ~f:
1768
                  (Action.Full.map ~f:(fun action ->
1769
                     let progress =
×
1770
                       Pkg_build_progress.progress_action
1771
                         pkg.info.name
1772
                         pkg.info.version
1773
                         `Downloading
1774
                     in
1775
                     Action.progn [ progress; action ]))
×
1776
         in
1777
         Memo.return (Dep.Set.of_files [ pkg.paths.source_dir ], [ loc, fetch ])
×
1778
       | `Local (`Directory, source_root) ->
×
1779
         let+ source_files, rules =
1780
           let source_root = Path.external_ source_root in
1781
           Pkg.source_files pkg ~loc
×
1782
           >>| Path.Local.Set.fold ~init:([], []) ~f:(fun file (source_files, rules) ->
×
1783
             let src = Path.append_local source_root file in
×
1784
             let dst = Path.Build.append_local pkg.write_paths.source_dir file in
×
1785
             let copy = loc, Action_builder.copy ~src ~dst in
×
1786
             Path.build dst :: source_files, copy :: rules)
×
1787
         in
1788
         Dep.Set.of_files source_files, rules)
×
1789
  in
1790
  let extra_source_deps, extra_copy_rules =
×
1791
    List.map pkg.info.extra_sources ~f:(fun (local, (fetch : Source.t)) ->
1792
      let extra_source = Paths.extra_source_build pkg.write_paths local in
×
1793
      let rule =
×
1794
        let loc = fst fetch.url in
1795
        (* We assume that [fetch] is always a file. Would be good
1796
           to give a decent error message if it's not *)
1797
        match Source.kind fetch with
×
1798
        | `Directory_or_archive src ->
×
1799
          loc, Action_builder.copy ~src:(Path.external_ src) ~dst:extra_source
×
1800
        | `Fetch ->
×
1801
          let rule = Fetch_rules.fetch ~target:extra_source `File fetch in
1802
          loc, rule
×
1803
      in
1804
      Path.build extra_source, rule)
×
1805
    |> List.unzip
×
1806
  in
1807
  let copy_rules = copy_rules @ extra_copy_rules in
×
1808
  let source_deps = Dep.Set.union source_deps (Dep.Set.of_files extra_source_deps) in
×
1809
  source_deps, Memo.parallel_iter copy_rules ~f:(fun (loc, copy) -> rule ~loc copy)
×
1810
;;
1811

1812
let build_rule context_name ~source_deps (pkg : Pkg.t) =
1813
  let+ build_action =
×
1814
    let+ copy_action, build_action, install_action =
1815
      let+ copy_action =
1816
        let+ copy_action =
1817
          Fs_memo.dir_exists
×
1818
            (In_source_dir (Path.Build.drop_build_context_exn pkg.files_dir))
×
1819
          >>= function
×
1820
          | false -> Memo.return []
×
1821
          | true ->
×
1822
            let+ deps, source_deps = Source_deps.files (Path.build pkg.files_dir) in
×
1823
            let open Action_builder.O in
×
1824
            [ Action_builder.with_no_targets
×
1825
              @@ (Action_builder.deps deps
×
1826
                  >>> (Path.Set.to_list_map source_deps ~f:(fun src ->
×
1827
                         let dst =
×
1828
                           let local_path =
1829
                             Path.drop_prefix_exn src ~prefix:(Path.build pkg.files_dir)
×
1830
                           in
1831
                           Path.Build.append_local pkg.write_paths.source_dir local_path
×
1832
                         in
1833
                         Action.progn
1834
                           [ Action.mkdir (Path.Build.parent_exn dst)
×
1835
                           ; Action.copy src dst
×
1836
                           ])
1837
                       |> Action.concurrent
×
1838
                       |> Action.Full.make
×
1839
                       |> Action_builder.return))
×
1840
            ]
1841
        in
1842
        copy_action
×
1843
        @ List.map pkg.info.extra_sources ~f:(fun (local, _) ->
×
1844
          (* If the package has extra sources, they will be
1845
             initially stored in the extra_sources directory for that
1846
             package. Prior to building, the contents of
1847
             extra_sources must be copied into the package's source
1848
             directory. *)
1849
          let src = Paths.extra_source pkg.paths local in
×
1850
          let dst = Path.Build.append_local pkg.write_paths.source_dir local in
×
1851
          Action.progn
×
1852
            [ (* If the package has no source directory (some
1853
                 low-level packages are exclusively made up of extra
1854
                 sources), the source directory is first created. *)
1855
              Action.mkdir pkg.write_paths.source_dir
×
1856
            ; (* It's possible for some extra sources to already be at
1857
                 the destination. If these files are write-protected
1858
                 then the copy action will fail if we don't first remove
1859
                 them. *)
1860
              Action.remove_tree dst
×
1861
            ; Action.copy src dst
×
1862
            ]
1863
          |> Action.Full.make
×
1864
          |> Action_builder.With_targets.return)
×
1865
      and+ build_action =
1866
        match Action_expander.build_command context_name pkg with
1867
        | None -> Memo.return []
×
1868
        | Some build_command -> build_command >>| List.singleton
×
1869
      and+ install_action =
1870
        match Action_expander.install_command context_name pkg with
1871
        | None -> Memo.return []
×
1872
        | Some install_action ->
×
1873
          let+ install_action = install_action in
1874
          let mkdir_install_dirs =
×
1875
            let install_paths = Paths.install_paths pkg.write_paths in
1876
            Install_action.installable_sections
×
1877
            |> List.rev_map ~f:(fun section ->
1878
              Install.Paths.get install_paths section |> Action.mkdir)
×
1879
            |> Action.progn
×
1880
            |> Action.Full.make
×
1881
            |> Action_builder.With_targets.return
×
1882
          in
1883
          [ mkdir_install_dirs; install_action ]
1884
      in
1885
      copy_action, build_action, install_action
×
1886
    in
1887
    let install_file_action =
×
1888
      let prefix_outside_build_dir = Path.as_outside_build_dir pkg.paths.prefix in
1889
      Install_action.action
×
1890
        pkg.write_paths
1891
        (match Action_expander.install_command context_name pkg with
1892
         | None -> `No_install_action
×
1893
         | Some _ -> `Has_install_action)
×
1894
        ~prefix_outside_build_dir
1895
      |> Action.Full.make
×
1896
      |> Action_builder.return
×
1897
      |> Action_builder.with_no_targets
×
1898
    in
1899
    (* Action to print a "Building" message for the package if its
1900
       target directory is not yet created. *)
1901
    let progress_building =
1902
      Pkg_build_progress.progress_action pkg.info.name pkg.info.version `Building
1903
      |> Action.Full.make
×
1904
      |> Action_builder.return
×
1905
      |> Action_builder.with_no_targets
×
1906
    in
1907
    Action_builder.progn
×
1908
      (copy_action
1909
       @ [ progress_building ]
1910
       @ build_action
1911
       @ install_action
1912
       @ [ install_file_action ])
1913
  in
1914
  let deps = Dep.Set.union source_deps (Pkg.package_deps pkg) in
×
1915
  let open Action_builder.With_targets.O in
×
1916
  Action_builder.deps deps
1917
  |> Action_builder.with_no_targets
×
1918
  (* TODO should we add env deps on these? *)
1919
  >>> add_env (Pkg.exported_env pkg) build_action
×
1920
  |> Action_builder.With_targets.add_directories
×
1921
       ~directory_targets:[ pkg.write_paths.target_dir ]
1922
;;
1923

1924
let gen_rules context_name (pkg : Pkg.t) =
1925
  let* source_deps, copy_rules = source_rules pkg in
×
1926
  let* () = copy_rules
×
1927
  and* build_rule = build_rule context_name pkg ~source_deps in
×
1928
  rule ~loc:Loc.none (* TODO *) build_rule
×
1929
;;
1930

1931
module Gen_rules = Build_config.Gen_rules
1932

1933
let setup_package_rules ~package_universe ~dir ~pkg_name : Gen_rules.result Memo.t =
1934
  let name = User_error.ok_exn (Package.Name.of_string_user_error (Loc.none, pkg_name)) in
×
1935
  let* db = DB.get package_universe in
×
1936
  let* pkg =
×
1937
    Resolve.resolve db (Loc.none, name) package_universe
×
1938
    >>| function
×
1939
    | `Inside_lock_dir pkg -> pkg
×
1940
    | `System_provided ->
×
1941
      User_error.raise
1942
        (* TODO loc *)
1943
        [ Pp.textf
×
1944
            "There are no rules for %S because it's set as provided by the system"
1945
            (Package.Name.to_string name)
×
1946
        ]
1947
  in
1948
  let paths = Paths.make package_universe name ~relative:Path.Build.relative in
×
1949
  let+ directory_targets =
×
1950
    let map =
1951
      let target_dir = paths.target_dir in
1952
      Path.Build.Map.singleton target_dir Loc.none
×
1953
    in
1954
    match pkg.info.source with
1955
    | None -> Memo.return map
×
1956
    | Some source ->
×
1957
      Lock_dir.source_kind source
×
1958
      >>| (function
×
1959
       | `Local (`Directory, _) -> map
×
1960
       | `Local (`File, _) | `Fetch ->
×
1961
         Path.Build.Map.add_exn map paths.source_dir (fst source.url))
×
1962
  in
1963
  let build_dir_only_sub_dirs =
×
1964
    Gen_rules.Build_only_sub_dirs.singleton ~dir Subdir_set.empty
1965
  in
1966
  let context_name = Package_universe.context_name package_universe in
×
1967
  let rules = Rules.collect_unit (fun () -> gen_rules context_name pkg) in
×
1968
  Gen_rules.make ~directory_targets ~build_dir_only_sub_dirs rules
×
1969
;;
1970

1971
let setup_rules ~components ~dir ctx =
1972
  (* Note that the path components in the following patterns must
1973
     correspond to the paths returned by [Paths.make]. The string
1974
     ".dev-tool" is hardcoded into several patterns, and must match
1975
     the value of [Pkg_dev_tool.install_path_base_dir_name]. *)
1976
  assert (String.equal Pkg_dev_tool.install_path_base_dir_name ".dev-tool");
×
1977
  match Context_name.is_default ctx, components with
×
1978
  | true, [ ".dev-tool"; pkg_name; pkg_dep_name ] ->
×
1979
    setup_package_rules
1980
      ~package_universe:
1981
        (Dev_tool (Package.Name.of_string pkg_name |> Dune_pkg.Dev_tool.of_package_name))
×
1982
      ~dir
1983
      ~pkg_name:pkg_dep_name
1984
  | true, [ ".dev-tool" ] ->
×
1985
    Gen_rules.make
1986
      ~build_dir_only_sub_dirs:
1987
        (Gen_rules.Build_only_sub_dirs.singleton ~dir Subdir_set.all)
×
1988
      (Memo.return Rules.empty)
×
1989
    |> Memo.return
×
1990
  | _, [ ".pkg" ] ->
×
1991
    Gen_rules.make
1992
      ~build_dir_only_sub_dirs:
1993
        (Gen_rules.Build_only_sub_dirs.singleton ~dir Subdir_set.all)
×
1994
      (Memo.return Rules.empty)
×
1995
    |> Memo.return
×
1996
  | _, [ ".pkg"; pkg_name ] ->
×
1997
    setup_package_rules ~package_universe:(Project_dependencies ctx) ~dir ~pkg_name
1998
  | _, ".pkg" :: _ :: _ ->
×
1999
    Memo.return @@ Gen_rules.redirect_to_parent Gen_rules.Rules.empty
×
2000
  | true, ".dev-tool" :: _ :: _ :: _ ->
×
2001
    Memo.return @@ Gen_rules.redirect_to_parent Gen_rules.Rules.empty
×
2002
  | is_default, [] ->
×
2003
    let sub_dirs = ".pkg" :: (if is_default then [ ".dev-tool" ] else []) in
×
2004
    let build_dir_only_sub_dirs =
2005
      Gen_rules.Build_only_sub_dirs.singleton ~dir @@ Subdir_set.of_list sub_dirs
×
2006
    in
2007
    Memo.return @@ Gen_rules.make ~build_dir_only_sub_dirs (Memo.return Rules.empty)
×
2008
  | _ -> Memo.return @@ Gen_rules.rules_here Gen_rules.Rules.empty
×
2009
;;
2010

2011
let db_project context = DB.get (Project_dependencies context)
×
2012

2013
let resolve_pkg_project context pkg =
2014
  let* db = db_project context in
×
2015
  Resolve.resolve db pkg (Project_dependencies context)
×
2016
;;
2017

2018
let ocaml_toolchain context =
NEW
2019
  Memo.push_stack_frame ~human_readable_description:(fun () ->
×
NEW
2020
    Pp.textf
×
2021
      "Loading OCaml toolchain from Lock directory for context %S"
NEW
2022
      (Context_name.to_string context))
×
2023
  @@ fun () ->
2024
  (let* lock_dir = Lock_dir.get_exn context in
×
2025
   match lock_dir.ocaml with
×
2026
   | None -> Memo.return `System_provided
×
2027
   | Some ocaml -> resolve_pkg_project context ocaml)
×
2028
  >>| function
2029
  | `System_provided -> None
×
2030
  | `Inside_lock_dir pkg ->
×
2031
    let toolchain =
2032
      let cookie = (Pkg_installed.of_paths pkg.paths).cookie in
×
2033
      let open Action_builder.O in
2034
      let* cookie = cookie in
2035
      (* TODO we should use the closure of [pkg] *)
2036
      let binaries =
×
2037
        Section.Map.find cookie.files Bin |> Option.value ~default:[] |> Path.Set.of_list
×
2038
      in
2039
      let env = Env.extend_env (Global.env ()) (Pkg.exported_env pkg) in
×
2040
      let path = Env_path.path (Global.env ()) in
×
2041
      Action_builder.of_memo @@ Ocaml_toolchain.of_binaries ~path context env binaries
×
2042
    in
2043
    Some (Action_builder.memoize "ocaml_toolchain" toolchain)
×
2044
;;
2045

2046
let all_packages context =
2047
  let* db = db_project context in
×
2048
  Dune_lang.Package_name.Map.values db.all
×
2049
  |> Memo.parallel_map ~f:(fun (package : Lock_dir.Pkg.t) ->
×
2050
    let package = package.info.name in
×
2051
    resolve_pkg_project context (Loc.none, package)
×
2052
    >>| function
2053
    | `Inside_lock_dir pkg -> Some pkg
×
2054
    | `System_provided -> None)
×
2055
  >>| List.filter_opt
×
2056
  >>| Pkg.top_closure
2057
;;
2058

2059
let which context =
2060
  let artifacts_and_deps =
×
2061
    Memo.lazy_
2062
      ~human_readable_description:(fun () ->
NEW
2063
        Pp.textf
×
2064
          "Loading all binaries in the lock directory for %S"
NEW
2065
          (Context_name.to_string context))
×
2066
      (fun () ->
NEW
2067
        let+ { binaries; dep_info = _ } =
×
NEW
2068
          all_packages context >>= Action_expander.Artifacts_and_deps.of_closure
×
2069
        in
NEW
2070
        binaries)
×
2071
  in
2072
  Staged.stage (fun program ->
×
2073
    let+ artifacts = Memo.Lazy.force artifacts_and_deps in
×
2074
    Filename.Map.find artifacts program)
×
2075
;;
2076

2077
let ocamlpath context =
2078
  let+ all_packages = all_packages context in
×
2079
  let env = Pkg.build_env_of_deps all_packages in
×
2080
  Env.Map.find env Dune_findlib.Config.ocamlpath_var
×
2081
  |> Option.value ~default:[]
×
2082
  |> List.map ~f:(function
×
2083
    | Value.Dir p | Path p -> p
×
2084
    | String s -> Path.of_filename_relative_to_initial_cwd s)
×
2085
;;
2086

2087
let lock_dir_active = Lock_dir.lock_dir_active
2088
let lock_dir_path = Lock_dir.get_path
2089

2090
let exported_env context =
NEW
2091
  Memo.push_stack_frame ~human_readable_description:(fun () ->
×
NEW
2092
    Pp.textf "lock directory environment for context %S" (Context_name.to_string context))
×
2093
  @@ fun () ->
2094
  let+ all_packages = all_packages context in
×
2095
  let env = Pkg.build_env_of_deps all_packages in
×
2096
  let vars = Env.Map.map env ~f:Value_list_env.string_of_env_values in
×
2097
  Env.extend Env.empty ~vars
×
2098
;;
2099

2100
let find_package ctx pkg =
2101
  lock_dir_active ctx
×
2102
  >>= function
2103
  | false -> Memo.return None
×
2104
  | true ->
×
2105
    resolve_pkg_project ctx (Loc.none, pkg)
×
2106
    >>| (function
×
2107
           | `System_provided -> Action_builder.return ()
×
2108
           | `Inside_lock_dir pkg ->
×
2109
             let open Action_builder.O in
2110
             let+ _cookie = (Pkg_installed.of_paths pkg.paths).cookie in
×
2111
             ())
×
2112
    >>| Option.some
2113
;;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc