diff --git a/src/file_tree.ml b/src/file_tree.ml index 58844479..e04827f4 100644 --- a/src/file_tree.ml +++ b/src/file_tree.ml @@ -155,18 +155,46 @@ let ignore_file fn ~is_directory = (is_directory && (fn.[0] = '.' || fn.[0] = '_')) || (fn.[0] = '.' && fn.[1] = '#') +module File = struct + type t = + { ino : int + ; dev : int + } + + let compare a b = + match Int.compare a.ino b.ino with + | Eq -> Int.compare a.dev b.dev + | ne -> ne + + let dummy = { ino = 0; dev = 0 } + + let of_stats (st : Unix.stats) = + { ino = st.st_ino + ; dev = st.st_dev + } +end + +module File_map = Map.Make(File) + let load ?(extra_ignored_subtrees=Path.Set.empty) path = - let rec walk path ~project ~ignored : Dir.t = + let rec walk path ~dirs_visited ~project ~ignored : Dir.t = let contents = lazy ( let files, sub_dirs = Path.readdir path |> List.filter_partition_map ~f:(fun fn -> let path = Path.relative path fn in - let is_directory = Path.is_directory path in + let is_directory, file = + match Unix.stat (Path.to_string path) with + | exception _ -> (false, File.dummy) + | { st_kind = S_DIR; _ } as st -> + (true, File.of_stats st) + | _ -> + (false, File.dummy) + in if ignore_file fn ~is_directory then Skip else if is_directory then - Right (fn, path) + Right (fn, path, file) else Left fn) in @@ -203,13 +231,27 @@ let load ?(extra_ignored_subtrees=Path.Set.empty) path = (dune_file, ignored_subdirs) in let sub_dirs = - List.fold_left sub_dirs ~init:String.Map.empty ~f:(fun acc (fn, path) -> - let ignored = - ignored - || String.Set.mem ignored_subdirs fn - || Path.Set.mem extra_ignored_subtrees path - in - String.Map.add acc fn (walk path ~project ~ignored)) + List.fold_left sub_dirs ~init:String.Map.empty + ~f:(fun acc (fn, path, file) -> + let dirs_visited = + if Sys.win32 then + dirs_visited + else + match File_map.find dirs_visited file with + | None -> File_map.add dirs_visited file path + | Some first_path -> + die "Path %s has already been scanned. \ + Cannot scan it again through symlink %s" + (Path.to_string_maybe_quoted first_path) + (Path.to_string_maybe_quoted path) + in + let ignored = + ignored + || String.Set.mem ignored_subdirs fn + || Path.Set.mem extra_ignored_subtrees path + in + String.Map.add acc fn + (walk path ~dirs_visited ~project ~ignored)) in { Dir. files; sub_dirs; dune_file; project }) in @@ -218,7 +260,14 @@ let load ?(extra_ignored_subtrees=Path.Set.empty) path = ; ignored } in - let root = walk path ~ignored:false ~project:None in + let root = + walk path + ~dirs_visited:(File_map.singleton + (File.of_stats (Unix.stat (Path.to_string path))) + path) + ~ignored:false + ~project:None + in let dirs = Hashtbl.create 1024 in Hashtbl.add dirs Path.root root; { root; dirs } diff --git a/src/stdune/path.ml b/src/stdune/path.ml index ec8fd4ad..22c8a334 100644 --- a/src/stdune/path.ml +++ b/src/stdune/path.ml @@ -520,4 +520,3 @@ let change_extension ~ext t = let extension = Filename.extension let pp ppf t = Format.pp_print_string ppf (to_string t) - diff --git a/test/blackbox-tests/dune.inc b/test/blackbox-tests/dune.inc index 3c9cf7b5..6863fa98 100644 --- a/test/blackbox-tests/dune.inc +++ b/test/blackbox-tests/dune.inc @@ -217,6 +217,16 @@ test-cases/github761 (progn (run ${exe:cram.exe} -test run.t) (diff? run.t run.t.corrected)))))) +(alias + ((name github764) + (deps ((package dune) (files_recursively_in test-cases/github764))) + (action + (chdir + test-cases/github764 + (progn + (run ${exe:cram.exe} -skip-platforms win -test run.t) + (diff? run.t run.t.corrected)))))) + (alias ((name ignored_subdirs) (deps ((package dune) (files_recursively_in test-cases/ignored_subdirs))) @@ -511,6 +521,7 @@ (alias github734) (alias github759) (alias github761) + (alias github764) (alias ignored_subdirs) (alias include-loop) (alias inline_tests) @@ -567,6 +578,7 @@ (alias github734) (alias github759) (alias github761) + (alias github764) (alias ignored_subdirs) (alias include-loop) (alias inline_tests) diff --git a/test/blackbox-tests/gen_tests.ml b/test/blackbox-tests/gen_tests.ml index 505bc1e0..085c22b6 100644 --- a/test/blackbox-tests/gen_tests.ml +++ b/test/blackbox-tests/gen_tests.ml @@ -104,6 +104,7 @@ let exclusions = ; make "menhir"~external_deps:true ; make "utop"~external_deps:true ; make "configurator" ~skip_platforms:[Win] + ; make "github764" ~skip_platforms:[Win] ] let all_tests = lazy ( diff --git a/test/blackbox-tests/test-cases/github764/run.t b/test/blackbox-tests/test-cases/github764/run.t new file mode 100644 index 00000000..2f04e5c7 --- /dev/null +++ b/test/blackbox-tests/test-cases/github764/run.t @@ -0,0 +1,36 @@ + $ mkdir -p c1 + $ cd c1 && ln -s . x + $ cd c1 && ln -s . y + $ cd c1 && dune build + Path . has already been scanned. Cannot scan it again through symlink x + [1] + + $ mkdir -p c2/a c2/b + $ cd c2/a && ln -s ../b x + $ cd c2/b && ln -s ../a x + $ cd c2 && dune build + Path a has already been scanned. Cannot scan it again through symlink a/x/x + [1] + + $ mkdir symlink-outside-root + $ cd symlink-outside-root && ln -s ../sample-exe sample + $ cd symlink-outside-root && jbuilder exec --root . -- sample/foo.exe + foo + + $ mkdir -p symlink-outside-root2/root + $ mkdir -p symlink-outside-root2/other/a + $ mkdir -p symlink-outside-root2/other/b + $ cd symlink-outside-root2/other/a && ln -s ../b x + $ cd symlink-outside-root2/other/b && ln -s ../a x + $ cd symlink-outside-root2/root && ln -s ../other src + $ cd symlink-outside-root2/root && dune build + Path src/a has already been scanned. Cannot scan it again through symlink src/a/x/x + [1] + + $ mkdir -p symlink-outside-root3/root + $ mkdir -p symlink-outside-root3/other + $ cd symlink-outside-root3/root && ln -s ../other src + $ cd symlink-outside-root3/other && ln -s ../other foo + $ cd symlink-outside-root3/root && dune build + Path src has already been scanned. Cannot scan it again through symlink src/foo + [1] diff --git a/test/blackbox-tests/test-cases/github764/sample-exe/dune b/test/blackbox-tests/test-cases/github764/sample-exe/dune new file mode 100644 index 00000000..dab7cb21 --- /dev/null +++ b/test/blackbox-tests/test-cases/github764/sample-exe/dune @@ -0,0 +1,2 @@ +(executable + ((name foo))) diff --git a/test/blackbox-tests/test-cases/github764/sample-exe/foo.ml b/test/blackbox-tests/test-cases/github764/sample-exe/foo.ml new file mode 100644 index 00000000..d35be9f1 --- /dev/null +++ b/test/blackbox-tests/test-cases/github764/sample-exe/foo.ml @@ -0,0 +1 @@ +let () = print_endline "foo"