diff --git a/bin/main.ml b/bin/main.ml index 2dfe288a..35955963 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -2,9 +2,12 @@ open Jbuilder open Import open Jbuilder_cmdliner.Cmdliner -module Suggest = Jbuilder_cmdliner.Cmdliner_suggest module Main = Jbuilder.Main +(* Things in src/ don't depend on cmdliner to speed up the bootstrap, so we set this + reference here *) +let () = suggest_function := Jbuilder_cmdliner.Cmdliner_suggest.value + let (>>=) = Future.(>>=) let create_log = Main.create_log @@ -81,18 +84,7 @@ let build_package pkg = Build_system.do_build_exn setup.build_system [path] | Error () -> - match Suggest.value pkg (String_map.keys setup.packages) with - | [] -> die "Unknown package %s!" pkg - | pkgs -> - let rec mk_hint = function - | [a; b] -> sprintf "%s or %s" a b - | [a] -> a - | a :: l -> sprintf "%s, %s" a (mk_hint l) - | [] -> "" - in - die "Unknown package %s!\nHint: did you mean %s?" - pkg - (mk_hint pkgs) + die "Unknown package %s!%s" pkg (hint pkg (String_map.keys setup.packages)) ) let build_package = diff --git a/src/gen_rules.ml b/src/gen_rules.ml index 1dfda347..8614c015 100644 --- a/src/gen_rules.ml +++ b/src/gen_rules.ml @@ -1555,13 +1555,9 @@ module Gen(P : Params) = struct | Executables ({ object_public_name = Some name; _ } as exes) when Findlib.root_package_name name = package -> obj_install_files ~dir exes - | Install { section; files; package = p } -> begin - match p with - | Some p when p <> package -> [] - | _ -> - List.map files ~f:(fun { Install_conf. src; dst } -> - Install.Entry.make section (Path.relative dir src) ?dst) - end + | Install { section; files; package = Some p } when p = package -> + List.map files ~f:(fun { Install_conf. src; dst } -> + Install.Entry.make section (Path.relative dir src) ?dst) | _ -> []) in let entries = diff --git a/src/import.ml b/src/import.ml index 72cbf5c9..a73cdfa2 100644 --- a/src/import.ml +++ b/src/import.ml @@ -363,3 +363,17 @@ let quote_for_shell s = | _ -> loop (i + 1) in loop 0 + +let suggest_function : (string -> string list -> string list) ref = ref (fun _ _ -> []) + +let hint name candidates = + match !suggest_function name candidates with + | [] -> "" + | l -> + let rec mk_hint = function + | [a; b] -> sprintf "%s or %s" a b + | [a] -> a + | a :: l -> sprintf "%s, %s" a (mk_hint l) + | [] -> "" + in + sprintf "\nHint: did you mean %s?" (mk_hint l) diff --git a/src/jbuild_load.ml b/src/jbuild_load.ml index b6e8389e..f54ade3a 100644 --- a/src/jbuild_load.ml +++ b/src/jbuild_load.ml @@ -8,13 +8,47 @@ type conf = ; packages : Path.t String_map.t } -let load fn ~dir = (dir, Sexp_load.many fn Stanza.t) +let load ~dir ~visible_packages = + let stanzas = Sexp_load.many (Path.relative dir "jbuild" |> Path.to_string) Stanza.t in + let stanzas = Stanza.resolve_packages stanzas ~dir ~visible_packages in + (dir, stanzas) let load () = - let rec walk dir stanzas packages = + let ftree = File_tree.load Path.root in + let packages = + File_tree.fold ftree ~init:[] ~f:(fun dir acc -> + let path = File_tree.Dir.path dir in + String_set.fold (File_tree.Dir.files dir) ~init:acc ~f:(fun fn acc -> + match Filename.split_ext fn with + | Some (pkg, ".opam") -> (pkg, path) :: acc + | _ -> acc)) + |> String_map.of_alist_multi + |> String_map.mapi ~f:(fun pkg dirs -> + match dirs with + | [dir] -> dir + | _ -> + die "Too many opam files for package %S:\n%s" + pkg + (String.concat ~sep:"\n" + (List.map dirs ~f:(fun dir -> + sprintf "- %s.opam" (Path.to_string dir))))) + in + let packages_per_dir = + String_map.bindings packages + |> List.map ~f:(fun (pkg, path) -> (path, pkg)) + |> Path.Map.of_alist_multi + in + let rec walk dir stanzas visible_packages = let path = File_tree.Dir.path dir in let files = File_tree.Dir.files dir in let sub_dirs = File_tree.Dir.sub_dirs dir in + let visible_packages = + match Path.Map.find path packages_per_dir with + | None -> visible_packages + | Some pkgs -> + List.fold_left pkgs ~init:visible_packages ~f:(fun acc pkg -> + String_map.add acc ~key:pkg ~data:path) + in let sub_dirs = if String_set.mem "jbuild-ignore" files then let ignore_set = @@ -26,41 +60,23 @@ let load () = else sub_dirs in - let children, stanzas, packages = - String_map.fold sub_dirs ~init:([], stanzas, []) - ~f:(fun ~key:_ ~data:dir (children, stanzas, packages) -> - let child, stanzas, packages = walk dir stanzas packages in - (child :: children, stanzas, packages)) + let children, stanzas = + String_map.fold sub_dirs ~init:([], stanzas) + ~f:(fun ~key:_ ~data:dir (children, stanzas) -> + let child, stanzas = walk dir stanzas visible_packages in + (child :: children, stanzas)) in let stanzas = if String_set.mem "jbuild" files then - load (Path.to_string (Path.relative path "jbuild")) ~dir:path :: stanzas + load ~dir:path ~visible_packages + :: stanzas else stanzas in - let packages = - String_set.fold files ~init:packages ~f:(fun fn acc -> - match Filename.split_ext fn with - | Some (pkg, ".opam") -> (pkg, path) :: acc - | _ -> acc) - in - (Alias.Node (path, children), stanzas, packages) + (Alias.Node (path, children), stanzas) in - let ftree = File_tree.load Path.root in let root = File_tree.root ftree in - let tree, stanzas, packages = walk root [] [] in - let packages = - String_map.of_alist_multi packages - |> String_map.mapi ~f:(fun pkg dirs -> - match dirs with - | [dir] -> dir - | _ -> - die "Too many opam files for package %S:\n%s" - pkg - (String.concat ~sep:"\n" - (List.map dirs ~f:(fun dir -> - sprintf "- %s.opam" (Path.to_string dir))))) - in + let tree, stanzas = walk root [] String_map.empty in { file_tree = ftree ; tree ; stanzas diff --git a/src/jbuild_types.ml b/src/jbuild_types.ml index e313f951..9910d29e 100644 --- a/src/jbuild_types.ml +++ b/src/jbuild_types.ml @@ -694,4 +694,54 @@ module Stanza = struct | None -> acc | Some n -> String_set.add n acc) | _ -> acc)) + + let resolve_packages ts ~dir ~visible_packages = + let error fmt = + die ("File \"%s\", line 1, characters 0-0:\n\ + Error: " ^^ fmt) + (Path.to_string (Path.relative dir "jbuild")) + in + let known_packages () = + let visible_packages = String_map.bindings visible_packages in + let longest_pkg = + List.fold_left visible_packages ~init:0 ~f:(fun acc (pkg, _) -> + max (String.length pkg) acc) + in + String.concat ~sep:"\n" + (List.map visible_packages ~f:(fun (pkg, dir) -> + sprintf "- %-*s (because of %s)" longest_pkg pkg + (Path.to_string (Path.relative dir (pkg ^ ".opam"))))) + in + let check pkg = + if not (String_map.mem pkg visible_packages) then + error "package %S is not visible here.\n\ + The only packages I know of in %S are:\n\ + %s%s" + pkg + (Path.to_string dir) + (known_packages ()) + (hint pkg (String_map.keys visible_packages)) + in + let default () = + match String_map.keys visible_packages with + | [pkg] -> pkg + | [] -> error "no packages are defined here" + | pkgs -> + error "there is more than one package visible here:\n\ + %s\n\ + You need to add a (package ...) field in your (install ...) stanzas" + (known_packages ()) + in + List.map ts ~f:(fun stanza -> + match stanza with + | Library { public_name = Some name; _ } + | Executables { object_public_name = Some name; _ } -> + check (Findlib.root_package_name name); + stanza + | Install { package = Some pkg; _ } -> + check pkg; + stanza + | Install ({ package = None; _ } as install) -> + Install { install with package = Some (default ()) } + | _ -> stanza) end