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

ocaml / dune / 29263

12 Dec 2024 10:24AM UTC coverage: 6.904%. First build
29263

Pull #11196

github

web-flow
Merge d91f2b6e4 into 046fe80a7
Pull Request #11196: Create manifest rule on setup_entries_js

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

2951 of 42743 relevant lines covered (6.9%)

26652.01 hits per line

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

0.28
/src/dune_rules/melange/melange_rules.ml
1
open Import
2
open Memo.O
3

4
let output_of_lib =
5
  let public_lib ~info ~target_dir lib_name =
6
    `Public_library
×
7
      ( Lib_info.src_dir info
×
8
      , Path.Build.L.relative target_dir [ "node_modules"; Lib_name.to_string lib_name ]
×
9
      )
10
  in
11
  fun ~target_dir lib ->
12
    let info = Lib.info lib in
×
13
    match Lib_info.status info with
×
14
    | Private (_, None) -> `Private_library_or_emit target_dir
×
15
    | Private (_, Some pkg) ->
×
16
      public_lib
17
        ~info
18
        ~target_dir
19
        (Lib_name.mangled (Package.name pkg) (Lib_name.to_local_exn (Lib.name lib)))
×
20
    | Installed | Installed_private | Public _ ->
×
21
      public_lib ~info ~target_dir (Lib_info.name info)
×
22
;;
23

24
let lib_output_path ~output_dir ~lib_dir src =
25
  match Path.drop_prefix_exn src ~prefix:lib_dir |> Path.Local.to_string with
×
26
  | "" -> output_dir
×
27
  | dir -> Path.Build.relative output_dir dir
×
28
;;
29

30
let make_js_name ~js_ext ~output m =
31
  let basename = Melange.js_basename m ^ js_ext in
×
32
  match output with
33
  | `Public_library (lib_dir, output_dir) ->
×
34
    let src_dir = Module.file m ~ml_kind:Impl |> Option.value_exn |> Path.parent_exn in
×
35
    let output_dir = lib_output_path ~output_dir ~lib_dir src_dir in
×
36
    Path.Build.relative output_dir basename
×
37
  | `Private_library_or_emit target_dir ->
×
38
    let dst_dir =
39
      Path.Build.append_source
40
        target_dir
41
        (Module.file m ~ml_kind:Impl
42
         |> Option.value_exn
×
43
         |> Path.as_in_build_dir_exn
×
44
         |> Path.Build.parent_exn
×
45
         |> Path.Build.drop_build_context_exn)
×
46
    in
47
    Path.Build.relative dst_dir basename
×
48
;;
49

50
module Manifest = struct
51
  type mapping =
52
    { source : Path.t
53
    ; targets : Path.Build.t list
54
    }
55

56
  type t = { mappings : mapping list }
57

58
  let sexp_of_mapping { source; targets } =
NEW
59
    let source_str = Path.to_string source in
×
NEW
60
    let target_strs = List.map targets ~f:Path.Build.to_string in
×
NEW
61
    Sexp.List
×
NEW
62
      [ Sexp.Atom source_str; Sexp.List (List.map target_strs ~f:(fun s -> Sexp.Atom s)) ]
×
63
  ;;
64

NEW
65
  let sexp_of_t t = Sexp.List (List.map t.mappings ~f:sexp_of_mapping)
×
NEW
66
  let to_string t = Sexp.to_string (sexp_of_t t)
×
67

68
  let create_mapping ~module_systems ~output m =
NEW
69
    let source = Module.file m ~ml_kind:Impl |> Option.value_exn in
×
NEW
70
    let targets =
×
NEW
71
      List.map module_systems ~f:(fun (_, js_ext) -> make_js_name ~js_ext ~output m)
×
72
    in
NEW
73
    { source; targets }
×
74
  ;;
75

76
  let create_manifest_rule ~sctx ~dir ~target_dir ~mode mappings =
NEW
77
    let manifest_path = Path.Build.relative target_dir "melange-manifest.sexp" in
×
NEW
78
    Format.eprintf "Creating manifest rule@.";
×
NEW
79
    Format.eprintf "  dir: %s@." (Path.Build.to_string dir);
×
NEW
80
    Format.eprintf "  target_dir: %s@." (Path.Build.to_string target_dir);
×
NEW
81
    Format.eprintf "  mappings count: %d@." (List.length mappings);
×
NEW
82
    Format.eprintf "  manifest path: %s@." (Path.Build.to_string manifest_path);
×
NEW
83
    let manifest = { mappings } in
×
84
    let manifest_str = to_string manifest in
NEW
85
    Format.eprintf "  manifest content:@.%s@." manifest_str;
×
NEW
86
    Action_builder.return manifest_str
×
NEW
87
    |> Action_builder.write_file_dyn manifest_path
×
NEW
88
    |> Super_context.add_rule sctx ~dir ~mode
×
89
  ;;
90
end
91

92
let modules_in_obj_dir ~sctx ~scope ~preprocess modules =
93
  let* version =
×
94
    let+ ocaml = Context.ocaml (Super_context.context sctx) in
×
95
    ocaml.version
×
96
  and* preprocess =
97
    Resolve.Memo.read_memo
×
98
      (Preprocess.Per_module.with_instrumentation
×
99
         preprocess
100
         ~instrumentation_backend:(Lib.DB.instrumentation_backend (Scope.libs scope)))
×
101
  in
102
  let pped_map = Staged.unstage (Pp_spec.pped_modules_map preprocess version) in
×
103
  Modules.map_user_written modules ~f:(fun m -> Memo.return @@ pped_map m)
×
104
;;
105

106
let impl_only_modules_defined_in_this_lib ~sctx ~scope lib =
107
  match Lib_info.modules (Lib.info lib) with
×
108
  | External None ->
×
109
    User_error.raise
110
      [ Pp.textf
×
111
          "The library %s was not compiled with Dune or it was compiled with Dune but \
112
           published with a META template. Such libraries are not compatible with \
113
           melange support"
114
          (Lib.name lib |> Lib_name.to_string)
×
115
      ]
116
  | External (Some modules) ->
×
117
    Memo.return
118
      ( modules
119
      , (Modules.With_vlib.split_by_lib modules).impl
×
120
        |> List.filter ~f:(Module.has ~ml_kind:Impl) )
×
121
  | Local ->
×
122
    let lib = Lib.Local.of_lib_exn lib in
123
    let info = Lib.Local.info lib in
×
124
    let+ modules =
×
125
      let* modules = Dir_contents.modules_of_local_lib sctx lib in
×
126
      let preprocess = Lib_info.preprocess info in
×
127
      modules_in_obj_dir ~sctx ~scope ~preprocess modules >>| Modules.With_vlib.modules
×
128
    in
129
    let () =
×
130
      let modes = Lib_info.modes info in
131
      match modes.melange with
×
132
      | false ->
×
133
        let lib_name = Lib_name.to_string (Lib_info.name info) in
×
134
        User_error.raise
×
135
          ~loc:(Lib_info.loc info)
×
136
          [ Pp.textf
×
137
              "The library `%s` was added as a dependency of a `melange.emit` stanza, \
138
               but this library is not compatible with Melange. To fix this, add \
139
               `melange` to the `modes` field of the library `%s`."
140
              lib_name
141
              lib_name
142
          ]
143
      | true -> ()
×
144
    in
145
    ( modules
146
    , (Modules.With_vlib.split_by_lib modules).impl
×
147
      |> List.filter ~f:(Module.has ~ml_kind:Impl) )
×
148
;;
149

150
let cmj_glob = Glob.of_string_exn Loc.none "*.cmj"
38✔
151

152
let cmj_includes ~(requires_link : Lib.t list Resolve.t) ~scope lib_config =
153
  let project = Scope.project scope in
×
154
  let deps_of_lib lib =
×
155
    let info = Lib.info lib in
×
156
    let obj_dir = Lib_info.obj_dir info in
×
157
    let dir = Obj_dir.melange_dir obj_dir in
×
158
    Dep.file_selector @@ File_selector.of_glob ~dir cmj_glob
×
159
  in
160
  Command.Args.memo
161
  @@ Resolve.args
×
162
  @@
163
  let open Resolve.O in
164
  let+ requires_link = requires_link in
165
  let deps = List.map requires_link ~f:deps_of_lib |> Dep.Set.of_list in
×
166
  Command.Args.S
×
167
    [ Lib_flags.L.melange_emission_include_flags ~project requires_link lib_config
×
168
    ; Hidden_deps deps
169
    ]
170
;;
171

172
let compile_info ~scope (mel : Melange_stanzas.Emit.t) =
173
  let dune_version = Scope.project scope |> Dune_project.dune_version in
×
174
  let+ pps =
×
175
    Resolve.Memo.read_memo
×
176
      (Preprocess.Per_module.with_instrumentation
×
177
         mel.preprocess
178
         ~instrumentation_backend:(Lib.DB.instrumentation_backend (Scope.libs scope)))
×
179
    >>| Preprocess.Per_module.pps
×
180
  in
181
  let libraries =
×
182
    match mel.emit_stdlib with
183
    | false -> mel.libraries
×
184
    | true ->
×
185
      let builtin_melange_dep = Lib_dep.Direct (mel.loc, Lib_name.of_string "melange") in
×
186
      builtin_melange_dep :: mel.libraries
187
  in
188
  Lib.DB.resolve_user_written_deps
189
    (Scope.libs scope)
×
190
    (`Melange_emit mel.target)
191
    ~allow_overlaps:mel.allow_overlapping_dependencies
192
    ~forbidden_libraries:[]
193
    libraries
194
    ~pps
195
    ~dune_version
196
;;
197

198
let js_targets_of_modules modules ~module_systems ~output =
199
  List.map module_systems ~f:(fun (_, js_ext) ->
×
200
    modules
×
201
    |> Modules.With_vlib.drop_vlib
202
    |> Modules.fold ~init:Path.Set.empty ~f:(fun m acc ->
×
203
      if Module.has m ~ml_kind:Impl
×
204
      then (
×
205
        let target = Path.build @@ make_js_name ~js_ext ~output m in
×
206
        Path.Set.add acc target)
×
207
      else acc))
×
208
  |> Path.Set.union_all
×
209
;;
210

211
let js_targets_of_libs ~sctx ~scope ~module_systems ~target_dir libs =
212
  Resolve.Memo.List.concat_map module_systems ~f:(fun (_, js_ext) ->
×
213
    let of_lib lib =
×
214
      let+ _, modules = impl_only_modules_defined_in_this_lib ~sctx ~scope lib in
×
215
      let output = output_of_lib ~target_dir lib in
×
216
      List.rev_map modules ~f:(fun m -> Path.build @@ make_js_name ~output ~js_ext m)
×
217
    in
218
    Resolve.Memo.List.concat_map libs ~f:(fun lib ->
219
      let* base = of_lib lib in
×
220
      match Lib.implements lib with
×
221
      | None -> Resolve.Memo.return base
×
222
      | Some vlib ->
×
223
        let open Resolve.Memo.O in
224
        let* vlib = vlib in
225
        let+ for_vlib = Resolve.Memo.lift_memo (of_lib vlib) in
×
226
        List.rev_append for_vlib base))
×
227
;;
228

229
let build_js
230
  ~loc
231
  ~dir
232
  ~pkg_name
233
  ~mode
234
  ~module_systems
235
  ~output
236
  ~obj_dir
237
  ~sctx
238
  ~includes
239
  ~local_modules_and_obj_dir
240
  m
241
  =
242
  let* compiler = Melange_binary.melc sctx ~loc:(Some loc) ~dir in
×
243
  Memo.parallel_iter module_systems ~f:(fun (module_system, js_ext) ->
×
244
    let build =
×
245
      let command =
246
        let src = Obj_dir.Module.cm_file_exn obj_dir m ~kind:(Melange Cmj) in
247
        let output = make_js_name ~output ~js_ext m in
×
248
        let obj_dir = [ Command.Args.A "-I"; Path (Obj_dir.melange_dir obj_dir) ] in
×
249
        let melange_package_args =
250
          let pkg_name_args =
251
            match pkg_name with
252
            | None -> []
×
253
            | Some pkg_name -> [ "--bs-package-name"; Package.Name.to_string pkg_name ]
×
254
          in
255
          let js_modules_str = Melange.Module_system.to_string module_system in
256
          "--bs-module-type" :: js_modules_str :: pkg_name_args
×
257
        in
258
        Command.run
×
259
          ~dir:(Super_context.context sctx |> Context.build_dir |> Path.build)
×
260
          compiler
261
          [ Command.Args.S obj_dir
262
          ; Command.Args.as_any includes
×
263
          ; As melange_package_args
264
          ; A "-o"
265
          ; Target output
266
          ; Dep src
267
          ]
268
      in
269
      With_targets.map_build command ~f:(fun command ->
×
270
        let open Action_builder.O in
×
271
        match local_modules_and_obj_dir with
272
        | Some (modules, obj_dir) ->
×
273
          let paths =
274
            let+ module_deps =
275
              Dep_rules.immediate_deps_of m modules ~obj_dir ~ml_kind:Impl
×
276
            in
277
            List.fold_left module_deps ~init:[] ~f:(fun acc dep_m ->
×
278
              if Module.has dep_m ~ml_kind:Impl
×
279
              then (
×
280
                let cmj_file =
281
                  let kind : Lib_mode.Cm_kind.t = Melange Cmj in
282
                  Obj_dir.Module.cm_file_exn obj_dir dep_m ~kind |> Path.build
×
283
                in
284
                cmj_file :: acc)
285
              else acc)
×
286
          in
287
          Action_builder.dyn_paths_unit paths >>> command
×
288
        | None -> command)
×
289
    in
290
    Super_context.add_rule sctx ~dir ~loc ~mode build)
291
;;
292

293
(* attach [deps] to the specified [alias] AND the (dune default) [all] alias.
294

295
   when [alias] is not supplied, {!Melange_stanzas.Emit.implicit_alias} is
296
   assumed. *)
297
let add_deps_to_aliases ?(alias = Melange_stanzas.Emit.implicit_alias) ~dir deps =
×
298
  let alias = Alias.make alias ~dir in
×
299
  let dune_default_alias = Alias.make Alias0.all ~dir in
×
300
  let attach alias = Rules.Produce.Alias.add_deps alias deps in
×
301
  Memo.parallel_iter ~f:attach [ alias; dune_default_alias ]
302
;;
303

304
let setup_emit_cmj_rules
305
  ~sctx
306
  ~dir
307
  ~scope
308
  ~expander
309
  ~dir_contents
310
  (mel : Melange_stanzas.Emit.t)
311
  =
312
  let* compile_info = compile_info ~scope mel in
×
313
  let ctx = Super_context.context sctx in
×
314
  let merlin_ident = Merlin_ident.for_melange ~target:mel.target in
×
315
  let f () =
316
    let* modules, obj_dir =
×
317
      Dir_contents.ocaml dir_contents
×
318
      >>= Ml_sources.modules_and_obj_dir
×
319
            ~libs:(Scope.libs scope)
×
320
            ~for_:(Melange { target = mel.target })
321
    in
322
    let* () = Check_rules.add_obj_dir sctx ~obj_dir Melange in
×
323
    let* modules, pp =
×
324
      let+ modules, pp =
325
        Buildable_rules.modules_rules
×
326
          sctx
327
          (Melange
328
             { preprocess = mel.preprocess
329
             ; preprocessor_deps = mel.preprocessor_deps
330
             ; lint = mel.lint
331
             ; (* why is this always false? *)
332
               empty_module_interface_if_absent = false
333
             })
334
          expander
335
          ~dir
336
          scope
337
          modules
338
      in
339
      Modules.With_vlib.modules modules, pp
×
340
    in
341
    let requires_link = Lib.Compile.requires_link compile_info in
×
342
    let* flags =
×
343
      let specific = Lib_mode.Map.make_all mel.compile_flags in
344
      Ocaml_flags.Spec.make ~common:Ordered_set_lang.Unexpanded.standard ~specific
×
345
      |> Ocaml_flags_db.ocaml_flags sctx ~dir
×
346
      >>| Ocaml_flags.allow_only_melange
×
347
    in
348
    let* cctx =
×
349
      let direct_requires = Lib.Compile.direct_requires compile_info in
350
      Compilation_context.create
×
351
        ()
352
        ~loc:mel.loc
353
        ~super_context:sctx
354
        ~scope
355
        ~obj_dir
356
        ~modules
357
        ~flags
358
        ~requires_link
359
        ~requires_compile:direct_requires
360
        ~preprocessing:pp
361
        ~js_of_ocaml:(Js_of_ocaml.Mode.Pair.make None)
×
362
        ~opaque:Inherit_from_settings
363
        ~melange_package_name:None
364
        ~package:mel.package
365
        ~modes:
366
          { ocaml = { byte = None; native = None }; melange = Some (Requested mel.loc) }
367
    in
368
    let* () = Module_compilation.build_all cctx in
×
369
    let* requires_compile = Compilation_context.requires_compile cctx in
×
370
    let* requires_hidden = Compilation_context.requires_hidden cctx in
×
371
    let stdlib_dir = (Compilation_context.ocaml cctx).lib_config.stdlib_dir in
×
372
    let+ () =
373
      let emit_and_libs_deps =
374
        let target_dir = Path.Build.relative dir mel.target in
375
        let module_systems = mel.module_systems in
×
376
        let open Action_builder.O in
377
        let+ () =
378
          js_targets_of_modules
379
            ~output:(`Private_library_or_emit target_dir)
380
            ~module_systems
381
            modules
382
          |> Action_builder.path_set
×
383
        and+ () =
384
          let* deps =
385
            Resolve.Memo.read
×
386
            @@
387
            let open Resolve.Memo.O in
388
            Compilation_context.requires_link cctx
×
389
            >>= js_targets_of_libs ~sctx ~scope ~module_systems ~target_dir
×
390
          in
391
          Action_builder.paths deps
×
392
        in
393
        ()
×
394
      in
395
      add_deps_to_aliases ?alias:mel.alias emit_and_libs_deps ~dir
×
396
    in
397
    ( cctx
×
398
    , Merlin.make
399
        ~requires_compile
400
        ~requires_hidden
401
        ~stdlib_dir
402
        ~flags
403
        ~modules
404
        ~libname:None
405
        ~preprocess:(Preprocess.Per_module.without_instrumentation mel.preprocess)
×
406
        ~obj_dir
407
        ~ident:merlin_ident
408
        ~dialects:(Dune_project.dialects (Scope.project scope))
×
409
        ~modes:`Melange_emit )
410
  in
411
  let* () = Buildable_rules.gen_select_rules sctx compile_info ~dir in
×
412
  Buildable_rules.with_lib_deps ctx merlin_ident ~dir ~f
×
413
;;
414

415
module Runtime_deps = struct
416
  let targets sctx ~dir ~output ~for_ (mel : Melange_stanzas.Emit.t) =
417
    let raise_external_dep_error src =
×
418
      let lib_info =
×
419
        match for_ with
420
        | `Library lib_info -> lib_info
×
421
        | `Emit -> assert false
422
      in
423
      let loc =
424
        match Lib_info.melange_runtime_deps lib_info with
425
        | Local (loc, _) -> loc
×
426
        | External _ -> assert false
427
      in
428
      Lib_file_deps.raise_disallowed_external_path ~loc (Lib_info.name lib_info) src
×
429
    in
430
    let+ deps =
431
      match for_ with
432
      | `Emit ->
×
433
        let* expander = Super_context.expander sctx ~dir in
×
434
        let loc, runtime_deps = mel.runtime_deps in
×
435
        Lib_file_deps.eval ~expander ~loc ~paths:Allow_all runtime_deps
436
      | `Library lib_info ->
×
437
        (match Lib_info.melange_runtime_deps lib_info with
438
         | External paths -> Memo.return (Path.Set.of_list paths)
×
439
         | Local (loc, dep_conf) ->
×
440
           let dir =
441
             let info = Lib_info.as_local_exn lib_info in
442
             Lib_info.src_dir info
×
443
           in
444
           let* expander = Super_context.expander sctx ~dir in
×
445
           Lib_file_deps.eval ~expander ~loc ~paths:Allow_all dep_conf)
×
446
    in
447
    Path.Set.fold ~init:([], []) deps ~f:(fun src (copy, non_copy) ->
×
448
      match output with
×
449
      | `Public_library (lib_dir, output_dir) ->
×
450
        (match Path.as_external src with
451
         | None -> (src, lib_output_path ~output_dir ~lib_dir src) :: copy, non_copy
×
452
         | Some src_e ->
×
453
           (match Path.as_external lib_dir with
454
            | Some lib_dir_e when Path.External.is_descendant src_e ~of_:lib_dir_e ->
×
455
              (src, lib_output_path ~output_dir ~lib_dir src) :: copy, non_copy
×
456
            | Some _ | None -> raise_external_dep_error src))
×
457
      | `Private_library_or_emit output_dir ->
×
458
        (match Path.as_in_build_dir src with
459
         | None -> copy, src :: non_copy
×
460
         | Some src_build ->
×
461
           let target = Path.Build.drop_build_context_exn src_build in
462
           (src, Path.Build.append_source output_dir target) :: copy, non_copy))
×
463
  ;;
464
end
465

466
let setup_runtime_assets_rules sctx ~dir ~target_dir ~mode ~output ~for_ mel =
467
  let* copy, non_copy = Runtime_deps.targets sctx ~dir ~output ~for_ mel in
×
468
  let deps =
×
469
    Action_builder.paths
470
      (non_copy @ List.rev_map copy ~f:(fun (_, target) -> Path.build target))
×
471
  in
472
  let+ () =
×
473
    let loc = mel.loc in
474
    Memo.parallel_iter copy ~f:(fun (src, dst) ->
×
475
      Super_context.add_rule ~loc ~dir ~mode sctx (Action_builder.copy ~src ~dst))
×
476
  and+ () = add_deps_to_aliases ?alias:mel.alias deps ~dir:target_dir in
×
477
  ()
×
478
;;
479

480
let modules_for_js_and_obj_dir ~sctx ~dir_contents ~scope (mel : Melange_stanzas.Emit.t) =
481
  let* modules, obj_dir =
×
482
    Dir_contents.ocaml dir_contents
×
483
    >>= Ml_sources.modules_and_obj_dir
×
484
          ~libs:(Scope.libs scope)
×
485
          ~for_:(Melange { target = mel.target })
486
  in
487
  let+ modules = modules_in_obj_dir ~sctx ~scope ~preprocess:mel.preprocess modules in
×
488
  let modules_for_js =
×
489
    Modules.fold modules ~init:[] ~f:(fun x acc ->
490
      if Module.has x ~ml_kind:Impl then x :: acc else acc)
×
491
  in
492
  modules, modules_for_js, obj_dir
×
493
;;
494

495
let setup_entries_js
496
  ~sctx
497
  ~dir
498
  ~dir_contents
499
  ~scope
500
  ~compile_info
501
  ~target_dir
502
  ~mode
503
  (mel : Melange_stanzas.Emit.t)
504
  =
505
  let* local_modules, modules_for_js, local_obj_dir =
×
506
    modules_for_js_and_obj_dir ~sctx ~dir_contents ~scope mel
×
507
  in
508
  let requires_link = Lib.Compile.requires_link compile_info in
×
509
  let pkg_name = Option.map mel.package ~f:Package.name in
×
510
  let loc = mel.loc in
×
511
  let module_systems = mel.module_systems in
512
  let* requires_link = Memo.Lazy.force requires_link in
×
513
  let* includes =
×
514
    let+ lib_config =
515
      let+ ocaml = Super_context.context sctx |> Context.ocaml in
×
516
      ocaml.lib_config
×
517
    in
518
    cmj_includes ~requires_link ~scope lib_config
×
519
  in
520
  let output = `Private_library_or_emit target_dir in
×
521
  let mappings =
522
    List.map modules_for_js ~f:(fun m ->
NEW
523
      Manifest.create_mapping ~module_systems ~output m)
×
524
  in
NEW
525
  let* () = Manifest.create_manifest_rule ~sctx ~dir ~target_dir ~mode mappings in
×
526
  let obj_dir = Obj_dir.of_local local_obj_dir in
×
527
  let* () =
×
528
    setup_runtime_assets_rules sctx ~dir ~target_dir ~mode ~output ~for_:`Emit mel
×
529
  in
530
  let local_modules_and_obj_dir =
×
531
    Some (Modules.With_vlib.modules local_modules, local_obj_dir)
×
532
  in
533
  Memo.parallel_iter modules_for_js ~f:(fun m ->
534
    build_js
×
535
      ~dir
536
      ~loc
537
      ~pkg_name
538
      ~mode
539
      ~module_systems
540
      ~output
541
      ~obj_dir
542
      ~sctx
543
      ~includes
544
      ~local_modules_and_obj_dir
545
      m)
546
;;
547

548
let setup_js_rules_libraries =
549
  let local_modules_and_obj_dir ~lib modules =
550
    Lib.Local.of_lib lib
×
551
    |> Option.map ~f:(fun lib ->
×
552
      let obj_dir = Lib.Local.obj_dir lib in
×
553
      modules, obj_dir)
×
554
  in
555
  let parallel_build_source_modules ~sctx ~scope ~f:build_js lib =
556
    let* local_modules_and_obj_dir, source_modules =
×
557
      let+ lib_modules, source_modules =
558
        impl_only_modules_defined_in_this_lib ~sctx ~scope lib
×
559
      in
560
      local_modules_and_obj_dir ~lib lib_modules, source_modules
×
561
    in
562
    Memo.parallel_iter source_modules ~f:(build_js ~local_modules_and_obj_dir)
×
563
  in
564
  fun ~dir ~scope ~target_dir ~sctx ~requires_link ~mode (mel : Melange_stanzas.Emit.t) ->
565
    let build_js = build_js ~sctx ~mode ~module_systems:mel.module_systems in
×
566
    let with_vlib_implementations =
567
      let vlib_implementations =
568
        (* vlib_name => concrete_impl *)
569
        List.fold_left requires_link ~init:Lib_name.Map.empty ~f:(fun acc dep ->
570
          match Lib_info.implements (Lib.info dep) with
×
571
          | None -> acc
×
572
          | Some (_, vlib_name) -> Lib_name.Map.add_exn acc vlib_name dep)
×
573
      in
574
      fun lib deps ->
×
575
        (* Depend on the concrete implementations of virtual libraries so
576
           that Melange can find their `.cmj` files. *)
577
        List.fold_left deps ~init:deps ~f:(fun acc dep ->
×
578
          match Lib_name.Map.find vlib_implementations (Lib.name dep) with
×
579
          | None -> acc
×
580
          | Some sub -> if Lib.equal sub lib then acc else sub :: acc)
×
581
    in
582
    let* lib_config =
583
      let+ ocaml = Super_context.context sctx |> Context.ocaml in
×
584
      ocaml.lib_config
×
585
    in
586
    Memo.parallel_iter requires_link ~f:(fun lib ->
×
587
      let lib_compile_info =
×
588
        Lib.Compile.for_lib
589
          ~allow_overlaps:mel.allow_overlapping_dependencies
590
          (Scope.libs scope)
×
591
          lib
592
      in
593
      let info = Lib.info lib in
×
594
      let loc = Lib_info.loc info in
×
595
      let build_js =
×
596
        let obj_dir = Lib_info.obj_dir info in
597
        let pkg_name = Lib_info.package info in
×
598
        build_js ~loc ~pkg_name ~obj_dir
×
599
      in
600
      let output = output_of_lib ~target_dir lib in
601
      let* includes =
×
602
        let+ requires_link =
603
          Memo.Lazy.force (Lib.Compile.requires_link lib_compile_info)
×
604
          |> Resolve.Memo.map ~f:(with_vlib_implementations lib)
×
605
        in
606
        cmj_includes ~requires_link ~scope lib_config
×
607
      in
608
      let+ () =
×
609
        setup_runtime_assets_rules
×
610
          sctx
611
          ~dir
612
          ~target_dir
613
          ~mode
614
          ~output
615
          ~for_:(`Library info)
616
          mel
617
      and+ () =
618
        match Lib.implements lib with
619
        | None -> Memo.return ()
×
620
        | Some vlib ->
×
621
          let* vlib = Resolve.Memo.read_memo vlib in
×
622
          let* includes =
×
623
            let+ requires_link =
624
              let+ requires_link =
625
                Lib.Compile.for_lib
626
                  ~allow_overlaps:mel.allow_overlapping_dependencies
627
                  (Scope.libs scope)
×
628
                  vlib
629
                |> Lib.Compile.requires_link
×
630
                |> Memo.Lazy.force
×
631
              in
632
              let open Resolve.O in
×
633
              let+ requires_link = requires_link in
634
              (* Whenever a `concrete_lib` implementation contains a field
635
                 `(implements virt_lib)`, we also set up the JS targets for the
636
                 modules defined in `virt_lib`.
637

638
                 In the cases where `virt_lib` (concrete) modules depend on any
639
                 virtual modules (i.e. programming against the interface), we
640
                 need to make sure that the JS rules that dune emits for
641
                 `virt_lib` depend on `concrete_lib`, such that Melange can find
642
                 the correct `.cmj` file, which is needed to emit the correct
643
                 path in `import` / `require`. *)
644
              lib :: requires_link
×
645
            in
646
            cmj_includes ~requires_link ~scope lib_config
×
647
          in
648
          parallel_build_source_modules
×
649
            ~sctx
650
            ~scope
651
            vlib
652
            ~f:(build_js ~dir ~output ~includes)
653
      and+ () =
654
        parallel_build_source_modules
×
655
          ~sctx
656
          ~scope
657
          lib
658
          ~f:(build_js ~dir ~output ~includes)
659
      in
660
      ())
×
661
;;
662

663
let setup_js_rules_libraries_and_entries
664
  ~dir_contents
665
  ~dir
666
  ~scope
667
  ~sctx
668
  ~compile_info
669
  ~requires_link
670
  ~mode
671
  ~target_dir
672
  mel
673
  =
674
  let+ () =
×
675
    setup_js_rules_libraries ~dir ~scope ~target_dir ~sctx ~requires_link ~mode mel
×
676
  and+ () =
677
    setup_entries_js ~sctx ~dir ~dir_contents ~scope ~compile_info ~target_dir ~mode mel
×
678
  in
679
  ()
×
680
;;
681

682
let setup_emit_js_rules ~dir_contents ~dir ~scope ~sctx mel =
683
  let target_dir =
×
684
    Melange_stanzas.Emit.target_dir ~dir:(Dir_contents.dir dir_contents) mel
×
685
  in
686
  let mode =
×
687
    match mel.promote with
688
    | None -> Rule.Mode.Standard
×
689
    | Some p -> Promote p
×
690
  in
691
  let* compile_info = compile_info ~scope mel in
×
692
  let* requires_link_resolve =
×
693
    Lib.Compile.requires_link compile_info |> Memo.Lazy.force
×
694
  in
695
  match Resolve.to_result requires_link_resolve with
×
696
  | Ok requires_link ->
×
697
    setup_js_rules_libraries_and_entries
698
      ~dir_contents
699
      ~dir
700
      ~scope
701
      ~sctx
702
      ~compile_info
703
      ~requires_link
704
      ~mode
705
      ~target_dir
706
      mel
707
  | Error resolve_error ->
×
708
    (* NOTE: in multi-package projects where [melange.emit] stanzas are
709
       present, we can't eagerly resolve the link-time closure for
710
       [melange.emit] stanzas since their targets aren't public (i.e. part of a
711
       package). When resolution fails, we replace the JS entries with the
712
       resolution error inside [Action_builder.fail] to give Dune a chance to
713
       fail if any of the targets end up attached to a package installation. *)
714
    let* _, modules_for_js, _obj_dir =
715
      modules_for_js_and_obj_dir ~sctx ~dir_contents ~scope mel
×
716
    in
717
    let module_systems = mel.module_systems in
×
718
    let output = `Private_library_or_emit target_dir in
719
    let loc = mel.loc in
720
    Memo.parallel_iter modules_for_js ~f:(fun m ->
721
      Memo.parallel_iter module_systems ~f:(fun (_module_system, js_ext) ->
×
722
        let file_targets = [ make_js_name ~output ~js_ext m ] in
×
723
        Super_context.add_rule
724
          sctx
725
          ~dir
726
          ~loc
727
          ~mode
728
          (Action_builder.fail
729
             { fail = (fun () -> Resolve.raise_error_with_stack_trace resolve_error) }
×
730
           |> Action_builder.with_file_targets ~file_targets)))
×
731
;;
732

733
(* The emit stanza of melange outputs in a single output directory (and its
734
   descendants). We attach all .js generating rules to this root directory.
735

736
   Since we allow user defined rules in this output directory, we need to know
737
   when we're under the emit directory so that we load both the user defined
738
   rules and the rules originating from the emit stanza. *)
739
type t =
740
  { (* the directory in which the emit stanza is defined. *)
741
    stanza_dir : Path.Build.t
742
  ; (* the emit stanza itself. *)
743
    stanza : Melange_stanzas.Emit.t
744
  }
745

746
let emit_rules sctx { stanza_dir; stanza } =
747
  Rules.collect_unit (fun () ->
×
748
    let* sctx = sctx in
×
749
    let* dir_contents = Dir_contents.get sctx ~dir:stanza_dir in
×
750
    let* scope = Scope.DB.find_by_dir stanza_dir in
×
751
    setup_emit_js_rules ~dir_contents ~dir:stanza_dir ~scope ~sctx stanza)
×
752
;;
753

754
(* Detect if [dir] is under the target directory of a melange.emit stanza. *)
755
let rec under_melange_emit_target ~sctx ~dir =
756
  match Path.Build.parent dir with
×
757
  | None -> Memo.return None
×
758
  | Some parent ->
×
759
    Dune_load.stanzas_in_dir parent
×
760
    >>= (function
761
     | None -> under_melange_emit_target ~sctx ~dir:parent
×
762
     | Some stanzas ->
×
763
       Dune_file.find_stanzas stanzas Melange_stanzas.Emit.key
×
764
       >>= Memo.List.find_map ~f:(fun (mel : Melange_stanzas.Emit.t) ->
×
765
         let target_dir = Melange_stanzas.Emit.target_dir ~dir:parent mel in
×
766
         match Path.Build.equal target_dir dir with
×
767
         | false -> Memo.return None
×
768
         | true ->
×
769
           (* In the case where we have two melange.emit stanzas in the same folder,
770
              with one enabled in the current context and one disabled, we want to
771
              make sure that we pick the enabled one *)
772
           let+ enabled =
773
             let* expander =
774
               let* sctx = sctx in
775
               Super_context.expander sctx ~dir
×
776
             in
777
             Expander.eval_blang expander mel.enabled_if
×
778
           in
779
           Option.some_if enabled mel)
×
780
       >>= (function
781
        | None -> under_melange_emit_target ~sctx ~dir:parent
×
782
        | Some stanza -> Memo.return @@ Some { stanza_dir = parent; stanza }))
×
783
;;
784

785
let gen_emit_rules sctx ~dir ({ stanza_dir; stanza } as for_melange) =
786
  match Path.Build.equal dir (Melange_stanzas.Emit.target_dir ~dir:stanza_dir stanza) with
×
787
  | false -> Memo.return None
×
788
  | true ->
×
789
    under_melange_emit_target ~sctx ~dir:stanza_dir
790
    >>| (function
791
     | None -> Some (emit_rules sctx for_melange)
×
792
     | Some { stanza_dir = _; stanza = parent_stanza } ->
×
793
       let main_message = Pp.text "melange.emit stanzas cannot be nested" in
794
       let annots =
×
795
         let main = User_message.make ~loc:stanza.loc [ main_message ] in
796
         let related =
×
797
           [ User_message.make
×
798
               ~loc:parent_stanza.loc
799
               [ Pp.text "under this melange stanza" ]
×
800
           ]
801
         in
802
         User_message.Annots.singleton
×
803
           Compound_user_error.annot
804
           [ Compound_user_error.make ~main ~related ]
805
       in
806
       User_error.raise
807
         ~loc:stanza.loc
808
         ~annots
809
         [ main_message
810
         ; Pp.enumerate ~f:Loc.pp_file_colon_line [ parent_stanza.loc; stanza.loc ]
×
811
         ]
812
         ~hints:[ Pp.text "Move both `melange.emit' stanzas to the same level." ])
×
813
;;
814

815
module Gen_rules = Build_config.Gen_rules
816

817
let setup_emit_js_rules sctx ~dir =
818
  under_melange_emit_target ~sctx ~dir
×
819
  >>= function
820
  | Some melange ->
×
821
    gen_emit_rules sctx ~dir melange
×
822
    >>| (function
823
     | None -> Gen_rules.redirect_to_parent Gen_rules.Rules.empty
×
824
     | Some melange -> Gen_rules.make melange)
×
825
  | None ->
×
826
    (* this should probably be handled by [Dir_status] *)
827
    Dune_load.stanzas_in_dir dir
×
828
    >>= (function
829
     | None -> Memo.return Gen_rules.no_rules
×
830
     | Some dune_file ->
×
831
       let+ build_dir_only_sub_dirs =
832
         Dune_file.find_stanzas dune_file Melange_stanzas.Emit.key
×
833
         >>| List.map ~f:(fun (mel : Melange_stanzas.Emit.t) -> mel.target)
×
834
         >>| Subdir_set.of_list
×
835
         >>| Gen_rules.Build_only_sub_dirs.singleton ~dir
×
836
       in
837
       Gen_rules.make ~build_dir_only_sub_dirs (Memo.return Rules.empty))
×
838
;;
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

© 2025 Coveralls, Inc