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

ocaml / dune / 29716

20 Jan 2025 01:40PM UTC coverage: 6.926%. First build
29716

Pull #11365

github

web-flow
Merge dff61dc90 into 12010a06d
Pull Request #11365: backport #11310: fix: package management working with ocaml.5.3.0

2 of 14 new or added lines in 2 files covered. (14.29%)

2957 of 42695 relevant lines covered (6.93%)

26682.01 hits per line

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

0.67
/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
  let target_dir t = t.target_dir
×
177
end
178

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

354
  let deps_closure t = top_closure t.depends
×
355

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

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

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

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

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

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

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

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

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

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

478
module Expander0 = struct
479
  include Expander0
480

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

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

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

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

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

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

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

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

582
  module A = Action_ext.Make (Spec)
583

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

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

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

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

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

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

626
    let io t = t.io
×
627

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

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

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

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

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

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

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

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

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

695
    let is_useful_to ~memoize:_ = true
×
696

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

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

766
  module A = Action_ext.Make (Spec)
767

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1278
    let to_dyn = Dyn.opaque
1279
  end
1280

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

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

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

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

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

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

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

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

1441
    let is_useful_to ~memoize = memoize
×
1442

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

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

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

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

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

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

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

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

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

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

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

1749
  module A = Action_ext.Make (Spec)
1750

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

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

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

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

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

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

1948
module Gen_rules = Build_config.Gen_rules
1949

1950
let setup_pkg_install_alias =
1951
  let build_packages_of_context ctx_name =
1952
    (* Fetching the package target implies that we will also fetch the extra
1953
       sources. *)
1954
    let open Action_builder.O in
×
1955
    let project_deps : Package_universe.t = Project_dependencies ctx_name in
1956
    let* lock_dir = Action_builder.of_memo (Package_universe.lock_dir project_deps) in
×
1957
    Dune_lang.Package_name.Map.keys lock_dir.packages
×
1958
    |> List.map ~f:(fun pkg ->
×
1959
      Paths.make ~relative:Path.Build.relative project_deps pkg
×
1960
      |> Paths.target_dir
×
1961
      |> Path.build)
×
1962
    |> Action_builder.paths
×
1963
  in
1964
  fun ~dir ctx_name ->
1965
    let rule =
×
1966
      (* We only need to build when the build_dir is the root of the context *)
1967
      match
1968
        let build_dir = Context_name.build_dir ctx_name in
1969
        Path.Build.equal dir build_dir
×
1970
      with
1971
      | false -> Memo.return Rules.empty
×
1972
      | true ->
×
1973
        Lock_dir.lock_dir_active ctx_name
×
1974
        >>= (function
×
1975
         | false -> Memo.return Rules.empty
×
1976
         | true ->
×
1977
           Rules.collect_unit (fun () ->
1978
             let alias = Alias.make ~dir Alias0.pkg_install in
×
1979
             let deps = build_packages_of_context ctx_name in
×
1980
             Rules.Produce.Alias.add_deps alias deps))
×
1981
    in
1982
    Gen_rules.rules_for ~dir ~allowed_subdirs:Filename.Set.empty rule
1983
    |> Gen_rules.rules_here
×
1984
;;
1985

1986
let setup_package_rules ~package_universe ~dir ~pkg_name : Gen_rules.result Memo.t =
1987
  let name = User_error.ok_exn (Package.Name.of_string_user_error (Loc.none, pkg_name)) in
×
1988
  let* db = DB.get package_universe in
×
1989
  let* pkg =
×
1990
    Resolve.resolve db (Loc.none, name) package_universe
×
1991
    >>| function
×
1992
    | `Inside_lock_dir pkg -> pkg
×
1993
    | `System_provided ->
×
1994
      User_error.raise
1995
        (* TODO loc *)
1996
        [ Pp.textf
×
1997
            "There are no rules for %S because it's set as provided by the system"
1998
            (Package.Name.to_string name)
×
1999
        ]
2000
  in
2001
  let paths = Paths.make package_universe name ~relative:Path.Build.relative in
×
2002
  let+ directory_targets =
×
2003
    let map =
2004
      let target_dir = paths.target_dir in
2005
      Path.Build.Map.singleton target_dir Loc.none
×
2006
    in
2007
    match pkg.info.source with
2008
    | None -> Memo.return map
×
2009
    | Some source ->
×
2010
      Lock_dir.source_kind source
×
2011
      >>| (function
×
2012
       | `Local (`Directory, _) -> map
×
2013
       | `Local (`File, _) | `Fetch ->
×
2014
         Path.Build.Map.add_exn map paths.source_dir (fst source.url))
×
2015
  in
2016
  let build_dir_only_sub_dirs =
×
2017
    Gen_rules.Build_only_sub_dirs.singleton ~dir Subdir_set.empty
2018
  in
2019
  let context_name = Package_universe.context_name package_universe in
×
2020
  let rules = Rules.collect_unit (fun () -> gen_rules context_name pkg) in
×
2021
  Gen_rules.make ~directory_targets ~build_dir_only_sub_dirs rules
×
2022
;;
2023

2024
let setup_rules ~components ~dir ctx =
2025
  (* Note that the path components in the following patterns must
2026
     correspond to the paths returned by [Paths.make]. The string
2027
     ".dev-tool" is hardcoded into several patterns, and must match
2028
     the value of [Pkg_dev_tool.install_path_base_dir_name]. *)
2029
  assert (String.equal Pkg_dev_tool.install_path_base_dir_name ".dev-tool");
×
2030
  match Context_name.is_default ctx, components with
×
2031
  | true, [ ".dev-tool"; pkg_name; pkg_dep_name ] ->
×
2032
    setup_package_rules
2033
      ~package_universe:
2034
        (Dev_tool (Package.Name.of_string pkg_name |> Dune_pkg.Dev_tool.of_package_name))
×
2035
      ~dir
2036
      ~pkg_name:pkg_dep_name
2037
  | true, [ ".dev-tool" ] ->
×
2038
    Gen_rules.make
2039
      ~build_dir_only_sub_dirs:
2040
        (Gen_rules.Build_only_sub_dirs.singleton ~dir Subdir_set.all)
×
2041
      (Memo.return Rules.empty)
×
2042
    |> Memo.return
×
2043
  | _, [ ".pkg" ] ->
×
2044
    Gen_rules.make
2045
      ~build_dir_only_sub_dirs:
2046
        (Gen_rules.Build_only_sub_dirs.singleton ~dir Subdir_set.all)
×
2047
      (Memo.return Rules.empty)
×
2048
    |> Memo.return
×
2049
  | _, [ ".pkg"; pkg_name ] ->
×
2050
    setup_package_rules ~package_universe:(Project_dependencies ctx) ~dir ~pkg_name
2051
  | _, ".pkg" :: _ :: _ ->
×
2052
    Memo.return @@ Gen_rules.redirect_to_parent Gen_rules.Rules.empty
×
2053
  | true, ".dev-tool" :: _ :: _ :: _ ->
×
2054
    Memo.return @@ Gen_rules.redirect_to_parent Gen_rules.Rules.empty
×
2055
  | is_default, [] ->
×
2056
    let sub_dirs = ".pkg" :: (if is_default then [ ".dev-tool" ] else []) in
×
2057
    let build_dir_only_sub_dirs =
2058
      Gen_rules.Build_only_sub_dirs.singleton ~dir @@ Subdir_set.of_list sub_dirs
×
2059
    in
2060
    Memo.return @@ Gen_rules.make ~build_dir_only_sub_dirs (Memo.return Rules.empty)
×
2061
  | _ -> Memo.return @@ Gen_rules.rules_here Gen_rules.Rules.empty
×
2062
;;
2063

2064
let db_project context = DB.get (Project_dependencies context)
×
2065

2066
let resolve_pkg_project context pkg =
2067
  let* db = db_project context in
×
2068
  Resolve.resolve db pkg (Project_dependencies context)
×
2069
;;
2070

2071
let ocaml_toolchain context =
2072
  Memo.push_stack_frame ~human_readable_description:(fun () ->
×
2073
    Pp.textf
×
2074
      "Loading OCaml toolchain from Lock directory for context %S"
2075
      (Context_name.to_string context))
×
2076
  @@ fun () ->
2077
  (let* lock_dir = Lock_dir.get_exn context in
×
2078
   match lock_dir.ocaml with
×
2079
   | None -> Memo.return `System_provided
×
2080
   | Some ocaml -> resolve_pkg_project context ocaml)
×
2081
  >>| function
2082
  | `System_provided -> None
×
2083
  | `Inside_lock_dir pkg ->
×
2084
    let toolchain =
2085
      let open Action_builder.O in
NEW
2086
      let transitive_deps = pkg :: Pkg.deps_closure pkg in
×
2087
      let* env, binaries =
NEW
2088
        Action_builder.List.fold_left
×
NEW
2089
          ~init:(Global.env (), Path.Set.empty)
×
2090
          ~f:(fun (env, binaries) pkg ->
NEW
2091
            let env = Env.extend_env env (Pkg.exported_env pkg) in
×
NEW
2092
            let+ cookie = (Pkg_installed.of_paths pkg.paths).cookie in
×
NEW
2093
            let binaries =
×
2094
              Section.Map.find cookie.files Bin
NEW
2095
              |> Option.value ~default:[]
×
NEW
2096
              |> Path.Set.of_list
×
NEW
2097
              |> Path.Set.union binaries
×
2098
            in
NEW
2099
            env, binaries)
×
2100
          transitive_deps
2101
      in
2102
      let path = Env_path.path (Global.env ()) in
×
2103
      Action_builder.of_memo @@ Ocaml_toolchain.of_binaries ~path context env binaries
×
2104
    in
2105
    Some (Action_builder.memoize "ocaml_toolchain" toolchain)
×
2106
;;
2107

2108
let all_packages context =
2109
  let* db = db_project context in
×
2110
  Dune_lang.Package_name.Map.values db.all
×
2111
  |> Memo.parallel_map ~f:(fun (package : Lock_dir.Pkg.t) ->
×
2112
    let package = package.info.name in
×
2113
    resolve_pkg_project context (Loc.none, package)
×
2114
    >>| function
2115
    | `Inside_lock_dir pkg -> Some pkg
×
2116
    | `System_provided -> None)
×
2117
  >>| List.filter_opt
×
2118
  >>| Pkg.top_closure
2119
;;
2120

2121
let which context =
2122
  let artifacts_and_deps =
×
2123
    Memo.lazy_
2124
      ~human_readable_description:(fun () ->
2125
        Pp.textf
×
2126
          "Loading all binaries in the lock directory for %S"
2127
          (Context_name.to_string context))
×
2128
      (fun () ->
2129
        let+ { binaries; dep_info = _ } =
×
2130
          all_packages context >>= Action_expander.Artifacts_and_deps.of_closure
×
2131
        in
2132
        binaries)
×
2133
  in
2134
  Staged.stage (fun program ->
×
2135
    let+ artifacts = Memo.Lazy.force artifacts_and_deps in
×
2136
    Filename.Map.find artifacts program)
×
2137
;;
2138

2139
let ocamlpath context =
2140
  let+ all_packages = all_packages context in
×
2141
  let env = Pkg.build_env_of_deps all_packages in
×
2142
  Env.Map.find env Dune_findlib.Config.ocamlpath_var
×
2143
  |> Option.value ~default:[]
×
2144
  |> List.map ~f:(function
×
2145
    | Value.Dir p | Path p -> p
×
2146
    | String s -> Path.of_filename_relative_to_initial_cwd s)
×
2147
;;
2148

2149
let lock_dir_active = Lock_dir.lock_dir_active
2150
let lock_dir_path = Lock_dir.get_path
2151

2152
let exported_env context =
2153
  Memo.push_stack_frame ~human_readable_description:(fun () ->
×
2154
    Pp.textf "lock directory environment for context %S" (Context_name.to_string context))
×
2155
  @@ fun () ->
2156
  let+ all_packages = all_packages context in
×
2157
  let env = Pkg.build_env_of_deps all_packages in
×
2158
  let vars = Env.Map.map env ~f:Value_list_env.string_of_env_values in
×
2159
  Env.extend Env.empty ~vars
×
2160
;;
2161

2162
let find_package ctx pkg =
2163
  lock_dir_active ctx
×
2164
  >>= function
2165
  | false -> Memo.return None
×
2166
  | true ->
×
2167
    resolve_pkg_project ctx (Loc.none, pkg)
×
2168
    >>| (function
×
2169
           | `System_provided -> Action_builder.return ()
×
2170
           | `Inside_lock_dir pkg ->
×
2171
             let open Action_builder.O in
2172
             let+ _cookie = (Pkg_installed.of_paths pkg.paths).cookie in
×
2173
             ())
×
2174
    >>| Option.some
2175
;;
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