diff --git a/src/dune_engine/lib_name.ml b/src/dune_engine/lib_name.ml index 7687e05326b1..0fb2b32387ef 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 7baa9c3265f3..227440c8319e 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 bacce2bb6304..670d3a990694 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 @@ -114,8 +114,9 @@ let context t = Super_context.context t.super_context 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 () = + 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 @@ -142,7 +143,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 78e9b407c25d..3a8fd743a548 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 @@ -763,7 +778,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 @@ -811,6 +826,7 @@ module Library = struct let version = match status with | Public (_, pkg) -> pkg.version + | Installed_private | Installed | Private _ -> None @@ -1872,19 +1888,22 @@ module Library_redirect = struct 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 -> None + | Private (Some package) -> + let loc, name = lib.name in + Some (loc, Lib_name.mangled package.name name) 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 + ; new_public_name = public_name } end end diff --git a/src/dune_rules/dune_file.mli b/src/dune_rules/dune_file.mli index c42342f3a25f..0f7ecf1d94f6 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 diff --git a/src/dune_rules/dune_package.ml b/src/dune_rules/dune_package.ml index b3be9fc2523e..2175008045df 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 029cd3b4569e..c4ae3ca6a3da 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 26180c7c7475..4864ab313a43 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/install_rules.ml b/src/dune_rules/install_rules.ml index b2bd68213ed9..0162887d0b50 100644 --- a/src/dune_rules/install_rules.ml +++ b/src/dune_rules/install_rules.ml @@ -317,7 +317,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 077bb7335bc1..1b2e3700294a 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,23 @@ 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_project of Dune_project.t + | Allow_all + +let check_private_deps lib ~loc ~(allow_private_deps : private_deps) = + match allow_private_deps with + | Allow_all -> Ok lib + | From_project project -> ( + match Lib_info.status lib.info with + | Private (_, Some (package : Package.t)) -> + let packages = Dune_project.packages project in + if Package.Name.Map.mem packages package.name then + Ok lib + else + Error.private_deps_not_allowed ~loc lib.info + | 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 +968,7 @@ module rec Resolve : sig val resolve_dep : db -> Loc.t * Lib_name.t - -> allow_private_deps:bool + -> allow_private_deps:private_deps -> stack:Dep_stack.t -> lib Or_exn.t @@ -943,7 +979,7 @@ module rec Resolve : sig val resolve_simple_deps : db -> (Loc.t * Lib_name.t) list - -> allow_private_deps:bool + -> allow_private_deps:private_deps -> stack:Dep_stack.t -> (t list, exn) Result.t @@ -957,7 +993,7 @@ module rec Resolve : sig val resolve_deps_and_add_runtime_deps : db -> Lib_dep.t list - -> allow_private_deps:bool + -> allow_private_deps:private_deps -> pps:(Loc.t * Lib_name.t) list -> dune_version:Dune_lang.Syntax.Version.t option -> stack:Dep_stack.t @@ -989,7 +1025,14 @@ 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 allow_private_deps = + match status with + | Installed_private + | Private _ + | Installed -> + Allow_all + | Public (project, _) -> From_project project + in let resolve name = resolve_dep db name ~allow_private_deps ~stack in let implements = let open Option.O in @@ -1077,6 +1120,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 +1139,7 @@ end = struct ; default_implementation ; lib_config = db.lib_config ; re_exports + ; project } in t.sub_systems <- @@ -1165,7 +1214,7 @@ 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) ~allow_private_deps:Allow_all ~stack |> Result.is_ok let resolve_simple_deps db names ~allow_private_deps ~stack = @@ -1295,7 +1344,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) ~allow_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 @@ -1477,7 +1526,11 @@ end = struct in Error.overlap ~in_workspace:lib'.info ~installed:(lib.info, req_by) - | _ -> assert false ) + | 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 +1701,26 @@ 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 () = + let resolve name = + match resolve name with + | Redirect (_, (_, name')) when Lib_name.equal name name' -> + Code_error.raise "redirect cycle" + [ ("name", Lib_name.to_dyn name); ("name'", Lib_name.to_dyn name') ] + | t -> t + in { 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) @@ -1730,7 +1792,7 @@ module DB = struct ; re_exports = _ } = Resolve.resolve_deps_and_add_runtime_deps t deps ~pps - ~allow_private_deps:true ~stack:Dep_stack.empty + ~allow_private_deps:Allow_all ~stack:Dep_stack.empty ~dune_version:(Some dune_version) in let requires_link = @@ -1766,7 +1828,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 ~allow_private_deps:Allow_all pps ~stack:Dep_stack.empty let rec all ?(recursive = false) t = @@ -1811,7 +1873,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 @@ -1830,7 +1898,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 = @@ -1849,13 +1917,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 3914083e439f..508ca547a512 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 19f1ca81ced2..795e1a8554f6 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,6 +533,8 @@ 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) diff --git a/src/dune_rules/lib_info.mli b/src/dune_rules/lib_info.mli index 64ecd1cdadd7..47f345b8fa21 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 b34a89e803a2..027f834e32d9 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/odoc.ml b/src/dune_rules/odoc.ml index 3661a4cb1472..d807ada96971 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 c5c5e4cfc9a6..f0ac47ac5627 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 a46f9bf9d17f..66417b709a1b 100644 --- a/src/dune_rules/scope.ml +++ b/src/dune_rules/scope.ml @@ -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 @@ -160,11 +160,9 @@ module DB = struct | Library (_, { buildable = { loc; _ }; visibility = Public 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 + | Deprecated_library_name + { Dune_file.Library_redirect.loc; old_name = p, _; _ } -> + Option.some_if (name = Dune_file.Public_lib.name p) loc | _ -> None) with | [] @@ -179,11 +177,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 +218,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_from_stanzas stanzas ~parent:(Some 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 +227,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 +248,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 +269,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 540844b76f70..a077d7d4b4e8 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 ea61ee27282b..8b370a271813 100644 --- a/src/dune_rules/super_context.ml +++ b/src/dune_rules/super_context.ml @@ -257,7 +257,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) @@ -453,8 +453,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 -> @@ -478,11 +486,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-package-lib.t/run.t b/test/blackbox-tests/test-cases/private-package-lib.t/run.t new file mode 100644 index 000000000000..0ad00a198f17 --- /dev/null +++ b/test/blackbox-tests/test-cases/private-package-lib.t/run.t @@ -0,0 +1,284 @@ +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.cmi + secret.cmt + 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/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.cmi" {"__private__/secret/secret.cmi"} + "_build/install/default/lib/foo/__private__/secret/secret.cmt" {"__private__/secret/secret.cmt"} + "_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/run.exe + Error: exception { exn = + ("Unexpected find result", { found = Not_found; lib.name = "secret" }) + ; backtrace = + [ { ocaml = + "Raised at file \"src/stdune/code_error.ml\", line 9, characters + 30-62\n\ + Called from file \"src/dune_rules/lib.ml\", line 1530, characters + 16-187\n\ + Called from file \"src/stdune/result.ml\", line 103, characters + 17-20\n\ + Called from file \"src/dune_rules/lib.ml\", line 1540, characters + 12-98\n\ + Called from file \"src/stdune/result.ml\", line 103, characters + 17-20\n\ + Called from file \"src/dune_rules/lib.ml\", line 1549, characters + 6-101\n\ + Called from file \"src/dune_rules/lib.ml\", line 1559, characters + 17-64\n\ + Called from file \"src/dune_rules/lib.ml\", line 1900, characters + 9-162\n\ + Called from file \"camlinternalLazy.ml\", line 31, characters + 17-27\n\ + Re-raised at file \"camlinternalLazy.ml\", line 36, characters + 4-11\n\ + Called from file \"src/dune_rules/exe_rules.ml\", line 133, + characters 4-232\n\ + Called from file \"src/stdune/exn.ml\", line 12, characters + 8-11\n\ + Re-raised at file \"src/stdune/exn.ml\", line 18, characters + 4-11\n\ + Called from file \"src/memo/implicit_output.ml\", line 120, + characters 4-162\n\ + Called from file \"src/dune_engine/rules.ml\" (inlined), line 192, + characters 20-71\n\ + Called from file \"src/dune_engine/rules.ml\", line 195, + characters 20-33\n\ + Called from file \"src/dune_engine/build_system.ml\", line 1818, + characters 19-34\n\ + Called from file \"src/dune_rules/gen_rules.ml\", line 90, + characters 8-70\n\ + Called from file \"src/dune_rules/gen_rules.ml\", line 143, + characters 6-96\n\ + Called from file \"list.ml\", line 121, characters 24-34\n\ + Called from file \"src/dune_rules/gen_rules.ml\", line 146, + characters 4-112\n\ + Called from file \"src/dune_rules/gen_rules.ml\", line 222, + characters 4-119\n\ + Called from file \"src/dune_rules/gen_rules.ml\", line 359, + characters 24-71\n\ + Called from file \"src/stdune/exn.ml\", line 12, characters + 8-11\n\ + Re-raised at file \"src/stdune/exn.ml\", line 18, characters + 4-11\n\ + Called from file \"src/memo/implicit_output.ml\", line 120, + characters 4-162\n\ + Called from file \"src/dune_engine/rules.ml\" (inlined), line 192, + characters 20-71\n\ + Called from file \"src/dune_engine/rules.ml\", line 195, + characters 20-33\n\ + Called from file \"src/dune_engine/build_system.ml\", line 900, + characters 6-76\n\ + Called from file \"src/stdune/exn_with_backtrace.ml\", line 9, + characters 8-12\n\ + " + ; memo = ("load-dir", In_build_dir "default/subproj") + } + ] + ; outer_call_stack = [] + } + Raised at file "src/stdune/code_error.ml", line 9, characters 30-62 + Called from file "src/dune_rules/lib.ml", line 1530, characters 16-187 + Called from file "src/stdune/result.ml", line 103, characters 17-20 + Called from file "src/dune_rules/lib.ml", line 1540, characters 12-98 + Called from file "src/stdune/result.ml", line 103, characters 17-20 + Called from file "src/dune_rules/lib.ml", line 1549, characters 6-101 + Called from file "src/dune_rules/lib.ml", line 1559, characters 17-64 + Called from file "src/dune_rules/lib.ml", line 1900, characters 9-162 + Called from file "camlinternalLazy.ml", line 31, characters 17-27 + Re-raised at file "camlinternalLazy.ml", line 36, characters 4-11 + Called from file "src/dune_rules/exe_rules.ml", line 133, characters 4-232 + Called from file "src/stdune/exn.ml", line 12, characters 8-11 + Re-raised at file "src/stdune/exn.ml", line 18, characters 4-11 + Called from file "src/memo/implicit_output.ml", line 120, characters 4-162 + Called from file "src/dune_engine/rules.ml" (inlined), line 192, characters + 20-71 + Called from file "src/dune_engine/rules.ml", line 195, characters 20-33 + Called from file "src/dune_engine/build_system.ml", line 1818, characters + 19-34 + Called from file "src/dune_rules/gen_rules.ml", line 90, characters 8-70 + Called from file "src/dune_rules/gen_rules.ml", line 143, characters 6-96 + Called from file "list.ml", line 121, characters 24-34 + Called from file "src/dune_rules/gen_rules.ml", line 146, characters 4-112 + Called from file "src/dune_rules/gen_rules.ml", line 222, characters 4-119 + Called from file "src/dune_rules/gen_rules.ml", line 359, characters 24-71 + Called from file "src/stdune/exn.ml", line 12, characters 8-11 + Re-raised at file "src/stdune/exn.ml", line 18, characters 4-11 + Called from file "src/memo/implicit_output.ml", line 120, characters 4-162 + Called from file "src/dune_engine/rules.ml" (inlined), line 192, characters + 20-71 + Called from file "src/dune_engine/rules.ml", line 195, characters 20-33 + Called from file "src/dune_engine/build_system.ml", line 900, characters 6-76 + Called from file "src/stdune/exn_with_backtrace.ml", line 9, characters 8-12 + Re-raised at file "src/stdune/exn.ml", line 36, characters 27-56 + Called from file "src/dune_engine/build_system.ml", line 685, characters + 10-23 + Called from file "src/stdune/exn_with_backtrace.ml", line 9, characters 8-12 + Re-raised at file "src/stdune/exn.ml", line 36, characters 27-56 + Called from file "src/dune_engine/build_system.ml", line 1864, characters + 34-74 + Called from file "bin/target.ml", line 69, characters 7-34 + Called from file "bin/target.ml", line 150, characters 12-35 + Called from file "list.ml", line 103, characters 22-25 + Called from file "src/stdune/list.ml", line 5, characters 19-33 + Called from file "bin/target.ml", line 145, characters 6-245 + Called from file "bin/exec.ml", line 63, characters 8-520 + Called from file "camlinternalLazy.ml", line 31, characters 17-27 + Re-raised at file "camlinternalLazy.ml", line 36, characters 4-11 + Called from file "vendor/cmdliner/src/cmdliner_term.ml", line 25, characters + 19-24 + Called from file "vendor/cmdliner/src/cmdliner.ml", line 146, characters 9-16 + Called from file "vendor/cmdliner/src/cmdliner.ml", line 176, characters + 18-36 + Called from file "vendor/cmdliner/src/cmdliner.ml", line 312, characters + 20-46 + Called from file "bin/main.ml", line 262, characters 10-51 + + I must not crash. Uncertainty is the mind-killer. Exceptions are the + little-death that brings total obliteration. I will fully express my cases. + Execution will pass over me and through me. And when it has gone past, I + will unwind the stack along its path. Where the cases are handled there will + be nothing. Only I will remain. + [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.7)" > 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' + direct access attempt: secret string