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

ocaml / dune / 28713

19 Oct 2024 09:13PM UTC coverage: 6.918% (-0.001%) from 6.919%
28713

push

github

web-flow
fix(pkg): dependency closure should be lazier (#11025)

This is a pre-requesite for fixing OCaml syntax dune files.

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

0 of 12 new or added lines in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

2932 of 42383 relevant lines covered (6.92%)

26877.84 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 Memo.t
484
    ; depends :
485
        (Variable.value Package_variable_name.Map.t * Path.t Paths.t) Package.Name.Map.t
486
          Memo.t
487
    ; depexts : string list
488
    ; context : Context_name.t
489
    ; version : Package_version.t
490
    ; env : Value.t list Env.Map.t
491
    }
492

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

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

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

514
           The two implementations are bound to drift. Better would be to
515
           reconstruct everything that is needed to call our one and only
516
           substitution function. *)
517
        expander : Expander.t
518
      ; depends :
519
          (Variable.value Package_variable_name.Map.t * Path.t Paths.t) Package.Name.Map.t
520
      ; artifacts : Path.t Filename.Map.t
521
      ; src : 'src
522
      ; dst : 'dst
523
      }
524

525
    let name = "substitute"
526
    let version = 2
527
    let bimap t f g = { t with src = f t.src; dst = g t.dst }
×
528
    let is_useful_to ~memoize = memoize
×
529

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

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

581
  module A = Action_ext.Make (Spec)
582

583
  let action (expander : Expander.t) ~src ~dst =
NEW
584
    let+ depends = expander.depends
×
585
    and+ artifacts = expander.artifacts in
NEW
586
    A.action { Spec.expander; depends; artifacts; src; dst }
×
587
  ;;
588
end
589

590
let depexts_hint = function
591
  | [] -> None
×
592
  | depexts ->
×
593
    [ Pp.textf "You may want to verify the following depexts are installed:"
×
594
    ; Pp.enumerate ~f:Pp.verbatim depexts
×
595
    ]
596
    |> Pp.concat_map ~sep:Pp.cut ~f:(fun pp -> Pp.box pp)
×
597
    |> Option.some
×
598
;;
599

600
module Run_with_path = struct
601
  module Output : sig
602
    type error
603

604
    val io : error -> Process.Io.output Process.Io.t
605

606
    val with_error
607
      :  accepted_exit_codes:int Predicate.t
608
      -> pkg:Dune_pkg.Package_name.t * Loc.t
609
      -> depexts:string list
610
      -> display:Display.t
611
      -> (error -> 'a)
612
      -> 'a
613

614
    val prerr : rc:int -> error -> unit
615
  end = struct
616
    type error =
617
      { pkg : Dune_pkg.Package_name.t * Loc.t
618
      ; depexts : string list
619
      ; filename : Dpath.t
620
      ; io : Process.Io.output Process.Io.t
621
      ; accepted_exit_codes : int Predicate.t
622
      ; display : Display.t
623
      }
624

625
    let io t = t.io
×
626

627
    let with_error ~accepted_exit_codes ~pkg ~depexts ~display f =
628
      let filename = Temp.create File ~prefix:"dune-pkg" ~suffix:"stderr" in
×
629
      let io = Process.Io.(file filename Out) in
×
630
      let t = { filename; io; accepted_exit_codes; display; pkg; depexts } in
631
      let result = f t in
632
      Temp.destroy File filename;
×
633
      result
×
634
    ;;
635

636
    let to_paragraphs t error =
637
      let pp_pkg = Pp.textf "Logs for package %s" (Package.Name.to_string (fst t.pkg)) in
×
638
      [ pp_pkg; Pp.verbatim error ]
×
639
    ;;
640

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

663
  module Spec = struct
664
    type 'path chunk =
665
      | String of string
666
      | Path of 'path
667

668
    type 'path arg = 'path chunk Array.Immutable.t
669

670
    type ('path, 'target) t =
671
      { prog : Action.Prog.t
672
      ; args : 'path arg Array.Immutable.t
673
      ; ocamlfind_destdir : 'path
674
      ; pkg : Dune_pkg.Package_name.t * Loc.t
675
      ; depexts : string list
676
      }
677

678
    let name = "run-with-path"
679
    let version = 2
680

681
    let map_arg arg ~f =
682
      Array.Immutable.map arg ~f:(function
×
683
        | String _ as s -> s
×
684
        | Path p -> Path (f p))
×
685
    ;;
686

687
    let bimap t f _g =
688
      { t with
×
689
        args = Array.Immutable.map t.args ~f:(map_arg ~f)
×
690
      ; ocamlfind_destdir = f t.ocamlfind_destdir
×
691
      }
692
    ;;
693

694
    let is_useful_to ~memoize:_ = true
×
695

696
    let encode { prog; args; ocamlfind_destdir; pkg = _; depexts = _ } path _ : Sexp.t =
697
      let prog : Sexp.t =
×
698
        Atom
699
          (match prog with
700
           | Ok p -> Path.reach p ~from:Path.root
×
701
           | Error e -> e.program)
×
702
      in
703
      let args =
704
        Array.Immutable.to_list_map args ~f:(fun x ->
705
          Sexp.List
×
706
            (Array.Immutable.to_list_map x ~f:(function
×
707
              | String s -> Sexp.Atom s
×
708
              | Path p -> path p)))
×
709
      in
710
      List [ List ([ prog ] @ args); path ocamlfind_destdir ]
×
711
    ;;
712

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

765
  module A = Action_ext.Make (Spec)
766

767
  let action ~pkg ~depexts prog args ~ocamlfind_destdir =
768
    A.action { Spec.prog; args; ocamlfind_destdir; pkg; depexts }
×
769
  ;;
770
end
771

772
module Action_expander = struct
773
  module Expander = struct
774
    include Expander0
775

776
    let map_exe _ x =
777
      (* TODO *)
778
      x
×
779
    ;;
780

781
    let dune_section_of_pform : Pform.Var.Pkg.Section.t -> Section.t = function
782
      | Lib -> Lib
×
783
      | Libexec -> Libexec
×
784
      | Bin -> Bin
×
785
      | Sbin -> Sbin
×
786
      | Toplevel -> Toplevel
×
787
      | Share -> Share
×
788
      | Etc -> Etc
×
789
      | Doc -> Doc
×
790
      | Stublibs -> Stublibs
×
791
      | Man -> Man
×
792
    ;;
793

794
    let section_dir_of_root
795
      (roots : _ Install.Roots.t)
796
      (section : Pform.Var.Pkg.Section.t)
797
      =
798
      match section with
×
799
      | Lib -> roots.lib_root
×
800
      | Libexec -> roots.libexec_root
×
801
      | Bin -> roots.bin
×
802
      | Sbin -> roots.sbin
×
803
      | Share -> roots.share_root
×
804
      | Etc -> roots.etc_root
×
805
      | Doc -> roots.doc_root
×
806
      | Man -> roots.man
×
807
      | Toplevel -> Path.relative roots.lib_root "toplevel"
×
808
      | Stublibs -> Path.relative roots.lib_root "stublibs"
×
809
    ;;
810

811
    let sys_poll_var accessor =
812
      accessor Lock_dir.Sys_vars.poll
×
813
      |> Memo.Lazy.force
×
814
      >>| function
815
      | Some v -> [ Value.String v ]
×
816
      | None ->
×
817
        (* TODO: in OPAM an unset variable evaluates to false, but we
818
           can't represent that in a string so it evaluates to an empty
819
           string instead *)
820
        [ Value.String "" ]
821
    ;;
822

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

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

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

922
    let () = Fdecl.set expand_pform_fdecl expand_pform
39✔
923

924
    let expand_pform_gen t =
925
      String_expander.Memo.expand ~dir:t.paths.source_dir ~f:(fun ~source pform ->
×
926
        expand_pform t ~source pform
×
927
        >>| function
928
        | Ok x -> x
×
929
        | Error (`Undefined_pkg_var variable_name) ->
×
930
          User_error.raise
931
            ~loc:(Dune_sexp.Template.Pform.loc source)
×
932
            [ Pp.textf
×
933
                "Undefined package variable: %s"
934
                (Package_variable_name.to_string variable_name)
×
935
            ])
936
    ;;
937

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

979
    let slang_expander t sw =
980
      String_expander.Memo.expand_result_deferred_concat sw ~mode:Many ~f:(expand_pform t)
×
981
    ;;
982

983
    let eval_blang t blang =
984
      Slang_expand.eval_blang blang ~dir:t.paths.source_dir ~f:(slang_expander t)
×
985
    ;;
986

987
    let eval_slangs_located t slangs =
988
      Slang_expand.eval_multi_located slangs ~dir:t.paths.source_dir ~f:(slang_expander t)
×
989
    ;;
990
  end
991

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

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

1117
  module Artifacts_and_deps = struct
1118
    type artifacts_and_deps =
1119
      { binaries : Path.t Filename.Map.t
1120
      ; dep_info :
1121
          (OpamVariable.variable_contents Package_variable_name.Map.t * Path.t Paths.t)
1122
            Package.Name.Map.t
1123
      }
1124

1125
    let empty = { binaries = Filename.Map.empty; dep_info = Package.Name.Map.empty }
1126

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

1154
  let expander context (pkg : Pkg.t) =
NEW
1155
    let closure =
×
1156
      Memo.lazy_
1157
        ~human_readable_description:(fun () ->
1158
          Pp.textf
×
1159
            "Computing closure for package %S"
1160
            (Package.Name.to_string pkg.info.name))
×
1161
        (fun () -> Pkg.deps_closure pkg |> Artifacts_and_deps.of_closure)
×
1162
    in
1163
    let env = Pkg.exported_value_env pkg in
×
1164
    let depends =
×
1165
      Memo.Lazy.map closure ~f:(fun { Artifacts_and_deps.dep_info; _ } ->
NEW
1166
        Package.Name.Map.add_exn
×
1167
          dep_info
1168
          pkg.info.name
NEW
1169
          (Pkg_info.variables pkg.info, pkg.paths))
×
NEW
1170
      |> Memo.Lazy.force
×
1171
    in
NEW
1172
    let artifacts =
×
NEW
1173
      let+ { Artifacts_and_deps.binaries; _ } = Memo.Lazy.force closure in
×
NEW
1174
      binaries
×
1175
    in
1176
    { Expander.paths = pkg.paths
1177
    ; name = pkg.info.name
1178
    ; artifacts
1179
    ; context
1180
    ; depends
1181
    ; depexts = pkg.depexts
1182
    ; version = pkg.info.version
1183
    ; env
1184
    }
1185
  ;;
1186

1187
  let sandbox = Sandbox_mode.Set.singleton Sandbox_mode.copy
39✔
1188

1189
  let expand context (pkg : Pkg.t) action =
1190
    let+ action =
×
1191
      let expander = expander context pkg in
1192
      expand action ~expander >>| Action.chdir pkg.paths.source_dir
×
1193
    in
1194
    (* TODO copying is needed for build systems that aren't dune and those
1195
       with an explicit install step *)
1196
    Action.Full.make ~sandbox action
×
1197
    |> Action_builder.return
×
1198
    |> Action_builder.with_no_targets
×
1199
  ;;
1200

1201
  let dune_exe context =
1202
    Which.which ~path:(Env_path.path Env.initial) "dune"
×
1203
    >>| function
1204
    | Some s -> Ok s
×
1205
    | None -> Error (Action.Prog.Not_found.create ~loc:None ~context ~program:"dune" ())
×
1206
  ;;
1207

1208
  let build_command context (pkg : Pkg.t) =
1209
    Option.map pkg.build_command ~f:(function
×
1210
      | Action action -> expand context pkg action
×
1211
      | Dune ->
×
1212
        (* CR-rgrinberg: respect [dune subst] settings. *)
1213
        Command.run_dyn_prog
1214
          (Action_builder.of_memo (dune_exe context))
×
1215
          ~dir:pkg.paths.source_dir
1216
          [ A "build"; A "-p"; A (Package.Name.to_string pkg.info.name) ]
×
1217
        |> Memo.return)
×
1218
  ;;
1219

1220
  let install_command context (pkg : Pkg.t) =
1221
    Option.map pkg.install_command ~f:(fun action -> expand context pkg action)
×
1222
  ;;
1223

1224
  let exported_env (expander : Expander.t) (env : _ Env_update.t) =
1225
    let+ value =
×
1226
      let+ value = Expander.expand_pform_gen expander env.value ~mode:Single in
×
1227
      value |> Value.to_string ~dir:expander.paths.source_dir
×
1228
    in
1229
    { env with value }
×
1230
  ;;
1231
end
1232

1233
module DB = struct
1234
  type t =
1235
    { all : Lock_dir.Pkg.t Package.Name.Map.t
1236
    ; system_provided : Package.Name.Set.t
1237
    }
1238

1239
  let equal t { all; system_provided } =
1240
    Package.Name.Map.equal ~equal:Lock_dir.Pkg.equal t.all all
×
1241
    && Package.Name.Set.equal t.system_provided system_provided
×
1242
  ;;
1243

1244
  let get package_universe =
1245
    let dune = Package.Name.Set.singleton (Package.Name.of_string "dune") in
×
1246
    let+ all = Package_universe.lock_dir package_universe in
×
1247
    { all = all.packages; system_provided = dune }
×
1248
  ;;
1249
end
1250

1251
module rec Resolve : sig
1252
  val resolve
1253
    :  DB.t
1254
    -> Loc.t * Package.Name.t
1255
    -> Package_universe.t
1256
    -> [ `Inside_lock_dir of Pkg.t | `System_provided ] Memo.t
1257
end = struct
1258
  open Resolve
1259

1260
  module Input = struct
1261
    type t =
1262
      { db : DB.t
1263
      ; package : Package.Name.t
1264
      ; universe : Package_universe.t
1265
      }
1266

1267
    let equal { db; package; universe } t =
1268
      DB.equal db t.db
×
1269
      && Package.Name.equal package t.package
×
1270
      && Package_universe.equal universe t.universe
×
1271
    ;;
1272

1273
    let hash { db; package; universe } =
1274
      Poly.hash (Poly.hash db, Package.Name.hash package, Poly.hash universe)
×
1275
    ;;
1276

1277
    let to_dyn = Dyn.opaque
1278
  end
1279

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

1365
  let resolve =
1366
    let memo =
1367
      Memo.create
1368
        "pkg-resolve"
1369
        ~input:(module Input)
1370
        ~human_readable_description:(fun t ->
1371
          Pp.textf "- package %s" (Package.Name.to_string t.package))
×
1372
        resolve_impl
1373
    in
1374
    fun (db : DB.t) (loc, name) package_universe ->
39✔
1375
      if Package.Name.Set.mem db.system_provided name
×
1376
      then Memo.return `System_provided
×
1377
      else
1378
        Memo.exec memo { db; package = name; universe = package_universe }
×
1379
        >>| function
1380
        | Some s -> `Inside_lock_dir s
×
1381
        | None ->
×
1382
          User_error.raise
1383
            ~loc
1384
            [ Pp.textf "Unknown package %S" (Package.Name.to_string name) ]
×
1385
  ;;
1386
end
1387

1388
module Install_action = struct
1389
  (* The install action does the following:
1390

1391
     1. Runs the install action in the lock file (if exists)
1392
     2. Reads the .install file produced by the build command
1393
     3. Discoves all the files produced by 1.
1394
     4. Combines the set of files in 2. and 3. to produce a "cookie" file
1395
  *)
1396

1397
  let installable_sections =
1398
    Section.(Set.diff all (Set.of_list [ Misc; Libexec; Libexec_root ]))
39✔
1399
    |> Section.Set.to_list
39✔
1400
  ;;
1401

1402
  module Spec = struct
1403
    type ('path, 'target) t =
1404
      { (* location of the install file we must read (if produced) *)
1405
        install_file : 'path
1406
      ; (* location of the variables we must read (if produced) *)
1407
        config_file : 'path
1408
      ; (* where we are supposed to put the installed artifacts *)
1409
        target_dir : 'target
1410
      ; (* if the package's installation prefix is outside the build
1411
           dir, it's stored here and will be used instead of [target_dir]
1412
           as the location of insntalled artifacts *)
1413
        prefix_outside_build_dir : Path.Outside_build_dir.t option
1414
      ; (* does the package have its own install command? *)
1415
        install_action : [ `Has_install_action | `No_install_action ]
1416
      ; package : Package.Name.t
1417
      }
1418

1419
    let name = "install-file-run"
1420
    let version = 1
1421

1422
    let bimap
1423
      ({ install_file
1424
       ; config_file
1425
       ; target_dir
1426
       ; prefix_outside_build_dir = _
1427
       ; install_action = _
1428
       ; package = _
1429
       } as t)
1430
      f
1431
      g
1432
      =
1433
      { t with
×
1434
        install_file = f install_file
×
1435
      ; config_file = f config_file
×
1436
      ; target_dir = g target_dir
×
1437
      }
1438
    ;;
1439

1440
    let is_useful_to ~memoize = memoize
×
1441

1442
    let encode
1443
      { install_file
1444
      ; config_file
1445
      ; target_dir
1446
      ; prefix_outside_build_dir
1447
      ; install_action
1448
      ; package
1449
      }
1450
      path
1451
      target
1452
      : Sexp.t
1453
      =
1454
      List
×
1455
        [ path install_file
×
1456
        ; path config_file
×
1457
        ; target target_dir
×
1458
        ; (match
1459
             Option.map
1460
               prefix_outside_build_dir
1461
               ~f:Path.Outside_build_dir.to_string_maybe_quoted
1462
           with
1463
           | None -> List []
×
1464
           | Some s -> List [ Atom s ])
×
1465
        ; Atom (Package.Name.to_string package)
×
1466
        ; Atom
1467
            (match install_action with
1468
             | `Has_install_action -> "has_install_action"
×
1469
             | `No_install_action -> "no_install_action")
×
1470
        ]
1471
    ;;
1472

1473
    let prepare_copy ~install_file ~target_dir entry =
1474
      let dst =
×
1475
        let paths =
1476
          let package =
1477
            Path.basename install_file
1478
            |> Filename.remove_extension
×
1479
            |> Package.Name.of_string
×
1480
          in
1481
          let roots =
×
1482
            Path.build target_dir
1483
            |> Install.Roots.opam_from_prefix ~relative:Path.relative
×
1484
          in
1485
          Install.Paths.make ~relative:Path.relative ~package ~roots
×
1486
        in
1487
        Install.Entry.relative_installed_path entry ~paths
×
1488
      in
1489
      Path.mkdir_p (Path.parent_exn dst);
×
1490
      dst
×
1491
    ;;
1492

1493
    let readdir path =
1494
      match Path.Untracked.readdir_unsorted_with_kinds path with
×
1495
      | Error _ -> [], []
×
1496
      | Ok listing ->
×
1497
        List.partition_map listing ~f:(fun (basename, kind) ->
1498
          let path = Path.relative path basename in
×
1499
          match kind with
×
1500
          | S_DIR -> Right path
×
1501
          | _ -> Left path)
×
1502
    ;;
1503

1504
    let rec collect paths acc =
1505
      match paths with
×
1506
      | [] -> acc
×
1507
      | path :: paths ->
×
1508
        let files, dirs = readdir path in
1509
        let acc = List.rev_append files acc in
×
1510
        collect (List.rev_append dirs paths) acc
×
1511
    ;;
1512

1513
    let skip path skip =
1514
      List.iter skip ~f:(fun s -> assert (Path.equal path (Path.parent_exn s)));
×
1515
      let files, dirs = readdir path in
×
1516
      let dirs =
×
1517
        List.filter_map dirs ~f:(fun path ->
1518
          if List.mem skip path ~equal:Path.equal then None else Some path)
×
1519
      in
1520
      files, dirs
×
1521
    ;;
1522

1523
    let maybe_drop_sandbox_dir path =
1524
      match Path.extract_build_context_dir_maybe_sandboxed path with
×
1525
      | None -> path
×
1526
      | Some (sandbox, source) ->
×
1527
        let ctx =
1528
          let name = Path.basename sandbox in
1529
          Path.relative (Path.build Path.Build.root) name
×
1530
        in
1531
        Path.append_source ctx source
1532
    ;;
1533

1534
    let section_map_of_dir install_paths =
1535
      (* reverse engineer the installed artifacts from running the install
1536
         action by looking at the file system post running the action and
1537
         taking educated guesses about which section each file belongs to *)
1538
      let get = Install.Paths.get install_paths in
×
1539
      List.concat_map installable_sections ~f:(fun section ->
×
1540
        let path = get section in
×
1541
        let acc, dirs =
×
1542
          match section with
1543
          | Lib_root -> skip path [ get Toplevel; get Stublibs; get Lib ]
×
1544
          | Share_root -> skip path [ get Share ]
×
1545
          | _ -> [], [ path ]
×
1546
        in
1547
        collect dirs acc
1548
        |> List.rev_map ~f:(fun file ->
×
1549
          let section =
×
1550
            match
1551
              match section with
1552
              | Lib_root -> Some Section.Libexec_root
×
1553
              | Lib -> Some Libexec
×
1554
              | _ -> None
×
1555
            with
1556
            | None -> section
×
1557
            | Some section' ->
×
1558
              let perm = (Path.Untracked.stat_exn file).st_perm in
×
1559
              if Path.Permissions.(test execute perm) then section' else section
×
1560
          in
1561
          section, maybe_drop_sandbox_dir file))
×
1562
      |> Section.Map.of_list_multi
×
1563
    ;;
1564

1565
    let maybe_set_executable section dst =
1566
      match Section.should_set_executable_bit section with
×
1567
      | false -> ()
×
1568
      | true ->
×
1569
        let permission =
1570
          let perm = (Path.Untracked.stat_exn dst).st_perm in
×
1571
          Path.Permissions.(add execute) perm
×
1572
        in
1573
        Path.chmod dst ~mode:permission
1574
    ;;
1575

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

1629
    let install_entry ~src ~install_file ~target_dir (entry : Path.t Install.Entry.t) =
1630
      match Path.Untracked.exists src, entry.optional with
×
1631
      | false, true -> None
×
1632
      | false, false ->
×
1633
        User_error.raise
1634
          (* TODO loc *)
1635
          [ Pp.textf
×
1636
              "entry %s in %s does not exist"
1637
              (Path.to_string_maybe_quoted src)
×
1638
              (Path.to_string install_file)
×
1639
          ]
1640
      | true, _ ->
×
1641
        let dst = prepare_copy ~install_file ~target_dir entry in
1642
        (let src =
×
1643
           match Path.to_string src |> Unix.readlink with
×
1644
           | exception Unix.Unix_error (_, _, _) -> src
×
1645
           | link ->
×
1646
             Path.external_
×
1647
               (let base = Path.parent_exn src in
1648
                Filename.concat (Path.to_absolute_filename base) link
×
1649
                |> Path.External.of_string)
×
1650
         in
1651
         Io.portable_hardlink ~src ~dst);
1652
        maybe_set_executable entry.section dst;
1653
        Some (entry.section, dst)
×
1654
    ;;
1655

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

1748
  module A = Action_ext.Make (Spec)
1749

1750
  let action (p : Path.Build.t Paths.t) install_action ~prefix_outside_build_dir =
1751
    A.action
×
1752
      { Spec.install_file = Path.build @@ Paths.install_file p
×
1753
      ; config_file = Path.build @@ Paths.config_file p
×
1754
      ; target_dir = p.target_dir
1755
      ; prefix_outside_build_dir
1756
      ; install_action
1757
      ; package = p.name
1758
      }
1759
  ;;
1760
end
1761

1762
let add_env env action =
1763
  Action_builder.With_targets.map action ~f:(Action.Full.add_env env)
×
1764
;;
1765

1766
let rule ?loc { Action_builder.With_targets.build; targets } =
1767
  (* TODO this ignores the workspace file *)
1768
  Rule.make ~info:(Rule.Info.of_loc_opt loc) ~targets build |> Rules.Produce.rule
×
1769
;;
1770

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

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

1940
let gen_rules context_name (pkg : Pkg.t) =
1941
  let* source_deps, copy_rules = source_rules pkg in
×
1942
  let* () = copy_rules
×
1943
  and* build_rule = build_rule context_name pkg ~source_deps in
×
1944
  rule ~loc:Loc.none (* TODO *) build_rule
×
1945
;;
1946

1947
module Gen_rules = Build_config.Gen_rules
1948

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

1987
let setup_rules ~components ~dir ctx =
1988
  (* Note that the path components in the following patterns must
1989
     correspond to the paths returned by [Paths.make]. The string
1990
     ".dev-tool" is hardcoded into several patterns, and must match
1991
     the value of [Pkg_dev_tool.install_path_base_dir_name]. *)
1992
  assert (String.equal Pkg_dev_tool.install_path_base_dir_name ".dev-tool");
×
1993
  match Context_name.is_default ctx, components with
×
1994
  | true, [ ".dev-tool"; pkg_name; pkg_dep_name ] ->
×
1995
    setup_package_rules
1996
      ~package_universe:
1997
        (Dev_tool (Package.Name.of_string pkg_name |> Dune_pkg.Dev_tool.of_package_name))
×
1998
      ~dir
1999
      ~pkg_name:pkg_dep_name
2000
  | true, [ ".dev-tool" ] ->
×
2001
    Gen_rules.make
2002
      ~build_dir_only_sub_dirs:
2003
        (Gen_rules.Build_only_sub_dirs.singleton ~dir Subdir_set.all)
×
2004
      (Memo.return Rules.empty)
×
2005
    |> Memo.return
×
2006
  | _, [ ".pkg" ] ->
×
2007
    Gen_rules.make
2008
      ~build_dir_only_sub_dirs:
2009
        (Gen_rules.Build_only_sub_dirs.singleton ~dir Subdir_set.all)
×
2010
      (Memo.return Rules.empty)
×
2011
    |> Memo.return
×
2012
  | _, [ ".pkg"; pkg_name ] ->
×
2013
    setup_package_rules ~package_universe:(Project_dependencies ctx) ~dir ~pkg_name
2014
  | _, ".pkg" :: _ :: _ ->
×
2015
    Memo.return @@ Gen_rules.redirect_to_parent Gen_rules.Rules.empty
×
2016
  | true, ".dev-tool" :: _ :: _ :: _ ->
×
2017
    Memo.return @@ Gen_rules.redirect_to_parent Gen_rules.Rules.empty
×
2018
  | is_default, [] ->
×
2019
    let sub_dirs = ".pkg" :: (if is_default then [ ".dev-tool" ] else []) in
×
2020
    let build_dir_only_sub_dirs =
2021
      Gen_rules.Build_only_sub_dirs.singleton ~dir @@ Subdir_set.of_list sub_dirs
×
2022
    in
2023
    Memo.return @@ Gen_rules.make ~build_dir_only_sub_dirs (Memo.return Rules.empty)
×
2024
  | _ -> Memo.return @@ Gen_rules.rules_here Gen_rules.Rules.empty
×
2025
;;
2026

2027
let db_project context = DB.get (Project_dependencies context)
×
2028

2029
let resolve_pkg_project context pkg =
2030
  let* db = db_project context in
×
2031
  Resolve.resolve db pkg (Project_dependencies context)
×
2032
;;
2033

2034
let ocaml_toolchain context =
2035
  Memo.push_stack_frame ~human_readable_description:(fun () ->
×
2036
    Pp.textf
×
2037
      "Loading OCaml toolchain from Lock directory for context %S"
2038
      (Context_name.to_string context))
×
2039
  @@ fun () ->
2040
  (let* lock_dir = Lock_dir.get_exn context in
×
2041
   match lock_dir.ocaml with
×
2042
   | None -> Memo.return `System_provided
×
2043
   | Some ocaml -> resolve_pkg_project context ocaml)
×
2044
  >>| function
2045
  | `System_provided -> None
×
2046
  | `Inside_lock_dir pkg ->
×
2047
    let toolchain =
2048
      let cookie = (Pkg_installed.of_paths pkg.paths).cookie in
×
2049
      let open Action_builder.O in
2050
      let* cookie = cookie in
2051
      (* TODO we should use the closure of [pkg] *)
2052
      let binaries =
×
2053
        Section.Map.find cookie.files Bin |> Option.value ~default:[] |> Path.Set.of_list
×
2054
      in
2055
      let env = Env.extend_env (Global.env ()) (Pkg.exported_env pkg) in
×
2056
      let path = Env_path.path (Global.env ()) in
×
2057
      Action_builder.of_memo @@ Ocaml_toolchain.of_binaries ~path context env binaries
×
2058
    in
2059
    Some (Action_builder.memoize "ocaml_toolchain" toolchain)
×
2060
;;
2061

2062
let all_packages context =
2063
  let* db = db_project context in
×
2064
  Dune_lang.Package_name.Map.values db.all
×
2065
  |> Memo.parallel_map ~f:(fun (package : Lock_dir.Pkg.t) ->
×
2066
    let package = package.info.name in
×
2067
    resolve_pkg_project context (Loc.none, package)
×
2068
    >>| function
2069
    | `Inside_lock_dir pkg -> Some pkg
×
2070
    | `System_provided -> None)
×
2071
  >>| List.filter_opt
×
2072
  >>| Pkg.top_closure
2073
;;
2074

2075
let which context =
2076
  let artifacts_and_deps =
×
2077
    Memo.lazy_
2078
      ~human_readable_description:(fun () ->
2079
        Pp.textf
×
2080
          "Loading all binaries in the lock directory for %S"
2081
          (Context_name.to_string context))
×
2082
      (fun () ->
2083
        let+ { binaries; dep_info = _ } =
×
2084
          all_packages context >>= Action_expander.Artifacts_and_deps.of_closure
×
2085
        in
2086
        binaries)
×
2087
  in
2088
  Staged.stage (fun program ->
×
2089
    let+ artifacts = Memo.Lazy.force artifacts_and_deps in
×
2090
    Filename.Map.find artifacts program)
×
2091
;;
2092

2093
let ocamlpath context =
2094
  let+ all_packages = all_packages context in
×
2095
  let env = Pkg.build_env_of_deps all_packages in
×
2096
  Env.Map.find env Dune_findlib.Config.ocamlpath_var
×
2097
  |> Option.value ~default:[]
×
2098
  |> List.map ~f:(function
×
2099
    | Value.Dir p | Path p -> p
×
2100
    | String s -> Path.of_filename_relative_to_initial_cwd s)
×
2101
;;
2102

2103
let lock_dir_active = Lock_dir.lock_dir_active
2104
let lock_dir_path = Lock_dir.get_path
2105

2106
let exported_env context =
2107
  Memo.push_stack_frame ~human_readable_description:(fun () ->
×
2108
    Pp.textf "lock directory environment for context %S" (Context_name.to_string context))
×
2109
  @@ fun () ->
2110
  let+ all_packages = all_packages context in
×
2111
  let env = Pkg.build_env_of_deps all_packages in
×
2112
  let vars = Env.Map.map env ~f:Value_list_env.string_of_env_values in
×
2113
  Env.extend Env.empty ~vars
×
2114
;;
2115

2116
let find_package ctx pkg =
2117
  lock_dir_active ctx
×
2118
  >>= function
2119
  | false -> Memo.return None
×
2120
  | true ->
×
2121
    resolve_pkg_project ctx (Loc.none, pkg)
×
2122
    >>| (function
×
2123
           | `System_provided -> Action_builder.return ()
×
2124
           | `Inside_lock_dir pkg ->
×
2125
             let open Action_builder.O in
2126
             let+ _cookie = (Pkg_installed.of_paths pkg.paths).cookie in
×
2127
             ())
×
2128
    >>| Option.some
2129
;;
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