Skip to content

Commit 040eb08

Browse files
committed
feature: bring back action runners
Signed-off-by: Rudi Grinberg <me@rgrinberg.com> feature: allow action runners to write to trace Signed-off-by: Rudi Grinberg <me@rgrinberg.com> action runners Signed-off-by: Rudi Grinberg <me@rgrinberg.com>
1 parent 5c705ee commit 040eb08

73 files changed

Lines changed: 2508 additions & 312 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bin/common.ml

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ end
6060

6161
let default_build_dir = "_build"
6262
let trace_file_name = "trace.csexp"
63+
let action_runner_name = Dune_engine.Action_runner.Name.of_string "action-runner"
6364

6465
let find_default_trace_file () =
6566
let trace_file = Filename.concat default_build_dir trace_file_name in
@@ -625,6 +626,8 @@ module Builder = struct
625626
; allow_builds : bool
626627
; default_root_is_cwd : bool
627628
; target_exec : string option
629+
; action_runner : bool
630+
; sandbox_actions : bool
628631
}
629632

630633
let set_no_build t no_build = { t with no_build }
@@ -952,6 +955,25 @@ module Builder = struct
952955
& info
953956
[ "stop-on-first-error" ]
954957
~doc:(Some "Stop the build as soon as an error is encountered."))
958+
and+ sandbox_actions =
959+
Arg.(
960+
value
961+
& flag
962+
& info
963+
[ "sandbox-actions" ]
964+
~docs
965+
~doc:
966+
(Some
967+
"Run spawned build processes in an external dune action runner wrapped \
968+
with bubblewrap."))
969+
and+ action_runner =
970+
Arg.(
971+
value
972+
& flag
973+
& info
974+
[ "action-runner" ]
975+
~docs
976+
~doc:(Some "Run spawned build processes in an external dune action runner."))
955977
in
956978
{ no_build
957979
; debug_dep_path
@@ -996,6 +1018,8 @@ module Builder = struct
9961018
; allow_builds = true
9971019
; default_root_is_cwd = false
9981020
; target_exec
1021+
; action_runner
1022+
; sandbox_actions
9991023
}
10001024
;;
10011025

@@ -1035,6 +1059,8 @@ module Builder = struct
10351059
; allow_builds
10361060
; default_root_is_cwd
10371061
; target_exec
1062+
; action_runner
1063+
; sandbox_actions
10381064
}
10391065
=
10401066
No_build.equal t.no_build no_build
@@ -1074,6 +1100,8 @@ module Builder = struct
10741100
&& Bool.equal t.allow_builds allow_builds
10751101
&& Bool.equal t.default_root_is_cwd default_root_is_cwd
10761102
&& Option.equal String.equal t.target_exec target_exec
1103+
&& Bool.equal t.action_runner action_runner
1104+
&& Bool.equal t.sandbox_actions sandbox_actions
10771105
;;
10781106
end
10791107

@@ -1082,13 +1110,16 @@ type t =
10821110
; root : Workspace_root.t
10831111
; rpc :
10841112
[ `Allow of Dune_lang.Dep_conf.t Dune_rpc_impl.Server.t Lazy.t | `Forbid_builds ]
1113+
; action_runner : Dune_engine.Action_runner.t option
10851114
}
10861115

10871116
let capture_outputs t = t.builder.capture_outputs
10881117
let root t = t.root
10891118
let watch t = t.builder.watch
10901119
let x t = t.builder.workspace_config.x
10911120
let file_watcher t = t.builder.file_watcher
1121+
let sandbox_actions t = t.builder.sandbox_actions
1122+
let action_runner t = t.action_runner
10921123
let prefix_target t s = t.root.reach_from_root_prefix ^ s
10931124

10941125
let rpc t =
@@ -1181,7 +1212,7 @@ let build (root : Workspace_root.t) (builder : Builder.t) =
11811212
Dune_rpc_impl.Server.create ~lock_timeout ~registry ~root:root.dir))
11821213
else `Forbid_builds
11831214
in
1184-
{ builder; root; rpc }
1215+
{ builder; root; rpc; action_runner = None }
11851216
;;
11861217

11871218
let maybe_init_cache (cache_config : Dune_cache.Config.t) =
@@ -1288,7 +1319,39 @@ let init_with_root ~(root : Workspace_root.t) (builder : Builder.t) =
12881319
, Dyn.string (Path.to_string (Lazy.force Dune_cache.Layout.build_cache_dir)) )
12891320
];
12901321
Dune_cache.Shared.config := maybe_init_cache cache_config;
1291-
Dune_rules.Main.init ~sandboxing_preference:config.sandboxing_preference ();
1322+
let action_runner =
1323+
match
1324+
c.builder.allow_builds, c.builder.action_runner || c.builder.sandbox_actions
1325+
with
1326+
| false, _ -> None
1327+
| true, false -> None
1328+
| true, true ->
1329+
let server =
1330+
match rpc c with
1331+
| `Allow server -> Dune_rpc_impl.Server.action_runner server
1332+
| `Forbid_builds ->
1333+
Code_error.raise "action runners require the dune RPC server" []
1334+
in
1335+
Some (Dune_engine.Action_runner.create server ~name:action_runner_name)
1336+
in
1337+
let c = { c with action_runner } in
1338+
let action_runners =
1339+
match action_runner with
1340+
| None -> fun () -> []
1341+
| Some runner ->
1342+
Dune_engine.Process.Runner.set (fun request ->
1343+
Some
1344+
(Dune_engine.Action_runner.exec_process
1345+
runner
1346+
~run_id:(Dune_scheduler.Scheduler.current_run_id ())
1347+
request));
1348+
fun () -> if Dune_engine.Action_runner.started runner then [ runner ] else []
1349+
in
1350+
Dune_rules.Main.init
1351+
~sandbox_actions:c.builder.sandbox_actions
1352+
~sandboxing_preference:config.sandboxing_preference
1353+
~action_runners
1354+
();
12921355
Only_packages.Clflags.set c.builder.only_packages;
12931356
Report_error.print_memo_stacks := c.builder.debug_dep_path;
12941357
Dune_engine.Clflags.report_errors_config := c.builder.report_errors_config;

bin/common.mli

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ val rpc
1818
val watch_exclusions : t -> string list
1919
val watch : t -> Dune_rpc_impl.Watch_mode_config.t
2020
val file_watcher : t -> Dune_scheduler.Scheduler.Run.file_watcher
21+
val sandbox_actions : t -> bool
22+
val action_runner : t -> Dune_engine.Action_runner.t option
2123
val prefix_target : t -> string -> string
2224
val find_default_trace_file : unit -> string
2325

bin/internal.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ let group =
111111
(Cmd.info "internal")
112112
[ Internal_dump.command
113113
; Internal_digest_db.command
114+
; Internal_action_runner.group
114115
; latest_lang_version
115116
; bootstrap_info
116117
; Sexp_pp.command

bin/internal_action_runner.ml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
open Import
2+
3+
let start =
4+
let+ builder = Common.Builder.term_no_trace_no_pkg
5+
and+ name =
6+
Arg.(required & pos 0 (some string) None (Arg.info [] ~docv:"NAME" ~doc:None))
7+
and+ where =
8+
Arg.(required & pos 1 (some string) None (Arg.info [] ~docv:"WHERE" ~doc:None))
9+
and+ trace_fd =
10+
Arg.(value & opt (some string) None (Arg.info [ "trace-fd" ] ~docv:"FD" ~doc:None))
11+
in
12+
let builder = Common.Builder.forbid_builds builder in
13+
let common, config = Common.init builder in
14+
let name = Dune_engine.Action_runner.Name.parse_string_exn (Loc.none, name) in
15+
Option.iter trace_fd ~f:(fun fd ->
16+
Dune_trace.set_global_inherited_fd_arg fd;
17+
Dune_trace.set_common_args
18+
[ "action_runner", Sexp.Atom (Dune_engine.Action_runner.Name.to_string name) ]);
19+
let where =
20+
match
21+
Dune_rpc.Conv.of_sexp
22+
Dune_rpc.Where.sexp
23+
~version:Dune_rpc.Version.latest
24+
(Sexp.Atom where)
25+
with
26+
| Ok where -> where
27+
| Error err ->
28+
User_error.raise
29+
[ Pp.textf "invalid action runner RPC address %S" where
30+
; Pp.text (Dyn.to_string (Dune_rpc.Conv.dyn_of_error err))
31+
]
32+
in
33+
Scheduler_setup.go_without_rpc_server ~common ~config (fun () ->
34+
Dune_engine.Action_runner.Worker.start ~name ~where)
35+
;;
36+
37+
let start = Cmd.v (Cmd.info "start") start
38+
let group = Cmd.group (Cmd.info "action-runner") [ start ]

bin/internal_action_runner.mli

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
open Import
2+
3+
val group : unit Cmd.t

bin/scheduler_setup.ml

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,134 @@ let rpc server =
7171
}
7272
;;
7373

74+
let with_action_runner_worker ~(common : Common.t) f =
75+
match Common.action_runner common with
76+
| None -> f ()
77+
| Some action_runner ->
78+
let open Fiber.O in
79+
let server =
80+
match Common.rpc common with
81+
| `Allow server -> server
82+
| `Forbid_builds -> Code_error.raise "action runners require the dune RPC server" []
83+
in
84+
let find_in_path_exn prog =
85+
match Bin.which ~path:(Env_path.path Env.initial) prog with
86+
| Some path -> path
87+
| None -> User_error.raise [ Pp.textf "unable to find %s in PATH" prog ]
88+
in
89+
let has_directory_component prog =
90+
String.exists prog ~f:(function
91+
| '/' | '\\' -> true
92+
| _ -> false)
93+
in
94+
let dune_prog =
95+
let prog = Sys.executable_name in
96+
if Filename.is_relative prog && not (has_directory_component prog)
97+
then find_in_path_exn prog
98+
else Path.of_filename_relative_to_initial_cwd prog
99+
in
100+
let jobs = Int.to_string !Dune_rules.Clflags.concurrency in
101+
let env =
102+
Env.initial
103+
|> Env.add ~var:"DUNE_JOBS" ~value:jobs
104+
|> Env.add ~var:"DUNE_BUILD_DIR" ~value:(Path.Build.to_string Path.Build.root)
105+
|> Env.to_unix
106+
|> Spawn.Env.of_list
107+
in
108+
let where = Dune_rpc_impl.Server.listening_address server in
109+
let monitor_pool = Fiber.Pool.create () in
110+
let started_worker = ref (None : Pid.t option) in
111+
let worker_command runner =
112+
let worker_argv =
113+
[ Path.to_string dune_prog
114+
; "internal"
115+
; "action-runner"
116+
; "start"
117+
; Dune_engine.Action_runner.Name.to_string (Dune_engine.Action_runner.name runner)
118+
; Dune_rpc.Where.to_string where
119+
]
120+
in
121+
if Common.sandbox_actions common
122+
then (
123+
let bwrap =
124+
match Platform.OS.value with
125+
| Linux -> find_in_path_exn "bwrap"
126+
| _ ->
127+
User_error.raise
128+
[ Pp.text "--sandbox-actions is currently only supported on Linux" ]
129+
in
130+
let cwd = Path.to_absolute_filename Path.root in
131+
let shared_cache_bindings =
132+
let build_cache_dir = Lazy.force Dune_cache.Layout.build_cache_dir in
133+
Path.mkdir_p build_cache_dir;
134+
let build_cache_dir = Path.to_string build_cache_dir in
135+
[ "--ro-bind"; build_cache_dir; build_cache_dir ]
136+
in
137+
( Path.to_string bwrap
138+
, [ Path.to_string bwrap; "--die-with-parent"; "--bind"; "/"; "/" ]
139+
@ shared_cache_bindings
140+
@ [ "--proc"; "/proc"; "--dev"; "/dev"; "--chdir"; cwd; "--" ]
141+
@ worker_argv ))
142+
else Path.to_string dune_prog, worker_argv
143+
in
144+
let close_trace_fd trace_fd = Option.iter trace_fd ~f:Fd.close in
145+
let start_worker ~runner =
146+
let* () = Root.Rpc.Global.ensure_ready () in
147+
let prog, argv = worker_command runner in
148+
let trace_fd = Dune_trace.duplicate_global_fd () in
149+
let argv =
150+
argv
151+
@
152+
match trace_fd with
153+
| None -> []
154+
| Some fd ->
155+
let fd_arg : int = Obj.magic (Fd.unsafe_to_unix_file_descr fd) in
156+
[ "--trace-fd"; Int.to_string fd_arg ]
157+
in
158+
let pid =
159+
match Spawn.spawn ~env ~prog ~argv ~setpgid:Spawn.Pgid.new_process_group () with
160+
| pid ->
161+
close_trace_fd trace_fd;
162+
let pid = Pid.of_int pid in
163+
Dune_trace.emit Action (fun () ->
164+
Dune_trace.Event.Action.runner_spawn
165+
~name:
166+
(Dune_engine.Action_runner.Name.to_string
167+
(Dune_engine.Action_runner.name runner))
168+
~pid);
169+
pid
170+
| exception exn ->
171+
close_trace_fd trace_fd;
172+
raise exn
173+
in
174+
started_worker := Some pid;
175+
Fiber.Pool.task monitor_pool ~f:(fun () ->
176+
let* _status = Scheduler.wait_for_process pid ~is_process_group_leader:true in
177+
let* () = Dune_engine.Action_runner.disconnect runner in
178+
(match !started_worker with
179+
| Some current_pid when Pid.equal current_pid pid -> started_worker := None
180+
| None | Some _ -> ());
181+
Fiber.return ())
182+
in
183+
let terminate_worker pid =
184+
match Unix.kill (-Pid.to_int pid) Sys.sigterm with
185+
| () -> Fiber.return ()
186+
| exception Unix.Unix_error (Unix.ESRCH, _, _) -> Fiber.return ()
187+
in
188+
Dune_engine.Action_runner.set_start action_runner start_worker;
189+
Fiber.fork_and_join_unit
190+
(fun () -> Fiber.Pool.run monitor_pool)
191+
(fun () ->
192+
Fiber.finalize f ~finally:(fun () ->
193+
let worker = !started_worker in
194+
let* () =
195+
match worker with
196+
| None -> Fiber.return ()
197+
| Some pid -> terminate_worker pid
198+
in
199+
Fiber.Pool.close monitor_pool))
200+
;;
201+
74202
let no_build_no_rpc ~config:dune_config f =
75203
let config =
76204
Dune_config.for_scheduler dune_config ~print_ctrl_c_warning:true ~watch_exclusions:[]
@@ -92,7 +220,10 @@ let go_without_rpc_server ~(common : Common.t) ~config:dune_config f =
92220
let go_with_rpc_server ~common ~config f =
93221
let f =
94222
match Common.rpc common with
95-
| `Allow server -> fun () -> Root.Rpc.Global.with_background_rpc (rpc server) f
223+
| `Allow server ->
224+
fun () ->
225+
Root.Rpc.Global.with_background_rpc (rpc server) (fun () ->
226+
with_action_runner_worker ~common f)
96227
| `Forbid_builds -> f
97228
in
98229
go_without_rpc_server ~common ~config f
@@ -119,7 +250,7 @@ let go_with_rpc_server_and_console_status_reporting
119250
Root.Rpc.Global.with_background_rpc server
120251
@@ fun () ->
121252
let* () = Root.Rpc.Global.ensure_ready () in
122-
run ()
253+
with_action_runner_worker ~common run
123254
in
124255
Run.go
125256
config

0 commit comments

Comments
 (0)