1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
open B0_std
open B0_std.Fut.Syntax
open B00
open B00_ocaml
let () = B0_def.Scope.lib "jsoo"
let tag = B0_meta.Key.tag "jsoo" ~doc:"js_of_ocaml related entity"
let comp =
let doc = "Options added to the jsoo compile command" in
let pp_value = Cmd.pp in
B0_meta.Key.v "jsoo-comp" ~doc ~pp_value
let link =
let doc = "Options added to the jsoo link command" in
let pp_value = Cmd.pp in
B0_meta.Key.v "jsoo-link" ~doc ~pp_value
let source_map =
let pp_kind ppf = function
| `Inline -> Fmt.string ppf "inline" | `File -> Fmt.string ppf "file"
in
let pp_value = Fmt.option ~none:(Fmt.any "none") pp_kind in
B0_meta.Key.v "jsoo-src-map" ~doc:"Source map desires" ~pp_value
let toplevel =
let pp_value = Fmt.bool in
B0_meta.Key.v "jsoo-toplevel" ~doc:"Compile with toplevel support" ~pp_value
type comp_mode = [ `Separate | `Whole ]
let comp_mode =
let pp_value ppf = function
| `Separate -> Fmt.string ppf "separate" | `Whole -> Fmt.string ppf "whole"
in
B0_meta.Key.v "jsoo-comp-mode" ~doc:"Compilation mode" ~pp_value
let assets_root =
let doc = "Root path from which assets are rerooted." in
let pp_value = Fpath.pp_unquoted in
B0_meta.Key.v "jsoo-assets-root" ~doc ~pp_value
let meta
?(meta = B0_meta.empty) ?assets_root:ar ?comp:c
?comp_mode:(cm = `Whole) ?link:l ?(requires = [])
?source_map:(s = Some `File) ?toplevel:(t = false) ()
=
meta
|> B0_meta.add_if_some assets_root ar
|> B0_meta.add_if_some comp c
|> B0_meta.add comp_mode cm
|> B0_meta.add_if_some link l
|> B0_meta.add B0_ocaml.Meta.requires requires
|> B0_meta.add source_map s
|> B0_meta.add toplevel t
let get_mod_srcs b ~srcs =
let build_dir = B0_build.current_build_dir b in
let src_root = B0_build.current_scope_dir b in
Mod.Src.map_of_files (B0_build.memo b) ~build_dir ~src_root ~srcs
let get_link_objs ~code ~resolver ~requires ~mod_srcs =
let mod_srcs = Mod.Src.sort ~deps:Mod.Src.ml_deps mod_srcs in
let mod_objs = List.filter_map (Mod.Src.impl_file ~code) mod_srcs in
let* link_requires = Lib.Resolver.get_list_and_deps resolver requires in
let lib_objs = List.filter_map Lib.cma link_requires in
let lib_jss = List.concat_map Lib.js_stubs link_requires in
Fut.return (lib_objs, mod_objs, lib_jss)
let compile_byte m ~opts ~resolver ~requires ~mod_srcs =
let code = `Byte in
let comp = Tool.ocamlc in
let* requires = Lib.Resolver.get_list resolver requires in
Compile.intfs ~and_cmti:true m ~comp ~opts ~requires ~mod_srcs;
Compile.impls ~and_cmt:true m ~code ~opts ~requires ~mod_srcs;
Fut.return ()
let link_byte m ~conf ~opts ~resolver ~requires ~mod_srcs ~o =
let code = `Byte in
let* lib_objs, mod_objs, lib_jss =
get_link_objs ~code ~resolver ~requires ~mod_srcs
in
let cobjs = lib_objs @ mod_objs in
Link.code m ~conf ~code ~opts ~c_objs:[] ~cobjs ~o;
Fut.return lib_jss
let byte_exe ~mod_srcs ~o b =
let meta = B0_build.current_meta b in
let requires = B0_meta.get B0_ocaml.Meta.requires meta in
let* conf = B0_build.get b B0_ocaml.conf in
let o = Fpath.(o + B00_ocaml.Conf.exe_ext conf) in
let* resolver = B0_build.get b B0_ocaml.lib_resolver in
let toplevel = Option.value ~default:false (B0_meta.find toplevel meta) in
let global_opts = Cmd.(atom "-g") in
let opts = global_opts in
let m = B0_build.memo b in
let* () = compile_byte m ~opts ~resolver ~requires ~mod_srcs in
let opts = Cmd.(global_opts %% if' toplevel (atom "-linkall")) in
let* lib_jss = link_byte m ~conf ~opts ~resolver ~requires ~mod_srcs ~o in
Fut.return (o, lib_jss)
let js_of_byte_exe ~jss ~mod_srcs ~o b =
let* byte, lib_jss = byte_exe ~mod_srcs ~o:Fpath.(o -+ ".byte") b in
let meta = B0_build.current_meta b in
let source_map = Option.join (B0_meta.find source_map meta) in
let opts = Option.value ~default:Cmd.empty (B0_meta.find comp meta) in
let toplevel = Option.value ~default:false (B0_meta.find toplevel meta) in
let opts = if toplevel then Cmd.(opts % "--toplevel") else opts in
let jss = List.append lib_jss jss in
B00_jsoo.compile (B0_build.memo b) ~opts ~source_map ~jss ~byte ~o;
Fut.return ()
let js_of_byte_objs ~jss ~mod_srcs ~o b =
let meta = B0_build.current_meta b in
let requires = B0_meta.get B0_ocaml.Meta.requires meta in
let* conf = B0_build.get b B0_ocaml.conf in
let* resolver = B0_build.get b B0_ocaml.lib_resolver in
let m = B0_build.memo b in
let global_opts = Cmd.(atom "-g") in
let opts = global_opts in
let* () = compile_byte m ~opts ~resolver ~requires ~mod_srcs in
let code = `Byte in
let* lib_objs, mod_objs, lib_jss =
get_link_objs ~code ~resolver ~requires ~mod_srcs
in
let jss = List.append lib_jss jss in
let source_map = Option.join (B0_meta.find source_map meta) in
let toplevel = Option.value ~default:false (B0_meta.find toplevel meta) in
let ocamlrt_js =
let opts = Cmd.empty in
let build_dir = B0_build.current_build_dir b in
let o = Fpath.(build_dir / "ocamlrt.js") in
B00_jsoo.build_runtime m ~opts ~jss ~o;
o
in
let opts = Option.value ~default:Cmd.empty (B0_meta.find comp meta) in
let opts = if toplevel then Cmd.(opts % "--toplevel") else opts in
let lib_jss, std_exit_js =
let build_dir = B0_build.current_build_dir b in
let compile obj =
let o = Fpath.(build_dir / (Fpath.basename obj ^ ".js")) in
let opts = Cmd.(opts %% atom "-I" %% path (Fpath.parent obj)) in
B00_jsoo.compile m ~opts ~source_map ~jss:[] ~byte:obj ~o;
o
in
let compile_lib acc obj = compile obj :: acc in
let jss = List.rev (List.fold_left compile_lib [] lib_objs) in
let stdlib_cma = Fpath.(Conf.where conf / "stdlib.cma") in
let stdlib_js = Memo.file_ready m stdlib_cma; compile stdlib_cma in
let std_exit_cmo = Fpath.(Conf.where conf / "std_exit.cmo") in
let std_exit_js = Memo.file_ready m std_exit_cmo; compile std_exit_cmo in
stdlib_js :: jss, std_exit_js
in
let mod_obj_jss =
let compile_obj acc obj =
let o = Fpath.(obj + ".js") in
B00_jsoo.compile m ~opts ~source_map ~jss:[] ~byte:obj ~o;
o :: acc
in
List.rev (List.fold_left compile_obj [] mod_objs)
in
let jss = ocamlrt_js :: (lib_jss @ mod_obj_jss @ [std_exit_js]) in
let opts = Option.value ~default:Cmd.empty (B0_meta.find link meta) in
B00_jsoo.link m ~opts ~source_map ~jss ~o;
Fut.return ()
let js_exe ~jss ~mod_srcs ~o b =
let comp_mode = B0_meta.find comp_mode (B0_build.current_meta b) in
match Option.value ~default:`Whole comp_mode with
| `Whole -> js_of_byte_exe ~jss ~mod_srcs ~o b
| `Separate -> js_of_byte_objs ~jss ~mod_srcs ~o b
let build_setup ~srcs b =
let* srcs = B0_srcs.(Fut.map by_ext @@ select b srcs) in
let* mod_srcs = get_mod_srcs b ~srcs in
let jss = B00_fexts.find_files B00_fexts.js srcs in
let build_dir = B0_build.current_build_dir b in
let exe_name = B0_meta.get B0_meta.exe_name (B0_build.current_meta b) in
let js = Fpath.(build_dir / exe_name) in
let html = Fpath.(js -+ ".html") in
let exe_html = Fpath.basename html in
let htmls = B00_fexts.find_files B00_fexts.html srcs in
let html = match htmls with
| [] -> html
| htmls ->
let base b f = Fpath.basename f = b in
let file = match List.find_opt (base exe_html) htmls with
| Some f -> f
| None ->
match List.find_opt (base "index.html") htmls with
| Some f -> f
| None -> List.hd htmls
in
match B0_meta.find assets_root (B0_build.current_meta b) with
| Some r when Fpath.is_prefix r file ->
Fpath.reroot ~root:r ~dst:build_dir file
| _ -> Fpath.(build_dir / Fpath.basename file)
in
Fut.return (srcs, mod_srcs, jss, js, html)
let exe_proc set_exe_path set_mod_srcs srcs b =
let* _srcs, mod_srcs, jss, js, _html = build_setup ~srcs b in
set_mod_srcs mod_srcs;
set_exe_path js;
js_exe ~mod_srcs ~jss ~o:js b
let copy_assets m srcs ~exts ~assets_root ~dst =
let assets = B00_fexts.find_files exts srcs in
let copy acc src =
let dst = match assets_root with
| Some r when Fpath.is_prefix r src -> Fpath.reroot ~root:r ~dst src
| _ -> Fpath.(dst / Fpath.basename src)
in
Memo.copy m ~src dst;
Fpath.Set.add dst acc
in
List.iter (Memo.file_ready m) assets;
List.fold_left copy Fpath.Set.empty assets
let web_exe ~srcs ~js ~o b =
let m = B0_build.memo b in
let build_dir = B0_build.current_build_dir b in
let assets_root =
match B0_meta.find assets_root (B0_build.current_meta b) with
| None -> None
| Some r -> Some (Fpath.(B0_build.current_scope_dir b // r))
in
let exts = String.Set.remove ".js" B00_fexts.www in
let assets = copy_assets m srcs ~exts ~assets_root ~dst:build_dir in
if Fpath.Set.mem o assets then Fut.return () else
let css = Fpath.Set.filter (Fpath.has_ext ".css") assets in
let base f = Fpath.to_string (Option.get (Fpath.strip_prefix build_dir f)) in
let styles = List.map base (Fpath.Set.elements css) in
B00_jsoo.write_page m ~styles ~scripts:[Fpath.basename js] ~o;
Fut.return ()
let web_proc set_exe_path set_mod_srcs srcs b =
let* srcs, mod_srcs, jss, js, html = build_setup ~srcs b in
let js = Fpath.(js + ".js") in
set_mod_srcs mod_srcs;
set_exe_path html;
let* () = js_exe ~mod_srcs ~jss ~o:js b in
web_exe ~srcs ~js ~o:html b
let node_action build u ~args =
let err e = Log.err (fun m -> m "%s" e); Fut.return B00_cli.Exit.some_error in
match B0_unit.get_meta B0_meta.exe_file u with
| Error e -> err e
| Ok exe_file ->
let* exe_file = exe_file in
let node = Fpath.v "node" in
match Os.Cmd.get_tool node with
| Error e -> err e
| Ok node_exe ->
let cmd = Cmd.(path node %% path exe_file %% list args) in
B0_unit.Action.exec_file build u node_exe cmd
let show_uri_action build u ~args =
let err e = Log.err (fun m -> m "%s" e); Fut.return B00_cli.Exit.some_error in
match B0_unit.get_meta B0_meta.exe_file u with
| Error e -> err e
| Ok exe_file ->
let* exe_file = exe_file in
let show_uri = Fpath.v "show-uri" in
match Os.Cmd.get_tool show_uri with
| Error e -> err e
| Ok show_uri_exe ->
let cmd = Cmd.(path show_uri %% path exe_file %% list args) in
B0_unit.Action.exec_file build u show_uri_exe cmd
let unit_meta ~meta ~name ~mod_srcs ~exe_name ~exe_path =
meta
|> B0_meta.tag tag
|> B0_meta.tag B0_ocaml.tag
|> B0_meta.tag B0_meta.exe
|> B0_meta.add B0_meta.exe_name exe_name
|> B0_meta.add B0_ocaml.Meta.mod_srcs mod_srcs
|> B0_meta.add B0_meta.exe_file exe_path
|> B0_meta.add B0_ocaml.Meta.supported_code `Byte
|> B0_meta.add B0_ocaml.Meta.needs_code `Byte
let exe
?(wrap = fun proc b -> proc b) ?doc ?(meta = B0_meta.empty)
?(action = show_uri_action) ?name exe_name ~srcs
=
let name = match name with
| None -> String.map (function '.' -> '-' | c -> c) exe_name
| Some name -> name
in
let mod_srcs, set_mod_srcs = Fut.create () in
let exe_path, set_exe_path = Fut.create () in
let meta = unit_meta ~meta ~name ~mod_srcs ~exe_name ~exe_path in
let proc = wrap (exe_proc set_exe_path set_mod_srcs srcs) in
B0_unit.v ?doc ~action ~meta name proc
let web
?(wrap = fun proc b -> proc b) ?doc ?(meta = B0_meta.empty)
?(action = show_uri_action) ?name page ~srcs
=
let name = Option.value ~default:page name in
let mod_srcs, set_mod_srcs = Fut.create () in
let exe_path, set_exe_path = Fut.create () in
let exe_name = page in
let meta = unit_meta ~meta ~name ~mod_srcs ~exe_name ~exe_path in
let proc = wrap (web_proc set_exe_path set_mod_srcs srcs) in
B0_unit.v ?doc ~action ~meta name proc
let () = B0_def.Scope.close ()