Skip to content

Commit 4f5effc

Browse files
committed
Let a build system start a request before its dependencies publish
Why: A dependent only needs a module's .cmi to type check, but a build system that runs requests in process can only start it after that module has also produced its lambda, JavaScript, .cmj and .cmt. A request now takes hooks from the build system. interface_computed receives the contents of the .cmi an implementation is about to write, once no warning reported as an error is pending; the contents are computed once (Cmi_format.cmi_contents) and also written. redirect_cmi hands Bs_cmi_load the contents of a .cmi that is not published yet, which it reads instead of the build file store; the store never sees them. before_effect is called before the request writes a .cmi, .cmt, .cmti, binary AST or a PPX's input, runs a command such as a PPX, or takes a command line action that writes a file (-e, -format); a build system that started the request early waits there, or raises to abandon it before any effect. Build_file_store.key is exported so that a build system matches paths as the store does. bsc attaches no hooks and behaves as before. Review: Can a redirected interface reach a later request through the store or the decoded interface cache? Only as the same bytes. The dependency writes the very string it announced through the store, and publishing copies it, so a domain's cache may reuse an interface it decoded from the redirect for the published file. A request that is abandoned or fails drops its domain's cache. Signed-off-by: Jono Prest <jjprest@gmail.com>
1 parent 227b697 commit 4f5effc

16 files changed

Lines changed: 435 additions & 26 deletions

‎compiler/bsc/rescript_compiler_driver.ml‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,10 @@ let anonymous ~(rev_args : string list) =
167167
process_file filename (ppf ()))
168168
else Bsc_args.bad_arg "can not handle multiple files"
169169

170+
(* Immediate actions of the command line write files: a request started
171+
early waits for the build system first. *)
170172
let format_file input =
173+
Compiler_request_state.before_effect ();
171174
let ext =
172175
Ext_file_extensions.classify_input (Ext_filename.get_extension_maybe input)
173176
in
@@ -192,6 +195,7 @@ let set_color_option option =
192195
directory under the process's working directory, while a request resolves
193196
relative paths against its own. *)
194197
let eval (s : string) ~suffix =
198+
Compiler_request_state.before_effect ();
195199
let tmpfile = Filename.temp_file "eval" suffix in
196200
let tmpfile =
197201
if Filename.is_relative tmpfile then Filename.concat (Sys.getcwd ()) tmpfile
@@ -584,16 +588,16 @@ let leave_cached_interfaces_clean action =
584588
Bs_cmi_load.discard_cached_cmis ();
585589
raise exn
586590

587-
let with_fresh_request_states ~cwd action =
591+
let with_fresh_request_states ?hooks ~cwd action =
588592
Predef.with_fresh (fun () ->
589593
Env.with_fresh_initial (fun () ->
590594
Ident.with_fresh (fun () ->
591595
Request_local.with_fresh (fun () ->
592-
Compiler_request_state.with_fresh ~cwd (fun () ->
596+
Compiler_request_state.with_fresh ~cwd ?hooks (fun () ->
593597
leave_cached_interfaces_clean action)))))
594598

595-
let run_argv ?run_external ~capture ~cwd argv =
596-
with_fresh_request_states ~cwd (fun () ->
599+
let run_argv ?run_external ?hooks ~capture ~cwd argv =
600+
with_fresh_request_states ?hooks ~cwd (fun () ->
597601
start_request ();
598602
Cmt_format.set_args argv;
599603
let execute () =
@@ -651,8 +655,12 @@ let run_argv ?run_external ~capture ~cwd argv =
651655
in
652656
{exit_code; stdout = ""; stderr = ""})
653657

654-
let run_request ~run_external ~cwd ~argv ~input =
658+
let run_request_with_hooks ~hooks ~run_external ~cwd ~argv ~input =
655659
let logical_argv = Array.of_list ("bsc" :: (argv @ [input])) in
656-
run_argv ?run_external ~capture:true ~cwd logical_argv
660+
run_argv ?run_external ~hooks ~capture:true ~cwd logical_argv
661+
662+
let run_request ~run_external ~cwd ~argv ~input =
663+
run_request_with_hooks ~hooks:Compiler_request_state.no_hooks ~run_external
664+
~cwd ~argv ~input
657665

658666
let run argv = (run_argv ~capture:false ~cwd:(Sys.getcwd ()) argv).exit_code

‎compiler/bsc/rescript_compiler_driver.mli‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ val run_request :
2424
I/O against [cwd] without changing the process working directory. Request
2525
state is restored on success and failure. *)
2626

27+
val run_request_with_hooks :
28+
hooks:Compiler_request_state.hooks ->
29+
run_external:(string -> int * string * string) option ->
30+
cwd:string ->
31+
argv:string list ->
32+
input:string ->
33+
result
34+
(** {!run_request} with build-system callbacks attached to the request; see
35+
{!Compiler_request_state.hooks}. *)
36+
2737
val run : string array -> int
2838
(** Command-line entry point of the standalone [bsc]. It writes to the process's
2939
stdout and stderr as it goes. *)

‎compiler/core/bs_cmi_load.ml‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,14 @@ let load_cmi ~unit_name : Env.Persistent_signature.t option =
9393
Misc.find_map_in_path_uncap (Config.get_load_path ()) (unit_name ^ ".cmi")
9494
(fun filename ->
9595
let path = Compiler_request_state.resolve_path filename in
96-
match Build_file_store.read path with
96+
(* A build system that starts a request before a dependency has
97+
published its interface hands over that interface's contents. *)
98+
let contents =
99+
match Compiler_request_state.redirect_cmi path with
100+
| Some _ as contents -> contents
101+
| None -> Build_file_store.read path
102+
in
103+
match contents with
97104
| None -> None
98105
| Some contents ->
99106
let cmi =

‎compiler/core/cmd_ppx_apply.ml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ let read_ast (type a) (kind : a Ml_binary.kind) fn : Ml_binary.ast0 =
7878
are stored as [-ppx3; -ppx2; -ppx1]
7979
[fold_right] happens to process the first one *)
8080
let rewrite kind ppxs ast =
81+
(* A request started early waits before it writes the AST for the PPXs. *)
82+
Compiler_request_state.before_effect ();
8183
(* Remove the temporary files also when a PPX fails or the build is
8284
interrupted, since the compiler can run inside a long-lived process. *)
8385
let temp_files = ref [] in

‎compiler/depends/binary_ast.ml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ let read_ast_exn (type t) ~fname (_ : t kind) : t =
7171
*)
7272
let write_ast (type t) ~(sourcefile : string) ~output (kind : t kind) (pt : t) :
7373
unit =
74+
Compiler_request_state.before_effect ();
7475
let output_set = Ast_extract.read_parse_and_extract kind pt in
7576
let buf = Ext_buffer.create 1000 in
7677
Ext_buffer.add_char buf magic_sep_char;

‎compiler/ext/build_file_store.ml‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
77
Within a build, a file is read only after the request that writes it has
88
finished, because the build system orders requests by their dependencies.
9-
So an entry changes only when it is written through the store, and the
10-
store is emptied before each build. *)
9+
A request it starts before then gets the interfaces not yet written from
10+
the build system instead (see [Compiler_request_state.redirect_cmi]): the
11+
same strings that are then written through the store. So an entry changes
12+
only when it is written through the store, and the store is emptied before
13+
each build. *)
1114

1215
type entry =
1316
| Contents of string

‎compiler/ext/build_file_store.mli‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ val generation : unit -> int
2222
(** Changes whenever the store is emptied, so caches derived from its contents
2323
know to start over. *)
2424

25+
val key : string -> string
26+
(** [key path] names the entry of [path]: [path] with [.] and [..] segments
27+
removed lexically, so that two spellings of one build file share it. *)
28+
2529
val read : string -> string option
2630
(** [read path] is the contents of [path], or [None] when it does not exist.
2731
Other failures raise [Sys_error]. *)

‎compiler/ext/compiler_request_state.ml‎

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
1+
type hooks = {
2+
before_effect: unit -> unit;
3+
interface_computed: string -> string -> unit;
4+
redirect_cmi: string -> string option;
5+
}
6+
7+
let no_hooks =
8+
{
9+
before_effect = ignore;
10+
interface_computed = (fun _ _ -> ());
11+
redirect_cmi = (fun _ -> None);
12+
}
13+
114
type t = {
215
active: bool;
16+
hooks: hooks;
317
cwd: string;
418
mutable input_name: string;
519
mutable command_runner: string -> int;
@@ -16,9 +30,10 @@ type t = {
1630
mutable load_path: string list;
1731
}
1832

19-
let create ~active cwd =
33+
let create ?(hooks = no_hooks) ~active cwd =
2034
{
2135
active;
36+
hooks;
2237
cwd;
2338
input_name = "_none_";
2439
command_runner = Sys.command;
@@ -47,13 +62,18 @@ let resolve_path path =
4762
Filename.concat state.cwd path
4863
else path
4964

50-
let with_fresh ?cwd:requested_cwd action =
65+
let before_effect () = (current ()).hooks.before_effect ()
66+
let interface_computed path bytes =
67+
(current ()).hooks.interface_computed path bytes
68+
let redirect_cmi path = (current ()).hooks.redirect_cmi path
69+
70+
let with_fresh ?cwd:requested_cwd ?hooks action =
5171
let previous = current () in
5272
let cwd =
5373
match requested_cwd with
5474
| None -> cwd ()
5575
| Some path when Filename.is_relative path -> Filename.concat (cwd ()) path
5676
| Some path -> path
5777
in
58-
Domain.DLS.set key (create ~active:true cwd);
78+
Domain.DLS.set key (create ?hooks ~active:true cwd);
5979
Fun.protect action ~finally:(fun () -> Domain.DLS.set key previous)

‎compiler/ext/compiler_request_state.mli‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
1+
type hooks = {
2+
before_effect: unit -> unit;
3+
(** Called before the request writes a file (such as an .ast, .cmi,
4+
.cmt or .cmti) or runs a command. A build system that starts a
5+
request before its dependencies have finished uses it to wait for
6+
them; it may raise to abandon the request before any effect. *)
7+
interface_computed: string -> string -> unit;
8+
(** Called with the path of a .cmi the request is about to write, and
9+
the file's contents, when no error is pending. Called before
10+
{!before_effect}. *)
11+
redirect_cmi: string -> string option;
12+
(** The bytes to read instead of the .cmi file at a resolved candidate
13+
path. The name the compiler records stays the candidate's. *)
14+
}
15+
(** Callbacks a build system attaches to one in-process request. *)
16+
17+
val no_hooks : hooks
18+
119
type t = {
220
active: bool;
21+
hooks: hooks;
322
cwd: string;
423
mutable input_name: string;
524
mutable command_runner: string -> int;
@@ -27,5 +46,9 @@ val resolve_path : string -> string
2746
(** Resolve a relative file path against the active request root. Outside a
2847
request, preserve the path so ordinary process-relative I/O is unchanged. *)
2948

30-
val with_fresh : ?cwd:string -> (unit -> 'a) -> 'a
49+
val before_effect : unit -> unit
50+
val interface_computed : string -> string -> unit
51+
val redirect_cmi : string -> string option
52+
53+
val with_fresh : ?cwd:string -> ?hooks:hooks -> (unit -> 'a) -> 'a
3154
(** Run with new request state and restore the prior state on success or failure. *)

‎compiler/ext/warnings.mli‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ exception Errors
8888

8989
val check_fatal : unit -> unit
9090

91+
val nerrors : unit -> int
92+
(** Warnings reported as errors since the last {!check_fatal}. *)
93+
9194
val reset_fatal : unit -> unit
9295

9396
val help_warnings : unit -> unit

0 commit comments

Comments
 (0)