Add public_interfaces selection for libraries (#106)

Add a field "public_interfaces" to library stanza listing which modules are public.

Private modules won't be accessible outside the scope where the library is defined.
This commit is contained in:
François Bobot 2017-07-19 16:26:48 +02:00 committed by Jérémie Dimino
parent 0d1a3b7378
commit a3933a2c18
28 changed files with 146 additions and 51 deletions

View File

@ -81,6 +81,10 @@ modules you want.
letter. For instance to exclude module ``Foo``: ``(modules (:standard \
foo))``
- ``(public_interfaces <modules>)`` specifies the interfaces `.cmi` of which
modules are installed. ``<public_interfaces>`` uses the same
`Ordered set language` than ``<modules>``.
- ``(libraries (<library-dependencies>))`` is used to specify the dependencies
of the library. See the section about `Library dependencies`_ for more details

View File

@ -66,7 +66,7 @@ module Gen(P : Params) = struct
in
List.map cclibs ~f
let build_lib (lib : Library.t) ~scope ~flags ~dir ~mode ~modules ~dep_graph =
let build_lib (lib : Library.t) ~flags ~dir ~mode ~modules ~dep_graph =
Option.iter (Context.compiler ctx mode) ~f:(fun compiler ->
let target = lib_archive lib ~dir ~ext:(Mode.compiled_lib_ext mode) in
let dep_graph = Ml_kind.Dict.get dep_graph Impl in
@ -107,7 +107,7 @@ module Gen(P : Params) = struct
; A "-a"; A "-o"; Target target
; As stubs_flags
; Dyn (fun (_, cclibs) -> Arg_spec.quote_args "-cclib" (map_cclibs cclibs))
; As (List.map lib.library_flags ~f:(SC.expand_vars sctx ~scope ~dir))
; As (List.map lib.library_flags ~f:(SC.expand_vars sctx ~scope:lib.scope ~dir))
; As (match lib.kind with
| Normal -> []
| Ppx_deriver | Ppx_rewriter -> ["-linkall"])
@ -131,7 +131,7 @@ module Gen(P : Params) = struct
(Dep ctx.ocamlc)
[ As (Utils.g ())
; Dyn (fun (c_flags, libs) ->
S [ Lib.c_include_flags libs
S [ Lib.c_include_flags ~context:ctx.name ~scope:lib.scope libs
; Arg_spec.quote_args "-ccopt" c_flags
])
; A "-o"; Target dst
@ -159,7 +159,7 @@ module Gen(P : Params) = struct
[ S [A "-I"; Path ctx.stdlib_dir]
; As (SC.cxx_flags sctx)
; Dyn (fun (cxx_flags, libs) ->
S [ Lib.c_include_flags libs
S [ Lib.c_include_flags ~context:ctx.name ~scope:lib.scope libs
; As cxx_flags
])
; A "-o"; Target dst
@ -176,7 +176,7 @@ module Gen(P : Params) = struct
let alias_module_build_sandbox = Scanf.sscanf ctx.version "%u.%u"
(fun a b -> a, b) <= (4, 02)
let library_rules (lib : Library.t) ~dir ~all_modules ~files ~scope =
let library_rules (lib : Library.t) ~dir ~all_modules ~files =
let dep_kind = if lib.optional then Build.Optional else Required in
let flags = Ocaml_flags.make lib.buildable in
let modules =
@ -225,7 +225,7 @@ module Gen(P : Params) = struct
SC.PP.pped_modules sctx ~dir ~dep_kind ~modules ~preprocess:lib.buildable.preprocess
~preprocessor_deps:lib.buildable.preprocessor_deps
~lib_name:(Some lib.name)
~scope
~scope:lib.scope
in
let modules =
match alias_module with
@ -268,7 +268,7 @@ module Gen(P : Params) = struct
let dynlink = lib.dynlink in
let js_of_ocaml = lib.buildable.js_of_ocaml in
Module_compilation.build_modules sctx
~js_of_ocaml ~dynlink ~flags ~dir ~dep_graph ~modules ~requires ~alias_module;
~js_of_ocaml ~dynlink ~flags ~scope:lib.scope ~dir ~dep_graph ~modules ~requires ~alias_module;
Option.iter alias_module ~f:(fun m ->
let flags = Ocaml_flags.default () in
Module_compilation.build_module sctx m
@ -276,6 +276,7 @@ module Gen(P : Params) = struct
~dynlink
~sandbox:alias_module_build_sandbox
~flags:{ flags with common = flags.common @ ["-w"; "-49"] }
~scope:lib.scope
~dir
~modules:(String_map.singleton m.name m)
~dep_graph:(Ml_kind.Dict.make_both (Build.return (String_map.singleton m.name [])))
@ -362,7 +363,7 @@ module Gen(P : Params) = struct
Path.relative dir (header ^ ".h")));
List.iter Mode.all ~f:(fun mode ->
build_lib lib ~scope ~flags ~dir ~mode ~modules ~dep_graph);
build_lib lib ~flags ~dir ~mode ~modules ~dep_graph);
(* Build *.cma.js *)
SC.add_rules sctx (
let src = lib_archive lib ~dir ~ext:(Mode.compiled_lib_ext Mode.Byte) in
@ -412,7 +413,7 @@ module Gen(P : Params) = struct
| Executables stuff |
+-----------------------------------------------------------------+ *)
let build_exe ~js_of_ocaml ~flags ~dir ~requires ~name ~mode ~modules ~dep_graph
let build_exe ~js_of_ocaml ~flags ~scope ~dir ~requires ~name ~mode ~modules ~dep_graph
~link_flags ~force_custom_bytecode =
let exe_ext = Mode.exe_ext mode in
let mode, link_flags, compiler =
@ -442,7 +443,7 @@ module Gen(P : Params) = struct
[ Ocaml_flags.get flags mode
; A "-o"; Target exe
; As link_flags
; Dyn (fun (libs, _) -> Lib.link_flags libs ~mode)
; Dyn (fun (libs, _) -> Lib.link_flags libs ~context:ctx.name ~scope ~mode)
; Dyn (fun (_, cm_files) -> Deps cm_files)
]);
if mode = Mode.Byte then
@ -488,12 +489,12 @@ module Gen(P : Params) = struct
(* CR-someday jdimino: this should probably say [~dynlink:false] *)
Module_compilation.build_modules sctx
~js_of_ocaml:exes.buildable.js_of_ocaml
~dynlink:true ~flags ~dir ~dep_graph ~modules
~dynlink:true ~flags ~scope ~dir ~dep_graph ~modules
~requires ~alias_module:None;
List.iter exes.names ~f:(fun name ->
List.iter Mode.all ~f:(fun mode ->
build_exe ~js_of_ocaml:exes.buildable.js_of_ocaml ~flags ~dir ~requires ~name
build_exe ~js_of_ocaml:exes.buildable.js_of_ocaml ~flags ~scope ~dir ~requires ~name
~mode ~modules ~dep_graph ~link_flags:exes.link_flags
~force_custom_bytecode:(mode = Native && not exes.modes.native)));
{ Merlin.
@ -694,13 +695,12 @@ Add it to your jbuild file to remove this warning.
match (stanza : Stanza.t) with
| Library lib ->
Some (library_rules lib ~dir
~all_modules:(Lazy.force all_modules) ~files:(Lazy.force files)
~scope)
~all_modules:(Lazy.force all_modules) ~files:(Lazy.force files))
| Executables exes ->
Some (executables_rules exes ~dir ~all_modules:(Lazy.force all_modules)
~scope)
| _ -> None)
|> Merlin.add_rules sctx ~dir:ctx_dir
|> Merlin.add_rules sctx ~scope ~dir:ctx_dir
let () = List.iter (SC.stanzas sctx) ~f:rules
let () =
@ -845,10 +845,20 @@ Add it to your jbuild file to remove this warning.
sprintf "<module table for context %s>"
(Path.to_string ctx.build_dir))
in
let public_interfaces =
Ordered_set_lang.eval_with_standard lib.public_interfaces
~standard:(List.map modules ~f:(fun s -> s.Module.name))
|> String_set.of_list
in
List.concat
[ List.concat_map modules ~f:(fun m ->
let intf =
if String_set.mem m.Module.name public_interfaces
then [ Module.cm_file m ~dir Cmi ]
else []
in
List.concat
[ [ Module.cm_file m ~dir Cmi ]
[ intf
; if_ native [ Module.cm_file m ~dir Cmx ]
; List.filter_map Ml_kind.all ~f:(Module.cmt_file m ~dir)
; [ match Module.file m ~dir Intf with
@ -894,7 +904,7 @@ Add it to your jbuild file to remove this warning.
in
let ppx_exe =
SC.PP.get_ppx_driver sctx pps
~dir ~dep_kind:(if lib.optional then Build.Optional else Required)
~scope:lib.scope ~dir ~dep_kind:(if lib.optional then Build.Optional else Required)
in
[ppx_exe]
in

View File

@ -128,6 +128,8 @@ module Entry = struct
Path.relative main_dir dst
end
let lib_install_path ~package = Path.relative Entry.Paths.lib package.Package.name
module SMap = Map.Make(Section)
let files entries =

View File

@ -33,3 +33,5 @@ end
val files : Entry.t list -> Path.Set.t
val gen_install_file : Entry.t list -> string
val lib_install_path: package:Package.t -> Path.t

View File

@ -70,6 +70,9 @@ module Scope = struct
; root : Path.t
}
let compare t1 t2 = Path.compare t1.root t2.root
let name t = Option.value ~default:"[root]" t.name
let empty =
{ name = None
; packages = String_map.empty
@ -523,6 +526,7 @@ module Library = struct
type t =
{ name : string
; scope : Scope.t
; public : Public_lib.t option
; synopsis : string option
; install_c_headers : string list
@ -541,6 +545,7 @@ module Library = struct
; optional : bool
; buildable : Buildable.t
; dynlink : bool
; public_interfaces : Ordered_set_lang.t
}
let v1 pkgs =
@ -564,8 +569,10 @@ module Library = struct
field_b "optional" >>= fun optional ->
field "self_build_stubs_archive" (option string) ~default:None >>= fun self_build_stubs_archive ->
field_b "no_dynlink" >>= fun no_dynlink ->
field_osl "public_interfaces" >>= fun public_interfaces ->
return
{ name
; scope = pkgs
; public
; synopsis
; install_c_headers
@ -584,6 +591,7 @@ module Library = struct
; optional
; buildable
; dynlink = not no_dynlink
; public_interfaces
})
let has_stubs t =

View File

@ -17,6 +17,10 @@ module Scope : sig
; root : Path.t
}
val compare : t -> t -> int
val name: t -> string
(** Pretty name *)
val make : Package.t list -> t
val empty : t
@ -128,6 +132,7 @@ module Library : sig
type t =
{ name : string
; scope : Scope.t
; public : Public_lib.t option
; synopsis : string option
; install_c_headers : string list
@ -146,6 +151,7 @@ module Library : sig
; optional : bool
; buildable : Buildable.t
; dynlink : bool
; public_interfaces : Ordered_set_lang.t
}
val has_stubs : t -> bool

View File

@ -22,23 +22,35 @@ end
include T
module Set = Set.Make(T)
let dir = function
| Internal (dir, _) -> dir
let dir ~context ~scope = function
| Internal (dir, lib) ->
if Jbuild.Scope.compare scope lib.scope = 0 then
dir
else begin
match lib.public with
| Some {sub_dir;package; _} ->
let install_dir = Config.local_install_dir ~context in
Path.relative
(Path.append install_dir (Install.lib_install_path ~package))
(Option.value ~default:"" sub_dir)
| _ -> code_errorf "The non public library %s is accessed in %s outside its scope." lib.name
(Jbuild.Scope.name scope)
end
| External pkg -> pkg.dir
let include_paths ts =
let include_paths ~context ~scope ts =
List.fold_left ts ~init:Path.Set.empty ~f:(fun acc t ->
Path.Set.add (dir t) acc)
Path.Set.add (dir ~context ~scope t) acc)
let include_flags ts =
let dirs = include_paths ts in
let include_flags ~context ~scope ts =
let dirs = include_paths ~context ~scope ts in
Arg_spec.S (List.concat_map (Path.Set.elements dirs) ~f:(fun dir ->
[Arg_spec.A "-I"; Path dir]))
let c_include_flags ts =
let c_include_flags ~context ~scope ts =
let dirs =
List.fold_left ts ~init:Path.Set.empty ~f:(fun acc t ->
Path.Set.add (dir t) acc)
Path.Set.add (dir ~context ~scope t) acc)
in
Arg_spec.S (List.concat_map (Path.Set.elements dirs) ~f:(fun dir ->
[Arg_spec.A "-I"; Path dir]))
@ -52,9 +64,9 @@ let describe = function
| External pkg ->
sprintf "%s (external)" pkg.name
let link_flags ts ~mode =
let link_flags ~context ~scope ts ~mode =
Arg_spec.S
(include_flags ts ::
(include_flags ~context ~scope ts ::
List.map ts ~f:(fun t ->
match t with
| External pkg ->

View File

@ -12,13 +12,22 @@ module Set : Set.S with type elt := t
(*val deps : t -> string list*)
val include_paths : t list -> Path.Set.t
val include_paths :
context:string -> scope:Jbuild.Scope.t ->
t list -> Path.Set.t
(** The scope given is the current one *)
val include_flags : t list -> _ Arg_spec.t
val include_flags :
context:string -> scope:Jbuild.Scope.t ->
t list -> _ Arg_spec.t
val c_include_flags : t list -> _ Arg_spec.t
val c_include_flags :
context:string -> scope:Jbuild.Scope.t ->
t list -> _ Arg_spec.t
val link_flags : t list -> mode:Mode.t -> _ Arg_spec.t
val link_flags :
context:string -> scope:Jbuild.Scope.t ->
t list -> mode:Mode.t -> _ Arg_spec.t
val archive_files : t list -> mode:Mode.t -> ext_lib:string -> Path.t list

View File

@ -11,10 +11,10 @@ type t =
; libname : string option
}
let ppx_flags sctx ~dir ~src_dir { preprocess; libname; _ } =
let ppx_flags sctx ~scope ~dir ~src_dir { preprocess; libname; _ } =
match preprocess with
| Pps { pps; flags } ->
let exe = SC.PP.get_ppx_driver sctx pps ~dir ~dep_kind:Optional in
let exe = SC.PP.get_ppx_driver sctx pps ~scope ~dir ~dep_kind:Optional in
let command =
List.map (Path.reach exe ~from:src_dir
:: "--as-ppx"
@ -26,7 +26,7 @@ let ppx_flags sctx ~dir ~src_dir { preprocess; libname; _ } =
[sprintf "FLG -ppx \"%s\"" command]
| _ -> []
let dot_merlin sctx ~dir ({ requires; flags; _ } as t) =
let dot_merlin sctx ~scope ~dir ({ requires; flags; _ } as t) =
match Path.extract_build_context dir with
| Some (_, remaindir) ->
let path = Path.relative remaindir ".merlin" in
@ -37,7 +37,7 @@ let dot_merlin sctx ~dir ({ requires; flags; _ } as t) =
SC.add_rule sctx (
requires
>>^ (fun libs ->
let ppx_flags = ppx_flags sctx ~dir ~src_dir:remaindir t in
let ppx_flags = ppx_flags sctx ~scope ~dir ~src_dir:remaindir t in
let internals, externals =
List.fold_left libs ~init:([], []) ~f:(fun (internals, externals) ->
function
@ -94,8 +94,8 @@ let merge_two a b =
| None -> b.libname
}
let add_rules sctx ~dir ts =
let add_rules sctx ~scope ~dir ts =
if (SC.context sctx).merlin then
match ts with
| [] -> ()
| t :: ts -> dot_merlin sctx ~dir (List.fold_left ts ~init:t ~f:merge_two)
| t :: ts -> dot_merlin sctx ~scope ~dir (List.fold_left ts ~init:t ~f:merge_two)

View File

@ -8,5 +8,4 @@ type t =
}
(** Add rules for generating the .merlin in a directory *)
val add_rules : Super_context.t -> dir:Path.t -> t list -> unit
val add_rules : Super_context.t -> scope:Jbuild.Scope.t -> dir:Path.t -> t list -> unit

View File

@ -5,7 +5,7 @@ open! No_io
module SC = Super_context
let build_cm sctx ?sandbox ~dynlink ~flags ~cm_kind ~(dep_graph:Ocamldep.dep_graph)
~requires ~(modules : Module.t String_map.t) ~dir ~alias_module (m : Module.t) =
~requires ~(modules : Module.t String_map.t) ~scope ~dir ~alias_module (m : Module.t) =
let ctx = SC.context sctx in
Option.iter (Mode.of_cm_kind cm_kind |> Context.compiler ctx) ~f:(fun compiler ->
Option.iter (Module.cm_source ~dir m cm_kind) ~f:(fun src ->
@ -66,7 +66,7 @@ let build_cm sctx ?sandbox ~dynlink ~flags ~cm_kind ~(dep_graph:Ocamldep.dep_gra
~extra_targets
[ Ocaml_flags.get_for_cm flags ~cm_kind
; cmt_args
; Dyn Lib.include_flags
; Dyn (Lib.include_flags ~context:ctx.name ~scope)
; As extra_args
; if dynlink || cm_kind <> Cmx then As [] else A "-nodynlink"
; A "-no-alias-deps"
@ -78,17 +78,17 @@ let build_cm sctx ?sandbox ~dynlink ~flags ~cm_kind ~(dep_graph:Ocamldep.dep_gra
; A "-c"; Ml_kind.flag ml_kind; Dep src
])))
let build_module sctx ?sandbox ~dynlink ~js_of_ocaml ~flags m ~dir ~dep_graph
let build_module sctx ?sandbox ~dynlink ~js_of_ocaml ~flags m ~scope ~dir ~dep_graph
~modules ~requires ~alias_module =
List.iter Cm_kind.all ~f:(fun cm_kind ->
let requires = Cm_kind.Dict.get requires cm_kind in
build_cm sctx ?sandbox ~dynlink ~flags ~dir ~dep_graph ~modules m ~cm_kind
build_cm sctx ?sandbox ~dynlink ~flags ~scope ~dir ~dep_graph ~modules m ~cm_kind
~requires ~alias_module);
(* Build *.cmo.js *)
let src = Module.cm_file m ~dir Cm_kind.Cmo in
SC.add_rules sctx (Js_of_ocaml_rules.build_cm sctx ~dir ~js_of_ocaml ~src)
let build_modules sctx ~dynlink ~js_of_ocaml ~flags ~dir ~dep_graph ~modules ~requires
let build_modules sctx ~dynlink ~js_of_ocaml ~flags ~scope ~dir ~dep_graph ~modules ~requires
~alias_module =
let cmi_requires =
Build.memoize "cmi library dependencies"
@ -113,5 +113,5 @@ let build_modules sctx ~dynlink ~js_of_ocaml ~flags ~dir ~dep_graph ~modules ~re
| None -> modules
| Some (m : Module.t) -> String_map.remove m.name modules)
~f:(fun ~key:_ ~data:m ->
build_module sctx m ~dynlink ~js_of_ocaml ~flags ~dir ~dep_graph ~modules ~requires
build_module sctx m ~dynlink ~js_of_ocaml ~flags ~scope ~dir ~dep_graph ~modules ~requires
~alias_module)

View File

@ -13,6 +13,7 @@ val build_module
-> js_of_ocaml:Jbuild.Js_of_ocaml.t
-> flags:Ocaml_flags.t
-> Module.t
-> scope:Jbuild.Scope.t
-> dir:Path.t
-> dep_graph:Ocamldep.dep_graph
-> modules:Module.t String_map.t
@ -26,6 +27,7 @@ val build_modules
-> dynlink:bool
-> js_of_ocaml:Jbuild.Js_of_ocaml.t
-> flags:Ocaml_flags.t
-> scope:Jbuild.Scope.t
-> dir:Path.t
-> dep_graph:Ocamldep.dep_graph
-> modules:Module.t String_map.t

View File

@ -133,7 +133,7 @@ let setup_library_rules sctx (lib : Library.t) ~dir ~modules ~requires
requires
>>>
SC.Libs.file_deps sctx ~ext:odoc_ext
>>^ Lib.include_flags
>>^ Lib.include_flags ~context:context.name ~scope:lib.scope
in
let modules_and_odoc_files =
List.map (String_map.values modules)

View File

@ -724,7 +724,7 @@ module PP = struct
let migrate_driver_main = "ocaml-migrate-parsetree.driver-main"
let build_ppx_driver sctx ~dir ~dep_kind ~target pp_names ~driver =
let build_ppx_driver sctx ~scope ~dir ~dep_kind ~target pp_names ~driver =
let ctx = sctx.context in
let mode = Context.best_mode ctx in
let compiler = Option.value_exn (Context.compiler ctx mode) in
@ -786,10 +786,10 @@ module PP = struct
>>>
Build.run ~context:ctx (Dep compiler)
[ A "-o" ; Target target
; Dyn (Lib.link_flags ~mode)
; Dyn (Lib.link_flags ~context:ctx.name ~scope ~mode)
])
let get_ppx_driver sctx pps ~dir ~dep_kind =
let get_ppx_driver sctx pps ~scope ~dir ~dep_kind =
let driver, names =
match List.rev_map pps ~f:Pp.to_string with
| [] -> (None, [])
@ -806,7 +806,7 @@ module PP = struct
| None ->
let ppx_dir = Path.relative sctx.ppx_dir key in
let exe = Path.relative ppx_dir "ppx.exe" in
build_ppx_driver sctx names ~dir ~dep_kind ~target:exe ~driver;
build_ppx_driver sctx names ~scope ~dir ~dep_kind ~target:exe ~driver;
Hashtbl.add sctx.ppx_drivers ~key ~data:exe;
exe
@ -878,7 +878,7 @@ module PP = struct
~targets:(Static [dst])
~scope))
| Pps { pps; flags } ->
let ppx_exe = get_ppx_driver sctx pps ~dir ~dep_kind in
let ppx_exe = get_ppx_driver sctx pps ~scope ~dir ~dep_kind in
pped_module m ~dir ~f:(fun kind src dst ->
add_rule sctx
(preprocessor_deps

View File

@ -158,6 +158,7 @@ module PP : sig
val get_ppx_driver
: t
-> Pp.t list
-> scope:Scope.t
-> dir:Path.t
-> dep_kind:Build.lib_dep_kind
-> Path.t

View File

@ -104,3 +104,15 @@
(deps ((alias sleep5)
(alias sleep4-and-fail)
(alias sleep1-and-fail)))))
(alias
((name runtest)
(deps ((files_recursively_in workspaces/public_interfaces)))
(action
(chdir workspaces/public_interfaces
(progn
(run ${exe:run.exe} -log log1 --
${bin:jbuilder} build --verbose -j1 @install --root . --only good,mylib,mylib2)
(run ${exe:run.exe} -log log2 -inverse --
${bin:jbuilder} build -j1 @install --root . --only bad,mylib,mylib2
))))))

View File

@ -0,0 +1,5 @@
(library (
(name bad)
(public_name bad)
(libraries (mylib))
))

View File

@ -0,0 +1 @@
let () = Printf.printf "%s" Mylib.Bad.v

View File

@ -0,0 +1,5 @@
(library (
(name good)
(public_name good)
(libraries (mylib mylib2.see_private))
))

View File

@ -0,0 +1 @@
let () = Printf.printf "%s (and bad size is %i)" Mylib.Good.v Mylib2_see_private.See_private.nb_bad

View File

@ -0,0 +1 @@
let v = "Bad"

View File

@ -0,0 +1 @@
let v = "Good"

View File

@ -0,0 +1,13 @@
(library (
(name mylib)
(public_name mylib)
(modules (good bad))
(public_interfaces (:standard \ Bad))
))
(library (
(name mylib2_see_private)
(public_name mylib2.see_private)
(modules (see_private))
(libraries (mylib))
))

View File

@ -0,0 +1 @@
let nb_bad = String.length Mylib.Bad.v