2016-12-31 15:12:39 +00:00
|
|
|
open! Import
|
|
|
|
|
2018-05-12 13:38:22 +00:00
|
|
|
module Dune_file = struct
|
|
|
|
module Plain = struct
|
|
|
|
type t =
|
|
|
|
{ path : Path.t
|
|
|
|
; mutable sexps : Sexp.Ast.t list
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
type t =
|
|
|
|
| Plain of Plain.t
|
|
|
|
| Ocaml_script of Path.t
|
|
|
|
|
|
|
|
let path = function
|
|
|
|
| Plain x -> x.path
|
|
|
|
| Ocaml_script p -> p
|
|
|
|
|
|
|
|
let ocaml_script_prefix = "(* -*- tuareg -*- *)"
|
|
|
|
let ocaml_script_prefix_len = String.length ocaml_script_prefix
|
|
|
|
|
|
|
|
let extract_ignored_subdirs =
|
|
|
|
let stanza =
|
|
|
|
let open Sexp.Of_sexp in
|
|
|
|
let sub_dir sexp =
|
|
|
|
let dn = string sexp in
|
|
|
|
if Filename.dirname dn <> Filename.current_dir_name ||
|
|
|
|
match string sexp with
|
|
|
|
| "" | "." | ".." -> true
|
|
|
|
| _ -> false
|
|
|
|
then
|
|
|
|
of_sexp_errorf sexp "Invalid sub-directory name %S" dn
|
|
|
|
else
|
|
|
|
dn
|
|
|
|
in
|
|
|
|
sum
|
|
|
|
[ cstr "ignored_subdirs" (list sub_dir @> nil) String.Set.of_list
|
|
|
|
]
|
|
|
|
in
|
|
|
|
fun sexps ->
|
|
|
|
let ignored_subdirs, sexps =
|
|
|
|
List.partition_map sexps ~f:(fun sexp ->
|
|
|
|
match (sexp : Sexp.Ast.t) with
|
|
|
|
| List (_, (Atom (_, A "ignored_subdirs") :: _)) ->
|
|
|
|
Left (stanza sexp)
|
|
|
|
| _ -> Right sexp)
|
|
|
|
in
|
|
|
|
let ignored_subdirs =
|
|
|
|
List.fold_left ignored_subdirs ~init:String.Set.empty ~f:String.Set.union
|
|
|
|
in
|
|
|
|
(ignored_subdirs, sexps)
|
|
|
|
|
|
|
|
let load file =
|
|
|
|
Io.with_file_in file ~f:(fun ic ->
|
|
|
|
let open Sexp in
|
|
|
|
let state = Parser.create ~fname:(Path.to_string file) ~mode:Many in
|
|
|
|
let buf = Bytes.create Io.buf_len in
|
|
|
|
let rec loop stack =
|
|
|
|
match input ic buf 0 Io.buf_len with
|
|
|
|
| 0 -> stack
|
|
|
|
| n -> loop (Parser.feed_subbytes state buf ~pos:0 ~len:n stack)
|
|
|
|
in
|
|
|
|
let finish stack =
|
|
|
|
let sexps = Parser.feed_eoi state stack in
|
|
|
|
let ignored_subdirs, sexps = extract_ignored_subdirs sexps in
|
|
|
|
(Plain { path = file; sexps },
|
|
|
|
ignored_subdirs)
|
|
|
|
in
|
|
|
|
let rec loop0 stack i =
|
|
|
|
match input ic buf i (Io.buf_len - i) with
|
|
|
|
| 0 ->
|
|
|
|
finish (Parser.feed_subbytes state buf ~pos:0 ~len:i stack)
|
|
|
|
| n ->
|
|
|
|
let i = i + n in
|
|
|
|
if i < ocaml_script_prefix_len then
|
|
|
|
loop0 stack i
|
|
|
|
else if Bytes.sub_string buf 0 ocaml_script_prefix_len
|
|
|
|
[@warning "-6"]
|
|
|
|
= ocaml_script_prefix then
|
|
|
|
(Ocaml_script file, String.Set.empty)
|
|
|
|
else
|
|
|
|
let stack = Parser.feed_subbytes state buf ~pos:0 ~len:i stack in
|
|
|
|
finish (loop stack)
|
|
|
|
in
|
|
|
|
loop0 Parser.Stack.empty 0)
|
|
|
|
end
|
|
|
|
|
|
|
|
let load_jbuild_ignore path =
|
|
|
|
List.filteri (Io.lines_of_file path) ~f:(fun i fn ->
|
|
|
|
if Filename.dirname fn = Filename.current_dir_name then
|
|
|
|
true
|
|
|
|
else begin
|
|
|
|
Loc.(warn (of_pos ( Path.to_string path
|
|
|
|
, i + 1, 0
|
|
|
|
, String.length fn
|
|
|
|
))
|
|
|
|
"subdirectory expression %s ignored" fn);
|
|
|
|
false
|
|
|
|
end)
|
|
|
|
|> String.Set.of_list
|
|
|
|
|
2016-12-31 15:12:39 +00:00
|
|
|
module Dir = struct
|
|
|
|
type t =
|
|
|
|
{ path : Path.t
|
2017-09-29 13:09:41 +00:00
|
|
|
; ignored : bool
|
2018-05-01 15:55:31 +00:00
|
|
|
; contents : contents Lazy.t
|
|
|
|
}
|
|
|
|
|
|
|
|
and contents =
|
2018-05-12 13:38:22 +00:00
|
|
|
{ files : String.Set.t
|
|
|
|
; sub_dirs : t String.Map.t
|
|
|
|
; dune_file : Dune_file.t option
|
2016-12-31 15:12:39 +00:00
|
|
|
}
|
|
|
|
|
2018-05-01 15:55:31 +00:00
|
|
|
let contents t = Lazy.force t.contents
|
|
|
|
|
2016-12-31 15:12:39 +00:00
|
|
|
let path t = t.path
|
2017-09-29 13:09:41 +00:00
|
|
|
let ignored t = t.ignored
|
2016-12-31 15:12:39 +00:00
|
|
|
|
2018-05-12 13:38:22 +00:00
|
|
|
let files t = (contents t).files
|
|
|
|
let sub_dirs t = (contents t).sub_dirs
|
|
|
|
let dune_file t = (contents t).dune_file
|
2018-05-01 15:55:31 +00:00
|
|
|
|
2018-01-19 08:50:06 +00:00
|
|
|
let file_paths t =
|
2018-05-01 15:55:31 +00:00
|
|
|
Path.Set.of_string_set (files t) ~f:(Path.relative t.path)
|
2018-01-19 08:50:06 +00:00
|
|
|
|
|
|
|
let sub_dir_names t =
|
2018-05-01 15:55:31 +00:00
|
|
|
String.Map.foldi (sub_dirs t) ~init:String.Set.empty
|
2018-04-23 05:08:09 +00:00
|
|
|
~f:(fun s _ acc -> String.Set.add acc s)
|
2018-01-19 08:50:06 +00:00
|
|
|
|
|
|
|
let sub_dir_paths t =
|
2018-05-01 15:55:31 +00:00
|
|
|
String.Map.foldi (sub_dirs t) ~init:Path.Set.empty
|
2018-02-25 16:35:25 +00:00
|
|
|
~f:(fun s _ acc -> Path.Set.add acc (Path.relative t.path s))
|
2018-01-19 08:50:06 +00:00
|
|
|
|
2017-09-29 13:09:41 +00:00
|
|
|
let rec fold t ~traverse_ignored_dirs ~init:acc ~f =
|
|
|
|
if not traverse_ignored_dirs && t.ignored then
|
|
|
|
acc
|
|
|
|
else
|
|
|
|
let acc = f t acc in
|
2018-05-01 15:55:31 +00:00
|
|
|
String.Map.fold (sub_dirs t) ~init:acc ~f:(fun t acc ->
|
2017-09-29 13:09:41 +00:00
|
|
|
fold t ~traverse_ignored_dirs ~init:acc ~f)
|
2016-12-31 15:12:39 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
type t =
|
|
|
|
{ root : Dir.t
|
2018-05-01 15:55:31 +00:00
|
|
|
; dirs : (Path.t, Dir.t) Hashtbl.t
|
2016-12-31 15:12:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let root t = t.root
|
|
|
|
|
2017-05-19 11:36:06 +00:00
|
|
|
let ignore_file fn ~is_directory =
|
|
|
|
fn = "" || fn = "." ||
|
|
|
|
(is_directory && (fn.[0] = '.' || fn.[0] = '_')) ||
|
|
|
|
(fn.[0] = '.' && fn.[1] = '#')
|
2016-12-31 15:12:39 +00:00
|
|
|
|
2017-09-29 13:27:27 +00:00
|
|
|
let load ?(extra_ignored_subtrees=Path.Set.empty) path =
|
2017-09-29 13:09:41 +00:00
|
|
|
let rec walk path ~ignored : Dir.t =
|
2018-05-01 15:55:31 +00:00
|
|
|
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
|
|
|
|
if ignore_file fn ~is_directory then
|
|
|
|
Skip
|
|
|
|
else if is_directory then
|
|
|
|
Right (fn, path)
|
|
|
|
else
|
|
|
|
Left fn)
|
|
|
|
in
|
|
|
|
let files = String.Set.of_list files in
|
2018-05-12 13:38:22 +00:00
|
|
|
let dune_file, ignored_subdirs =
|
|
|
|
if ignored then
|
|
|
|
(None, String.Set.empty)
|
|
|
|
else
|
|
|
|
let dune_file, ignored_subdirs =
|
|
|
|
match List.filter ["dune"; "jbuild"] ~f:(String.Set.mem files) with
|
|
|
|
| [] -> (None, String.Set.empty)
|
|
|
|
| [fn] ->
|
|
|
|
let dune_file, ignored_subdirs =
|
|
|
|
Dune_file.load (Path.relative path fn)
|
|
|
|
in
|
|
|
|
(Some dune_file, ignored_subdirs)
|
|
|
|
| _ ->
|
|
|
|
die "Directory %s has both a 'dune' and 'jbuild' file.\n\
|
|
|
|
This is not allowed"
|
|
|
|
(Path.to_string_maybe_quoted path)
|
2018-05-01 15:55:31 +00:00
|
|
|
in
|
2018-05-12 13:38:22 +00:00
|
|
|
let ignored_subdirs =
|
|
|
|
if String.Set.mem files "jbuild-ignore" then
|
|
|
|
String.Set.union ignored_subdirs
|
|
|
|
(load_jbuild_ignore (Path.relative path "jbuild-ignore"))
|
|
|
|
else
|
|
|
|
ignored_subdirs
|
2018-05-01 15:55:31 +00:00
|
|
|
in
|
2018-05-12 13:38:22 +00:00
|
|
|
(dune_file, ignored_subdirs)
|
2018-05-01 15:55:31 +00:00
|
|
|
in
|
|
|
|
let sub_dirs =
|
2018-05-12 13:38:22 +00:00
|
|
|
List.fold_left sub_dirs ~init:String.Map.empty ~f:(fun acc (fn, path) ->
|
2018-05-01 15:55:31 +00:00
|
|
|
let ignored =
|
|
|
|
ignored
|
2018-05-12 13:38:22 +00:00
|
|
|
|| String.Set.mem ignored_subdirs fn
|
2018-05-01 15:55:31 +00:00
|
|
|
|| Path.Set.mem extra_ignored_subtrees path
|
|
|
|
in
|
2018-05-12 13:38:22 +00:00
|
|
|
String.Map.add acc fn (walk path ~ignored))
|
2018-05-01 15:55:31 +00:00
|
|
|
in
|
2018-05-12 13:38:22 +00:00
|
|
|
{ Dir. files; sub_dirs; dune_file })
|
2016-12-31 15:12:39 +00:00
|
|
|
in
|
|
|
|
{ path
|
2018-05-01 15:55:31 +00:00
|
|
|
; contents
|
2017-09-29 13:09:41 +00:00
|
|
|
; ignored
|
2016-12-31 15:12:39 +00:00
|
|
|
}
|
|
|
|
in
|
2017-09-29 13:09:41 +00:00
|
|
|
let root = walk path ~ignored:false in
|
2018-05-01 15:55:31 +00:00
|
|
|
let dirs = Hashtbl.create 1024 in
|
|
|
|
Hashtbl.add dirs Path.root root;
|
|
|
|
{ root; dirs }
|
2016-12-31 15:12:39 +00:00
|
|
|
|
2017-09-29 13:09:41 +00:00
|
|
|
let fold t ~traverse_ignored_dirs ~init ~f =
|
|
|
|
Dir.fold t.root ~traverse_ignored_dirs ~init ~f
|
2016-12-31 15:12:39 +00:00
|
|
|
|
2018-05-01 15:55:31 +00:00
|
|
|
let rec find_dir t path =
|
|
|
|
if not (Path.is_local path) then
|
|
|
|
None
|
|
|
|
else
|
|
|
|
match Hashtbl.find t.dirs path with
|
|
|
|
| Some _ as res -> res
|
|
|
|
| None ->
|
|
|
|
match
|
|
|
|
let open Option.O in
|
2018-05-08 16:56:58 +00:00
|
|
|
Path.parent path
|
|
|
|
>>= find_dir t
|
2018-05-01 15:55:31 +00:00
|
|
|
>>= fun parent ->
|
|
|
|
String.Map.find (Dir.sub_dirs parent) (Path.basename path)
|
|
|
|
with
|
|
|
|
| Some dir as res ->
|
|
|
|
Hashtbl.add t.dirs path dir;
|
|
|
|
res
|
|
|
|
| None ->
|
|
|
|
(* We don't cache failures in [t.dirs]. The expectation is
|
|
|
|
that these only happen when the user writes an invalid path
|
|
|
|
in a jbuild file, so there is no need to cache them. *)
|
|
|
|
None
|
2017-02-23 14:58:18 +00:00
|
|
|
|
2018-01-19 08:50:06 +00:00
|
|
|
let files_of t path =
|
|
|
|
match find_dir t path with
|
|
|
|
| None -> Path.Set.empty
|
|
|
|
| Some dir ->
|
|
|
|
Path.Set.of_string_set (Dir.files dir) ~f:(Path.relative path)
|
|
|
|
|
2017-02-23 14:58:18 +00:00
|
|
|
let file_exists t path fn =
|
2018-05-01 15:55:31 +00:00
|
|
|
match find_dir t path with
|
2017-02-23 14:58:18 +00:00
|
|
|
| None -> false
|
2018-05-01 15:55:31 +00:00
|
|
|
| Some dir -> String.Set.mem (Dir.files dir) fn
|
|
|
|
|
|
|
|
let dir_exists t path = Option.is_some (find_dir t path)
|
2017-02-23 14:58:18 +00:00
|
|
|
|
|
|
|
let exists t path =
|
2018-05-01 15:55:31 +00:00
|
|
|
dir_exists t path ||
|
2018-05-08 16:56:58 +00:00
|
|
|
file_exists t (Path.parent_exn path) (Path.basename path)
|
2017-03-15 09:10:22 +00:00
|
|
|
|
|
|
|
let files_recursively_in t ?(prefix_with=Path.root) path =
|
|
|
|
match find_dir t path with
|
|
|
|
| None -> Path.Set.empty
|
|
|
|
| Some dir ->
|
2017-09-29 13:09:41 +00:00
|
|
|
Dir.fold dir ~init:Path.Set.empty ~traverse_ignored_dirs:true
|
|
|
|
~f:(fun dir acc ->
|
|
|
|
let path = Path.append prefix_with (Dir.path dir) in
|
2018-04-23 05:08:09 +00:00
|
|
|
String.Set.fold (Dir.files dir) ~init:acc ~f:(fun fn acc ->
|
2018-02-25 16:35:25 +00:00
|
|
|
Path.Set.add acc (Path.relative path fn)))
|