diff --git a/native_toplevel/opttopdirs.ml b/native_toplevel/opttopdirs.ml index 67a04508bce..e8f094fa491 100644 --- a/native_toplevel/opttopdirs.ml +++ b/native_toplevel/opttopdirs.ml @@ -34,7 +34,7 @@ let _ = Hashtbl.add directive_table "quit" (Directive_none dir_quit) let dir_directory s = let d = expand_directory Config.standard_library s in - let dir = Load_path.Dir.create d in + let dir = Load_path.Dir.create ~hidden:false d in Load_path.append_dir dir; toplevel_env := Stdlib.String.Set.fold @@ -62,7 +62,7 @@ let _ = let _ = Hashtbl.add directive_table "show_dirs" (Directive_none (fun () -> - List.iter print_endline (Load_path.get_paths ()) + List.iter print_endline (Load_path.get_path_list ()) )) (* To change the current directory *) diff --git a/native_toplevel/opttoploop.ml b/native_toplevel/opttoploop.ml index 322f656bb98..f8052dbfc2d 100644 --- a/native_toplevel/opttoploop.ml +++ b/native_toplevel/opttoploop.ml @@ -720,17 +720,22 @@ let set_paths () = but keep the directories that user code linked in with ocamlmktop may have added to load_path. *) let expand = Misc.expand_directory Config.standard_library in - let current_load_path = Load_path.get_paths () in - let load_path = List.concat [ + let Load_path.{ visible; hidden } = Load_path.get_paths () in + let visible = List.concat [ [ "" ]; List.map expand (List.rev !Compenv.first_include_dirs); List.map expand (List.rev !Clflags.include_dirs); List.map expand (List.rev !Compenv.last_include_dirs); - current_load_path; + visible; [expand "+camlp4"]; ] in - Load_path.init load_path ~auto_include:Compmisc.auto_include + let hidden = List.concat [ + List.map expand (List.rev !Clflags.hidden_include_dirs); + hidden + ] + in + Load_path.init ~auto_include:Compmisc.auto_include ~visible ~hidden let initialize_toplevel_env () = toplevel_env := Compmisc.initial_env(); diff --git a/ocaml/.depend b/ocaml/.depend index 720edbcb329..34869cf6884 100644 --- a/ocaml/.depend +++ b/ocaml/.depend @@ -1280,6 +1280,7 @@ typing/persistent_env.cmi : \ typing/subst.cmi \ utils/misc.cmi \ parsing/location.cmi \ + utils/load_path.cmi \ utils/lazy_backtrack.cmi \ utils/import_info.cmi \ utils/consistbl.cmi \ @@ -4543,6 +4544,7 @@ file_formats/cmt_format.cmi : \ typing/typedtree.cmi \ typing/shape.cmi \ parsing/location.cmi \ + utils/load_path.cmi \ utils/import_info.cmi \ typing/env.cmi \ utils/compilation_unit.cmi \ diff --git a/ocaml/bytecomp/bytelink.ml b/ocaml/bytecomp/bytelink.ml index 09c7a7ebe3e..4e5e0e0c948 100644 --- a/ocaml/bytecomp/bytelink.ml +++ b/ocaml/bytecomp/bytelink.ml @@ -376,7 +376,7 @@ let link_bytecode ?final_name tolink exec_name standalone = if check_dlls then begin (* Initialize the DLL machinery *) Dll.init_compile !Clflags.no_std_include; - Dll.add_path (Load_path.get_paths ()); + Dll.add_path (Load_path.get_path_list ()); try Dll.open_dlls Dll.For_checking sharedobjs with Failure reason -> raise(Error(Cannot_open_dll reason)) end; diff --git a/ocaml/debugger/command_line.ml b/ocaml/debugger/command_line.ml index f311295bbc1..43f4a95f402 100644 --- a/ocaml/debugger/command_line.ml +++ b/ocaml/debugger/command_line.ml @@ -262,7 +262,8 @@ let instr_dir ppf lexbuf = let new_directory = argument_list_eol argument lexbuf in if new_directory = [] then begin if yes_or_no "Reinitialize directory list" then begin - Load_path.init ~auto_include:Compmisc.auto_include !default_load_path; + Load_path.init ~auto_include:Compmisc.auto_include + ~visible:!default_load_path ~hidden:[]; Envaux.reset_cache ~preserve_persistent_env:false; Hashtbl.clear Debugger_config.load_path_for; flush_buffer_list () @@ -278,7 +279,8 @@ let instr_dir ppf lexbuf = List.iter (function x -> add_path (expand_path x)) new_directory' end; let print_dirs ppf l = List.iter (function x -> fprintf ppf "@ %s" x) l in - fprintf ppf "@[<2>Directories: %a@]@." print_dirs (Load_path.get_paths ()); + fprintf ppf "@[<2>Directories: %a@]@." print_dirs + (Load_path.get_path_list ()); Hashtbl.iter (fun mdl dirs -> fprintf ppf "@[<2>Source directories for %s: %a@]@." mdl print_dirs diff --git a/ocaml/debugger/loadprinter.ml b/ocaml/debugger/loadprinter.ml index 558cb2c116d..9768c0d0de3 100644 --- a/ocaml/debugger/loadprinter.ml +++ b/ocaml/debugger/loadprinter.ml @@ -40,8 +40,8 @@ let rec loadfiles ppf name = Dynlink.loadfile filename; let d = Filename.dirname name in if d <> Filename.current_dir_name then begin - if not (List.mem d (Load_path.get_paths ())) then - Load_path.add_dir d; + if not (List.mem d (Load_path.get_path_list ())) then + Load_path.add_dir ~hidden:false d; end; fprintf ppf "File %s loaded@." (if d <> Filename.current_dir_name then diff --git a/ocaml/debugger/main.ml b/ocaml/debugger/main.ml index b504ebeee61..006e8fd5bb7 100644 --- a/ocaml/debugger/main.ml +++ b/ocaml/debugger/main.ml @@ -224,7 +224,8 @@ let main () = end; if !Parameters.version then printf "\tOCaml Debugger version %s@.@." Config.version; - Load_path.init ~auto_include:Compmisc.auto_include !default_load_path; + Load_path.init ~auto_include:Compmisc.auto_include + ~visible:!default_load_path ~hidden:[]; Clflags.recursive_types := true; (* Allow recursive types. *) toplevel_loop (); (* Toplevel. *) kill_program (); diff --git a/ocaml/debugger/parameters.ml b/ocaml/debugger/parameters.ml index e7b84c8e295..42fc89cd68d 100644 --- a/ocaml/debugger/parameters.ml +++ b/ocaml/debugger/parameters.ml @@ -31,7 +31,7 @@ let time = ref true let version = ref true let add_path dir = - Load_path.add_dir dir; + Load_path.add_dir ~hidden:false dir; Envaux.reset_cache ~preserve_persistent_env:false let add_path_for mdl dir = diff --git a/ocaml/debugger/program_management.ml b/ocaml/debugger/program_management.ml index b4a2f2d1ec1..8f6d62bf8e4 100644 --- a/ocaml/debugger/program_management.ml +++ b/ocaml/debugger/program_management.ml @@ -128,8 +128,9 @@ let initialize_loading () = end; Symbols.clear_symbols (); Symbols.read_symbols Debugcom.main_frag !program_name; - let dirs = Load_path.get_paths () @ !Symbols.program_source_dirs in - Load_path.init ~auto_include:Compmisc.auto_include dirs; + let Load_path.{visible; hidden} = Load_path.get_paths () in + let visible = visible @ !Symbols.program_source_dirs in + Load_path.init ~auto_include:Compmisc.auto_include ~visible ~hidden; Envaux.reset_cache ~preserve_persistent_env:false; if !debug_loading then prerr_endline "Opening a socket..."; diff --git a/ocaml/debugger/source.ml b/ocaml/debugger/source.ml index b1f9b2ea767..d2045cf61b6 100644 --- a/ocaml/debugger/source.ml +++ b/ocaml/debugger/source.ml @@ -40,7 +40,7 @@ let source_of_module pos mdle = else acc) Debugger_config.load_path_for - (Load_path.get_paths ()) in + (Load_path.get_path_list ()) in let fname = pos.Lexing.pos_fname in if fname = "" then let innermost_module = diff --git a/ocaml/driver/compmisc.ml b/ocaml/driver/compmisc.ml index d1af4ab0832..c0e2a0c57e3 100644 --- a/ocaml/driver/compmisc.ml +++ b/ocaml/driver/compmisc.ml @@ -23,28 +23,36 @@ let auto_include find_in_dir fn = (* Initialize the search path. [dir] (default: the current directory) is always searched first unless -nocwd is specified, - then the directories specified with the -I option (in command-line order), - then the standard library directory (unless the -nostdlib option is given). + then the directories specified with the -I option (in command line order), + then the standard library directory (unless the -nostdlib option is given), + then the directories specified with the -H option (in command line order). *) let init_path ?(auto_include=auto_include) ?(dir="") () = - let dirs = + let visible = if !Clflags.use_threads then "+threads" :: !Clflags.include_dirs else !Clflags.include_dirs in - let dirs = - !Compenv.last_include_dirs @ dirs @ Config.flexdll_dirs @ - !Compenv.first_include_dirs + let visible = + List.concat + [!Compenv.last_include_dirs; + visible; + Config.flexdll_dirs; + !Compenv.first_include_dirs] in - let exp_dirs = - List.map (Misc.expand_directory Config.standard_library) dirs + let visible = + List.map (Misc.expand_directory Config.standard_library) visible in - let dirs = + let visible = (if !Clflags.no_cwd then [] else [dir]) - @ List.rev_append exp_dirs (Clflags.std_include_dir ()) + @ List.rev_append visible (Clflags.std_include_dir ()) in - Load_path.init ~auto_include dirs; + let hidden = + List.rev_map (Misc.expand_directory Config.standard_library) + !Clflags.hidden_include_dirs + in + Load_path.init ~auto_include ~visible ~hidden; Env.reset_cache ~preserve_persistent_env:false (* Return the initial environment in which compilation proceeds. *) diff --git a/ocaml/driver/main_args.ml b/ocaml/driver/main_args.ml index efbf682af6f..d545df86b83 100644 --- a/ocaml/driver/main_args.ml +++ b/ocaml/driver/main_args.ml @@ -153,6 +153,11 @@ let mk_i f = let mk_I f = "-I", Arg.String f, " Add to the list of include directories" +let mk_H f = + "-H", Arg.String f, + " Add to the list of \"hidden\" include directories\n\ + \ (Like -I, but the program can not directly reference these dependencies)" + let mk_impl f = "-impl", Arg.String f, " Compile as a .ml file" @@ -849,6 +854,7 @@ module type Common_options = sig val _no_absname : unit -> unit val _alert : string -> unit val _I : string -> unit + val _H : string -> unit val _labels : unit -> unit val _alias_deps : unit -> unit val _no_alias_deps : unit -> unit @@ -1136,6 +1142,7 @@ struct mk_stop_after ~native:false F._stop_after; mk_i F._i; mk_I F._I; + mk_H F._H; mk_impl F._impl; mk_intf F._intf; mk_intf_suffix F._intf_suffix; @@ -1238,6 +1245,7 @@ struct mk_no_absname F._no_absname; mk_alert F._alert; mk_I F._I; + mk_H F._H; mk_init F._init; mk_labels F._labels; mk_alias_deps F._alias_deps; @@ -1346,6 +1354,7 @@ struct mk_no_probes F._no_probes; mk_i F._i; mk_I F._I; + mk_H F._H; mk_impl F._impl; mk_inline F._inline; mk_inline_toplevel F._inline_toplevel; @@ -1489,6 +1498,7 @@ module Make_opttop_options (F : Opttop_options) = struct mk_alert F._alert; mk_compact F._compact; mk_I F._I; + mk_H F._H; mk_init F._init; mk_inline F._inline; mk_inline_toplevel F._inline_toplevel; @@ -1602,6 +1612,7 @@ struct mk_no_absname F._no_absname; mk_alert F._alert; mk_I F._I; + mk_H F._H; mk_impl F._impl; mk_intf F._intf; mk_intf_suffix F._intf_suffix; @@ -1745,7 +1756,8 @@ module Default = struct module Core = struct include Common - let _I dir = include_dirs := (dir :: (!include_dirs)) + let _I dir = include_dirs := dir :: (!include_dirs) + let _H dir = hidden_include_dirs := dir :: (!hidden_include_dirs) let _color = Misc.set_or_ignore color_reader.parse color let _dlambda = set dump_lambda let _dparsetree = set dump_parsetree @@ -1994,6 +2006,11 @@ module Default = struct (* placeholder: Odoc_global.include_dirs := (s :: (!Odoc_global.include_dirs)) *) () + let _H(_:string) = + (* placeholder: + Odoc_global.hidden_include_dirs := + (s :: (!Odoc_global.hidden_include_dirs)) + *) () let _impl (_:string) = (* placeholder: Odoc_global.files := ((!Odoc_global.files) @ [Odoc_global.Impl_file s]) diff --git a/ocaml/driver/main_args.mli b/ocaml/driver/main_args.mli index ce43a464353..c6b66bfe9e7 100644 --- a/ocaml/driver/main_args.mli +++ b/ocaml/driver/main_args.mli @@ -21,6 +21,7 @@ module type Common_options = sig val _no_absname : unit -> unit val _alert : string -> unit val _I : string -> unit + val _H : string -> unit val _labels : unit -> unit val _alias_deps : unit -> unit val _no_alias_deps : unit -> unit diff --git a/ocaml/driver/makedepend.ml b/ocaml/driver/makedepend.ml index b0bc4b56205..e0076083bce 100644 --- a/ocaml/driver/makedepend.ml +++ b/ocaml/driver/makedepend.ml @@ -408,7 +408,8 @@ let process_file_as process_fun def source_file = load_path := []; let cwd = if !nocwd then [] else [Filename.current_dir_name] in List.iter add_to_load_path ( - (!Compenv.last_include_dirs @ + (!Clflags.hidden_include_dirs @ + !Compenv.last_include_dirs @ !Clflags.include_dirs @ !Compenv.first_include_dirs @ cwd @@ -609,6 +610,8 @@ let run_main argv = " Dump the delayed dependency map for each map file"; "-I", Arg.String (add_to_list Clflags.include_dirs), " Add to the list of include directories"; + "-H", Arg.String (add_to_list Clflags.hidden_include_dirs), + " Add to the list of include directories"; "-nocwd", Arg.Set nocwd, " Do not add current working directory to \ the list of include directories"; diff --git a/ocaml/file_formats/cmt_format.ml b/ocaml/file_formats/cmt_format.ml index 1a82c5131a6..9e8381bb395 100644 --- a/ocaml/file_formats/cmt_format.ml +++ b/ocaml/file_formats/cmt_format.ml @@ -54,7 +54,7 @@ type cmt_infos = { cmt_args : string array; cmt_sourcefile : string option; cmt_builddir : string; - cmt_loadpath : string list; + cmt_loadpath : Load_path.paths; cmt_source_digest : Digest.t option; cmt_initial_env : Env.t; cmt_imports : Import_info.t array; diff --git a/ocaml/file_formats/cmt_format.mli b/ocaml/file_formats/cmt_format.mli index e1b233fa684..a2b6fd6c464 100644 --- a/ocaml/file_formats/cmt_format.mli +++ b/ocaml/file_formats/cmt_format.mli @@ -57,7 +57,7 @@ type cmt_infos = { cmt_args : string array; cmt_sourcefile : string option; cmt_builddir : string; - cmt_loadpath : string list; + cmt_loadpath : Load_path.paths; cmt_source_digest : string option; cmt_initial_env : Env.t; cmt_imports : Import_info.t array; diff --git a/ocaml/manual/src/cmds/ocamldep.etex b/ocaml/manual/src/cmds/ocamldep.etex index 93d6741d02b..1213cb9b1b2 100644 --- a/ocaml/manual/src/cmds/ocamldep.etex +++ b/ocaml/manual/src/cmds/ocamldep.etex @@ -65,6 +65,12 @@ and no dependencies are generated. For programs that span multiple directories, it is recommended to pass "ocamldep" the same "-I" options that are passed to the compiler. +\item["-H" \var{directory}] +Behaves identically to "-I", except that the "-H" directories are searched +last. This flag is included to make it easier to invoke "ocamldep" with +the same options as the compiler, where "-H" is used for transitive +dependencies that the program should not directly mention. + \item["-nocwd"] Do not add current working directory to the list of include directories. diff --git a/ocaml/manual/src/cmds/ocamldoc.etex b/ocaml/manual/src/cmds/ocamldoc.etex index e2488f6f0a3..51022fd571c 100644 --- a/ocaml/manual/src/cmds/ocamldoc.etex +++ b/ocaml/manual/src/cmds/ocamldoc.etex @@ -207,6 +207,10 @@ They have the same meaning as for the "ocamlc" and "ocamlopt" commands. Add \var{directory} to the list of directories search for compiled interface files (".cmi" files). +\item["-H" \var{directory}] +Like "-I", but the "-H" directories are searched last and the program may +not directly refer to the modules added to the search path this way. + \item["-nolabels"] Ignore non-optional labels in types. diff --git a/ocaml/manual/src/cmds/unified-options.etex b/ocaml/manual/src/cmds/unified-options.etex index b855ba4a2bf..e74f291da86 100644 --- a/ocaml/manual/src/cmds/unified-options.etex +++ b/ocaml/manual/src/cmds/unified-options.etex @@ -300,6 +300,16 @@ the toplevel is running with the "#directory" directive (section~\ref{s:toplevel-directives}). }%top +\notop{% +\item["-H" \var{directory}] +Behaves identically to "-I", except that (a) programs may not directly refer to +modules added to the search path this way, and (b) these directories are +searched after any "-I" directories. This makes it possible to provide the +compiler with compiled interface and object code files for the current program's +transitive dependencies (the dependencies of its dependencies) without allowing +them to silently become direct dependencies. +}%notop + \top{% \item["-init" \var{file}] Load the given file instead of the default initialization file. diff --git a/ocaml/ocamldoc/odoc_args.ml b/ocaml/ocamldoc/odoc_args.ml index cad9ae4a13e..8d61505931d 100644 --- a/ocaml/ocamldoc/odoc_args.ml +++ b/ocaml/ocamldoc/odoc_args.ml @@ -187,6 +187,7 @@ let anonymous f = module Options = Main_args.Make_ocamldoc_options(struct include Main_args.Default.Odoc_args let _I s = Odoc_global.include_dirs := s :: !Odoc_global.include_dirs + let _H s = Odoc_global.hidden_include_dirs := s :: !Odoc_global.hidden_include_dirs let _impl s = Odoc_global.files := !Odoc_global.files @ [Odoc_global.Impl_file s] let _intf s = Odoc_global.files := !Odoc_global.files @ [Odoc_global.Intf_file s] end) diff --git a/ocaml/ocamldoc/odoc_global.ml b/ocaml/ocamldoc/odoc_global.ml index 3bb1a67cd4a..221745438a0 100644 --- a/ocaml/ocamldoc/odoc_global.ml +++ b/ocaml/ocamldoc/odoc_global.ml @@ -24,6 +24,7 @@ type source_file = | Text_file of string let include_dirs = Clflags.include_dirs +let hidden_include_dirs = Clflags.hidden_include_dirs let errors = ref 0 diff --git a/ocaml/ocamldoc/odoc_global.mli b/ocaml/ocamldoc/odoc_global.mli index c85b453453b..1e3df3ec2ae 100644 --- a/ocaml/ocamldoc/odoc_global.mli +++ b/ocaml/ocamldoc/odoc_global.mli @@ -24,6 +24,9 @@ type source_file = (** The include_dirs in the OCaml compiler. *) val include_dirs : string list ref +(** The hidden_include_dirs in the OCaml compiler. *) +val hidden_include_dirs : string list ref + (** The merge options to be used. *) val merge_options : Odoc_types.merge_option list ref diff --git a/ocaml/ocamldoc/odoc_info.ml b/ocaml/ocamldoc/odoc_info.ml index d4a73fe1ed9..8a80224452f 100644 --- a/ocaml/ocamldoc/odoc_info.ml +++ b/ocaml/ocamldoc/odoc_info.ml @@ -111,6 +111,7 @@ module Module = Odoc_module let analyse_files ?(merge_options=([] : Odoc_types.merge_option list)) ?(include_dirs=([] : string list)) + ?(hidden_include_dirs=([] : string list)) ?(labels=false) ?(sort_modules=false) ?(no_stop=false) @@ -118,6 +119,7 @@ let analyse_files files = Odoc_global.merge_options := merge_options; Odoc_global.include_dirs := include_dirs; + Odoc_global.hidden_include_dirs := hidden_include_dirs; Odoc_global.classic := not labels; Odoc_global.sort_modules := sort_modules; Odoc_global.no_stop := no_stop; diff --git a/ocaml/ocamldoc/odoc_info.mli b/ocaml/ocamldoc/odoc_info.mli index da53554f892..bb4aa1d89e3 100644 --- a/ocaml/ocamldoc/odoc_info.mli +++ b/ocaml/ocamldoc/odoc_info.mli @@ -1082,12 +1082,13 @@ end val analyse_files : ?merge_options:Odoc_types.merge_option list -> ?include_dirs:string list -> - ?labels:bool -> - ?sort_modules:bool -> - ?no_stop:bool -> - ?init: Odoc_module.t_module list -> - Odoc_global.source_file list -> - Module.t_module list + ?hidden_include_dirs:string list -> + ?labels:bool -> + ?sort_modules:bool -> + ?no_stop:bool -> + ?init: Odoc_module.t_module list -> + Odoc_global.source_file list -> + Module.t_module list (** Dump of a list of modules into a file. @raise Failure if an error occurs.*) diff --git a/ocaml/parsing/ast_mapper.ml b/ocaml/parsing/ast_mapper.ml index 2784aee0209..8a9e908da57 100644 --- a/ocaml/parsing/ast_mapper.ml +++ b/ocaml/parsing/ast_mapper.ml @@ -1124,11 +1124,16 @@ module PpxContext = struct } let make ~tool_name () = + let Load_path.{ visible; hidden } = Load_path.get_paths () in let fields = [ lid "tool_name", make_string tool_name; - lid "include_dirs", make_list make_string !Clflags.include_dirs; - lid "load_path", make_list make_string (Load_path.get_paths ()); + lid "include_dirs", make_list make_string (!Clflags.include_dirs); + lid "hidden_include_dirs", + make_list make_string (!Clflags.hidden_include_dirs); + lid "load_path", + make_pair (make_list make_string) (make_list make_string) + (visible, hidden); lid "open_modules", make_list make_string !Clflags.open_modules; lid "for_package", make_option make_string !Clflags.for_package; lid "debug", make_bool !Clflags.debug; @@ -1197,6 +1202,8 @@ module PpxContext = struct tool_name_ref := get_string payload | "include_dirs" -> Clflags.include_dirs := get_list get_string payload + | "hidden_include_dirs" -> + Clflags.hidden_include_dirs := get_list get_string payload | "load_path" -> (* Duplicates Compmisc.auto_include, since we can't reference Compmisc from this module. *) @@ -1207,7 +1214,10 @@ module PpxContext = struct let alert = Location.auto_include_alert in Load_path.auto_include_otherlibs alert find_in_dir fn in - Load_path.init ~auto_include (get_list get_string payload) + let visible, hidden = + get_pair (get_list get_string) (get_list get_string) payload + in + Load_path.init ~auto_include ~visible ~hidden | "open_modules" -> Clflags.open_modules := get_list get_string payload | "for_package" -> diff --git a/ocaml/parsing/ast_mapper.mli b/ocaml/parsing/ast_mapper.mli index 25965b63792..89b6b23bb17 100644 --- a/ocaml/parsing/ast_mapper.mli +++ b/ocaml/parsing/ast_mapper.mli @@ -148,8 +148,8 @@ val tool_name: unit -> string ["ocaml"], ... Some global variables that reflect command-line options are automatically synchronized between the calling tool and the ppx preprocessor: {!Clflags.include_dirs}, - {!Load_path}, {!Clflags.open_modules}, {!Clflags.for_package}, - {!Clflags.debug}. *) + {!Clflags.hidden_include_dirs}, {!Load_path}, {!Clflags.open_modules}, + {!Clflags.for_package}, {!Clflags.debug}. *) val apply: source:string -> target:string -> mapper -> unit diff --git a/ocaml/testsuite/tests/hidden_includes/cant_reference_hidden.ocamlc.reference b/ocaml/testsuite/tests/hidden_includes/cant_reference_hidden.ocamlc.reference new file mode 100644 index 00000000000..caae6f73442 --- /dev/null +++ b/ocaml/testsuite/tests/hidden_includes/cant_reference_hidden.ocamlc.reference @@ -0,0 +1,4 @@ +File "libc/c3.ml", line 1, characters 8-11: +1 | let x = A.x + 1 + ^^^ +Error: Unbound module "A" diff --git a/ocaml/testsuite/tests/hidden_includes/hidden_stays_hidden.ocamlc.reference b/ocaml/testsuite/tests/hidden_includes/hidden_stays_hidden.ocamlc.reference new file mode 100644 index 00000000000..8ba62b877af --- /dev/null +++ b/ocaml/testsuite/tests/hidden_includes/hidden_stays_hidden.ocamlc.reference @@ -0,0 +1,4 @@ +File "libc/c4.ml", line 2, characters 8-11: +2 | let y = A.x + 1 + ^^^ +Error: Unbound module "A" diff --git a/ocaml/testsuite/tests/hidden_includes/liba/a.ml b/ocaml/testsuite/tests/hidden_includes/liba/a.ml new file mode 100644 index 00000000000..22b40309256 --- /dev/null +++ b/ocaml/testsuite/tests/hidden_includes/liba/a.ml @@ -0,0 +1,5 @@ +type t = int + +let x = 1 + +type s = Baz diff --git a/ocaml/testsuite/tests/hidden_includes/liba_alt/a.ml b/ocaml/testsuite/tests/hidden_includes/liba_alt/a.ml new file mode 100644 index 00000000000..e907a667ba2 --- /dev/null +++ b/ocaml/testsuite/tests/hidden_includes/liba_alt/a.ml @@ -0,0 +1,3 @@ +type t = string + +let x = "hi" diff --git a/ocaml/testsuite/tests/hidden_includes/libb/b.ml b/ocaml/testsuite/tests/hidden_includes/libb/b.ml new file mode 100644 index 00000000000..7e41643e960 --- /dev/null +++ b/ocaml/testsuite/tests/hidden_includes/libb/b.ml @@ -0,0 +1,7 @@ +type t = A.t + +let x : A.t = A.x + +let f : A.t -> A.t = fun x -> x + +let g : A.s -> unit = fun _ -> () diff --git a/ocaml/testsuite/tests/hidden_includes/libc/c1.ml b/ocaml/testsuite/tests/hidden_includes/libc/c1.ml new file mode 100644 index 00000000000..e0e5a1fd8d9 --- /dev/null +++ b/ocaml/testsuite/tests/hidden_includes/libc/c1.ml @@ -0,0 +1,3 @@ +let x = B.x + 1 + +let () = Printf.printf "%d\n" x diff --git a/ocaml/testsuite/tests/hidden_includes/libc/c2.ml b/ocaml/testsuite/tests/hidden_includes/libc/c2.ml new file mode 100644 index 00000000000..dee5a2e74b9 --- /dev/null +++ b/ocaml/testsuite/tests/hidden_includes/libc/c2.ml @@ -0,0 +1 @@ +let x = B.f B.x diff --git a/ocaml/testsuite/tests/hidden_includes/libc/c3.ml b/ocaml/testsuite/tests/hidden_includes/libc/c3.ml new file mode 100644 index 00000000000..88c0aa8d0d6 --- /dev/null +++ b/ocaml/testsuite/tests/hidden_includes/libc/c3.ml @@ -0,0 +1 @@ +let x = A.x + 1 diff --git a/ocaml/testsuite/tests/hidden_includes/libc/c4.ml b/ocaml/testsuite/tests/hidden_includes/libc/c4.ml new file mode 100644 index 00000000000..7c14a26c835 --- /dev/null +++ b/ocaml/testsuite/tests/hidden_includes/libc/c4.ml @@ -0,0 +1,6 @@ +let x = B.x + 1 +let y = A.x + 1 + +(* Typing x requires loading A's cmi. When it is made available with -H, y + should fail to typecheck because direct references to A are not allowed, even + though it has been loaded. *) diff --git a/ocaml/testsuite/tests/hidden_includes/libc/c5.ml b/ocaml/testsuite/tests/hidden_includes/libc/c5.ml new file mode 100644 index 00000000000..bd0715ec1e8 --- /dev/null +++ b/ocaml/testsuite/tests/hidden_includes/libc/c5.ml @@ -0,0 +1,7 @@ +let _ = B.g Baz + +(* Type-directed disambiguation: Baz is defined in A, and even when a.cmi is + provided with -H this still typechecks. It's not obvious that this is + necessary (rejecting this program also seems fine, in that case), but this + test is here to record the current behavior so any change will be + intentional. *) diff --git a/ocaml/testsuite/tests/hidden_includes/not_included.ocamlc.reference b/ocaml/testsuite/tests/hidden_includes/not_included.ocamlc.reference new file mode 100644 index 00000000000..a3c0d1055eb --- /dev/null +++ b/ocaml/testsuite/tests/hidden_includes/not_included.ocamlc.reference @@ -0,0 +1,7 @@ +File "libc/c1.ml", line 1, characters 8-11: +1 | let x = B.x + 1 + ^^^ +Error: This expression has type "A.t" but an expression was expected of type + "int" + Type "A.t" is abstract because no corresponding cmi file was found + in path. diff --git a/ocaml/testsuite/tests/hidden_includes/test.ml b/ocaml/testsuite/tests/hidden_includes/test.ml new file mode 100644 index 00000000000..4ea735dfba6 --- /dev/null +++ b/ocaml/testsuite/tests/hidden_includes/test.ml @@ -0,0 +1,159 @@ +(* TEST +(* This tests the -H flag. + + The basic structure is that libc depends on libb, which depends on liba. We + want to test a few things: + + - Compiling libc with -I liba allows the compiler to see the type definitions + in liba and allows c.ml to reference it directly. + + - Compiling libc with -H liba allows the compiler to see the type definitions + in liba, but doesn't allow c.ml to reference it directly. + + - If -H and -I are are passed for two different versions of liba, the -I one + takes priority. + + - If -H is passed twice with two different versions of liba, the first takes + priority. + + The liba_alt directory has an alternate versions of liba used for testing the + precedence order of the includes. +*) + +subdirectories = "liba liba_alt libb libc"; +setup-ocamlc.byte-build-env; + +flags = "-I liba -nocwd"; +module = "liba/a.ml"; +ocamlc.byte; + +flags = "-I liba_alt -nocwd"; +module = "liba_alt/a.ml"; +ocamlc.byte; + +flags = "-I liba -I libb -nocwd"; +module = "libb/b.ml"; +ocamlc.byte; +{ + (* Test hiding A completely *) + flags = "-I libb -nocwd"; + module = "libc/c2.ml"; + setup-ocamlc.byte-build-env; + ocamlc.byte; +} +{ + (* Test hiding A completely, but using it *) + flags = "-I libb -nocwd"; + module = "libc/c1.ml"; + setup-ocamlc.byte-build-env; + ocamlc_byte_exit_status = "2"; + ocamlc.byte; + compiler_reference = "${test_source_directory}/not_included.ocamlc.reference"; + check-ocamlc.byte-output; +} +{ + (* Test transitive use of A's cmi with -I. *) + flags = "-I liba -I libb -nocwd"; + module = "libc/c1.ml"; + setup-ocamlc.byte-build-env; + ocamlc.byte; +} +{ + (* Test transitive use of A's cmi with -H. *) + flags = "-H liba -I libb -nocwd"; + module = "libc/c1.ml"; + setup-ocamlc.byte-build-env; + ocamlc.byte; +} +{ + (* Test direct use of A cmi with -H. *) + flags = "-H liba -I libb -nocwd"; + module = "libc/c3.ml"; + setup-ocamlc.byte-build-env; + ocamlc_byte_exit_status = "2"; + ocamlc.byte; + compiler_reference = + "${test_source_directory}/cant_reference_hidden.ocamlc.reference"; + check-ocamlc.byte-output; +} + +(* The next four tests check that -I takes priority over -H regardless of the + order on the command line. +*) +{ + flags = "-H liba_alt -I liba -I libb -nocwd"; + module = "libc/c1.ml"; + setup-ocamlc.byte-build-env; + ocamlc.byte; +} +{ + flags = "-I liba -H liba_alt -I libb -nocwd"; + module = "libc/c1.ml"; + setup-ocamlc.byte-build-env; + ocamlc.byte; +} +{ + not-windows; + flags = "-H liba -I liba_alt -I libb -nocwd"; + module = "libc/c1.ml"; + setup-ocamlc.byte-build-env; + ocamlc_byte_exit_status = "2"; + ocamlc.byte; + compiler_reference = + "${test_source_directory}/wrong_include_order.ocamlc.reference"; + check-ocamlc.byte-output; +} +{ + not-windows; + flags = "-I liba_alt -H liba -I libb -nocwd"; + module = "libc/c1.ml"; + setup-ocamlc.byte-build-env; + ocamlc_byte_exit_status = "2"; + ocamlc.byte; + compiler_reference = + "${test_source_directory}/wrong_include_order.ocamlc.reference"; + check-ocamlc.byte-output; +} + +(* The next two tests show that earlier -Hs take priority over later -Hs *) +{ + not-windows; + flags = "-H liba_alt -H liba -I libb -nocwd"; + module = "libc/c1.ml"; + setup-ocamlc.byte-build-env; + ocamlc_byte_exit_status = "2"; + ocamlc.byte; + compiler_reference = + "${test_source_directory}/wrong_include_order.ocamlc.reference"; + check-ocamlc.byte-output; +} +{ + flags = "-H liba -H liba_alt -I libb -nocwd"; + module = "libc/c1.ml"; + setup-ocamlc.byte-build-env; + ocamlc.byte; +} + +(* Test that a hidden `A` doesn't become visible as a result of the typechecker + using it. *) +{ + flags = "-H liba -I libb -nocwd"; + module = "libc/c4.ml"; + setup-ocamlc.byte-build-env; + ocamlc_byte_exit_status = "2"; + ocamlc.byte; + compiler_reference = + "${test_source_directory}/hidden_stays_hidden.ocamlc.reference"; + check-ocamlc.byte-output; +} + +(* Test that type-directed constructor disambiguation works through -H (at + least, for now). *) +{ + flags = "-H liba -I libb -nocwd"; + module = "libc/c5.ml"; + setup-ocamlc.byte-build-env; + ocamlc.byte; +} + +*) diff --git a/ocaml/testsuite/tests/hidden_includes/wrong_include_order.ocamlc.reference b/ocaml/testsuite/tests/hidden_includes/wrong_include_order.ocamlc.reference new file mode 100644 index 00000000000..6863dffdf5e --- /dev/null +++ b/ocaml/testsuite/tests/hidden_includes/wrong_include_order.ocamlc.reference @@ -0,0 +1,3 @@ +File "libc/c1.ml", line 1: +Error: The files "libb/b.cmi" and "liba_alt/a.cmi" make inconsistent assumptions + over interface "A" diff --git a/ocaml/testsuite/tests/self-contained-toplevel/main.ml b/ocaml/testsuite/tests/self-contained-toplevel/main.ml index c89c0485cdf..46cb4377627 100644 --- a/ocaml/testsuite/tests/self-contained-toplevel/main.ml +++ b/ocaml/testsuite/tests/self-contained-toplevel/main.ml @@ -23,7 +23,7 @@ let () = if Sys.file_exists "foo.cmi" then Sys.remove "foo.cmi"; let module Persistent_signature = Persistent_env.Persistent_signature in let old_loader = !Persistent_signature.load in - Persistent_signature.load := (fun ~unit_name -> + Persistent_signature.load := (fun ~allow_hidden ~unit_name -> match unit_name |> Compilation_unit.Name.to_string with | "Foo" -> let {Cmi_format.cmi_name; cmi_sign; cmi_crcs; cmi_flags} = @@ -37,10 +37,11 @@ let () = } in Some { Persistent_signature. - filename = Sys.executable_name - ; cmi = cmi + filename = Sys.executable_name + ; cmi = cmi + ; visibility = Visible } - | _ -> old_loader unit_name); + | _ -> old_loader ~allow_hidden ~unit_name); Toploop.add_hook (function | Toploop.After_setup -> Toploop.toplevel_env := diff --git a/ocaml/tools/objinfo.ml b/ocaml/tools/objinfo.ml index ba958bec3bf..2e20960d9a3 100644 --- a/ocaml/tools/objinfo.ml +++ b/ocaml/tools/objinfo.ml @@ -107,8 +107,10 @@ let print_cmt_infos cmt = (match cmt.cmt_sourcefile with None -> "(none)" | Some f -> f); printf "Compilation flags:"; Array.iter print_spaced_string cmt.cmt_args; - printf "\nLoad path:"; - List.iter print_spaced_string cmt.cmt_loadpath; + printf "\nLoad path:\n Visible:"; + List.iter print_spaced_string cmt.cmt_loadpath.visible; + printf "\n Hidden:"; + List.iter print_spaced_string cmt.cmt_loadpath.hidden; printf "\n"; printf "cmt interface digest: %s\n" (match cmt.cmt_interface_digest with diff --git a/ocaml/tools/ocamlcmt.ml b/ocaml/tools/ocamlcmt.ml index 1bb39e10cc3..31169a1877d 100644 --- a/ocaml/tools/ocamlcmt.ml +++ b/ocaml/tools/ocamlcmt.ml @@ -69,7 +69,10 @@ let print_info cmt = Printf.fprintf oc "sourcefile: %s\n" name; end; Printf.fprintf oc "build directory: %s\n" cmt.cmt_builddir; - List.iter (Printf.fprintf oc "load path: %s\n%!") cmt.cmt_loadpath; + List.iter (Printf.fprintf oc "load path (visible): %s\n%!") + cmt.cmt_loadpath.visible; + List.iter (Printf.fprintf oc "load path (hidden): %s\n%!") + cmt.cmt_loadpath.hidden; begin match cmt.cmt_source_digest with None -> () @@ -161,7 +164,8 @@ let record_cmt_info cmt = Annot.Idef (location_file value))) in let open Cmt_format in - List.iter (fun dir -> record_info "include" dir) cmt.cmt_loadpath; + List.iter (fun dir -> record_info "include" dir) cmt.cmt_loadpath.visible; + List.iter (fun dir -> record_info "include" dir) cmt.cmt_loadpath.hidden; record_info "chdir" cmt.cmt_builddir; (match cmt.cmt_sourcefile with None -> () | Some file -> record_info "source" file) @@ -186,7 +190,8 @@ let main () = | Some _ as x -> x in Envaux.reset_cache ~preserve_persistent_env:false; - List.iter Load_path.add_dir cmt.cmt_loadpath; + List.iter (Load_path.add_dir ~hidden:false) cmt.cmt_loadpath.visible; + List.iter (Load_path.add_dir ~hidden:true) cmt.cmt_loadpath.hidden; Cmt2annot.gen_annot target_filename ~sourcefile:cmt.cmt_sourcefile ~use_summaries:cmt.cmt_use_summaries diff --git a/ocaml/toplevel/topcommon.ml b/ocaml/toplevel/topcommon.ml index 443fcc64f77..cba2403ca53 100644 --- a/ocaml/toplevel/topcommon.ml +++ b/ocaml/toplevel/topcommon.ml @@ -268,18 +268,23 @@ let set_paths ?(auto_include=Compmisc.auto_include) () = but keep the directories that user code linked in with ocamlmktop may have added to load_path. *) let expand = Misc.expand_directory Config.standard_library in - let current_load_path = Load_path.get_paths () in - let load_path = List.concat [ + let Load_path.{ visible; hidden } = Load_path.get_paths () in + let visible = List.concat [ [ "" ]; List.map expand (List.rev !Compenv.first_include_dirs); List.map expand (List.rev !Clflags.include_dirs); List.map expand (List.rev !Compenv.last_include_dirs); - current_load_path; + visible; [expand "+camlp4"]; ] in - Load_path.init ~auto_include load_path; - Dll.add_path load_path + let hidden = List.concat [ + List.map expand (List.rev !Clflags.hidden_include_dirs); + hidden + ] + in + Load_path.init ~auto_include ~visible ~hidden; + Dll.add_path (visible @ hidden) let update_search_path_from_env () = let extra_paths = diff --git a/ocaml/toplevel/topdirs.ml b/ocaml/toplevel/topdirs.ml index 3c352c0c59b..2232e4cdc3d 100644 --- a/ocaml/toplevel/topdirs.ml +++ b/ocaml/toplevel/topdirs.ml @@ -75,7 +75,7 @@ let _ = add_directive "quit" (Directive_none dir_quit) let dir_directory s = let d = expand_directory Config.standard_library s in Dll.add_path [d]; - let dir = Load_path.Dir.create d in + let dir = Load_path.Dir.create ~hidden:false d in Load_path.prepend_dir dir; toplevel_env := Stdlib.String.Set.fold @@ -110,7 +110,7 @@ let _ = add_directive "remove_directory" (Directive_string dir_remove_directory) } let dir_show_dirs () = - List.iter print_endline (Load_path.get_paths ()) + List.iter print_endline (Load_path.get_path_list ()) let _ = add_directive "show_dirs" (Directive_none dir_show_dirs) { diff --git a/ocaml/typing/env.ml b/ocaml/typing/env.ml index b34cb2c1077..7849f12efc3 100644 --- a/ocaml/typing/env.ml +++ b/ocaml/typing/env.ml @@ -1113,7 +1113,7 @@ let find_ident_module id env = match find_same_module id env.modules with | Mod_local data -> data | Mod_unbound _ -> raise Not_found - | Mod_persistent -> find_pers_mod (id |> modname_of_ident) + | Mod_persistent -> find_pers_mod ~allow_hidden:true (id |> modname_of_ident) let rec find_module_components path env = match path with @@ -2695,8 +2695,10 @@ let save_signature_with_transform cmi_transform ~alerts sg modname filename = let cmi = Persistent_env.make_cmi !persistent_env modname sg alerts |> cmi_transform in - Persistent_env.save_cmi !persistent_env - { Persistent_env.Persistent_signature.filename; cmi }; + let pers_sig = + Persistent_env.Persistent_signature.{ filename; cmi; visibility = Visible } + in + Persistent_env.save_cmi !persistent_env pers_sig; cmi let save_signature ~alerts sg modname filename = @@ -2935,10 +2937,10 @@ let lookup_ident_module (type a) (load : a load) ~errors ~use ~loc s env = let name = s |> Compilation_unit.Name.of_string in match load with | Don't_load -> - check_pers_mod ~loc name; + check_pers_mod ~allow_hidden:false ~loc name; path, (() : a) | Load -> begin - match find_pers_mod name with + match find_pers_mod ~allow_hidden:false name with | mda -> use_module ~use ~loc path mda; path, (mda : a) @@ -3546,7 +3548,10 @@ let bound_module name env = | exception Not_found -> if Current_unit_name.is name then false else begin - match find_pers_mod (name |> Compilation_unit.Name.of_string) with + match + find_pers_mod ~allow_hidden:false + (name |> Compilation_unit.Name.of_string) + with | _ -> true | exception Not_found -> false end diff --git a/ocaml/typing/persistent_env.ml b/ocaml/typing/persistent_env.ml index dc7430cf983..fd32d09c6da 100644 --- a/ocaml/typing/persistent_env.ml +++ b/ocaml/typing/persistent_env.ml @@ -40,13 +40,18 @@ let error err = raise (Error err) module Persistent_signature = struct type t = { filename : string; - cmi : Cmi_format.cmi_infos_lazy } - - let load = ref (fun ~unit_name -> - let unit_name = CU.Name.to_string unit_name in - match Load_path.find_uncap (unit_name ^ ".cmi") with - | filename -> Some { filename; cmi = read_cmi_lazy filename } - | exception Not_found -> None) + cmi : Cmi_format.cmi_infos_lazy; + visibility : Load_path.visibility } + + let load = ref (fun ~allow_hidden ~unit_name -> + let unit_name = CU.Name.to_string unit_name in + match Load_path.find_uncap_with_visibility (unit_name ^ ".cmi") with + | filename, visibility when allow_hidden -> + Some { filename; cmi = read_cmi_lazy filename; visibility} + | filename, Visible -> + Some { filename; cmi = read_cmi_lazy filename; visibility = Visible} + | _, Hidden + | exception Not_found -> None) end type can_load_cmis = @@ -58,6 +63,7 @@ type pers_struct = { ps_crcs: Import_info.t array; ps_filename: string; ps_flags: pers_flags list; + ps_visibility: Load_path.visibility; } (* If a .cmi file is missing (or invalid), we @@ -180,7 +186,7 @@ let save_pers_struct penv crc comp_unit flags filename = add_import penv modname let process_pers_struct penv check modname pers_sig = - let { Persistent_signature.filename; cmi } = pers_sig in + let { Persistent_signature.filename; cmi; visibility } = pers_sig in let name = cmi.cmi_name in let crcs = cmi.cmi_crcs in let flags = cmi.cmi_flags in @@ -188,6 +194,7 @@ let process_pers_struct penv check modname pers_sig = ps_crcs = crcs; ps_filename = filename; ps_flags = flags; + ps_visibility = visibility; } in let found_name = CU.name name in if not (CU.Name.equal modname found_name) then @@ -225,27 +232,29 @@ let acknowledge_pers_struct penv check modname pers_sig pm = let read_pers_struct penv val_of_pers_sig check modname filename ~add_binding = add_import penv modname; let cmi = read_cmi_lazy filename in - let pers_sig = { Persistent_signature.filename; cmi } in + let pers_sig = { Persistent_signature.filename; cmi; visibility = Visible } in let pm = val_of_pers_sig pers_sig in let ps = process_pers_struct penv check modname pers_sig in if add_binding then bind_pers_struct penv modname ps pm; (ps, pm) -let find_pers_struct penv val_of_pers_sig check name = +let find_pers_struct ~allow_hidden penv val_of_pers_sig check name = let {persistent_structures; _} = penv in if CU.Name.equal name CU.Name.predef_exn then raise Not_found; match Hashtbl.find persistent_structures name with - | Found (ps, pm) -> (ps, pm) + | Found (ps, pm) when allow_hidden || ps.ps_visibility = Load_path.Visible -> + (ps, pm) + | Found _ -> raise Not_found | Missing -> raise Not_found | exception Not_found -> match can_load_cmis penv with | Cannot_load_cmis _ -> raise Not_found | Can_load_cmis -> let psig = - match !Persistent_signature.load ~unit_name:name with + match !Persistent_signature.load ~allow_hidden ~unit_name:name with | Some psig -> psig | None -> - Hashtbl.add persistent_structures name Missing; + if allow_hidden then Hashtbl.add persistent_structures name Missing; raise Not_found in add_import penv name; @@ -260,10 +269,10 @@ let describe_prefix ppf prefix = Format.fprintf ppf "package %a" CU.Prefix.print prefix (* Emits a warning if there is no valid cmi for name *) -let check_pers_struct penv f ~loc name = +let check_pers_struct ~allow_hidden penv f ~loc name = let name_as_string = CU.Name.to_string name in try - ignore (find_pers_struct penv f false name) + ignore (find_pers_struct ~allow_hidden penv f false name) with | Not_found -> let warn = Warnings.No_cmi_file(name_as_string, None) in @@ -300,10 +309,10 @@ let check_pers_struct penv f ~loc name = let read penv f modname filename ~add_binding = snd (read_pers_struct penv f true modname filename ~add_binding) -let find penv f name = - snd (find_pers_struct penv f true name) +let find ~allow_hidden penv f name = + snd (find_pers_struct ~allow_hidden penv f true name) -let check penv f ~loc name = +let check ~allow_hidden penv f ~loc name = let {persistent_structures; _} = penv in if not (Hashtbl.mem persistent_structures name) then begin (* PR#6843: record the weak dependency ([add_import]) regardless of @@ -312,7 +321,7 @@ let check penv f ~loc name = add_import penv name; if (Warnings.is_active (Warnings.No_cmi_file("", None))) then !add_delayed_check_forward - (fun () -> check_pers_struct penv f ~loc name) + (fun () -> check_pers_struct ~allow_hidden penv f ~loc name) end (* CR mshinwell: delete this having moved to 4.14 build compilers *) @@ -336,7 +345,7 @@ let crc_of_unit penv f name = match Consistbl.find penv.crc_units name with | Some (_, crc) -> crc | None -> - let (ps, _pm) = find_pers_struct penv f true name in + let (ps, _pm) = find_pers_struct ~allow_hidden:true penv f true name in match Array.find_opt (Import_info.has_name ~name) ps.ps_crcs with | None -> assert false | Some import_info -> @@ -379,7 +388,7 @@ let make_cmi penv modname sign alerts = } let save_cmi penv psig = - let { Persistent_signature.filename; cmi } = psig in + let { Persistent_signature.filename; cmi; _ } = psig in Misc.try_finally (fun () -> let { cmi_name = modname; diff --git a/ocaml/typing/persistent_env.mli b/ocaml/typing/persistent_env.mli index bfa3f05345d..4e4e8a2cbea 100644 --- a/ocaml/typing/persistent_env.mli +++ b/ocaml/typing/persistent_env.mli @@ -38,12 +38,15 @@ val report_error: Format.formatter -> error -> unit module Persistent_signature : sig type t = { filename : string; (** Name of the file containing the signature. *) - cmi : Cmi_format.cmi_infos_lazy } + cmi : Cmi_format.cmi_infos_lazy; + visibility : Load_path.visibility + } (** Function used to load a persistent signature. The default is to look for the .cmi file in the load path. This function can be overridden to load it from memory, for instance to build a self-contained toplevel. *) - val load : (unit_name:Compilation_unit.Name.t -> t option) ref + val load : + (allow_hidden:bool -> unit_name:Compilation_unit.Name.t -> t option) ref end type can_load_cmis = @@ -63,12 +66,12 @@ val fold : 'a t -> (Compilation_unit.Name.t -> 'a -> 'b -> 'b) -> 'b -> 'b bind the module name in the environment. *) val read : 'a t -> (Persistent_signature.t -> 'a) -> Compilation_unit.Name.t -> filepath -> add_binding:bool -> 'a -val find : 'a t -> (Persistent_signature.t -> 'a) +val find : allow_hidden:bool -> 'a t -> (Persistent_signature.t -> 'a) -> Compilation_unit.Name.t -> 'a val find_in_cache : 'a t -> Compilation_unit.Name.t -> 'a option -val check : 'a t -> (Persistent_signature.t -> 'a) +val check : allow_hidden:bool -> 'a t -> (Persistent_signature.t -> 'a) -> loc:Location.t -> Compilation_unit.Name.t -> unit (* [looked_up penv md] checks if one has already tried diff --git a/ocaml/typing/typemod.ml b/ocaml/typing/typemod.ml index 9a717f44e6f..b12439ed6a5 100644 --- a/ocaml/typing/typemod.ml +++ b/ocaml/typing/typemod.ml @@ -210,7 +210,7 @@ let initial_env ~loc ~initially_opened_module env in let units = - List.map Env.persistent_structures_of_dir (Load_path.get ()) + List.map Env.persistent_structures_of_dir (Load_path.get_visible ()) in let env, units = match initially_opened_module with diff --git a/ocaml/utils/ccomp.ml b/ocaml/utils/ccomp.ml index 41d481b2e80..a6318cf0922 100644 --- a/ocaml/utils/ccomp.ml +++ b/ocaml/utils/ccomp.ml @@ -115,7 +115,8 @@ let compile_file ?output ?(opt="") ?stable_name name = (String.concat " " (List.rev !Clflags.all_ccopts)) (quote_prefixed ~response_files:true "-I" (List.map (Misc.expand_directory Config.standard_library) - (List.rev !Clflags.include_dirs))) + (List.rev ( !Clflags.hidden_include_dirs + @ !Clflags.include_dirs)))) (Clflags.std_include_flag "-I") (Filename.quote name) (* cl tediously includes the name of the C file as the first thing it @@ -185,7 +186,7 @@ let call_linker ?(native_toplevel = false) mode output_name files extra = Config.native_pack_linker (Filename.quote output_name) (quote_prefixed ~response_files:true - l_prefix (Load_path.get_paths ())) + l_prefix (Load_path.get_path_list ())) (quote_files ~response_files:true (remove_Wl files)) extra else @@ -201,7 +202,8 @@ let call_linker ?(native_toplevel = false) mode output_name files extra = "" (*(Clflags.std_include_flag "-I")*) (if native_toplevel then "" else - quote_prefixed ~response_files:true "-L" (Load_path.get_paths ())) + quote_prefixed ~response_files:true "-L" + (Load_path.get_path_list ())) (String.concat " " (List.rev !Clflags.all_ccopts)) (quote_files ~response_files:true files) extra diff --git a/ocaml/utils/clflags.ml b/ocaml/utils/clflags.ml index adb56fdf341..e34c9fba21a 100644 --- a/ocaml/utils/clflags.ml +++ b/ocaml/utils/clflags.ml @@ -46,7 +46,8 @@ let cmi_file = ref None let compile_only = ref false (* -c *) and output_name = ref (None : string option) (* -o *) -and include_dirs = ref ([] : string list)(* -I *) +and include_dirs = ref ([] : string list) (* -I *) +and hidden_include_dirs = ref ([] : string list) (* -H *) and no_std_include = ref false (* -nostdlib *) and no_cwd = ref false (* -nocwd *) and print_types = ref false (* -i *) diff --git a/ocaml/utils/clflags.mli b/ocaml/utils/clflags.mli index 4bdae1f5dcc..c7857f53340 100644 --- a/ocaml/utils/clflags.mli +++ b/ocaml/utils/clflags.mli @@ -58,6 +58,7 @@ val cmi_file : string option ref val compile_only : bool ref val output_name : string option ref val include_dirs : string list ref +val hidden_include_dirs : string list ref val no_std_include : bool ref val no_cwd : bool ref val print_types : bool ref diff --git a/ocaml/utils/load_path.ml b/ocaml/utils/load_path.ml index 42330d56232..d3e41aa12e3 100644 --- a/ocaml/utils/load_path.ml +++ b/ocaml/utils/load_path.ml @@ -19,17 +19,22 @@ module STbl = Misc.Stdlib.String.Tbl (* Mapping from basenames to full filenames *) type registry = string STbl.t -let files : registry ref = s_table STbl.create 42 -let files_uncap : registry ref = s_table STbl.create 42 +let visible_files : registry ref = s_table STbl.create 42 +let visible_files_uncap : registry ref = s_table STbl.create 42 + +let hidden_files : registry ref = s_table STbl.create 42 +let hidden_files_uncap : registry ref = s_table STbl.create 42 module Dir = struct type t = { path : string; files : string list; + hidden : bool; } let path t = t.path let files t = t.files + let hidden t = t.hidden let find t fn = if List.mem fn t.files then @@ -56,26 +61,43 @@ module Dir = struct with Sys_error _ -> [||] - let create path = - { path; files = Array.to_list (readdir_compat path) } + let create ~hidden path = + { path; files = Array.to_list (readdir_compat path); hidden } end type auto_include_callback = (Dir.t -> string -> string option) -> string -> string -let dirs = s_ref [] +let visible_dirs = s_ref [] +let hidden_dirs = s_ref [] let no_auto_include _ _ = raise Not_found let auto_include_callback = ref no_auto_include let reset () = assert (not Config.merlin || Local_store.is_bound ()); - STbl.clear !files; - STbl.clear !files_uncap; - dirs := []; + STbl.clear !hidden_files; + STbl.clear !hidden_files_uncap; + STbl.clear !visible_files; + STbl.clear !visible_files_uncap; + hidden_dirs := []; + visible_dirs := []; auto_include_callback := no_auto_include -let get () = List.rev !dirs -let get_paths () = List.rev_map Dir.path !dirs +let get_visible () = List.rev !visible_dirs + +let get_path_list () = + Misc.rev_map_end Dir.path !visible_dirs (List.rev_map Dir.path !hidden_dirs) + +type paths = + { visible : string list; + hidden : string list } + +let get_paths () = + { visible = List.rev_map Dir.path !visible_dirs; + hidden = List.rev_map Dir.path !hidden_dirs } + +let get_visible_path_list () = List.rev_map Dir.path !visible_dirs +let get_hidden_path_list () = List.rev_map Dir.path !hidden_dirs (* Optimized version of [add] below, for use in [init] and [remove_dir]: since we are starting from an empty cache, we can avoid checking whether a unit @@ -84,51 +106,72 @@ let get_paths () = List.rev_map Dir.path !dirs let prepend_add dir = List.iter (fun base -> let fn = Filename.concat dir.Dir.path base in - STbl.replace !files base fn; - STbl.replace !files_uncap (String.uncapitalize_ascii base) fn + if dir.Dir.hidden then begin + STbl.replace !hidden_files base fn; + STbl.replace !hidden_files_uncap (String.uncapitalize_ascii base) fn + end else begin + STbl.replace !visible_files base fn; + STbl.replace !visible_files_uncap (String.uncapitalize_ascii base) fn + end ) dir.Dir.files -let init ~auto_include l = +let init ~auto_include ~visible ~hidden = reset (); - dirs := List.rev_map Dir.create l; - List.iter prepend_add !dirs; + visible_dirs := List.rev_map (Dir.create ~hidden:false) visible; + hidden_dirs := List.rev_map (Dir.create ~hidden:true) hidden; + List.iter prepend_add !hidden_dirs; + List.iter prepend_add !visible_dirs; auto_include_callback := auto_include let remove_dir dir = assert (not Config.merlin || Local_store.is_bound ()); - let new_dirs = List.filter (fun d -> Dir.path d <> dir) !dirs in - if List.compare_lengths new_dirs !dirs <> 0 then begin + let visible = List.filter (fun d -> Dir.path d <> dir) !visible_dirs in + let hidden = List.filter (fun d -> Dir.path d <> dir) !hidden_dirs in + if List.compare_lengths visible !visible_dirs <> 0 + || List.compare_lengths hidden !hidden_dirs <> 0 then begin reset (); - List.iter prepend_add new_dirs; - dirs := new_dirs + visible_dirs := visible; + hidden_dirs := hidden; + List.iter prepend_add hidden; + List.iter prepend_add visible end (* General purpose version of function to add a new entry to load path: We only - add a basename to the cache if it is not already present in the cache, in - order to enforce left-to-right precedence. *) -let add dir = + add a basename to the cache if it is not already present, in order to enforce + left-to-right precedence. *) +let add (dir : Dir.t) = assert (not Config.merlin || Local_store.is_bound ()); + let update base fn visible_files hidden_files = + if dir.hidden && not (STbl.mem !hidden_files base) then + STbl.replace !hidden_files base fn + else if not (STbl.mem !visible_files base) then + STbl.replace !visible_files base fn + in List.iter (fun base -> let fn = Filename.concat dir.Dir.path base in - if not (STbl.mem !files base) then - STbl.replace !files base fn; + update base fn visible_files hidden_files; let ubase = String.uncapitalize_ascii base in - if not (STbl.mem !files_uncap ubase) then - STbl.replace !files_uncap ubase fn) - dir.Dir.files; - dirs := dir :: !dirs + update ubase fn visible_files_uncap hidden_files_uncap) + dir.files; + if dir.hidden then + hidden_dirs := dir :: !hidden_dirs + else + visible_dirs := dir :: !visible_dirs let append_dir = add -let add_dir dir = add (Dir.create dir) +let add_dir ~hidden dir = add (Dir.create ~hidden dir) (* Add the directory at the start of load path - so basenames are unconditionally added. *) -let prepend_dir dir = +let prepend_dir (dir : Dir.t) = assert (not Config.merlin || Local_store.is_bound ()); prepend_add dir; - dirs := !dirs @ [dir] + if dir.hidden then + hidden_dirs := !hidden_dirs @ [dir] + else + visible_dirs := !visible_dirs @ [dir] let is_basename fn = Filename.basename fn = fn @@ -150,27 +193,40 @@ let auto_include_otherlibs = (* Ensure directories are only ever scanned once *) let expand = Misc.expand_directory Config.standard_library in let otherlibs = - let read_lib lib = lazy (Dir.create (expand ("+" ^ lib))) in + let read_lib lib = lazy (Dir.create ~hidden:false (expand ("+" ^ lib))) in List.map (fun lib -> (lib, read_lib lib)) ["dynlink"; "str"; "unix"] in auto_include_libs otherlibs +type visibility = Visible | Hidden + +let find_file_in_cache fn visible_files hidden_files = + try (STbl.find !visible_files fn, Visible) with + | Not_found -> (STbl.find !hidden_files fn, Hidden) + let find fn = assert (not Config.merlin || Local_store.is_bound ()); try if is_basename fn && not !Sys.interactive then - STbl.find !files fn + fst (find_file_in_cache fn visible_files hidden_files) else - Misc.find_in_path (get_paths ()) fn + Misc.find_in_path (get_path_list ()) fn with Not_found -> !auto_include_callback Dir.find fn -let find_uncap fn = +let find_uncap_with_visibility fn = assert (not Config.merlin || Local_store.is_bound ()); try if is_basename fn && not !Sys.interactive then - STbl.find !files_uncap (String.uncapitalize_ascii fn) + find_file_in_cache (String.uncapitalize_ascii fn) + visible_files_uncap hidden_files_uncap else - Misc.find_in_path_uncap (get_paths ()) fn + try + (Misc.find_in_path_uncap (get_visible_path_list ()) fn, Visible) + with + | Not_found -> + (Misc.find_in_path_uncap (get_hidden_path_list ()) fn, Hidden) with Not_found -> let fn_uncap = String.uncapitalize_ascii fn in - !auto_include_callback Dir.find_uncap fn_uncap + (!auto_include_callback Dir.find_uncap fn_uncap, Visible) + +let find_uncap fn = fst (find_uncap_with_visibility fn) diff --git a/ocaml/utils/load_path.mli b/ocaml/utils/load_path.mli index fe3abaf95de..757324f148c 100644 --- a/ocaml/utils/load_path.mli +++ b/ocaml/utils/load_path.mli @@ -14,15 +14,15 @@ (** Management of include directories. - This module offers a high level interface to locating files in the - load path, which is constructed from [-I] command line flags and a few + This module offers a high level interface to locating files in the load + path, which is constructed from [-I] and [-H] command line flags and a few other parameters. It makes the assumption that the contents of include directories doesn't change during the execution of the compiler. *) -val add_dir : string -> unit +val add_dir : hidden:bool -> string -> unit (** Add a directory to the end of the load path (i.e. at lowest priority.) *) val remove_dir : string -> unit @@ -35,7 +35,7 @@ module Dir : sig type t (** Represent one directory in the load path. *) - val create : string -> t + val create : hidden:bool -> string -> t val path : t -> string @@ -43,6 +43,10 @@ module Dir : sig (** All the files in that directory. This doesn't include files in sub-directories of this directory. *) + val hidden : t -> bool + (** If the modules in this directory should not be bound in the initial + scope *) + val find : t -> string -> string option (** [find dir fn] returns the full path to [fn] in [dir]. *) @@ -59,8 +63,13 @@ val no_auto_include : auto_include_callback (** No automatic directory inclusion: misses in the load path raise [Not_found] as normal. *) -val init : auto_include:auto_include_callback -> string list -> unit -(** [init l] is the same as [reset (); List.iter add_dir (List.rev l)] *) +val init : + auto_include:auto_include_callback -> visible:string list -> + hidden:string list -> unit +(** [init ~visible ~hidden] is the same as + [reset (); + List.iter add_dir (List.rev hidden); + List.iter add_dir (List.rev visible)] *) val auto_include_otherlibs : (string -> unit) -> auto_include_callback @@ -68,9 +77,16 @@ val auto_include_otherlibs : {!Load_path.init} and automatically adds [-I +lib] to the load path after calling [alert lib]. *) -val get_paths : unit -> string list +val get_path_list : unit -> string list (** Return the list of directories passed to [add_dir] so far. *) +type paths = + { visible : string list; + hidden : string list } + +val get_paths : unit -> paths +(** Return the directories passed to [add_dir] so far. *) + val find : string -> string (** Locate a file in the load path. Raise [Not_found] if the file cannot be found. This function is optimized for the case where the @@ -81,6 +97,12 @@ val find_uncap : string -> string (** Same as [find], but search also for uncapitalized name, i.e. if name is Foo.ml, allow /path/Foo.ml and /path/foo.ml to match. *) +type visibility = Visible | Hidden + +val find_uncap_with_visibility : string -> string * visibility +(** Same as [find_uncap], but also reports whether the cmi was found in a -I + directory (Visible) or a -H directory (Hidden) *) + val[@deprecated] add : Dir.t -> unit (** Old name for {!append_dir} *) @@ -92,5 +114,6 @@ val prepend_dir : Dir.t -> unit (** [prepend_dir d] adds [d] to the start of the load path (i.e. at highest priority. *) -val get : unit -> Dir.t list -(** Same as [get_paths ()], except that it returns a [Dir.t list]. *) +val get_visible : unit -> Dir.t list +(** Same as [get_paths ()], except that it returns a [Dir.t list], and doesn't + include the -H paths. *) diff --git a/ocaml/utils/misc.ml b/ocaml/utils/misc.ml index 68fd23bd1a1..674b9ebd45e 100644 --- a/ocaml/utils/misc.ml +++ b/ocaml/utils/misc.ml @@ -70,6 +70,13 @@ let rec map_end f l1 l2 = [] -> l2 | hd::tl -> f hd :: map_end f tl l2 +let rev_map_end f l1 l2 = + let rec rmap_f accu = function + | [] -> accu + | hd::tl -> rmap_f (f hd :: accu) tl + in + rmap_f l2 l1 + let rec map_left_right f = function [] -> [] | hd::tl -> let res = f hd in res :: map_left_right f tl diff --git a/ocaml/utils/misc.mli b/ocaml/utils/misc.mli index 263749f7374..170c080c588 100644 --- a/ocaml/utils/misc.mli +++ b/ocaml/utils/misc.mli @@ -77,6 +77,9 @@ val reraise_preserving_backtrace : exn -> (unit -> unit) -> 'a val map_end: ('a -> 'b) -> 'a list -> 'b list -> 'b list (** [map_end f l t] is [map f l @ t], just more efficient. *) +val rev_map_end: ('a -> 'b) -> 'a list -> 'b list -> 'b list + (** [rev_map_end f l t] is [map f (rev l) @ t], just more efficient. *) + val map_left_right: ('a -> 'b) -> 'a list -> 'b list (** Like [List.map], with guaranteed left-to-right evaluation order *) diff --git a/tools/flambda_backend_objinfo.ml b/tools/flambda_backend_objinfo.ml index e9889d8ae5d..730b24640a2 100644 --- a/tools/flambda_backend_objinfo.ml +++ b/tools/flambda_backend_objinfo.ml @@ -113,8 +113,10 @@ let print_cmt_infos cmt = (match cmt.cmt_sourcefile with None -> "(none)" | Some f -> f); printf "Compilation flags:"; Array.iter print_spaced_string cmt.cmt_args; - printf "\nLoad path:"; - List.iter print_spaced_string cmt.cmt_loadpath; + printf "\nLoad path:\n Visible:"; + List.iter print_spaced_string cmt.cmt_loadpath.visible; + printf "\n Hidden:"; + List.iter print_spaced_string cmt.cmt_loadpath.hidden; printf "\n"; printf "cmt interface digest: %s\n" (match cmt.cmt_interface_digest with diff --git a/tools/merge_archives.ml b/tools/merge_archives.ml index 2863e6da8ec..fb3aa897206 100644 --- a/tools/merge_archives.ml +++ b/tools/merge_archives.ml @@ -23,7 +23,7 @@ let merge_cma ~target ~archives = Clflags.all_ccopts := []; Clflags.dllibs := []; List.iter - (fun archive -> Load_path.add_dir (Filename.dirname archive)) + (fun archive -> Load_path.add_dir ~hidden:false (Filename.dirname archive)) archives; let error reporter err = Format.eprintf "Error whilst merging .cma files:@ %a\n%!" reporter err;