Skip to content

Commit 24638e9

Browse files
authored
Merge branch 'main' into lang-dune-parsing-warning
2 parents 92573f5 + a0e9a3a commit 24638e9

File tree

9 files changed

+230
-186
lines changed

9 files changed

+230
-186
lines changed

otherlibs/stdune-unstable/io.ml

+45
Original file line numberDiff line numberDiff line change
@@ -300,3 +300,48 @@ module String_path = Make (struct
300300

301301
let to_string x = x
302302
end)
303+
304+
let portable_symlink ~src ~dst =
305+
if Stdlib.Sys.win32 then
306+
copy_file ~src ~dst ()
307+
else
308+
let src =
309+
match Path.parent dst with
310+
| None -> Path.to_string src
311+
| Some from -> Path.reach ~from src
312+
in
313+
let dst = Path.to_string dst in
314+
match Unix.readlink dst with
315+
| target ->
316+
if target <> src then (
317+
(* @@DRA Win32 remove read-only attribute needed when symlinking
318+
enabled *)
319+
Unix.unlink dst;
320+
Unix.symlink src dst
321+
)
322+
| exception _ -> Unix.symlink src dst
323+
324+
let portable_hardlink ~src ~dst =
325+
(* CR-someday amokhov: Instead of always falling back to copying, we could
326+
detect if hardlinking works on Windows and if yes, use it. We do this in
327+
the Dune cache implementation, so we can share some code. *)
328+
match Stdlib.Sys.win32 with
329+
| true -> copy_file ~src ~dst ()
330+
| false -> (
331+
let rec follow_symlinks name =
332+
match Unix.readlink name with
333+
| link_name ->
334+
let name = Filename.concat (Filename.dirname name) link_name in
335+
follow_symlinks name
336+
| exception Unix.Unix_error (Unix.EINVAL, _, _) -> name
337+
in
338+
let src = follow_symlinks (Path.to_string src) in
339+
let dst = Path.to_string dst in
340+
try Unix.link src dst with
341+
| Unix.Unix_error (Unix.EEXIST, _, _) ->
342+
(* CR-someday amokhov: Investigate why we need to occasionally clear the
343+
destination (we also do this in the symlink case above). Perhaps, the
344+
list of dependencies may have duplicates? If yes, it may be better to
345+
filter out the duplicates first. *)
346+
Unix.unlink dst;
347+
Unix.link src dst)

otherlibs/stdune-unstable/io.mli

+6
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,9 @@ val read_all : in_channel -> string
1717
include Io_intf.S with type path = Path.t
1818

1919
module String_path : Io_intf.S with type path = string
20+
21+
(** Symlink with fallback to copy on systems that don't support it. *)
22+
val portable_symlink : src:Path.t -> dst:Path.t -> unit
23+
24+
(** Hardlink with fallback to copy on systems that don't support it. *)
25+
val portable_hardlink : src:Path.t -> dst:Path.t -> unit

otherlibs/stdune-unstable/path.mli

+3
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ module Build : sig
137137

138138
val append_local : t -> Local.t -> t
139139

140+
(** [append x y] is [append_local x (local y] *)
141+
val append : t -> t -> t
142+
140143
module L : sig
141144
val relative : ?error_loc:Loc0.t -> t -> string list -> t
142145
end

src/dune_engine/action.ml

+9-56
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,6 @@ end
7878

7979
include Action_ast.Make (Prog) (Dpath) (Dpath.Build) (String_with_sexp) (Ast)
8080

81-
type path = Path.t
82-
83-
type target = Path.Build.t
84-
8581
type string = String.t
8682

8783
module For_shell = struct
@@ -205,60 +201,17 @@ let rec is_dynamic = function
205201
| Format_dune_file _ ->
206202
false
207203

208-
let prepare_managed_paths ~link ~sandboxed deps =
209-
let steps =
210-
Path.Map.foldi deps ~init:[] ~f:(fun path _ acc ->
211-
match Path.as_in_build_dir path with
212-
| None ->
213-
(* This can actually raise if we try to sandbox the "copy from source
214-
dir" rules. There is no reason to do that though. *)
215-
if Path.is_in_source_tree path then
216-
Code_error.raise
217-
"Action depends on source tree. All actions should depend on the \
218-
copies in build directory instead"
219-
[ ("path", Path.to_dyn path) ];
220-
acc
221-
| Some p -> link path (sandboxed p) :: acc)
222-
in
223-
Progn steps
224-
225-
let link_function ~(mode : Sandbox_mode.some) : path -> target -> t =
226-
let win32_error mode =
227-
let mode = Sandbox_mode.to_string (Some mode) in
228-
Code_error.raise
229-
(sprintf
230-
"Don't have %ss on win32, but [%s] sandboxing mode was selected. To \
231-
use emulation via copy, the [copy] sandboxing mode should be \
232-
selected."
233-
mode mode)
234-
[]
235-
in
236-
match mode with
237-
| Symlink -> (
238-
match Sys.win32 with
239-
| true -> win32_error mode
240-
| false -> fun a b -> Symlink (a, b))
241-
| Copy -> fun a b -> Copy (a, b)
242-
| Hardlink -> (
243-
match Sys.win32 with
244-
| true -> win32_error mode
245-
| false -> fun a b -> Hardlink (a, b))
246-
247-
let maybe_sandbox_path f p =
204+
let maybe_sandbox_path sandbox p =
248205
match Path.as_in_build_dir p with
249206
| None -> p
250-
| Some p -> Path.build (f p)
251-
252-
let sandbox t ~sandboxed ~mode ~deps : t =
253-
let link = link_function ~mode in
254-
Progn
255-
[ prepare_managed_paths ~sandboxed ~link deps
256-
; map t ~dir:Path.root
257-
~f_string:(fun ~dir:_ x -> x)
258-
~f_path:(fun ~dir:_ p -> maybe_sandbox_path sandboxed p)
259-
~f_target:(fun ~dir:_ -> sandboxed)
260-
~f_program:(fun ~dir:_ -> Result.map ~f:(maybe_sandbox_path sandboxed))
261-
]
207+
| Some p -> Path.build (Sandbox.map_path sandbox p)
208+
209+
let sandbox t sandbox : t =
210+
map t ~dir:Path.root
211+
~f_string:(fun ~dir:_ x -> x)
212+
~f_path:(fun ~dir:_ p -> maybe_sandbox_path sandbox p)
213+
~f_target:(fun ~dir:_ p -> Sandbox.map_path sandbox p)
214+
~f_program:(fun ~dir:_ p -> Result.map p ~f:(maybe_sandbox_path sandbox))
262215

263216
type is_useful =
264217
| Clearly_not

src/dune_engine/action.mli

+2-9
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,8 @@ val empty : t
8888
(** Checks, if action contains a [Dynamic_run]. *)
8989
val is_dynamic : t -> bool
9090

91-
(** Return a sandboxed version of an action. It takes care of preparing deps in
92-
the sandbox, but it does not copy the targets back out. It's the
93-
responsibility of the caller to do that. *)
94-
val sandbox :
95-
t
96-
-> sandboxed:(Path.Build.t -> Path.Build.t)
97-
-> mode:Sandbox_mode.some
98-
-> deps:_ Path.Map.t
99-
-> t
91+
(** Re-root all the paths in the action to their sandbox version *)
92+
val sandbox : t -> Sandbox.t -> t
10093

10194
type is_useful =
10295
| Clearly_not

src/dune_engine/action_exec.ml

+2-44
Original file line numberDiff line numberDiff line change
@@ -226,52 +226,10 @@ let rec exec t ~ectx ~eenv =
226226
Io.copy_file ~src ~dst ();
227227
Fiber.return Done
228228
| Symlink (src, dst) ->
229-
(if Sys.win32 then
230-
let dst = Path.build dst in
231-
Io.copy_file ~src ~dst ()
232-
else
233-
let src =
234-
match Path.Build.parent dst with
235-
| None -> Path.to_string src
236-
| Some from ->
237-
let from = Path.build from in
238-
Path.reach ~from src
239-
in
240-
let dst = Path.Build.to_string dst in
241-
match Unix.readlink dst with
242-
| target ->
243-
if target <> src then (
244-
(* @@DRA Win32 remove read-only attribute needed when symlinking
245-
enabled *)
246-
Unix.unlink dst;
247-
Unix.symlink src dst
248-
)
249-
| exception _ -> Unix.symlink src dst);
229+
Io.portable_symlink ~src ~dst:(Path.build dst);
250230
Fiber.return Done
251231
| Hardlink (src, dst) ->
252-
(* CR-someday amokhov: Instead of always falling back to copying, we could
253-
detect if hardlinking works on Windows and if yes, use it. We do this in
254-
the Dune cache implementation, so we can share some code. *)
255-
(match Sys.win32 with
256-
| true -> Io.copy_file ~src ~dst:(Path.build dst) ()
257-
| false -> (
258-
let rec follow_symlinks name =
259-
match Unix.readlink name with
260-
| link_name ->
261-
let name = Filename.concat (Filename.dirname name) link_name in
262-
follow_symlinks name
263-
| exception Unix.Unix_error (Unix.EINVAL, _, _) -> name
264-
in
265-
let src = follow_symlinks (Path.to_string src) in
266-
let dst = Path.Build.to_string dst in
267-
try Unix.link src dst with
268-
| Unix.Unix_error (Unix.EEXIST, _, _) ->
269-
(* CR-someday amokhov: Investigate why we need to occasionally clear the
270-
destination (we also do this in the symlink case above). Perhaps, the
271-
list of dependencies may have duplicates? If yes, it may be better to
272-
filter out the duplicates first. *)
273-
Unix.unlink dst;
274-
Unix.link src dst));
232+
Io.portable_hardlink ~src ~dst:(Path.build dst);
275233
Fiber.return Done
276234
| Copy_and_add_line_directive (src, dst) ->
277235
Io.with_file_in src ~f:(fun ic ->

src/dune_engine/build_system.ml

+18-77
Original file line numberDiff line numberDiff line change
@@ -607,26 +607,6 @@ let compute_target_digests_or_raise_error exec_params ~loc targets =
607607
(pp_path (Path.build target) :: error))
608608
])
609609

610-
let sandbox_dir = Path.Build.relative Path.Build.root ".sandbox"
611-
612-
let init_sandbox =
613-
let init =
614-
lazy
615-
(let dir = Path.build sandbox_dir in
616-
Path.mkdir_p (Path.relative dir ".hg");
617-
(* We create an empty [.git] file to prevent git from escaping the
618-
sandbox. It will choke on this empty .git and report an error about
619-
its format being invalid. *)
620-
Io.write_file (Path.relative dir ".git") "";
621-
(* We create a [.hg/requires] file to prevent hg from escaping the
622-
sandbox. It will complain that "Escaping the Dune sandbox" is an
623-
unkown feature. *)
624-
Io.write_file
625-
(Path.relative dir ".hg/requires")
626-
"Escaping the Dune sandbox")
627-
in
628-
fun () -> Lazy.force init
629-
630610
let rec with_locks t mutexes ~f =
631611
match mutexes with
632612
| [] -> f ()
@@ -1371,18 +1351,6 @@ end = struct
13711351

13721352
let start_rule t _rule = t.rule_total <- t.rule_total + 1
13731353

1374-
(* Same as [rename] except that if the source doesn't exist we delete the
1375-
destination *)
1376-
let rename_optional_file ~src ~dst =
1377-
let src = Path.Build.to_string src in
1378-
let dst = Path.Build.to_string dst in
1379-
match Unix.rename src dst with
1380-
| () -> ()
1381-
| exception Unix.Unix_error ((ENOENT | ENOTDIR), _, _) -> (
1382-
match Unix.unlink dst with
1383-
| exception Unix.Unix_error (ENOENT, _, _) -> ()
1384-
| () -> ())
1385-
13861354
(* The current version of the rule digest scheme. We should increment it when
13871355
making any changes to the scheme, to avoid collisions. *)
13881356
let rule_digest_version = 7
@@ -1463,45 +1431,18 @@ end = struct
14631431
action
14641432
in
14651433
pending_targets := Path.Build.Set.union targets !pending_targets;
1434+
let chdirs = Action.chdirs action in
14661435
let sandbox =
14671436
Option.map sandbox_mode ~f:(fun mode ->
1468-
let sandbox_suffix = rule_digest |> Digest.to_string in
1469-
(Path.Build.relative sandbox_dir sandbox_suffix, mode))
1437+
Sandbox.create ~mode ~deps ~rule_dir:dir ~chdirs ~rule_digest
1438+
~expand_aliases:
1439+
(Execution_parameters.expand_aliases_in_sandbox
1440+
execution_parameters))
14701441
in
1471-
let chdirs = Action.chdirs action in
1472-
let sandboxed, action =
1442+
let action =
14731443
match sandbox with
1474-
| None -> (None, action)
1475-
| Some (sandbox_dir, sandbox_mode) ->
1476-
init_sandbox ();
1477-
Path.rm_rf (Path.build sandbox_dir);
1478-
let sandboxed path : Path.Build.t =
1479-
Path.Build.append_local sandbox_dir (Path.Build.local path)
1480-
in
1481-
Path.Set.iter
1482-
(Path.Set.union (Dep.Facts.dirs deps) chdirs)
1483-
~f:(fun path ->
1484-
match Path.as_in_build_dir path with
1485-
| None ->
1486-
(* This [path] is not in the build directory, so we do not need to
1487-
create it. If it comes from [deps], it must exist already. If
1488-
it comes from [chdirs], we'll ensure that it exists in the call
1489-
to [Fs.mkdir_p_or_assert_existence] below. *)
1490-
()
1491-
| Some path ->
1492-
(* There is no point in using the memoized version [Fs.mkdir_p]
1493-
since these directories are not shared between actions. *)
1494-
Path.mkdir_p (Path.build (sandboxed path)));
1495-
Path.mkdir_p (Path.build (sandboxed dir));
1496-
let deps =
1497-
if Execution_parameters.expand_aliases_in_sandbox execution_parameters
1498-
then
1499-
Dep.Facts.paths deps
1500-
else
1501-
Dep.Facts.paths_without_expanding_aliases deps
1502-
in
1503-
( Some sandboxed
1504-
, Action.sandbox action ~sandboxed ~mode:sandbox_mode ~deps )
1444+
| None -> action
1445+
| Some sandbox -> Action.sandbox action sandbox
15051446
in
15061447
let* () =
15071448
Fiber.parallel_iter_set
@@ -1511,26 +1452,26 @@ end = struct
15111452
in
15121453
let build_deps deps = Memo.Build.run (build_deps deps) in
15131454
let root =
1514-
(match context with
1455+
match context with
15151456
| None -> Path.Build.root
1516-
| Some context -> context.build_dir)
1517-
|> Option.value sandboxed ~default:Fun.id
1518-
|> Path.build
1457+
| Some context -> context.build_dir
1458+
in
1459+
let root =
1460+
Path.build
1461+
(match sandbox with
1462+
| None -> root
1463+
| Some sandbox -> Sandbox.map_path sandbox root)
15191464
in
15201465
let+ exec_result =
15211466
with_locks t locks ~f:(fun () ->
1522-
let copy_files_from_sandbox sandboxed =
1523-
Path.Build.Set.iter targets ~f:(fun target ->
1524-
rename_optional_file ~src:(sandboxed target) ~dst:target)
1525-
in
15261467
let+ exec_result =
15271468
Action_exec.exec ~root ~context ~env ~targets ~rule_loc:loc
15281469
~build_deps ~execution_parameters action
15291470
in
1530-
Option.iter sandboxed ~f:copy_files_from_sandbox;
1471+
Option.iter sandbox ~f:(Sandbox.move_targets_to_build_dir ~targets);
15311472
exec_result)
15321473
in
1533-
Option.iter sandbox ~f:(fun (p, _mode) -> Path.rm_rf (Path.build p));
1474+
Option.iter sandbox ~f:Sandbox.destroy;
15341475
(* All went well, these targets are no longer pending *)
15351476
pending_targets := Path.Build.Set.diff !pending_targets targets;
15361477
exec_result

0 commit comments

Comments
 (0)