diff --git a/doc/reference/dune/library.rst b/doc/reference/dune/library.rst index 01a964507d4..2f58b99380c 100644 --- a/doc/reference/dune/library.rst +++ b/doc/reference/dune/library.rst @@ -210,6 +210,13 @@ order to declare a multi-directory library, you need to use the unless you use Dune to synthesize the ``depends`` and ``depopts`` sections of your opam file. +.. describe:: (implements ) + + ``name`` defines the name of the virtual library or the parameter library you + are implementing. + + See :doc:`/virtual-libraries` or :doc:`/reference/dune/library_parameter`. + .. describe:: (js_of_ocaml ...) Sets options for JavaScript compilation, see :ref:`jsoo-field`. diff --git a/src/dune_rules/compilation_context.ml b/src/dune_rules/compilation_context.ml index 31ad378b113..4d6045141ef 100644 --- a/src/dune_rules/compilation_context.ml +++ b/src/dune_rules/compilation_context.ml @@ -87,6 +87,7 @@ type t = ; requires_compile : Lib.t list Resolve.Memo.t ; requires_hidden : Lib.t list Resolve.Memo.t ; requires_link : Lib.t list Resolve.t Memo.Lazy.t + ; implements : Virtual_rules.t ; includes : Includes.t ; preprocessing : Pp_spec.t ; opaque : bool @@ -94,7 +95,6 @@ type t = ; js_of_ocaml : Js_of_ocaml.In_context.t option Js_of_ocaml.Mode.Pair.t ; sandbox : Sandbox_config.t ; package : Package.t option - ; vimpl : Vimpl.t option ; melange_package_name : Lib_name.t option ; modes : Lib_mode.Map.Set.t ; bin_annot : bool @@ -121,7 +121,7 @@ let sandbox t = t.sandbox let set_sandbox t sandbox = { t with sandbox } let package t = t.package let melange_package_name t = t.melange_package_name -let vimpl t = t.vimpl +let implements t = t.implements let modes t = t.modes let bin_annot t = t.bin_annot let context t = Super_context.context t.super_context @@ -142,7 +142,7 @@ let create ~js_of_ocaml ~package ~melange_package_name - ?vimpl + ?(implements = Virtual_rules.no_implements) ?modes ?bin_annot ?loc @@ -185,7 +185,7 @@ let create ~sandbox ~obj_dir ~sctx:super_context - ~vimpl + ~impl:implements ~modules and+ bin_annot = match bin_annot with @@ -200,6 +200,7 @@ let create ; requires_compile = direct_requires ; requires_hidden = hidden_requires ; requires_link + ; implements ; includes = Includes.make ~project ~opaque ~direct_requires ~hidden_requires ocaml.lib_config ; preprocessing @@ -208,7 +209,6 @@ let create ; js_of_ocaml ; sandbox ; package - ; vimpl ; melange_package_name ; modes ; bin_annot diff --git a/src/dune_rules/compilation_context.mli b/src/dune_rules/compilation_context.mli index 92c58a9da25..47eafad027c 100644 --- a/src/dune_rules/compilation_context.mli +++ b/src/dune_rules/compilation_context.mli @@ -33,7 +33,7 @@ val create -> js_of_ocaml:Js_of_ocaml.In_context.t option Js_of_ocaml.Mode.Pair.t -> package:Package.t option -> melange_package_name:Lib_name.t option - -> ?vimpl:Vimpl.t + -> ?implements:Virtual_rules.t -> ?modes:Mode_conf.Set.Details.t Lib_mode.Map.t -> ?bin_annot:bool -> ?loc:Loc.t @@ -65,7 +65,7 @@ val js_of_ocaml : t -> Js_of_ocaml.In_context.t option Js_of_ocaml.Mode.Pair.t val sandbox : t -> Sandbox_config.t val set_sandbox : t -> Sandbox_config.t -> t val package : t -> Package.t option -val vimpl : t -> Vimpl.t option +val implements : t -> Virtual_rules.t val melange_package_name : t -> Lib_name.t option val modes : t -> Lib_mode.Map.Set.t val for_wrapped_compat : t -> t diff --git a/src/dune_rules/dep_rules.ml b/src/dune_rules/dep_rules.ml index 6aa9906f81d..2da4643b15c 100644 --- a/src/dune_rules/dep_rules.ml +++ b/src/dune_rules/dep_rules.ml @@ -131,7 +131,7 @@ let rec deps_of ~obj_dir ~modules ~sandbox - ~vimpl + ~impl ~dir ~sctx ~ml_kind @@ -156,14 +156,14 @@ let rec deps_of in match m with | Imported_from_vlib _ -> - let vimpl = Option.value_exn vimpl in + let vimpl = Virtual_rules.vimpl_exn impl in skip_if_source_absent (deps_of_vlib_module ~obj_dir ~vimpl ~dir ~sctx ~ml_kind) m | Normal m -> skip_if_source_absent (deps_of_module ~modules ~sandbox ~sctx ~dir ~obj_dir ~ml_kind) m | Impl_of_virtual_module impl_or_vlib -> - deps_of ~obj_dir ~modules ~sandbox ~vimpl ~dir ~sctx ~ml_kind + deps_of ~obj_dir ~modules ~sandbox ~impl ~dir ~sctx ~ml_kind @@ let m = Ml_kind.Dict.get impl_or_vlib ml_kind in (match ml_kind with @@ -198,12 +198,12 @@ let dict_of_func_concurrently f = Ml_kind.Dict.make ~impl ~intf ;; -let for_module ~obj_dir ~modules ~sandbox ~vimpl ~dir ~sctx module_ = +let for_module ~obj_dir ~modules ~sandbox ~impl ~dir ~sctx module_ = dict_of_func_concurrently - (deps_of ~obj_dir ~modules ~sandbox ~vimpl ~dir ~sctx (Normal module_)) + (deps_of ~obj_dir ~modules ~sandbox ~impl ~dir ~sctx (Normal module_)) ;; -let rules ~obj_dir ~modules ~sandbox ~vimpl ~sctx ~dir = +let rules ~obj_dir ~modules ~sandbox ~impl ~sctx ~dir = match Modules.With_vlib.as_singleton modules with | Some m -> Memo.return (Dep_graph.Ml_kind.dummy m) | None -> @@ -211,7 +211,7 @@ let rules ~obj_dir ~modules ~sandbox ~vimpl ~sctx ~dir = let+ per_module = Modules.With_vlib.obj_map modules |> Parallel_map.parallel_map ~f:(fun _obj_name m -> - deps_of ~obj_dir ~modules ~sandbox ~vimpl ~sctx ~dir ~ml_kind m) + deps_of ~obj_dir ~modules ~sandbox ~impl ~sctx ~dir ~ml_kind m) in Dep_graph.make ~dir ~per_module) ;; diff --git a/src/dune_rules/dep_rules.mli b/src/dune_rules/dep_rules.mli index 0e14d5a627c..a83040db896 100644 --- a/src/dune_rules/dep_rules.mli +++ b/src/dune_rules/dep_rules.mli @@ -6,7 +6,7 @@ val for_module : obj_dir:Path.Build.t Obj_dir.t -> modules:Modules.With_vlib.t -> sandbox:Sandbox_config.t - -> vimpl:Vimpl.t option + -> impl:Virtual_rules.t -> dir:Path.Build.t -> sctx:Super_context.t -> Module.t @@ -23,7 +23,7 @@ val rules : obj_dir:Path.Build.t Obj_dir.t -> modules:Modules.With_vlib.t -> sandbox:Sandbox_config.t - -> vimpl:Vimpl.t option + -> impl:Virtual_rules.t -> sctx:Super_context.t -> dir:Path.Build.t -> Dep_graph.Ml_kind.t Memo.t diff --git a/src/dune_rules/install_rules.ml b/src/dune_rules/install_rules.ml index de0211b7e1a..c1a6e85b7f8 100644 --- a/src/dune_rules/install_rules.ml +++ b/src/dune_rules/install_rules.ml @@ -97,14 +97,16 @@ end = struct >>| Modules.With_vlib.modules >>| Option.some and+ foreign_archives = - match Lib_info.virtual_ lib with - | false -> Memo.return (Mode.Map.Multi.to_flat_list @@ Lib_info.foreign_archives lib) - | true -> + match Lib_info.kind lib with + | Dune_file _ -> + Memo.return (Mode.Map.Multi.to_flat_list @@ Lib_info.foreign_archives lib) + | Virtual -> let+ foreign_sources = Dir_contents.foreign_sources dir_contents in let name = Lib_info.name lib in let files = Foreign_sources.for_lib foreign_sources ~name in let { Lib_config.ext_obj; _ } = lib_config in Foreign.Sources.object_files files ~dir ~ext_obj + | Parameter -> Memo.return [] in List.rev_append (List.rev_concat_map @@ -209,7 +211,7 @@ end = struct ~libs:(Scope.libs scope) ~for_:(Library (Lib_info.lib_id info |> Lib_id.to_local_exn)) and+ impl = Virtual_rules.impl sctx ~lib ~scope in - Vimpl.impl_modules impl modules |> Modules.With_vlib.split_by_lib + Virtual_rules.impl_modules impl modules |> Modules.With_vlib.split_by_lib in let lib_src_dir = Lib_info.src_dir info in let sources = @@ -266,7 +268,6 @@ end = struct let { Lib_mode.Map.ocaml = { Mode.Dict.byte; native } as ocaml; melange } = Mode_conf.Lib.Set.eval lib.modes ~has_native in - let is_parameter = Library.is_parameter lib in let+ melange_runtime_entries = additional_deps lib.melange_runtime_deps and+ public_headers = additional_deps lib.public_headers and+ module_files = @@ -293,26 +294,27 @@ end = struct | Some f -> [ cm_kind, f ]) else [] in - let common = - let virtual_library = Library.is_virtual lib in - fun m -> - let cm_file kind = Obj_dir.Module.cm_file obj_dir m ~kind in - let open Lib_mode.Cm_kind in - let cmi = if_ (native || byte) (Ocaml Cmi, cm_file (Ocaml Cmi)) in - let rest = - if is_parameter - then [] - else - [ if_ native (Ocaml Cmx, cm_file (Ocaml Cmx)) - ; if_ (byte && virtual_library) (Ocaml Cmo, cm_file (Ocaml Cmo)) - ; if_ - (native && virtual_library) - (Ocaml Cmx, Obj_dir.Module.o_file obj_dir m ~ext_obj) - ; if_ melange (Melange Cmi, cm_file (Melange Cmi)) - ; if_ melange (Melange Cmj, cm_file (Melange Cmj)) + let common m = + let cm_file kind = Obj_dir.Module.cm_file obj_dir m ~kind in + let open Lib_mode.Cm_kind in + let cmi = if_ (native || byte) (Ocaml Cmi, cm_file (Ocaml Cmi)) in + let common_module_impls virtual_only = + (if_ native (Ocaml Cmx, cm_file (Ocaml Cmx)) :: virtual_only) + @ [ if_ melange (Melange Cmi, cm_file (Melange Cmi)) + ; if_ melange (Melange Cmj, cm_file (Melange Cmj)) + ] + in + let rest = + match (lib.kind : Lib_kind.t) with + | Parameter -> [] + | Virtual -> + common_module_impls + [ if_ byte (Ocaml Cmo, cm_file (Ocaml Cmo)) + ; if_ native (Ocaml Cmx, Obj_dir.Module.o_file obj_dir m ~ext_obj) ] - in - cmi :: rest |> List.rev_concat + | _ -> common_module_impls [] + in + cmi :: rest |> List.rev_concat in let set_dir m = List.rev_map ~f:(fun (cm_kind, p) -> cm_dir m cm_kind, p) in let+ modules_impl = @@ -354,9 +356,9 @@ end = struct [ sources ; melange_runtime_entries ; List.rev_map module_files ~f:(fun (sub_dir, file) -> make_entry ?sub_dir Lib file) - ; (match is_parameter with - | true -> [] - | false -> + ; (match lib.kind with + | Parameter -> [] + | Virtual | Dune_file _ -> List.rev_concat [ List.rev_map lib_files ~f:(fun (section, file) -> make_entry section file) ; List.rev_map execs ~f:(make_entry Libexec) @@ -837,8 +839,9 @@ end = struct let* { Scope.DB.Lib_entry.Set.libraries; _ } = Action_builder.of_memo entries in match List.find_map libraries ~f:(fun lib -> - let info = Lib.Local.info lib in - Option.some_if (Lib_info.virtual_ info) lib) + match Lib_info.kind (Lib.Local.info lib) with + | Parameter | Virtual -> Some lib + | Dune_file _ -> None) with | None -> Action_builder.lines_of meta_template | Some vlib -> diff --git a/src/dune_rules/lib.ml b/src/dune_rules/lib.ml index 759dcae4fbd..085b17e8131 100644 --- a/src/dune_rules/lib.ml +++ b/src/dune_rules/lib.ml @@ -235,15 +235,16 @@ module Error = struct ] ;; - let not_virtual_lib ~loc ~impl ~not_vlib = - let impl = Lib_info.name impl in - let not_vlib = Lib_info.name not_vlib in + let not_implementable ~loc ~lib ~not_impl = + let lib = Lib_info.name lib in + let not_impl = Lib_info.name not_impl in make ~loc [ Pp.textf - "Library %S is not virtual. It cannot be implemented by %S." - (Lib_name.to_string not_vlib) - (Lib_name.to_string impl) + "Library %S is neither a virtual library nor a library parameter. It cannot be \ + implemented by %S." + (Lib_name.to_string not_impl) + (Lib_name.to_string lib) ] ;; end @@ -724,12 +725,12 @@ end = struct let rec loop acc = function | [] -> Resolve.Memo.return acc | (lib, stack) :: libs -> - let virtual_ = Lib_info.virtual_ lib.info in - (match lib.implements, virtual_ with - | None, false -> loop acc libs - | Some _, true -> assert false (* can't be virtual and implement *) - | None, true -> loop (Map.set acc lib (No_impl stack)) libs - | Some vlib, false -> + (match lib.implements, Lib_info.kind lib.info with + | None, Dune_file _ -> loop acc libs + | None, (Parameter | Virtual) -> loop (Map.set acc lib (No_impl stack)) libs + | Some _, (Parameter | Virtual) -> + assert false (* can't be virtual and implement *) + | Some vlib, Dune_file _ -> let* vlib = Memo.return vlib in (match Map.find acc vlib with | None -> @@ -945,11 +946,11 @@ end = struct | Some ((loc, _) as name) -> let res = let open Resolve.Memo.O in - let* vlib = resolve_forbid_ignore name in - let virtual_ = Lib_info.virtual_ vlib.info in - match virtual_ with - | false -> Error.not_virtual_lib ~loc ~impl:info ~not_vlib:vlib.info - | true -> Resolve.Memo.return vlib + let* implements = resolve_forbid_ignore name in + match Lib_info.kind implements.info with + | Dune_file _ -> + Error.not_implementable ~loc ~lib:info ~not_impl:implements.info + | Parameter | Virtual -> Resolve.Memo.return implements in Memo.map res ~f:Option.some in diff --git a/src/dune_rules/lib_rules.ml b/src/dune_rules/lib_rules.ml index e8d3a0e132a..4c501c6d9b2 100644 --- a/src/dune_rules/lib_rules.ml +++ b/src/dune_rules/lib_rules.ml @@ -443,9 +443,9 @@ let setup_build_archives (lib : Library.t) ~top_sorted_modules ~cctx ~expander ~ Lib_info.eval_native_archives_exn lib_info ~modules:(Some modules) in let* () = - if Library.is_parameter lib - then Memo.return () - else ( + match lib.kind with + | Parameter -> Memo.return () + | Virtual | Dune_file _ -> let cm_files = let excluded_modules = (* ctypes type_gen and function_gen scripts should not be included in the @@ -457,7 +457,7 @@ let setup_build_archives (lib : Library.t) ~top_sorted_modules ~cctx ~expander ~ Cm_files.make ~excluded_modules ~obj_dir ~ext_obj ~modules ~top_sorted_modules () in iter_modes_concurrently modes.ocaml ~f:(fun mode -> - build_lib lib ~native_archives ~dir ~sctx ~expander ~flags ~mode ~cm_files)) + build_lib lib ~native_archives ~dir ~sctx ~expander ~flags ~mode ~cm_files) and* () = (* Build *.cma.js / *.wasma *) Memo.when_ modes.ocaml.byte (fun () -> @@ -489,7 +489,7 @@ let setup_build_archives (lib : Library.t) ~top_sorted_modules ~cctx ~expander ~ let cctx (lib : Library.t) ~sctx ~source_modules ~dir ~expander ~scope ~compile_info = let* flags = Buildable_rules.ocaml_flags sctx ~dir lib.buildable.flags - and* vimpl = Virtual_rules.impl sctx ~lib ~scope in + and* implements = Virtual_rules.impl sctx ~lib ~scope in let obj_dir = Library.obj_dir ~dir lib in let* modules, pp = Buildable_rules.modules_rules @@ -500,7 +500,7 @@ let cctx (lib : Library.t) ~sctx ~source_modules ~dir ~expander ~scope ~compile_ scope source_modules in - let modules = Vimpl.impl_modules vimpl modules in + let modules = Virtual_rules.impl_modules implements modules in let requires_compile = Lib.Compile.direct_requires compile_info in let requires_link = Lib.Compile.requires_link compile_info in let* modes = @@ -533,12 +533,12 @@ let cctx (lib : Library.t) ~sctx ~source_modules ~dir ~expander ~scope ~compile_ ~flags ~requires_compile ~requires_link + ~implements ~preprocessing:pp ~opaque:Inherit_from_settings ~js_of_ocaml:(Js_of_ocaml.Mode.Pair.map ~f:Option.some js_of_ocaml) ?stdlib:lib.stdlib ~package - ?vimpl ~melange_package_name ~modes ;; @@ -554,7 +554,7 @@ let library_rules = let modules = Compilation_context.modules cctx in let obj_dir = Compilation_context.obj_dir cctx in - let vimpl = Compilation_context.vimpl cctx in + let implements = Compilation_context.implements cctx in let sctx = Compilation_context.super_context cctx in let dir = Compilation_context.dir cctx in let scope = Compilation_context.scope cctx in @@ -566,9 +566,7 @@ let library_rules (Compilation_context.dep_graphs cctx).impl impl_only in - let* () = - Memo.Option.iter vimpl ~f:(Virtual_rules.setup_copy_rules_for_impl ~sctx ~dir) - in + let* () = Virtual_rules.setup_copy_rules_for_impl ~sctx ~dir implements in let* expander = Super_context.expander sctx ~dir in let* () = Check_rules.add_cycle_check sctx ~dir top_sorted_modules in let* () = gen_wrapped_compat_modules lib cctx @@ -593,7 +591,7 @@ let library_rules (not (Library.is_virtual lib)) (fun () -> setup_build_archives lib ~lib_info ~top_sorted_modules ~cctx ~expander) and+ () = - let vlib_stubs_o_files = Vimpl.vlib_stubs_o_files vimpl in + let vlib_stubs_o_files = Virtual_rules.stubs_o_files implements in Memo.when_ (Library.has_foreign lib || List.is_non_empty vlib_stubs_o_files) (fun () -> diff --git a/src/dune_rules/menhir/menhir_rules.ml b/src/dune_rules/menhir/menhir_rules.ml index 5a9eaf34d43..ebb0472510a 100644 --- a/src/dune_rules/menhir/menhir_rules.ml +++ b/src/dune_rules/menhir/menhir_rules.ml @@ -276,9 +276,9 @@ module Run (P : PARAMS) = struct let* deps = let obj_dir = Compilation_context.obj_dir cctx in let modules = Compilation_context.modules cctx in - let vimpl = Compilation_context.vimpl cctx in + let impl = Compilation_context.implements cctx in let dir = Obj_dir.dir obj_dir in - Dep_rules.for_module ~obj_dir ~modules ~sandbox ~vimpl ~dir ~sctx mock_module + Dep_rules.for_module ~obj_dir ~modules ~sandbox ~impl ~dir ~sctx mock_module in let* () = Module_compilation.ocamlc_i ~deps cctx mock_module ~output:(inferred_mli base) diff --git a/src/dune_rules/ml_sources.ml b/src/dune_rules/ml_sources.ml index 0787ecc3107..389f156a1a2 100644 --- a/src/dune_rules/ml_sources.ml +++ b/src/dune_rules/ml_sources.ml @@ -364,9 +364,11 @@ let make_lib_modules | From _ -> assert false in let kind : Modules_field_evaluator.kind = - match lib.virtual_modules with - | None -> if Library.is_parameter lib then Parameter else Exe_or_normal_lib - | Some virtual_modules -> Virtual { virtual_modules } + match lib.kind, lib.virtual_modules with + | Dune_file _, None -> Exe_or_normal_lib + | Parameter, None -> Parameter + | Virtual, Some virtual_modules -> Virtual { virtual_modules } + | (Dune_file _ | Parameter), Some _ | Virtual, None -> assert false in Memo.return (Resolve.return (kind, main_module_name, wrapped)) | Some _ -> @@ -427,11 +429,12 @@ let make_lib_modules | _, _ -> () in let () = - if Library.is_parameter lib && Option.is_none (Module_trie.as_singleton modules) - then + match lib.kind, Module_trie.as_singleton modules with + | Parameter, None -> User_error.raise ~loc:lib.buildable.loc - [ Pp.text "a library_parameter can't declare more than one module." ] + [ Pp.text "a library_parameter must declare exactly one module." ] + | _ -> () in let implements = Option.is_some lib.implements in let _loc, lib_name = lib.name in diff --git a/src/dune_rules/module_compilation.ml b/src/dune_rules/module_compilation.ml index b438fd900f4..8683ec407c2 100644 --- a/src/dune_rules/module_compilation.ml +++ b/src/dune_rules/module_compilation.ml @@ -238,6 +238,15 @@ let build_cm else Command.Args.empty in let as_parameter_arg = if Module.kind m = Parameter then [ "-as-parameter" ] else [] in + let as_argument_for = + Command.Args.dyn + (let open Action_builder.O in + let impl = Compilation_context.implements cctx in + let+ argument = Resolve.Memo.read @@ Virtual_rules.implements_parameter impl m in + match argument with + | None -> [] + | Some parameter -> [ "-as-argument-for"; Module_name.to_string parameter ]) + in let flags, sandbox = let flags = Command.Args.dyn (Ocaml_flags.get (Compilation_context.flags cctx) mode) @@ -291,6 +300,7 @@ let build_cm (Lib_mode.Cm_kind.Map.get (Compilation_context.includes cctx) cm_kind) ; extra_args ; As as_parameter_arg + ; as_argument_for ; S (melange_args cctx cm_kind m) ; A "-no-alias-deps" ; opaque_arg diff --git a/src/dune_rules/stanzas/library.ml b/src/dune_rules/stanzas/library.ml index b3a563bb6fb..1d29bf375d2 100644 --- a/src/dune_rules/stanzas/library.ml +++ b/src/dune_rules/stanzas/library.ml @@ -394,7 +394,6 @@ let best_name t = | Public p -> snd p.name ;; -let is_parameter t = t.kind = Parameter let is_virtual t = t.kind = Virtual let is_impl t = Option.is_some t.implements @@ -456,8 +455,11 @@ let to_lib_info let archive ?(dir = dir) ext = archive conf ~dir ~ext in let modes = Mode_conf.Lib.Set.eval ~has_native conf.modes in let archive_for_mode ~f_ext ~mode = - if Mode.Dict.get modes.ocaml mode && not (is_parameter conf) - then Some (archive (f_ext mode)) + if Mode.Dict.get modes.ocaml mode + then ( + match conf.kind with + | Parameter -> None + | Virtual | Dune_file _ -> Some (archive (f_ext mode))) else None in let archives_for_mode ~f_ext = diff --git a/src/dune_rules/stanzas/library.mli b/src/dune_rules/stanzas/library.mli index 702b037fe08..6119f2aa107 100644 --- a/src/dune_rules/stanzas/library.mli +++ b/src/dune_rules/stanzas/library.mli @@ -73,7 +73,6 @@ val archive : t -> dir:Path.Build.t -> ext:string -> Path.Build.t val best_name : t -> Lib_name.t val is_virtual : t -> bool -val is_parameter : t -> bool val is_impl : t -> bool val obj_dir : dir:Path.Build.t -> t -> Path.Build.t Obj_dir.t val main_module_name : t -> Lib_info.Main_module_name.t diff --git a/src/dune_rules/stanzas/parameter.ml b/src/dune_rules/stanzas/parameter.ml index 9669d17832f..587bb9d1855 100644 --- a/src/dune_rules/stanzas/parameter.ml +++ b/src/dune_rules/stanzas/parameter.ml @@ -27,7 +27,10 @@ let to_library t = ; library_flags = Ordered_set_lang.Unexpanded.standard ; c_library_flags = Ordered_set_lang.Unexpanded.standard ; virtual_deps = [] - ; wrapped = This (Simple false) + ; wrapped = + This (Simple true) + (* We set it as Simple true because, otherwise, we can't extract the + Singleton main module name. *) ; buildable = t.buildable ; dynlink = Dynlink_supported.of_bool false ; project = t.project diff --git a/src/dune_rules/vimpl.ml b/src/dune_rules/vimpl.ml index 85d8360419f..2a95ae7cb8d 100644 --- a/src/dune_rules/vimpl.ml +++ b/src/dune_rules/vimpl.ml @@ -12,14 +12,55 @@ let vlib_modules t = t.vlib_modules let vlib t = t.vlib let impl t = t.impl let impl_cm_kind t = t.impl_cm_kind +let vlib_stubs_o_files t = t.vlib_foreign_objects +let vlib_obj_map t = Modules.obj_map t.vlib_modules -let impl_modules t m = - match t with - | None -> Modules.With_vlib.modules m - | Some t -> Modules.With_vlib.impl ~vlib:t.vlib_modules m -;; - -let make ~vlib ~impl ~vlib_modules ~vlib_foreign_objects = +let make ~sctx ~scope ~(lib : Library.t) ~info ~vlib = + let open Memo.O in + let+ vlib_modules, vlib_foreign_objects = + match Lib_info.modules info, Lib_info.foreign_objects info with + | External modules, External fa -> + let modules = Option.value_exn modules in + Memo.return (Modules.With_vlib.drop_vlib modules, fa) + | External _, Local | Local, External _ -> assert false + | Local, Local -> + let name = Lib.name vlib in + let vlib = Lib.Local.of_lib_exn vlib in + let* dir_contents = + let info = Lib.Local.info vlib in + let dir = Lib_info.src_dir info in + Dir_contents.get sctx ~dir + in + let* ocaml = Context.ocaml (Super_context.context sctx) in + let* modules = + let db = Scope.libs scope in + let* preprocess = + (* TODO wrong, this should be delayed *) + Instrumentation.with_instrumentation + lib.buildable.preprocess + ~instrumentation_backend:(Lib.DB.instrumentation_backend db) + |> Resolve.Memo.read_memo + in + Dir_contents.ocaml dir_contents + >>= Ml_sources.modules + ~libs:db + ~for_:(Library (Lib_info.lib_id info |> Lib_id.to_local_exn)) + >>= + let pp_spec = + Staged.unstage (Pp_spec.pped_modules_map preprocess ocaml.version) + in + Modules.map_user_written ~f:(fun m -> Memo.return (pp_spec m)) + in + let+ foreign_objects = + Dir_contents.foreign_sources dir_contents + >>| Foreign_sources.for_lib ~name + >>| (let ext_obj = ocaml.lib_config.ext_obj in + let dir = Obj_dir.obj_dir (Lib.Local.obj_dir vlib) in + Foreign.Sources.object_files ~ext_obj ~dir) + >>| List.map ~f:Path.build + in + modules, foreign_objects + in let impl_cm_kind = let vlib_info = Lib.info vlib in let { Lib_mode.Map.ocaml = { byte; native = _ }; melange = _ } = @@ -27,12 +68,67 @@ let make ~vlib ~impl ~vlib_modules ~vlib_foreign_objects = in Mode.cm_kind (if byte then Byte else Native) in - { impl; impl_cm_kind; vlib; vlib_modules; vlib_foreign_objects } + { impl = lib; impl_cm_kind; vlib; vlib_modules; vlib_foreign_objects } ;; -let vlib_stubs_o_files = function - | None -> [] - | Some t -> t.vlib_foreign_objects +let setup_copy_rules ~sctx ~dir vimpl = + let open Memo.O in + let ctx = Super_context.context sctx in + let impl_obj_dir = Library.obj_dir ~dir vimpl.impl in + let vlib_obj_dir = Lib.info vimpl.vlib |> Lib_info.obj_dir in + let add_rule = Super_context.add_rule sctx ~dir in + let copy_to_obj_dir ~src ~dst = + add_rule ~loc:(Loc.of_pos __POS__) (Action_builder.symlink ~src ~dst) + in + let* { Lib_config.has_native; ext_obj; _ } = + let+ ocaml = Context.ocaml ctx in + ocaml.lib_config + in + let { Lib_mode.Map.ocaml = { byte; native }; melange } = + Mode_conf.Lib.Set.eval vimpl.impl.modes ~has_native + in + let copy_obj_file m kind = + let src = Obj_dir.Module.cm_file_exn vlib_obj_dir m ~kind in + let dst = Obj_dir.Module.cm_file_exn impl_obj_dir m ~kind in + copy_to_obj_dir ~src ~dst + in + let copy_ocamldep_file m = + match Obj_dir.to_local vlib_obj_dir with + | None -> Memo.return () + | Some vlib_obj_dir -> + (match Obj_dir.Module.dep vlib_obj_dir (Immediate (m, Impl)) with + | None -> Memo.return () + | Some src -> + let src = Path.build src in + let dst = + Obj_dir.Module.dep impl_obj_dir (Immediate (m, Impl)) |> Option.value_exn + in + copy_to_obj_dir ~src ~dst) + in + let copy_interface_to_impl ~src kind () = + let dst = Obj_dir.Module.cm_public_file_exn impl_obj_dir src ~kind in + let src = Obj_dir.Module.cm_public_file_exn vlib_obj_dir src ~kind in + copy_to_obj_dir ~src ~dst + in + let copy_objs src = + Memo.when_ (byte || native) (fun () -> copy_obj_file src (Ocaml Cmi)) + >>> Memo.when_ melange (fun () -> copy_obj_file src (Melange Cmi)) + >>> Memo.when_ + (Module.visibility src = Public + && Obj_dir.need_dedicated_public_dir impl_obj_dir) + (fun () -> + Memo.when_ (byte || native) (copy_interface_to_impl ~src (Ocaml Cmi)) + >>> Memo.when_ melange (copy_interface_to_impl ~src (Melange Cmi))) + >>> Memo.when_ (Module.has src ~ml_kind:Impl) (fun () -> + Memo.when_ byte (fun () -> copy_obj_file src (Ocaml Cmo)) + >>> Memo.when_ melange (fun () -> + copy_obj_file src (Melange Cmj) >>> copy_ocamldep_file src) + >>> Memo.when_ native (fun () -> + copy_obj_file src (Ocaml Cmx) + >>> + let object_file dir = Obj_dir.Module.o_file_exn dir src ~ext_obj in + copy_to_obj_dir ~src:(object_file vlib_obj_dir) ~dst:(object_file impl_obj_dir))) + in + let vlib_modules = vlib_modules vimpl in + Modules.fold vlib_modules ~init:(Memo.return ()) ~f:(fun m acc -> acc >>> copy_objs m) ;; - -let vlib_obj_map t = Modules.obj_map t.vlib_modules diff --git a/src/dune_rules/vimpl.mli b/src/dune_rules/vimpl.mli index c756b4cd9e2..77ddfcfb948 100644 --- a/src/dune_rules/vimpl.mli +++ b/src/dune_rules/vimpl.mli @@ -6,11 +6,12 @@ open Import type t val make - : vlib:Lib.t - -> impl:Library.t - -> vlib_modules:Modules.t - -> vlib_foreign_objects:Path.t list - -> t + : sctx:Super_context.t + -> scope:Scope.t + -> lib:Library.t + -> info:Path.t Lib_info.t + -> vlib:Lib.t + -> t Memo.t val impl : t -> Library.t @@ -18,12 +19,12 @@ val impl : t -> Library.t setting up the copying rules *) val vlib_modules : t -> Modules.t -val impl_modules : t option -> Modules.t -> Modules.With_vlib.t val vlib : t -> Lib.t (** Return the combined list of .o files for stubs consisting of .o files from the implementation and virtual library.*) -val vlib_stubs_o_files : t option -> Path.t list +val vlib_stubs_o_files : t -> Path.t list val impl_cm_kind : t -> Cm_kind.t val vlib_obj_map : t -> Modules.Sourced_module.t Module_name.Unique.Map.t +val setup_copy_rules : sctx:Super_context.t -> dir:Path.Build.t -> t -> unit Memo.t diff --git a/src/dune_rules/virtual_rules.ml b/src/dune_rules/virtual_rules.ml index 8e3e6c45fd1..3f00caef4b1 100644 --- a/src/dune_rules/virtual_rules.ml +++ b/src/dune_rules/virtual_rules.ml @@ -1,72 +1,48 @@ open Import open Memo.O -let setup_copy_rules_for_impl ~sctx ~dir vimpl = - let ctx = Super_context.context sctx in - let vlib = Vimpl.vlib vimpl in - let impl = Vimpl.impl vimpl in - let impl_obj_dir = Library.obj_dir ~dir impl in - let vlib_obj_dir = Lib.info vlib |> Lib_info.obj_dir in - let add_rule = Super_context.add_rule sctx ~dir in - let copy_to_obj_dir ~src ~dst = - add_rule ~loc:(Loc.of_pos __POS__) (Action_builder.symlink ~src ~dst) - in - let* { Lib_config.has_native; ext_obj; _ } = - let+ ocaml = Context.ocaml ctx in - ocaml.lib_config - in - let { Lib_mode.Map.ocaml = { byte; native }; melange } = - Mode_conf.Lib.Set.eval impl.modes ~has_native - in - let copy_obj_file m kind = - let src = Obj_dir.Module.cm_file_exn vlib_obj_dir m ~kind in - let dst = Obj_dir.Module.cm_file_exn impl_obj_dir m ~kind in - copy_to_obj_dir ~src ~dst - in - let copy_ocamldep_file m = - match Obj_dir.to_local vlib_obj_dir with - | None -> Memo.return () - | Some vlib_obj_dir -> - (match Obj_dir.Module.dep vlib_obj_dir (Immediate (m, Impl)) with - | None -> Memo.return () - | Some src -> - let src = Path.build src in - let dst = - Obj_dir.Module.dep impl_obj_dir (Immediate (m, Impl)) |> Option.value_exn - in - copy_to_obj_dir ~src ~dst) - in - let copy_interface_to_impl ~src kind () = - let dst = Obj_dir.Module.cm_public_file_exn impl_obj_dir src ~kind in - let src = Obj_dir.Module.cm_public_file_exn vlib_obj_dir src ~kind in - copy_to_obj_dir ~src ~dst - in - let copy_objs src = - Memo.when_ (byte || native) (fun () -> copy_obj_file src (Ocaml Cmi)) - >>> Memo.when_ melange (fun () -> copy_obj_file src (Melange Cmi)) - >>> Memo.when_ - (Module.visibility src = Public - && Obj_dir.need_dedicated_public_dir impl_obj_dir) - (fun () -> - Memo.when_ (byte || native) (copy_interface_to_impl ~src (Ocaml Cmi)) - >>> Memo.when_ melange (copy_interface_to_impl ~src (Melange Cmi))) - >>> Memo.when_ (Module.has src ~ml_kind:Impl) (fun () -> - Memo.when_ byte (fun () -> copy_obj_file src (Ocaml Cmo)) - >>> Memo.when_ melange (fun () -> - copy_obj_file src (Melange Cmj) >>> copy_ocamldep_file src) - >>> Memo.when_ native (fun () -> - copy_obj_file src (Ocaml Cmx) - >>> - let object_file dir = Obj_dir.Module.o_file_exn dir src ~ext_obj in - copy_to_obj_dir ~src:(object_file vlib_obj_dir) ~dst:(object_file impl_obj_dir))) - in - let vlib_modules = Vimpl.vlib_modules vimpl in - Modules.fold vlib_modules ~init:(Memo.return ()) ~f:(fun m acc -> acc >>> copy_objs m) +type t = + | No_implements + | Virtual of Vimpl.t + | Parameter of + { main_module : Module_name.t + ; implements_parameter : Module_name.t option Resolve.Memo.t + } + +let no_implements = No_implements + +let setup_copy_rules_for_impl ~sctx ~dir t = + match t with + | No_implements | Parameter _ -> Memo.return () + | Virtual vimpl -> Vimpl.setup_copy_rules ~sctx ~dir vimpl +;; + +let stubs_o_files = function + | No_implements | Parameter _ -> [] + | Virtual vimpl -> Vimpl.vlib_stubs_o_files vimpl +;; + +let implements_parameter t m = + match t with + | Parameter { main_module; implements_parameter } + when Module_name.equal main_module (Module.name m) -> implements_parameter + | _ -> Resolve.Memo.return None +;; + +let impl_modules t m = + match t with + | No_implements | Parameter _ -> Modules.With_vlib.modules m + | Virtual vimpl -> Modules.With_vlib.impl ~vlib:(Vimpl.vlib_modules vimpl) m +;; + +let vimpl_exn = function + | Virtual vimpl -> vimpl + | No_implements | Parameter _ -> Code_error.raise "Virtual_rules.vimpl_exn" [] ;; let impl sctx ~(lib : Library.t) ~scope = match lib.implements with - | None -> Memo.return None + | None -> Memo.return No_implements | Some (loc, implements) -> Lib.DB.find (Scope.libs scope) implements >>= (function @@ -79,57 +55,19 @@ let impl sctx ~(lib : Library.t) ~scope = ] | Some vlib -> let info = Lib.info vlib in - if not (Lib_info.virtual_ info) - then - User_error.raise - ~loc:lib.buildable.loc - [ Pp.textf - "Library %s isn't virtual and cannot be implemented" - (Lib_name.to_string implements) - ]; - let+ vlib_modules, vlib_foreign_objects = - match Lib_info.modules info, Lib_info.foreign_objects info with - | External modules, External fa -> - let modules = Option.value_exn modules in - Memo.return (Modules.With_vlib.drop_vlib modules, fa) - | External _, Local | Local, External _ -> assert false - | Local, Local -> - let name = Lib.name vlib in - let vlib = Lib.Local.of_lib_exn vlib in - let* dir_contents = - let info = Lib.Local.info vlib in - let dir = Lib_info.src_dir info in - Dir_contents.get sctx ~dir - in - let* ocaml = Context.ocaml (Super_context.context sctx) in - let* modules = - let db = Scope.libs scope in - let* preprocess = - (* TODO wrong, this should be delayed *) - Instrumentation.with_instrumentation - lib.buildable.preprocess - ~instrumentation_backend:(Lib.DB.instrumentation_backend db) - |> Resolve.Memo.read_memo - in - Dir_contents.ocaml dir_contents - >>= Ml_sources.modules - ~libs:db - ~for_:(Library (Lib_info.lib_id info |> Lib_id.to_local_exn)) - >>= - let pp_spec = - Staged.unstage (Pp_spec.pped_modules_map preprocess ocaml.version) - in - Modules.map_user_written ~f:(fun m -> Memo.return (pp_spec m)) - in - let+ foreign_objects = - Dir_contents.foreign_sources dir_contents - >>| Foreign_sources.for_lib ~name - >>| (let ext_obj = ocaml.lib_config.ext_obj in - let dir = Obj_dir.obj_dir (Lib.Local.obj_dir vlib) in - Foreign.Sources.object_files ~ext_obj ~dir) - >>| List.map ~f:Path.build - in - modules, foreign_objects - in - Some (Vimpl.make ~impl:lib ~vlib ~vlib_modules ~vlib_foreign_objects)) + (match Lib_info.kind info with + | Dune_file _ -> + User_error.raise + ~loc:lib.buildable.loc + [ Pp.textf + "Library %s isn't virtual and cannot be implemented" + (Lib_name.to_string implements) + ] + | Parameter -> + let main_module = Module_name.of_local_lib_name lib.name in + Memo.return + (Parameter { main_module; implements_parameter = Lib.main_module_name vlib }) + | Virtual -> + let+ vimpl = Vimpl.make ~sctx ~scope ~lib ~info ~vlib in + Virtual vimpl)) ;; diff --git a/src/dune_rules/virtual_rules.mli b/src/dune_rules/virtual_rules.mli index 64adc0e8aa4..f262415ed95 100644 --- a/src/dune_rules/virtual_rules.mli +++ b/src/dune_rules/virtual_rules.mli @@ -1,9 +1,16 @@ open Import +type t + val setup_copy_rules_for_impl : sctx:Super_context.t -> dir:Path.Build.t - -> Vimpl.t + -> t -> unit Memo.t -val impl : Super_context.t -> lib:Library.t -> scope:Scope.t -> Vimpl.t option Memo.t +val no_implements : t +val impl : Super_context.t -> lib:Library.t -> scope:Scope.t -> t Memo.t +val impl_modules : t -> Modules.t -> Modules.With_vlib.t +val stubs_o_files : t -> Path.t list +val implements_parameter : t -> Module.t -> Module_name.t option Resolve.Memo.t +val vimpl_exn : t -> Vimpl.t diff --git a/test/blackbox-tests/test-cases/oxcaml/helpers.sh b/test/blackbox-tests/test-cases/oxcaml/helpers.sh index 2ae043cb0b5..de4c91cf3ea 100644 --- a/test/blackbox-tests/test-cases/oxcaml/helpers.sh +++ b/test/blackbox-tests/test-cases/oxcaml/helpers.sh @@ -13,6 +13,34 @@ make_dir_with_dune() { cat > "$path/dune" } +make_dummy_intf() { + dir="$1" + name="$2" + cat >> "$dir/$name.mli" < unit +EOF +} + +make_dummy_impl() { + dir="$1" + name="$2" + cat >> "$dir/$name.ml" <> dune-project << EOF + > (package (name public_foo)) + > EOF + + $ make_dir_with_dune "foo" < (library_parameter + > (public_name public_foo) + > (name foo)) + > EOF + $ make_dummy_intf "foo" "foo" + + $ dune build $(target_cmi "foo") + +It should fail if we try to implement an unknown library: + + $ make_dir_with_dune "foo_impl" < (library + > (name foo_impl) + > (implements missing_foo)) + > EOF + $ dune build + File "foo_impl/dune", line 3, characters 13-24: + 3 | (implements missing_foo)) + ^^^^^^^^^^^ + Error: Library "missing_foo" not found. + -> required by alias default + [1] + +It should also fail if we try to implement a library that is neither a +parameter nor virtual: + + $ make_dir_with_dune "a_standard_lib" < (library + > (name a_standard_lib)) + > EOF + + $ rm -rf foo_impl + $ make_dir_with_dune "foo_impl" < (library + > (name foo_impl) + > (implements a_standard_lib)) + > EOF + + $ dune build + File "foo_impl/dune", line 3, characters 13-27: + 3 | (implements a_standard_lib)) + ^^^^^^^^^^^^^^ + Error: Library "a_standard_lib" is neither a virtual library nor a library + parameter. It cannot be implemented by "foo_impl". + -> required by alias default + [1] + +We implement the parameter using a library with a correct parameter this time. + + $ rm -rf foo_impl + $ make_dir_with_dune "foo_impl" < (library + > (name foo_impl) + > (implements foo)) + > EOF + $ make_dummy_impl "foo_impl" "foo_impl" + $ dune build + +The implementation can use multiple files, as long as the root module satisfies +the parameter interface: + + $ cat > foo_impl/foo_impl.ml < type t = string + > let f = Util.f + > EOF + $ cat > foo_impl/util.ml < let f = print_endline + > EOF + + $ dune build + +We change the implementation to be public instead of a local one. + + $ cat >> dune-project << EOF + > (package (name foo_impl)) + > EOF + $ make_dir_with_dune "foo_impl" < (library + > (public_name foo_impl) + > (implements foo)) + > EOF + $ dune build + +We create a library implementing the parameter with a bigger interface than what +the parameter expects. + + $ echo "let ignore_me = 42" >> foo_impl/foo_impl.ml + + $ dune build + +The compiler will signal an error if the library doesn't implement the required +interface: + + $ echo "type t = int" > foo_impl/foo_impl.ml + + $ dune build + File "foo_impl/foo_impl.ml", line 1: + Error: The argument module foo_impl/foo_impl.ml + does not match the parameter signature foo/.foo.objs/byte/foo.cmi: + The value f is required but not provided + File "foo/foo.mli", line 2, characters 0-17: Expected declaration + [1] + +A library implementing the parameter, but importing the content from other files. + + $ echo "type t = int" > foo_impl/aux_type.ml + $ echo "type t" > foo_impl/aux_type.mli + + $ echo "let f _ = ()" > foo_impl/aux_impl.ml + $ echo "val f: Aux_type.t -> unit" > foo_impl/aux_impl.mli + + $ cat > foo_impl/foo_impl.ml < include Aux_type + > include Aux_impl + > EOF + $ dune build + +We ensure we have all the necessary information for the implementation to be +used with findlib. It should use the public name of the parameter: + + $ dune build @install + $ cat _build/install/default/lib/foo_impl/dune-package | grep "implements" + (implements public_foo) + +We introduce a new parameter that is not public: + + $ make_dir_with_dune "bar" < (library_parameter + > (name bar)) + > EOF + $ make_dummy_intf "bar" "bar" + +A private library can implement this private parameter: + + $ make_dir_with_dune "bar_impl" < (library + > (name bar_impl) + > (implements bar)) + > EOF + $ make_dummy_impl "bar_impl" "bar_impl" + $ dune build + +We can check that the right flag was given to the compiler with ocamlobjinfo: + + $ ocamlobjinfo _build/default/bar_impl/bar_impl.cma | grep 'Parameter' + Parameter implemented: Bar + +A private library can also implement a public parameter: + + $ cat > bar_impl/dune < (library + > (name bar_impl) + > (implements foo)) + > EOF + $ dune build + + $ ocamlobjinfo _build/default/bar_impl/bar_impl.cma | grep Parameter + Parameter implemented: Foo + +But it's an error for a public library to implement a private parameter: + + $ cat >> dune-project << EOF + > (package (name bar_impl)) + > EOF + $ rm -rf bar_impl + $ make_dir_with_dune "bar_impl" < (library + > (public_name bar_impl) + > (implements bar)) + > EOF + $ dune build + File "bar_impl/dune", line 3, characters 13-16: + 3 | (implements bar)) + ^^^ + Error: Library "bar" is private, it cannot be a dependency of a public + library. You need to give "bar" a public name. + [1] + +It's impossible for a library to implement two parameters: + + $ rm -rf bar_impl + $ make_dir_with_dune "bar_impl" < (library + > (name bar_impl) + > (implements foo bar)) + > EOF + $ dune build + File "bar_impl/dune", line 3, characters 17-20: + 3 | (implements foo bar)) + ^^^ + Error: Too many arguments for "implements" + [1] + +An unwrapped library can't implement a parameter: + + $ rm -rf bar_impl + $ make_dir_with_dune "bar_impl" < (library + > (name bar_impl) + > (wrapped false) + > (implements bar)) + > EOF + $ dune build + File "bar_impl/dune", line 3, characters 10-15: + 3 | (wrapped false) + ^^^^^ + Error: Wrapped cannot be set for implementations. It is inherited from the + virtual library. + [1] diff --git a/test/blackbox-tests/test-cases/oxcaml/library_parameter.t b/test/blackbox-tests/test-cases/oxcaml/library_parameter.t index 2b91861ff48..c97d4bb0c3b 100644 --- a/test/blackbox-tests/test-cases/oxcaml/library_parameter.t +++ b/test/blackbox-tests/test-cases/oxcaml/library_parameter.t @@ -85,7 +85,7 @@ limit the parameter to one module now. 2 | (public_name multiple_param) 3 | (name multiple_param) 4 | (modules multiple_param data)) - Error: a library_parameter can't declare more than one module. + Error: a library_parameter must declare exactly one module. [1] We make sure the same happened if multiple modules exists in one directory. @@ -100,7 +100,7 @@ We make sure the same happened if multiple modules exists in one directory. 1 | (library_parameter 2 | (public_name multiple_param) 3 | (name multiple_param)) - Error: a library_parameter can't declare more than one module. + Error: a library_parameter must declare exactly one module. [1] We build the installable version to ensure we have generated the correct @@ -155,6 +155,7 @@ parameters. (library (name param.intf) (kind parameter) + (main_module_name Param_intf) (modes byte) (modules (singleton @@ -165,6 +166,7 @@ parameters. (library (name param.intf2) (kind parameter) + (main_module_name Param_intf2) (modes byte) (modules (singleton diff --git a/test/blackbox-tests/test-cases/virtual-libraries/impl-not-virtual-external.t b/test/blackbox-tests/test-cases/virtual-libraries/impl-not-virtual-external.t index 4e9ea999c58..f85f3dc01b5 100644 --- a/test/blackbox-tests/test-cases/virtual-libraries/impl-not-virtual-external.t +++ b/test/blackbox-tests/test-cases/virtual-libraries/impl-not-virtual-external.t @@ -20,6 +20,7 @@ an appropriate error message. File "dune", line 1, characters 21-29: 1 | (library (implements foodummy) (name bar)) ^^^^^^^^ - Error: Library "foodummy" is not virtual. It cannot be implemented by "bar". + Error: Library "foodummy" is neither a virtual library nor a library + parameter. It cannot be implemented by "bar". Leaving directory 'test' [1] diff --git a/test/blackbox-tests/test-cases/virtual-libraries/impl-not-virtual.t/run.t b/test/blackbox-tests/test-cases/virtual-libraries/impl-not-virtual.t/run.t index 2939ecab44d..a5759006c19 100644 --- a/test/blackbox-tests/test-cases/virtual-libraries/impl-not-virtual.t/run.t +++ b/test/blackbox-tests/test-cases/virtual-libraries/impl-not-virtual.t/run.t @@ -4,6 +4,7 @@ appropriate error message. File "impl/dune", line 3, characters 13-16: 3 | (implements lib)) ^^^ - Error: Library "lib" is not virtual. It cannot be implemented by "impl". + Error: Library "lib" is neither a virtual library nor a library parameter. It + cannot be implemented by "impl". -> required by alias default in dune:1 [1]