diff --git a/CHANGES.md b/CHANGES.md index 5087b21ee8b..34bf9fa575c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -40,6 +40,10 @@ Unreleased - Fix cram tests inside vendored directories not being interpreted correctly. (@rgrinberg, #3860) +- Add `package` field to private libraries. This allows such libraries to be + installed and to be usable by other public libraries in the same project + (#3655, fixes #1017, @rgrinberg) + 2.7.1 (2/09/2020) ----------------- diff --git a/doc/dune-files.rst b/doc/dune-files.rst index 9e8467a0b4b..dd4938cf166 100644 --- a/doc/dune-files.rst +++ b/doc/dune-files.rst @@ -356,6 +356,12 @@ to use the :ref:`include_subdirs` stanza. want. The package name must be one of the packages that dune knows about, as determined by the :ref:`opam-files` +- ``(package )`` Install private library under the specified package. + Such a library is now usable by public libraries defined in the same project. + The findlib name for this library will be ``.__private__.``, + however the library's interface will be hidden from consumers outside the + project. + - ``(synopsis )`` should give a one-line description of the library. This is used by tools that list installed libraries diff --git a/src/dune_engine/lib_name.ml b/src/dune_engine/lib_name.ml index 7687e05326b..0fb2b32387e 100644 --- a/src/dune_engine/lib_name.ml +++ b/src/dune_engine/lib_name.ml @@ -1,5 +1,7 @@ open Stdune +let private_key = "__private__" + module Local = struct type t = string @@ -67,6 +69,9 @@ module Local = struct loop false 0 end) : Stringlike_intf.S with type t := t ) + + let mangled_path_under_package local_name = + [ private_key; to_string local_name ] end let split t = @@ -76,6 +81,13 @@ let split t = let to_local = Local.of_string_user_error +let to_local_exn t = + match Local.of_string_opt t with + | Some s -> s + | None -> + Code_error.raise "invalid Lib_name.t -> Lib_name.Local.t conversion" + [ ("t", String t) ] + include Stringlike.Make (struct type nonrec t = string @@ -95,6 +107,20 @@ include Stringlike.Make (struct | s -> Option.some_if (s.[0] <> '.') s end) +type analyze = + | Public of Package.Name.t * string list + | Private of Package.Name.t * Local.t + +let analyze t = + let pkg, rest = split t in + match rest with + | [ pkey; name ] when pkey = private_key -> Private (pkg, Local.of_string name) + | _ -> Public (pkg, rest) + +let mangled pkg local_name = + let under_pkg = Local.mangled_path_under_package local_name in + Package.Name.to_string pkg :: under_pkg |> String.concat ~sep:"." |> of_string + let of_local (_loc, t) = t let of_package_name p = Package.Name.to_string p diff --git a/src/dune_engine/lib_name.mli b/src/dune_engine/lib_name.mli index 7baa9c3265f..227440c8319 100644 --- a/src/dune_engine/lib_name.mli +++ b/src/dune_engine/lib_name.mli @@ -11,6 +11,8 @@ module Local : sig (** Description of valid library names *) val valid_format_doc : User_message.Style.t Pp.t + + val mangled_path_under_package : t -> string list end val compare : t -> t -> Ordering.t @@ -21,12 +23,22 @@ val of_local : Loc.t * Local.t -> t val to_local : Loc.t * t -> (Local.t, User_message.t) result +val to_local_exn : t -> Local.t + val split : t -> Package.Name.t * string list val package_name : t -> Package.Name.t val of_package_name : Package.Name.t -> t +type analyze = + | Public of Package.Name.t * string list + | Private of Package.Name.t * Local.t + +val analyze : t -> analyze + +val mangled : Package.Name.t -> Local.t -> t + module Map : Map.S with type key = t module Set : sig diff --git a/src/dune_rules/compilation_context.ml b/src/dune_rules/compilation_context.ml index cb022a534bf..e5eff1cb062 100644 --- a/src/dune_rules/compilation_context.ml +++ b/src/dune_rules/compilation_context.ml @@ -6,12 +6,12 @@ module SC = Super_context module Includes = struct type t = Command.Args.dynamic Command.Args.t Cm_kind.Dict.t - let make ~opaque ~requires : _ Cm_kind.Dict.t = + let make ~project ~opaque ~requires : _ Cm_kind.Dict.t = match requires with | Error exn -> Cm_kind.Dict.make_all (Command.Args.Fail { fail = (fun () -> raise exn) }) | Ok libs -> - let iflags = Lib.L.include_flags libs in + let iflags = Lib.L.include_flags ~project libs in let cmi_includes = Command.Args.memo (Command.Args.S @@ -118,8 +118,9 @@ let create ~super_context ~scope ~expander ~obj_dir ~modules ~flags ~requires_compile ~requires_link ?(preprocessing = Preprocessing.dummy) ~opaque ?stdlib ~js_of_ocaml ~dynlink ~package ?vimpl ?modes ?(bin_annot = true) () = + let project = Scope.project scope in let requires_compile = - if Dune_project.implicit_transitive_deps (Scope.project scope) then + if Dune_project.implicit_transitive_deps project then Lazy.force requires_link else requires_compile @@ -146,7 +147,7 @@ let create ~super_context ~scope ~expander ~obj_dir ~modules ~flags ; flags ; requires_compile ; requires_link - ; includes = Includes.make ~opaque ~requires:requires_compile + ; includes = Includes.make ~project ~opaque ~requires:requires_compile ; preprocessing ; opaque ; stdlib diff --git a/src/dune_rules/dune_file.ml b/src/dune_rules/dune_file.ml index 5fbdac46814..526b440cf00 100644 --- a/src/dune_rules/dune_file.ml +++ b/src/dune_rules/dune_file.ml @@ -507,7 +507,7 @@ module Library = struct type visibility = | Public of Public_lib.t - | Private + | Private of Package.t option type t = { name : Loc.t * Lib_name.Local.t @@ -609,6 +609,10 @@ module Library = struct field_o "instrumentation.backend" ( Dune_lang.Syntax.since Stanza.syntax (2, 7) >>> fields (field "ppx" (located Lib_name.decode)) ) + and+ package = + field_o "package" + ( Dune_lang.Syntax.since Stanza.syntax (2, 8) + >>> located Stanza_common.Pkg.decode ) in let wrapped = Wrapped.make ~wrapped ~implements ~special_builtin_support @@ -646,9 +650,17 @@ module Library = struct ] in let visibility = - match public with - | None -> Private - | Some public -> Public public + match (public, package) with + | None, None -> Private None + | Some public, None -> Public public + | None, Some (_loc, package) -> Private (Some package) + | Some public, Some (loc, _) -> + User_error.raise ~loc + [ Pp.textf + "This library has a pullic_name, it already belongs to the \ + package %s" + (Package.Name.to_string public.package.name) + ] in Option.both virtual_modules implements |> Option.iter ~f:(fun (virtual_modules, (_, impl)) -> @@ -694,12 +706,15 @@ module Library = struct let package t = match t.visibility with | Public p -> Some p.package - | Private -> None + | Private p -> p let sub_dir t = match t.visibility with | Public p -> p.sub_dir - | Private -> None + | Private None -> None + | Private (Some _) -> + Lib_name.Local.mangled_path_under_package (snd t.name) + |> String.concat ~sep:"/" |> Option.some let has_foreign t = Buildable.has_foreign t.buildable @@ -723,7 +738,7 @@ module Library = struct let best_name t = match t.visibility with - | Private -> Lib_name.of_local t.name + | Private _ -> Lib_name.of_local t.name | Public p -> snd p.name let is_virtual t = Option.is_some t.virtual_modules @@ -731,9 +746,16 @@ module Library = struct let is_impl t = Option.is_some t.implements let obj_dir ~dir t = + let private_lib = + match t.visibility with + | Private (Some _) -> true + | Private None + | Public _ -> + false + in Obj_dir.make_lib ~dir ~has_private_modules:(t.private_modules <> None) - (snd t.name) + ~private_lib (snd t.name) let main_module_name t : Lib_info.Main_module_name.t = match (t.implements, t.wrapped) with @@ -772,7 +794,7 @@ module Library = struct in let status = match conf.visibility with - | Private -> Lib_info.Status.Private conf.project + | Private pkg -> Lib_info.Status.Private (conf.project, pkg) | Public p -> Public (conf.project, p.package) in let virtual_library = is_virtual conf in @@ -835,6 +857,7 @@ module Library = struct let version = match status with | Public (_, pkg) -> pkg.version + | Installed_private | Installed | Private _ -> None @@ -1895,22 +1918,31 @@ module Library_redirect = struct module Local = struct type nonrec t = (Loc.t * Lib_name.Local.t) t + let for_lib (lib : Library.t) ~new_public_name ~loc : t = + { loc; new_public_name; old_name = lib.name; project = lib.project } + + let of_private_lib (lib : Library.t) : t option = + match lib.visibility with + | Public _ + | Private None -> + None + | Private (Some package) -> + let loc, name = lib.name in + let new_public_name = (loc, Lib_name.mangled package.name name) in + Some (for_lib lib ~loc ~new_public_name) + let of_lib (lib : Library.t) : t option = let open Option.O in - let* public = + let* public_name = match lib.visibility with - | Public p -> Some p - | Private -> None + | Public plib -> Some plib.name + | Private _ -> None in - if Lib_name.equal (Lib_name.of_local lib.name) (snd public.name) then + if Lib_name.equal (Lib_name.of_local lib.name) (snd public_name) then None else - Some - { loc = Loc.none - ; project = lib.project - ; old_name = lib.name - ; new_public_name = public.name - } + let loc = fst public_name in + Some (for_lib lib ~loc ~new_public_name:public_name) end end diff --git a/src/dune_rules/dune_file.mli b/src/dune_rules/dune_file.mli index 544ce7e3037..bd3ba3c1caa 100644 --- a/src/dune_rules/dune_file.mli +++ b/src/dune_rules/dune_file.mli @@ -114,7 +114,7 @@ end module Library : sig type visibility = | Public of Public_lib.t - | Private + | Private of Package.t option type t = { name : Loc.t * Lib_name.Local.t @@ -365,6 +365,8 @@ module Library_redirect : sig module Local : sig type nonrec t = (Loc.t * Lib_name.Local.t) t + + val of_private_lib : Library.t -> t option end end diff --git a/src/dune_rules/dune_package.ml b/src/dune_rules/dune_package.ml index b3be9fc2523..2175008045d 100644 --- a/src/dune_rules/dune_package.ml +++ b/src/dune_rules/dune_package.ml @@ -156,7 +156,11 @@ module Lib = struct let info : Path.t Lib_info.t = let src_dir = Obj_dir.dir obj_dir in let enabled = Lib_info.Enabled_status.Normal in - let status = Lib_info.Status.Installed in + let status = + match Lib_name.analyze name with + | Private (_, _) -> Lib_info.Status.Installed_private + | Public (_, _) -> Lib_info.Status.Installed + in let version = None in let main_module_name = Lib_info.Inherited.This main_module_name in let foreign_objects = Lib_info.Source.External foreign_objects in diff --git a/src/dune_rules/findlib/findlib.ml b/src/dune_rules/findlib/findlib.ml index 029cd3b4569..c4ae3ca6a3d 100644 --- a/src/dune_rules/findlib/findlib.ml +++ b/src/dune_rules/findlib/findlib.ml @@ -297,7 +297,11 @@ end = struct let kind = kind t in let sub_systems = Sub_system_name.Map.empty in let synopsis = description t in - let status = Lib_info.Status.Installed in + let status = + match Lib_name.analyze t.name with + | Private (_, _) -> Lib_info.Status.Installed_private + | Public (_, _) -> Lib_info.Status.Installed + in let src_dir = Obj_dir.dir obj_dir in let version = version t in let dune_version = None in diff --git a/src/dune_rules/gen_meta.ml b/src/dune_rules/gen_meta.ml index 26180c7c747..4864ab313a4 100644 --- a/src/dune_rules/gen_meta.ml +++ b/src/dune_rules/gen_meta.ml @@ -8,9 +8,7 @@ module Pub_name = struct | Dot of t * string | Id of string - let parse s = - let s = Lib_name.to_string s in - match String.split s ~on:'.' with + let of_list = function | [] -> assert false | x :: l -> let rec loop acc l = @@ -20,6 +18,10 @@ module Pub_name = struct in loop (Id x) l + let of_lib_name s = + let pkg, xs = Lib_name.split s in + of_list (Package.Name.to_string pkg :: xs) + let rec root = function | Dot (t, _) -> root t | Id n -> n @@ -157,18 +159,39 @@ let gen ~(package : Package.t) ~add_directory_entry entries = List.map entries ~f:(fun (e : Super_context.Lib_entry.t) -> match e with | Library lib -> ( - let name = Lib.Local.info lib |> Lib_info.name in - let pub_name = Pub_name.parse name in + let info = Lib.Local.info lib in + let pub_name = + let name = Lib_info.name info in + Pub_name.of_lib_name name + in match Pub_name.to_list pub_name with | [] -> assert false - | _package :: path -> + | package :: path -> + let pub_name, path = + match Lib_info.status info with + | Private (_, None) -> + (* Not possible b/c we wouldn't be generating a META file for a + private library without a package. *) + assert false + | Private (_, Some pkg) -> + assert (path = []); + let path = + Lib_name.Local.mangled_path_under_package + (Lib_name.Local.of_string package) + in + let pub_name = + Pub_name.of_list (Package.Name.to_string pkg.name :: path) + in + (pub_name, path) + | _ -> (pub_name, path) + in (pub_name, gen_lib pub_name ~path (Lib.Local.to_lib lib) ~version) ) | Deprecated_library_name { old_name = old_public_name, _ ; new_public_name = _, new_public_name ; _ } -> - ( Pub_name.parse (Dune_file.Public_lib.name old_public_name) + ( Pub_name.of_lib_name (Dune_file.Public_lib.name old_public_name) , version @ [ requires (Lib_name.Set.singleton new_public_name) ] )) in let pkgs = diff --git a/src/dune_rules/gen_rules.ml b/src/dune_rules/gen_rules.ml index c20f06eceaf..053fcf3291c 100644 --- a/src/dune_rules/gen_rules.ml +++ b/src/dune_rules/gen_rules.ml @@ -381,7 +381,15 @@ let filter_out_stanzas_from_hidden_packages ~visible_pkgs = | None -> true | Some package -> Package.Name.Map.mem visible_pkgs package.name in - Option.some_if include_stanza stanza) + if include_stanza then + Some stanza + else + match stanza with + | Library l -> + let open Option.O in + let+ redirect = Dune_file.Library_redirect.Local.of_private_lib l in + Dune_file.Library_redirect redirect + | _ -> None) let gen ~contexts ?only_packages conf = let open Fiber.O in diff --git a/src/dune_rules/install_rules.ml b/src/dune_rules/install_rules.ml index 04da64e53b9..256371e075f 100644 --- a/src/dune_rules/install_rules.ml +++ b/src/dune_rules/install_rules.ml @@ -134,31 +134,51 @@ end = struct let modes = Dune_file.Mode_conf.Set.eval lib.modes ~has_native in let { Mode.Dict.byte; native } = modes in let module_files = + let inside_subdir f = + match lib_subdir with + | None -> f + | Some d -> Filename.concat d f + in + let external_obj_dir = + Obj_dir.convert_to_external obj_dir ~dir:(Path.build dir) + in + let cm_dir m cm_kind = + let visibility = Module.visibility m in + let dir' = Obj_dir.cm_dir external_obj_dir cm_kind visibility in + if Path.equal (Path.build dir) dir' then + None + else + Path.basename dir' |> inside_subdir |> Option.some + in let virtual_library = Library.is_virtual lib in List.concat_map installable_modules ~f:(fun m -> - let cmi_file = - (Module.visibility m, Obj_dir.Module.cm_file_exn obj_dir m ~kind:Cmi) - in let cm_file kind = Obj_dir.Module.cm_file obj_dir m ~kind in - let if_ b x = + let if_ b (cm_kind, f) = if b then - Option.to_list x + match f with + | None -> [] + | Some f -> [ (cm_kind, f) ] else [] in + let cm_dir = cm_dir m in let other_cm_files = - [ if_ native (cm_file Cmx) - ; if_ (byte && virtual_library) (cm_file Cmo) + let open Cm_kind in + [ if_ true (Cmi, cm_file Cmi) + ; if_ native (Cmx, cm_file Cmx) + ; if_ (byte && virtual_library) (Cmo, cm_file Cmo) ; if_ (native && virtual_library) - (Obj_dir.Module.o_file obj_dir m ~ext_obj) + (Cmx, Obj_dir.Module.o_file obj_dir m ~ext_obj) ; List.filter_map Ml_kind.all ~f:(fun ml_kind -> - Obj_dir.Module.cmt_file obj_dir m ~ml_kind) + let open Option.O in + let+ cmt = Obj_dir.Module.cmt_file obj_dir m ~ml_kind in + (Cmi, cmt)) ] |> List.concat - |> List.map ~f:(fun f -> (Visibility.Public, f)) + |> List.map ~f:(fun (cm_kind, f) -> (cm_dir cm_kind, f)) in - cmi_file :: other_cm_files) + other_cm_files) in let lib_files, dll_files = let lib_files = lib_files ~modes ~dir ~dir_contents ~lib_config info in @@ -174,13 +194,7 @@ end = struct in List.concat [ sources - ; List.map module_files ~f:(fun (visibility, file) -> - let sub_dir = - match ((visibility : Visibility.t), lib_subdir) with - | Public, _ -> lib_subdir - | Private, None -> Some ".private" - | Private, Some dir -> Some (Filename.concat dir ".private") - in + ; List.map module_files ~f:(fun (sub_dir, file) -> make_entry ?sub_dir Lib file) ; List.map lib_files ~f:(make_entry Lib) ; List.map execs ~f:(make_entry Libexec) @@ -372,7 +386,18 @@ end = struct Config.local_install_lib_dir ~context:ctx.name ~package:pkg.name in let lib_root lib = - let _, subdir = Lib_name.split (Lib.name lib) in + let subdir = + let name = Lib.name lib in + let _, subdir = Lib_name.split name in + match + let info = Lib.info lib in + Lib_info.status info + with + | Private (_, Some _) -> + Lib_name.Local.mangled_path_under_package (Lib_name.to_local_exn name) + @ subdir + | _ -> subdir + in Path.Build.L.relative pkg_root subdir in let entries = diff --git a/src/dune_rules/lib.ml b/src/dune_rules/lib.ml index 2db80fc8e39..0f4df682cd3 100644 --- a/src/dune_rules/lib.ml +++ b/src/dune_rules/lib.ml @@ -246,6 +246,7 @@ module T = struct ; user_written_deps : Dune_file.Lib_deps.t ; implements : t Or_exn.t option ; lib_config : Lib_config.t + ; project : Dune_project.t option ; (* these fields cannot be forced until the library is instantiated *) default_implementation : t Or_exn.t Lazy.t option ; (* This is mutable to avoid this error: @@ -320,6 +321,7 @@ type db = ; all : Lib_name.t list Lazy.t ; lib_config : Lib_config.t ; instrument_with : Lib_name.t list + ; projects_by_package : Dune_project.t Package.Name.Map.t } and resolve_result = @@ -327,7 +329,9 @@ and resolve_result = | Found of Lib_info.external_ | Hidden of Lib_info.external_ Hidden.t | Invalid of exn - | Redirect of db option * (Loc.t * Lib_name.t) + | (* Redirect (None, lib) looks up lib in the same database *) + Redirect of + db option * (Loc.t * Lib_name.t) let lib_config (t : lib) = t.lib_config @@ -462,20 +466,41 @@ module L = struct Command.Args.Path dir :: A "-I" :: acc) |> List.rev ) - let include_paths ts = + let include_paths ?project ts = + let visible_cmi = + match project with + | None -> fun _ -> true + | Some project -> ( + let check_project lib = + match lib.project with + | None -> false + | Some project' -> Dune_project.equal project project' + in + fun lib -> + match Lib_info.status lib.info with + | Private (_, Some _) + | Installed_private -> + check_project lib + | _ -> true ) + in let dirs = List.fold_left ts ~init:Path.Set.empty ~f:(fun acc t -> let obj_dir = Lib_info.obj_dir t.info in - let public_cmi_dir = Obj_dir.public_cmi_dir obj_dir in + let acc = + if visible_cmi t then + let public_cmi_dir = Obj_dir.public_cmi_dir obj_dir in + Path.Set.add acc public_cmi_dir + else + acc + in let native_dir = Obj_dir.native_dir obj_dir in - List.fold_left ~f:Path.Set.add ~init:acc - [ public_cmi_dir; native_dir ]) + Path.Set.add acc native_dir) in match ts with | [] -> dirs | x :: _ -> Path.Set.remove dirs x.lib_config.stdlib_dir - let include_flags ts = to_iflags (include_paths ts) + let include_flags ?project ts = to_iflags (include_paths ?project ts) let c_include_paths ts = let dirs = @@ -717,12 +742,18 @@ module Dep_stack = struct Ok { stack = x :: t.stack; seen = Id.Set.add t.seen x; implements_via } end -let check_private_deps lib ~loc ~allow_private_deps = - let status = Lib_info.status lib.info in - if (not allow_private_deps) && Lib_info.Status.is_private status then - Error.private_deps_not_allowed ~loc lib.info - else - Ok lib +type private_deps = + | From_same_project + | Allow_all + +let check_private_deps lib ~loc ~(private_deps : private_deps) = + match private_deps with + | Allow_all -> Ok lib + | From_same_project -> ( + match Lib_info.status lib.info with + | Private (_, Some _) -> Ok lib + | Private (_, None) -> Error.private_deps_not_allowed ~loc lib.info + | _ -> Ok lib ) let already_in_table info name x = let to_dyn = Dyn.Encoder.(pair Path.to_dyn Lib_name.to_dyn) in @@ -932,7 +963,7 @@ module rec Resolve : sig val resolve_dep : db -> Loc.t * Lib_name.t - -> allow_private_deps:bool + -> private_deps:private_deps -> stack:Dep_stack.t -> lib Or_exn.t @@ -943,7 +974,7 @@ module rec Resolve : sig val resolve_simple_deps : db -> (Loc.t * Lib_name.t) list - -> allow_private_deps:bool + -> private_deps:private_deps -> stack:Dep_stack.t -> (t list, exn) Result.t @@ -957,7 +988,7 @@ module rec Resolve : sig val resolve_deps_and_add_runtime_deps : db -> Lib_dep.t list - -> allow_private_deps:bool + -> private_deps:private_deps -> pps:(Loc.t * Lib_name.t) list -> dune_version:Dune_lang.Syntax.Version.t option -> stack:Dep_stack.t @@ -989,8 +1020,18 @@ end = struct (* Add [id] to the table, to detect loops *) Table.add_exn db.table name (Status.Initializing unique_id); let status = Lib_info.status info in - let allow_private_deps = Lib_info.Status.is_private status in - let resolve name = resolve_dep db name ~allow_private_deps ~stack in + let private_deps = + match status with + (* [Allow_all] is used for libraries that are installed because we don't + have to check it again. It has been checked when compiling the + libraries before their installation *) + | Installed_private + | Private _ + | Installed -> + Allow_all + | Public (_, _) -> From_same_project + in + let resolve name = resolve_dep db name ~private_deps ~stack in let implements = let open Option.O in let+ ((loc, _) as name) = Lib_info.implements info in @@ -1054,8 +1095,8 @@ end = struct in let dune_version = Lib_info.dune_version info in Lib_info.requires info - |> resolve_deps_and_add_runtime_deps db ~allow_private_deps ~dune_version - ~pps ~stack + |> resolve_deps_and_add_runtime_deps db ~private_deps ~dune_version ~pps + ~stack in let requires = match implements with @@ -1067,7 +1108,7 @@ end = struct in let ppx_runtime_deps = Lib_info.ppx_runtime_deps info - |> resolve_simple_deps db ~allow_private_deps ~stack + |> resolve_simple_deps db ~private_deps ~stack in let src_dir = Lib_info.src_dir info in let map_error x = @@ -1077,6 +1118,11 @@ end = struct in let requires = map_error requires in let ppx_runtime_deps = map_error ppx_runtime_deps in + let project = + let open Option.O in + let* package = Lib_info.package info in + Package.Name.Map.find db.projects_by_package package + in let t = { info ; name @@ -1091,6 +1137,7 @@ end = struct ; default_implementation ; lib_config = db.lib_config ; re_exports + ; project } in t.sub_systems <- @@ -1126,10 +1173,10 @@ end = struct | Some x -> x | None -> resolve_name db name ~stack - let resolve_dep db (loc, name) ~allow_private_deps ~stack : t Or_exn.t = + let resolve_dep db (loc, name) ~private_deps ~stack : t Or_exn.t = match find_internal db name ~stack with | Initializing id -> Dep_stack.dependency_cycle stack id - | Found lib -> check_private_deps lib ~loc ~allow_private_deps + | Found lib -> check_private_deps lib ~loc ~private_deps | Not_found -> Error.not_found ~loc ~name | Invalid why -> Error why | Hidden h -> Hidden.error h ~loc ~name @@ -1165,11 +1212,11 @@ end = struct | _ -> instantiate db name info ~stack ~hidden:(Some hidden) ) let available_internal db (name : Lib_name.t) ~stack = - resolve_dep db (Loc.none, name) ~allow_private_deps:true ~stack + resolve_dep db (Loc.none, name) ~private_deps:Allow_all ~stack |> Result.is_ok - let resolve_simple_deps db names ~allow_private_deps ~stack = - Result.List.map names ~f:(resolve_dep db ~allow_private_deps ~stack) + let resolve_simple_deps db names ~private_deps ~stack = + Result.List.map names ~f:(resolve_dep db ~private_deps ~stack) let re_exports_closure ts = let visited = ref Set.empty in @@ -1200,7 +1247,7 @@ end = struct ; re_exports : lib list Or_exn.t } - let resolve_complex_deps db deps ~allow_private_deps ~stack : resolved_deps = + let resolve_complex_deps db deps ~private_deps ~stack : resolved_deps = let resolve_select { Lib_dep.Select.result_fn; choices; loc } = let res, src_fn = match @@ -1214,7 +1261,7 @@ end = struct Lib_name.Set.fold required ~init:[] ~f:(fun x acc -> (loc, x) :: acc) in - resolve_simple_deps ~allow_private_deps db deps ~stack + resolve_simple_deps ~private_deps db deps ~stack with | Ok ts -> Some (ts, file) | Error _ -> None) @@ -1231,7 +1278,7 @@ end = struct ~f:(fun (acc_res, acc_selects, acc_re_exports) dep -> match (dep : Lib_dep.t) with | Re_export (loc, name) -> - let lib = resolve_dep db (loc, name) ~allow_private_deps ~stack in + let lib = resolve_dep db (loc, name) ~private_deps ~stack in let acc_re_exports = let+ lib = lib and+ acc_re_exports = acc_re_exports in @@ -1245,7 +1292,7 @@ end = struct (acc_res, acc_selects, acc_re_exports) | Direct (loc, name) -> let acc_res = - let+ lib = resolve_dep db (loc, name) ~allow_private_deps ~stack + let+ lib = resolve_dep db (loc, name) ~private_deps ~stack and+ acc_res = acc_res in lib :: acc_res in @@ -1268,7 +1315,7 @@ end = struct ; runtime_deps : t list Or_exn.t } - let pp_deps db pps ~stack ~dune_version ~allow_private_deps = + let pp_deps db pps ~stack ~dune_version ~private_deps = let allow_only_ppx_deps = match dune_version with | Some version -> Dune_lang.Syntax.Version.Infix.(version >= (2, 2)) @@ -1295,7 +1342,7 @@ end = struct let* pps = Result.List.map pps ~f:(fun (loc, name) -> let* lib = - resolve_dep db (loc, name) ~allow_private_deps:true ~stack + resolve_dep db (loc, name) ~private_deps:Allow_all ~stack in match (allow_only_ppx_deps, Lib_info.kind lib.info) with | true, Normal -> Error.only_ppx_deps_allowed ~loc lib.info @@ -1310,16 +1357,16 @@ end = struct Result.List.concat_map pps ~f:(fun pp -> let* ppx_runtime_deps = pp.ppx_runtime_deps in Result.List.map ppx_runtime_deps - ~f:(check_private_deps ~loc ~allow_private_deps)) + ~f:(check_private_deps ~loc ~private_deps)) in pps_deps in { runtime_deps = deps; pps } - let add_pp_runtime_deps db resolved ~allow_private_deps ~pps ~dune_version - ~stack : resolved = + let add_pp_runtime_deps db resolved ~private_deps ~pps ~dune_version ~stack : + resolved = let { runtime_deps; pps } = - pp_deps db pps ~stack ~dune_version ~allow_private_deps + pp_deps db pps ~stack ~dune_version ~private_deps in let deps = let* runtime_deps = runtime_deps in @@ -1332,10 +1379,10 @@ end = struct ; re_exports = resolved.re_exports } - let resolve_deps_and_add_runtime_deps db deps ~allow_private_deps ~pps - ~dune_version ~stack = - resolve_complex_deps db ~allow_private_deps ~stack deps - |> add_pp_runtime_deps db ~allow_private_deps ~dune_version ~pps ~stack + let resolve_deps_and_add_runtime_deps db deps ~private_deps ~pps ~dune_version + ~stack = + resolve_complex_deps db ~private_deps ~stack deps + |> add_pp_runtime_deps db ~private_deps ~dune_version ~pps ~stack (* Compute transitive closure of libraries to figure which ones will trigger their default implementation. @@ -1467,17 +1514,24 @@ end = struct match t.db with | None -> Ok () | Some db -> ( - match find_internal db lib.name ~stack with - | Status.Found lib' -> - if lib = lib' then - Ok () - else - let req_by = - Dep_stack.to_required_by stack ~stop_at:t.orig_stack - in - Error.overlap ~in_workspace:lib'.info - ~installed:(lib.info, req_by) - | _ -> assert false ) + match Lib_info.status lib.info with + | Private (_, Some _) -> Ok () + | _ -> ( + match find_internal db lib.name ~stack with + | Status.Found lib' -> + if lib = lib' then + Ok () + else + let req_by = + Dep_stack.to_required_by stack ~stop_at:t.orig_stack + in + Error.overlap ~in_workspace:lib'.info + ~installed:(lib.info, req_by) + | found -> + Code_error.raise "Unexpected find result" + [ ("found", Status.to_dyn found) + ; ("lib.name", Lib_name.to_dyn lib.name) + ] ) ) in let* new_stack = Dep_stack.push stack ~implements_via (to_id lib) in let* deps = lib.requires in @@ -1648,17 +1702,18 @@ module DB = struct (* CR-soon amokhov: this whole module should be rewritten using the memoization framework instead of using mutable state. *) - let create ~parent ~resolve ~all ~lib_config () = + let create ~parent ~resolve ~projects_by_package ~all ~lib_config () = { parent ; resolve ; table = Table.create (module Lib_name) 1024 ; all = Lazy.from_fun all ; lib_config ; instrument_with = lib_config.Lib_config.instrument_with + ; projects_by_package } - let create_from_findlib ~lib_config findlib = - create () ~parent:None ~lib_config + let create_from_findlib ~lib_config ~projects_by_package findlib = + create () ~parent:None ~lib_config ~projects_by_package ~resolve:(fun name -> match Findlib.find findlib name with | Ok (Library pkg) -> Found (Dune_package.Lib.info pkg) @@ -1735,7 +1790,7 @@ module DB = struct ; re_exports = _ } = Resolve.resolve_deps_and_add_runtime_deps t deps ~pps - ~allow_private_deps:true ~stack:Dep_stack.empty + ~private_deps:Allow_all ~stack:Dep_stack.empty ~dune_version:(Some dune_version) in let requires_link = @@ -1771,7 +1826,7 @@ module DB = struct (* Here we omit the [only_ppx_deps_allowed] check because by the time we reach this point, all preprocess dependencies should have been checked already. *) let resolve_pps t pps = - Resolve.resolve_simple_deps t ~allow_private_deps:true pps + Resolve.resolve_simple_deps t ~private_deps:Allow_all pps ~stack:Dep_stack.empty let rec all ?(recursive = false) t = @@ -1816,7 +1871,13 @@ end let to_dune_lib ({ info; _ } as lib) ~modules ~foreign_objects ~dir = let loc = Lib_info.loc info in - let add_loc = List.map ~f:(fun x -> (loc, x.name)) in + let mangled_name lib = + match Lib_info.status lib.info with + | Private (_, Some pkg) -> + Lib_name.mangled pkg.name (Lib_name.to_local_exn lib.name) + | _ -> lib.name + in + let add_loc = List.map ~f:(fun x -> (loc, mangled_name x)) in let obj_dir = match Obj_dir.to_local (obj_dir lib) with | None -> assert false @@ -1835,7 +1896,7 @@ let to_dune_lib ({ info; _ } as lib) ~modules ~foreign_objects ~dir = | Some (loc, _), Some field -> let open Result.O in let+ field = field in - Some (loc, field.name) + Some (loc, mangled_name field) in let open Result.O in let+ implements = @@ -1854,13 +1915,14 @@ let to_dune_lib ({ info; _ } as lib) ~modules ~foreign_objects ~dir = let requires = List.map requires ~f:(fun lib -> if List.exists re_exports ~f:(fun r -> r = lib) then - Lib_dep.Re_export (loc, lib.name) + Lib_dep.Re_export (loc, mangled_name lib) else - Direct (loc, lib.name)) + Direct (loc, mangled_name lib)) in + let name = mangled_name lib in let info = - Lib_info.for_dune_package info ~ppx_runtime_deps ~requires ~foreign_objects - ~obj_dir ~implements ~default_implementation ~sub_systems + Lib_info.for_dune_package info ~name ~ppx_runtime_deps ~requires + ~foreign_objects ~obj_dir ~implements ~default_implementation ~sub_systems in Dune_package.Lib.make ~info ~modules:(Some modules) ~main_module_name diff --git a/src/dune_rules/lib.mli b/src/dune_rules/lib.mli index 81f636e6cfe..846e5bd51c5 100644 --- a/src/dune_rules/lib.mli +++ b/src/dune_rules/lib.mli @@ -66,9 +66,9 @@ module L : sig val to_iflags : Path.Set.t -> _ Command.Args.t - val include_paths : t -> Path.Set.t + val include_paths : ?project:Dune_project.t -> t -> Path.Set.t - val include_flags : t -> _ Command.Args.t + val include_flags : ?project:Dune_project.t -> t -> _ Command.Args.t val c_include_flags : t -> _ Command.Args.t @@ -174,12 +174,17 @@ module DB : sig val create : parent:t option -> resolve:(Lib_name.t -> Resolve_result.t) + -> projects_by_package:Dune_project.t Package.Name.Map.t -> all:(unit -> Lib_name.t list) -> lib_config:Lib_config.t -> unit -> t - val create_from_findlib : lib_config:Lib_config.t -> Findlib.t -> t + val create_from_findlib : + lib_config:Lib_config.t + -> projects_by_package:Dune_project.t Package.Name.Map.t + -> Findlib.t + -> t val find : t -> Lib_name.t -> lib option diff --git a/src/dune_rules/lib_info.ml b/src/dune_rules/lib_info.ml index a1eef34e87e..b877a4bab54 100644 --- a/src/dune_rules/lib_info.ml +++ b/src/dune_rules/lib_info.ml @@ -176,27 +176,35 @@ end module Status = struct type t = + | Installed_private | Installed | Public of Dune_project.t * Package.t - | Private of Dune_project.t + | Private of Dune_project.t * Package.t option let to_dyn x = let open Dyn.Encoder in match x with + | Installed_private -> constr "Installed_private" [] | Installed -> constr "Installed" [] | Public (project, package) -> constr "Public" [ Dune_project.to_dyn project; Package.to_dyn package ] - | Private proj -> constr "Private" [ Dune_project.to_dyn proj ] + | Private (proj, package) -> + constr "Private" + [ Dune_project.to_dyn proj; option Package.to_dyn package ] let is_private = function - | Private _ -> true + | Installed_private + | Private _ -> + true | Installed | Public _ -> false let project = function - | Installed -> None - | Private project + | Installed_private + | Installed -> + None + | Private (project, _) | Public (project, _) -> Some project end @@ -344,8 +352,8 @@ let best_src_dir t = Option.value ~default:t.src_dir t.orig_src_dir let set_version t version = { t with version } -let for_dune_package t ~ppx_runtime_deps ~requires ~foreign_objects ~obj_dir - ~implements ~default_implementation ~sub_systems = +let for_dune_package t ~name ~ppx_runtime_deps ~requires ~foreign_objects + ~obj_dir ~implements ~default_implementation ~sub_systems = let foreign_objects = Source.External foreign_objects in let orig_src_dir = match !Clflags.store_orig_src_dir with @@ -363,6 +371,7 @@ let for_dune_package t ~ppx_runtime_deps ~requires ~foreign_objects ~obj_dir in { t with ppx_runtime_deps + ; name ; requires ; foreign_objects ; obj_dir @@ -524,9 +533,11 @@ let to_dyn path let package t = match t.status with - | Installed -> Some (Lib_name.package_name t.name) + | Installed_private + | Installed -> + Some (Lib_name.package_name t.name) | Public (_, p) -> Some p.name - | Private _ -> None + | Private (_, p) -> Option.map p ~f:(fun t -> t.name) let has_native_archive lib_config modules = Lib_config.linker_can_create_empty_archives lib_config diff --git a/src/dune_rules/lib_info.mli b/src/dune_rules/lib_info.mli index ff6e3ace2ce..0460dc859f8 100644 --- a/src/dune_rules/lib_info.mli +++ b/src/dune_rules/lib_info.mli @@ -11,9 +11,10 @@ open Stdune module Status : sig type t = + | Installed_private | Installed | Public of Dune_project.t * Package.t - | Private of Dune_project.t + | Private of Dune_project.t * Package.t option val is_private : t -> bool @@ -175,6 +176,7 @@ val set_version : 'a t -> string option -> 'a t val for_dune_package : Path.t t + -> name:Lib_name.t -> ppx_runtime_deps:(Loc.t * Lib_name.t) list -> requires:Lib_dep.t list -> foreign_objects:Path.t list diff --git a/src/dune_rules/link_time_code_gen.ml b/src/dune_rules/link_time_code_gen.ml index b34a89e803a..027f834e32d 100644 --- a/src/dune_rules/link_time_code_gen.ml +++ b/src/dune_rules/link_time_code_gen.ml @@ -135,7 +135,9 @@ let build_info_code cctx ~libs ~api_version = | Some v -> sprintf "Some %S" v | None -> ( match Lib_info.status (Lib.info lib) with - | Installed -> "None" + | Installed_private + | Installed -> + "None" | Public (_, p) -> version_of_package p | Private _ -> let p = diff --git a/src/dune_rules/obj_dir.ml b/src/dune_rules/obj_dir.ml index 759186caaec..4839d9c3669 100644 --- a/src/dune_rules/obj_dir.ml +++ b/src/dune_rules/obj_dir.ml @@ -22,24 +22,32 @@ module External = struct type t = { public_dir : Path.t ; private_dir : Path.t option + ; public_cmi_dir : Path.t option } - let make ~dir ~has_private_modules = + let make ~dir ~has_private_modules ~private_lib = let private_dir = if has_private_modules then Some (Path.relative dir ".private") else None in - { public_dir = dir; private_dir } + let public_cmi_dir = + if private_lib then + Some (Path.relative dir ".public_cmi") + else + None + in + { public_dir = dir; private_dir; public_cmi_dir } - let public_cmi_dir t = t.public_dir + let public_cmi_dir t = Option.value ~default:t.public_dir t.public_cmi_dir - let to_dyn { public_dir; private_dir } = + let to_dyn { public_dir; private_dir; public_cmi_dir } = let open Dyn.Encoder in record [ ("public_dir", Path.to_dyn public_dir) ; ("private_dir", option Path.to_dyn private_dir) + ; ("public_cmi_dir", option Path.to_dyn public_cmi_dir) ] let cm_dir t (cm_kind : Cm_kind.t) (visibility : Visibility.t) = @@ -47,25 +55,30 @@ module External = struct | Cmi, Private, Some p -> p | Cmi, Private, None -> Code_error.raise "External.cm_dir" [ ("t", to_dyn t) ] - | Cmi, Public, _ - | (Cmo | Cmx), _, _ -> - t.public_dir + | Cmi, Public, _ -> public_cmi_dir t + | (Cmo | Cmx), _, _ -> t.public_dir - let encode { public_dir; private_dir } = + let encode { public_dir; private_dir; public_cmi_dir } = let open Dune_lang.Encoder in let extract d = Path.descendant ~of_:public_dir d |> Option.value_exn |> Path.to_string in let private_dir = Option.map ~f:extract private_dir in - record_fields [ field_o "private_dir" string private_dir ] + let public_cmi_dir = Option.map ~f:extract public_cmi_dir in + record_fields + [ field_o "private_dir" string private_dir + ; field_o "public_cmi_dir" string public_cmi_dir + ] let decode ~dir = + let public_dir = dir in let open Dune_lang.Decoder in fields - (let+ private_dir = field_o "private_dir" string in - let public_dir = dir in + (let+ private_dir = field_o "private_dir" string + and+ public_cmi_dir = field_o "public_cmi_dir" string in let private_dir = Option.map ~f:(Path.relative dir) private_dir in - { public_dir; private_dir }) + let public_cmi_dir = Option.map ~f:(Path.relative dir) public_cmi_dir in + { public_dir; private_dir; public_cmi_dir }) let byte_dir t = t.public_dir @@ -79,8 +92,8 @@ module External = struct let all_obj_dirs t ~mode:_ = [ t.public_dir ] - let all_cmis { public_dir; private_dir } = - List.filter_opt [ Some public_dir; private_dir ] + let all_cmis { public_dir; private_dir; public_cmi_dir } = + List.filter_opt [ Some public_dir; private_dir; public_cmi_dir ] let cm_public_dir t (cm_kind : Cm_kind.t) = match cm_kind with @@ -96,9 +109,11 @@ module Local = struct ; native_dir : Path.Build.t ; byte_dir : Path.Build.t ; public_cmi_dir : Path.Build.t option + ; private_lib : bool } - let to_dyn { dir; obj_dir; native_dir; byte_dir; public_cmi_dir } = + let to_dyn { dir; obj_dir; native_dir; byte_dir; public_cmi_dir; private_lib } + = let open Dyn.Encoder in record [ ("dir", Path.Build.to_dyn dir) @@ -106,10 +121,11 @@ module Local = struct ; ("native_dir", Path.Build.to_dyn native_dir) ; ("byte_dir", Path.Build.to_dyn byte_dir) ; ("public_cmi_dir", option Path.Build.to_dyn public_cmi_dir) + ; ("private_lib", bool private_lib) ] - let make ~dir ~obj_dir ~native_dir ~byte_dir ~public_cmi_dir = - { dir; obj_dir; native_dir; byte_dir; public_cmi_dir } + let make ~dir ~obj_dir ~native_dir ~byte_dir ~public_cmi_dir ~private_lib = + { dir; obj_dir; native_dir; byte_dir; public_cmi_dir; private_lib } let need_dedicated_public_dir t = Option.is_some t.public_cmi_dir @@ -134,7 +150,7 @@ module Local = struct in Path.Build.Set.of_list dirs |> Path.Build.Set.to_list - let make_lib ~dir ~has_private_modules lib_name = + let make_lib ~dir ~has_private_modules ~private_lib lib_name = let obj_dir = Paths.library_object_directory ~dir lib_name in let public_cmi_dir = Option.some_if has_private_modules (Paths.library_public_cmi_dir ~obj_dir) @@ -142,14 +158,14 @@ module Local = struct make ~dir ~obj_dir ~native_dir:(Paths.library_native_dir ~obj_dir) ~byte_dir:(Paths.library_byte_dir ~obj_dir) - ~public_cmi_dir + ~public_cmi_dir ~private_lib let make_exe ~dir ~name = let obj_dir = Paths.executable_object_directory ~dir name in make ~dir ~obj_dir ~native_dir:(Paths.library_native_dir ~obj_dir) ~byte_dir:(Paths.library_byte_dir ~obj_dir) - ~public_cmi_dir:None + ~public_cmi_dir:None ~private_lib:false let cm_dir t cm_kind _ = match cm_kind with @@ -190,11 +206,11 @@ let decode ~dir = let+ external_ = External.decode ~dir in External external_ -let make_lib ~dir ~has_private_modules lib_name = - Local (Local.make_lib ~dir ~has_private_modules lib_name) +let make_lib ~dir ~has_private_modules ~private_lib lib_name = + Local (Local.make_lib ~dir ~has_private_modules ~private_lib lib_name) let make_external_no_private ~dir = - External (External.make ~dir ~has_private_modules:false) + External (External.make ~dir ~has_private_modules:false ~private_lib:false) let get_path : type a. a t -> l:(Local.t -> Path.Build.t) -> e:(External.t -> Path.t) -> a @@ -226,7 +242,8 @@ let convert_to_external (t : Path.Build.t t) ~dir = match t with | Local e -> let has_private_modules = Local.need_dedicated_public_dir e in - External (External.make ~dir ~has_private_modules) + External + (External.make ~dir ~has_private_modules ~private_lib:e.private_lib) | _ -> assert false let all_cmis (type path) (t : path t) : path list = diff --git a/src/dune_rules/obj_dir.mli b/src/dune_rules/obj_dir.mli index c182a754945..22048d1917a 100644 --- a/src/dune_rules/obj_dir.mli +++ b/src/dune_rules/obj_dir.mli @@ -57,6 +57,7 @@ val all_obj_dirs : 'path t -> mode:Mode.t -> 'path list val make_lib : dir:Path.Build.t -> has_private_modules:bool + -> private_lib:bool -> Lib_name.Local.t -> Path.Build.t t @@ -72,8 +73,6 @@ val convert_to_external : Path.Build.t t -> dir:Path.t -> Path.t t val cm_dir : 'path t -> Cm_kind.t -> Visibility.t -> 'path -val cm_public_dir : 'path t -> Cm_kind.t -> 'path - val to_dyn : _ t -> Dyn.t val make_exe : dir:Path.Build.t -> name:string -> Path.Build.t t diff --git a/src/dune_rules/odoc.ml b/src/dune_rules/odoc.ml index 3661a4cb147..d807ada9697 100644 --- a/src/dune_rules/odoc.ml +++ b/src/dune_rules/odoc.ml @@ -35,9 +35,11 @@ let lib_unique_name lib = let info = Lib.info lib in let status = Lib_info.status info in match status with - | Installed -> assert false + | Installed_private + | Installed -> + assert false | Public _ -> Lib_name.to_string name - | Private project -> Scope_key.to_string name project + | Private (project, _) -> Scope_key.to_string name project let pkg_or_lnu lib = match Lib_info.package (Lib.info lib) with @@ -645,7 +647,7 @@ let init sctx = | Dune_file.Library (l : Dune_file.Library.t) -> ( match l.visibility with | Public _ -> None - | Private -> + | Private _ -> let scope = SC.find_scope_by_dir sctx w.ctx_dir in Library.best_name l |> Lib.DB.find_even_when_hidden (Scope.libs scope) diff --git a/src/dune_rules/preprocessing.ml b/src/dune_rules/preprocessing.ml index c5c5e4cfc9a..f0ac47ac562 100644 --- a/src/dune_rules/preprocessing.ml +++ b/src/dune_rules/preprocessing.ml @@ -60,7 +60,8 @@ end = struct let info = Lib.info lib in let status = Lib_info.status info in match status with - | Private scope_name -> Some scope_name + | Private (scope_name, _) -> Some scope_name + | Installed_private | Public _ | Installed -> None diff --git a/src/dune_rules/scope.ml b/src/dune_rules/scope.ml index a46f9bf9d17..4afd4ea5df9 100644 --- a/src/dune_rules/scope.ml +++ b/src/dune_rules/scope.ml @@ -52,7 +52,7 @@ module DB = struct | Deprecated_library_name of Dune_file.Deprecated_library_name.t end - let create_from_stanzas ~parent ~lib_config stanzas = + let create_db_from_stanzas ~parent ~lib_config stanzas = let map : Found_or_redirect.t Lib_name.Map.t = List.concat_map stanzas ~f:(fun stanza -> match (stanza : Library_related_stanza.t) with @@ -95,7 +95,7 @@ module DB = struct ; Pp.textf "- %s" (Loc.to_file_colon_line loc2) ]) in - Lib.DB.create () ~parent + Lib.DB.create () ~parent:(Some parent) ~resolve:(fun name -> match Lib_name.Map.find map name with | None -> Lib.DB.Resolve_result.not_found @@ -135,7 +135,7 @@ module DB = struct | Some (Name name) -> Lib.DB.Resolve_result.redirect None name (* Create a database from the public libraries defined in the stanzas *) - let public_libs t ~installed_libs ~lib_config stanzas = + let public_libs t ~installed_libs ~lib_config ~projects_by_package stanzas = let public_libs = List.filter_map stanzas ~f:(fun (stanza : Library_related_stanza.t) -> match stanza with @@ -158,13 +158,9 @@ module DB = struct let named p loc = Option.some_if (name = p) loc in match stanza with | Library (_, { buildable = { loc; _ }; visibility = Public p; _ }) - -> + | Deprecated_library_name + { Dune_file.Library_redirect.loc; old_name = p, _; _ } -> named (Dune_file.Public_lib.name p) loc - | Deprecated_library_name d -> - let old_name = - Dune_file.Deprecated_library_name.old_public_name d - in - named old_name d.loc | _ -> None) with | [] @@ -179,11 +175,12 @@ module DB = struct ] ) in let resolve = resolve t public_libs in - Lib.DB.create ~parent:(Some installed_libs) ~resolve + Lib.DB.create ~parent:(Some installed_libs) ~resolve ~projects_by_package ~all:(fun () -> Lib_name.Map.keys public_libs) ~lib_config () - let scopes_by_dir context ~projects ~public_libs stanzas coq_stanzas = + let scopes_by_dir context ~projects_by_package ~projects ~public_libs stanzas + coq_stanzas = let projects_by_dir = List.map projects ~f:(fun (project : Dune_project.t) -> (Dune_project.root project, project)) @@ -219,7 +216,8 @@ module DB = struct let project = Option.value_exn project in let stanzas, coq_stanzas = Option.value stanzas ~default:([], []) in let db = - create_from_stanzas stanzas ~parent:(Some public_libs) ~lib_config + create_db_from_stanzas stanzas ~parent:public_libs + ~projects_by_package ~lib_config in let coq_db = Coq_lib.DB.create_from_coqlib_stanzas coq_stanzas in let root = @@ -227,14 +225,16 @@ module DB = struct in Some { project; db; coq_db; root }) - let create ~projects ~context ~installed_libs stanzas coq_stanzas = + let create ~projects_by_package ~context ~installed_libs ~projects stanzas + coq_stanzas = let t = Fdecl.create Dyn.Encoder.opaque in let public_libs = let lib_config = Context.lib_config context in - public_libs t ~installed_libs ~lib_config stanzas + public_libs t ~installed_libs ~lib_config ~projects_by_package stanzas in let by_dir = - scopes_by_dir context ~projects ~public_libs stanzas coq_stanzas + scopes_by_dir context ~projects ~projects_by_package ~public_libs stanzas + coq_stanzas in let value = { by_dir } in Fdecl.set t value; @@ -246,7 +246,8 @@ module DB = struct [ ("dir", Path.Build.to_dyn dir) ]; find_by_dir t (Path.Build.drop_build_context_exn dir) - let create_from_stanzas ~projects ~context ~installed_libs stanzas = + let create_from_stanzas ~projects ~projects_by_package ~context + ~installed_libs stanzas = let stanzas, coq_stanzas = Dune_load.Dune_file.fold_stanzas stanzas ~init:([], []) ~f:(fun dune_file stanza (acc, coq_acc) -> @@ -266,5 +267,6 @@ module DB = struct (acc, (ctx_dir, coq_lib) :: coq_acc) | _ -> (acc, coq_acc)) in - create ~projects ~context ~installed_libs stanzas coq_stanzas + create ~projects ~context ~installed_libs ~projects_by_package stanzas + coq_stanzas end diff --git a/src/dune_rules/scope.mli b/src/dune_rules/scope.mli index 540844b76f7..a077d7d4b4e 100644 --- a/src/dune_rules/scope.mli +++ b/src/dune_rules/scope.mli @@ -25,6 +25,7 @@ module DB : sig (** Return the new scope database as well as the public libraries database *) val create_from_stanzas : projects:Dune_project.t list + -> projects_by_package:Dune_project.t Package.Name.Map.t -> context:Context.t -> installed_libs:Lib.DB.t -> Dune_load.Dune_file.t list diff --git a/src/dune_rules/super_context.ml b/src/dune_rules/super_context.ml index 42aaefb9c86..8b9b7539593 100644 --- a/src/dune_rules/super_context.ml +++ b/src/dune_rules/super_context.ml @@ -256,7 +256,7 @@ let internal_lib_names t = | Dune_file.Library lib -> Lib_name.Set.add ( match lib.visibility with - | Private -> acc + | Private _ -> acc | Public public -> Lib_name.Set.add acc (Dune_file.Public_lib.name public) ) (Lib_name.of_local lib.name) @@ -452,8 +452,16 @@ let get_installed_binaries stanzas ~(context : Context.t) = | _ -> acc) let create_lib_entries_by_package ~public_libs stanzas = - Dir_with_dune.deep_fold stanzas ~init:[] ~f:(fun _ stanza acc -> + Dir_with_dune.deep_fold stanzas ~init:[] ~f:(fun d stanza acc -> match stanza with + | Dune_file.Library ({ visibility = Private (Some pkg); _ } as lib) -> ( + match + let db = Scope.libs d.scope in + Lib.DB.find db (Dune_file.Library.best_name lib) + with + | None -> acc + | Some lib -> + (pkg.name, Lib_entry.Library (Lib.Local.of_lib_exn lib)) :: acc ) | Dune_file.Library { visibility = Public pub; _ } -> ( match Lib.DB.find public_libs (Dune_file.Public_lib.name pub) with | None -> @@ -476,11 +484,22 @@ let create_lib_entries_by_package ~public_libs stanzas = (List.sort ~compare:(fun a b -> Lib_name.compare (Lib_entry.name a) (Lib_entry.name b))) +let create_projects_by_package projects : Dune_project.t Package.Name.Map.t = + List.concat_map projects ~f:(fun project -> + Dune_project.packages project + |> Package.Name.Map.values + |> List.map ~f:(fun (pkg : Package.t) -> (pkg.name, project))) + |> Package.Name.Map.of_list_exn + let create ~(context : Context.t) ?host ~projects ~packages ~stanzas = let lib_config = Context.lib_config context in - let installed_libs = Lib.DB.create_from_findlib context.findlib ~lib_config in + let projects_by_package = create_projects_by_package projects in + let installed_libs = + Lib.DB.create_from_findlib context.findlib ~lib_config ~projects_by_package + in let scopes, public_libs = - Scope.DB.create_from_stanzas ~projects ~context ~installed_libs stanzas + Scope.DB.create_from_stanzas ~projects ~projects_by_package ~context + ~installed_libs stanzas in let stanzas = List.map stanzas ~f:(fun { Dune_load.Dune_file.dir; project; stanzas } -> diff --git a/test/blackbox-tests/test-cases/private-modules.t/run.t b/test/blackbox-tests/test-cases/private-modules.t/run.t index 3d2518895f5..320bd992a7d 100644 --- a/test/blackbox-tests/test-cases/private-modules.t/run.t +++ b/test/blackbox-tests/test-cases/private-modules.t/run.t @@ -15,10 +15,10 @@ Private modules are not excluded from the install file, but installed in the .pr $ dune build --root private-subdir | grep -i priv Entering directory 'private-subdir' "_build/install/default/lib/lib/.private/lib__Priv.cmi" {".private/lib__Priv.cmi"} + "_build/install/default/lib/lib/.private/lib__Priv.cmt" {".private/lib__Priv.cmt"} "_build/install/default/lib/lib/foo/.private/priv2.cmi" {"foo/.private/priv2.cmi"} - "_build/install/default/lib/lib/foo/priv2.cmt" {"foo/priv2.cmt"} + "_build/install/default/lib/lib/foo/.private/priv2.cmt" {"foo/.private/priv2.cmt"} "_build/install/default/lib/lib/foo/priv2.cmx" {"foo/priv2.cmx"} "_build/install/default/lib/lib/foo/priv2.ml" {"foo/priv2.ml"} - "_build/install/default/lib/lib/lib__Priv.cmt" "_build/install/default/lib/lib/lib__Priv.cmx" "_build/install/default/lib/lib/priv.ml" diff --git a/test/blackbox-tests/test-cases/private-package-lib.t/run.t b/test/blackbox-tests/test-cases/private-package-lib.t/run.t new file mode 100644 index 00000000000..44eb6aedcee --- /dev/null +++ b/test/blackbox-tests/test-cases/private-package-lib.t/run.t @@ -0,0 +1,167 @@ +This test demonstrates private libraries that belong to a package. Such +libraries are installed as public libraries under the package.__private__. +findlib name, and are only available to libraries and executables in the same +package. + + $ cat >dune-project < (lang dune 2.8) + > (package (name foo)) + > (package (name bar)) + > EOF + +First, we define a private library: + + $ mkdir private + $ cat >private/secret.ml < let secret = "secret string" + > EOF + $ cat >private/dune < (library + > (name secret) + > (package foo)) + > EOF + $ dune build @all + +A public library may build against it: + + $ mkdir public + $ cat >public/dune < (library + > (name foo) + > (libraries secret) + > (public_name foo.bar)) + > EOF + $ cat >public/foo.ml < let foo = "from library foo " ^ Secret.secret + > EOF + $ dune build @install + +The naming convention puts the artifacts of private libs under __private__: + + $ ls _build/install/default/lib/foo/__private__/secret | grep -i \.cm + secret.cma + secret.cmx + secret.cmxa + secret.cmxs + +Note the name mangling convention in the META file: + + $ cat _build/install/default/lib/foo/META + package "__private__" ( + directory = "__private__" + package "secret" ( + directory = "secret" + description = "" + requires = "" + archive(byte) = "secret.cma" + archive(native) = "secret.cmxa" + plugin(byte) = "secret.cma" + plugin(native) = "secret.cmxs" + ) + ) + package "bar" ( + directory = "bar" + description = "" + requires = "secret" + archive(byte) = "foo.cma" + archive(native) = "foo.cmxa" + plugin(byte) = "foo.cma" + plugin(native) = "foo.cmxs" + ) + +We want to see mangled names in the dune-package file as well: + + $ cat _build/install/default/lib/foo/dune-package | grep __private__ + (requires foo.__private__.secret) + (name foo.__private__.secret) + (byte __private__/secret/secret.cma) + (native __private__/secret/secret.cmxa)) + (byte __private__/secret/secret.cma) + (native __private__/secret/secret.cmxs)) + (native_archives __private__/secret/secret.a) + +Cmi for secret library must not be visible for normal users. Hence they must be +hidden. + + $ grep __private__ _build/default/foo.install + "_build/install/default/lib/foo/__private__/secret/.public_cmi/secret.cmi" {"__private__/secret/.public_cmi/secret.cmi"} + "_build/install/default/lib/foo/__private__/secret/.public_cmi/secret.cmt" {"__private__/secret/.public_cmi/secret.cmt"} + "_build/install/default/lib/foo/__private__/secret/secret.a" {"__private__/secret/secret.a"} + "_build/install/default/lib/foo/__private__/secret/secret.cma" {"__private__/secret/secret.cma"} + "_build/install/default/lib/foo/__private__/secret/secret.cmx" {"__private__/secret/secret.cmx"} + "_build/install/default/lib/foo/__private__/secret/secret.cmxa" {"__private__/secret/secret.cmxa"} + "_build/install/default/lib/foo/__private__/secret/secret.cmxs" {"__private__/secret/secret.cmxs"} + "_build/install/default/lib/foo/__private__/secret/secret.ml" {"__private__/secret/secret.ml"} + +We make sure that executables can use the secret library like they can use any other private library + + $ mkdir bar + $ cat >bar/dune < (executable + > (name bar) + > (libraries secret)) + > EOF + $ cat >bar/bar.ml < print_endline "from bar.ml" + > EOF + $ dune exec ./bar/bar.exe + from bar.ml + +Now we try to use the library in a subproject: + + $ mkdir subproj + $ echo "(lang dune 2.8)" > subproj/dune-project + $ cat >subproj/dune < (executable + > (name subproj) + > (libraries foo.bar)) + > EOF + $ cat >subproj/subproj.ml < print_endline Foo.foo + > EOF + $ dune exec ./subproj/subproj.exe + from library foo secret string + +But we shouldn't be able to access it directly: + + $ cat >subproj/subproj.ml < print_endline Secret.secret + > EOF + $ dune exec ./subproj/subproj.exe + File "subproj/subproj.ml", line 1, characters 14-27: + 1 | print_endline Secret.secret + ^^^^^^^^^^^^^ + Error: Unbound module Secret + [1] + +Now we make sure such libraries are transitively usable when installed: + + $ mkdir use + $ cat >use/dune < (executable + > (name run) + > (libraries foo.bar)) + > EOF + $ cat >use/run.ml < print_endline ("Using library foo: " ^ Foo.foo) + > EOF + $ echo "(lang dune 2.8)" > use/dune-project + $ export OCAMLPATH=$PWD/_build/install/default/lib + $ dune exec --root use -- ./run.exe + Entering directory 'use' + Entering directory 'use' + Using library foo: from library foo secret string + +But we cannot use such libraries directly: + + $ cat >use/run.ml < print_endline ("direct access attempt: " ^ Secret.secret) + > EOF + $ dune exec --root use -- ./run.exe + Entering directory 'use' + Entering directory 'use' + File "run.ml", line 1, characters 43-56: + 1 | print_endline ("direct access attempt: " ^ Secret.secret) + ^^^^^^^^^^^^^ + Error: Unbound module Secret + [1]