diff --git a/boot/libs.ml b/boot/libs.ml index 387ab3842a8..17df999b95b 100644 --- a/boot/libs.ml +++ b/boot/libs.ml @@ -1,5 +1,5 @@ open Types -let external_libraries = [ "unix"; "threads" ] +let external_libraries = [ "pp"; "unix"; "csexp"; "threads" ] let local_libraries = [ { path = "otherlibs/top-closure" diff --git a/doc/changes/added/12623.md b/doc/changes/added/12623.md new file mode 100644 index 00000000000..2ced155cd9a --- /dev/null +++ b/doc/changes/added/12623.md @@ -0,0 +1,2 @@ +- Introduce an `unused-libs` alias to detect unused libraries. + (#12623, fixes #650, @rgrinberg) diff --git a/doc/reference/aliases.rst b/doc/reference/aliases.rst index b94e5be3483..1e4ef835183 100644 --- a/doc/reference/aliases.rst +++ b/doc/reference/aliases.rst @@ -82,6 +82,7 @@ Some aliases are defined and managed by Dune itself: aliases/runtest aliases/fmt aliases/lint + aliases/unused-libs .. grid-item:: diff --git a/doc/reference/aliases/unused-libs.rst b/doc/reference/aliases/unused-libs.rst new file mode 100644 index 00000000000..4b434eb7281 --- /dev/null +++ b/doc/reference/aliases/unused-libs.rst @@ -0,0 +1,5 @@ +@unused-libs +============ + +This alias is used to detect unused entries in the libraries field of +executables and stanzas. diff --git a/src/dune_rules/alias0.ml b/src/dune_rules/alias0.ml index 296d4d7b166..9d12941abe3 100644 --- a/src/dune_rules/alias0.ml +++ b/src/dune_rules/alias0.ml @@ -26,6 +26,7 @@ let install = standard "install" let pkg_install = Alias.Name.of_string "pkg-install" let ocaml_index = standard "ocaml-index" let runtest = standard "runtest" +let unused_libs = standard "unused-libs" let all = standard "all" let default = standard "default" let empty = standard "empty" diff --git a/src/dune_rules/alias0.mli b/src/dune_rules/alias0.mli index 24ca314786d..3cf8331bfe5 100644 --- a/src/dune_rules/alias0.mli +++ b/src/dune_rules/alias0.mli @@ -15,6 +15,7 @@ val ocaml_index : Name.t val install : Name.t val pkg_install : Name.t val runtest : Name.t +val unused_libs : Name.t val empty : Name.t val all : Name.t val default : Name.t diff --git a/src/dune_rules/artifacts_db.ml b/src/dune_rules/artifacts_db.ml index f0ee01fe688..14e9f096a5a 100644 --- a/src/dune_rules/artifacts_db.ml +++ b/src/dune_rules/artifacts_db.ml @@ -23,6 +23,7 @@ let available_exes ~dir (exes : Executables.t) = libs (`Exe exes.names) exes.buildable.libraries + ~allow_unused_libraries:exes.buildable.allow_unused_libraries ~pps ~dune_version ~forbidden_libraries:exes.forbidden_libraries diff --git a/src/dune_rules/cinaps.ml b/src/dune_rules/cinaps.ml index f23a827f1ed..942238caa9b 100644 --- a/src/dune_rules/cinaps.ml +++ b/src/dune_rules/cinaps.ml @@ -140,6 +140,7 @@ let gen_rules sctx t ~dir ~scope = (Scope.libs scope) (`Exe names) (Lib_dep.Direct (loc, Lib_name.of_string "cinaps.runtime") :: t.libraries) + ~allow_unused_libraries:[] ~pps:(Preprocess.Per_module.pps t.preprocess) ~dune_version ~allow_overlaps:false diff --git a/src/dune_rules/dep_rules.ml b/src/dune_rules/dep_rules.ml index 45b946cbe11..ba3e67cf32b 100644 --- a/src/dune_rules/dep_rules.ml +++ b/src/dune_rules/dep_rules.ml @@ -36,7 +36,10 @@ let ooi_deps let ctx = Super_context.context sctx in Context.ocaml ctx in - Ocamlobjinfo.rules ocaml ~sandbox ~dir ~unit + Ocamlobjinfo.rules ocaml ~sandbox ~dir ~units:[ unit ] + |> Action_builder.map ~f:(function + | [ x ] -> x + | [] | _ :: _ -> assert false) in let add_rule = Super_context.add_rule sctx ~dir in let read = diff --git a/src/dune_rules/dune_package.ml b/src/dune_rules/dune_package.ml index 1704afeb558..4c002c1a8b7 100644 --- a/src/dune_rules/dune_package.ml +++ b/src/dune_rules/dune_package.ml @@ -289,6 +289,7 @@ module Lib = struct ~plugins ~archives ~ppx_runtime_deps + ~allow_unused_libraries:[] ~foreign_archives ~native_archives:(Files native_archives) ~foreign_dll_files diff --git a/src/dune_rules/exe_rules.ml b/src/dune_rules/exe_rules.ml index b9c48809009..5923c815c9f 100644 --- a/src/dune_rules/exe_rules.ml +++ b/src/dune_rules/exe_rules.ml @@ -208,6 +208,20 @@ let executables_rules in let lib_config = ocaml.lib_config in let* requires_compile = Compilation_context.requires_compile cctx in + let* () = + let toolchain = Compilation_context.ocaml cctx in + let direct_requires = Lib.Compile.direct_requires compile_info in + let allow_unused_libraries = Lib.Compile.allow_unused_libraries compile_info in + Unused_libs_rules.gen_rules + sctx + toolchain + exes.buildable.loc + ~obj_dir + ~modules + ~dir + ~direct_requires + ~allow_unused_libraries + in let* () = let* dep_graphs = (* Building an archive for foreign stubs, we link the corresponding object @@ -328,6 +342,7 @@ let compile_info ~scope (exes : Executables.t) = (Scope.libs scope) (`Exe exes.names) exes.buildable.libraries + ~allow_unused_libraries:exes.buildable.allow_unused_libraries ~pps ~dune_version ~allow_overlaps:exes.buildable.allow_overlapping_dependencies diff --git a/src/dune_rules/findlib.ml b/src/dune_rules/findlib.ml index 6a24d276fdf..b2b0ecd365c 100644 --- a/src/dune_rules/findlib.ml +++ b/src/dune_rules/findlib.ml @@ -260,6 +260,7 @@ let to_dune_library (t : Findlib.Package.t) ~dir_contents ~ext_lib ~external_loc ~plugins ~archives ~ppx_runtime_deps + ~allow_unused_libraries:[] ~foreign_archives ~native_archives:(Files native_archives) ~foreign_dll_files:[] diff --git a/src/dune_rules/install_rules.ml b/src/dune_rules/install_rules.ml index 9a6705ed52d..8bb0e012720 100644 --- a/src/dune_rules/install_rules.ml +++ b/src/dune_rules/install_rules.ml @@ -427,6 +427,7 @@ end = struct ~forbidden_libraries:[] (`Exe exes.names) exes.buildable.libraries + ~allow_unused_libraries:exes.buildable.allow_unused_libraries ~pps ~dune_version ~allow_overlaps:exes.buildable.allow_overlapping_dependencies diff --git a/src/dune_rules/lib.ml b/src/dune_rules/lib.ml index a1cd3726a67..6caa33e82f9 100644 --- a/src/dune_rules/lib.ml +++ b/src/dune_rules/lib.ml @@ -362,6 +362,7 @@ module T = struct ; pps : t list Resolve.t ; resolved_selects : Resolved_select.t list Resolve.t ; parameters : t list Resolve.t + ; allow_unused_libraries : t list Resolve.t ; implements : t Resolve.t option ; project : Dune_project.t option ; (* these fields cannot be forced until the library is instantiated *) @@ -1106,6 +1107,9 @@ end = struct let* ppx_runtime_deps = Lib_info.ppx_runtime_deps info |> resolve_simple_deps db ~private_deps in + let* allow_unused_libraries = + Lib_info.allow_unused_libraries info |> resolve_simple_deps db ~private_deps + in let src_dir = Lib_info.src_dir info in let map_error x = Resolve.push_stack_frame x ~human_readable_description:(fun () -> @@ -1138,6 +1142,7 @@ end = struct ; re_exports ; implements ; parameters + ; allow_unused_libraries ; default_implementation ; project ; sub_systems = @@ -1906,6 +1911,7 @@ module Compile = struct ; requires_link : t list Resolve.t Memo.Lazy.t ; pps : t list Resolve.Memo.t ; resolved_selects : Resolved_select.t list Resolve.Memo.t + ; allow_unused_libraries : t list Resolve.Memo.t ; sub_systems : Sub_system0.Instance.t Memo.Lazy.t Sub_system_name.Map.t } @@ -1934,6 +1940,7 @@ module Compile = struct ; requires_link ; resolved_selects = Memo.return t.resolved_selects ; pps = Memo.return t.pps + ; allow_unused_libraries = Memo.return t.allow_unused_libraries ; sub_systems = t.sub_systems } ;; @@ -1942,6 +1949,7 @@ module Compile = struct let requires_link t = t.requires_link let resolved_selects t = t.resolved_selects let pps t = t.pps + let allow_unused_libraries t = t.allow_unused_libraries let sub_systems t = Sub_system_name.Map.values t.sub_systems @@ -2134,6 +2142,7 @@ module DB = struct ~allow_overlaps ~forbidden_libraries deps + ~allow_unused_libraries ~pps ~dune_version = @@ -2201,10 +2210,14 @@ module DB = struct let+ resolved = Memo.Lazy.force resolved in resolved.selects in + let allow_unused_libraries = + Resolve_names.resolve_simple_deps t ~private_deps:Allow_all allow_unused_libraries + in { Compile.direct_requires ; requires_link ; pps ; resolved_selects = resolved_selects |> Memo.map ~f:Resolve.return + ; allow_unused_libraries ; sub_systems = Sub_system_name.Map.empty } ;; diff --git a/src/dune_rules/lib.mli b/src/dune_rules/lib.mli index 9698c567e76..9a063dc2148 100644 --- a/src/dune_rules/lib.mli +++ b/src/dune_rules/lib.mli @@ -77,6 +77,9 @@ module Compile : sig (** Transitive closure of all used ppx rewriters *) val pps : t -> lib list Resolve.Memo.t + (** Libraries allowed to be unused *) + val allow_unused_libraries : t -> lib list Resolve.Memo.t + (** Sub-systems used in this compilation context *) val sub_systems : t -> sub_system list Memo.t end @@ -155,6 +158,7 @@ module DB : sig -> allow_overlaps:bool -> forbidden_libraries:(Loc.t * Lib_name.t) list -> Lib_dep.t list + -> allow_unused_libraries:(Loc.t * Lib_name.t) list -> pps:(Loc.t * Lib_name.t) list -> dune_version:Dune_lang.Syntax.Version.t -> Compile.t diff --git a/src/dune_rules/lib_info.ml b/src/dune_rules/lib_info.ml index d8dcd660289..360eb28bb39 100644 --- a/src/dune_rules/lib_info.ml +++ b/src/dune_rules/lib_info.ml @@ -320,6 +320,7 @@ type 'path t = ; requires : Lib_dep.t list ; parameters : (Loc.t * Lib_name.t) list ; ppx_runtime_deps : (Loc.t * Lib_name.t) list + ; allow_unused_libraries : (Loc.t * Lib_name.t) list ; preprocess : Preprocess.With_instrumentation.t Preprocess.Per_module.t ; enabled : Enabled_status.t Memo.t ; virtual_deps : (Loc.t * Lib_name.t) list @@ -349,6 +350,7 @@ let requires t = t.requires let parameters t = t.parameters let preprocess t = t.preprocess let ppx_runtime_deps t = t.ppx_runtime_deps +let allow_unused_libraries t = t.allow_unused_libraries let sub_systems t = t.sub_systems let modes t = t.modes let modules t = t.modules @@ -412,6 +414,7 @@ let create ~plugins ~archives ~ppx_runtime_deps + ~allow_unused_libraries ~foreign_archives ~native_archives ~foreign_dll_files @@ -451,6 +454,7 @@ let create ; plugins ; archives ; ppx_runtime_deps + ; allow_unused_libraries ; foreign_archives ; native_archives ; foreign_dll_files @@ -548,6 +552,7 @@ let to_dyn ; plugins ; archives ; ppx_runtime_deps + ; allow_unused_libraries = _ ; foreign_archives ; native_archives ; foreign_dll_files diff --git a/src/dune_rules/lib_info.mli b/src/dune_rules/lib_info.mli index 896e37b61f1..ac31834be63 100644 --- a/src/dune_rules/lib_info.mli +++ b/src/dune_rules/lib_info.mli @@ -151,6 +151,7 @@ val implements : _ t -> (Loc.t * Lib_name.t) option val requires : _ t -> Lib_dep.t list val parameters : _ t -> (Loc.t * Lib_name.t) list val ppx_runtime_deps : _ t -> (Loc.t * Lib_name.t) list +val allow_unused_libraries : _ t -> (Loc.t * Lib_name.t) list val preprocess : _ t -> Preprocess.With_instrumentation.t Preprocess.Per_module.t val sub_systems : _ t -> Sub_system_info.t Sub_system_name.Map.t val enabled : _ t -> Enabled_status.t Memo.t @@ -211,6 +212,7 @@ val create -> plugins:'a list Mode.Dict.t -> archives:'a list Mode.Dict.t -> ppx_runtime_deps:(Loc.t * Lib_name.t) list + -> allow_unused_libraries:(Loc.t * Lib_name.t) list -> foreign_archives:'a Mode.Map.Multi.t -> native_archives:'a native_archives -> foreign_dll_files:'a list diff --git a/src/dune_rules/lib_rules.ml b/src/dune_rules/lib_rules.ml index a46ca8f203b..e0e52c44aa0 100644 --- a/src/dune_rules/lib_rules.ml +++ b/src/dune_rules/lib_rules.ml @@ -621,6 +621,19 @@ let library_rules in Sub_system.gen_rules { super_context = sctx; dir; stanza = lib; scope; source_modules; compile_info } + and+ () = + let toolchain = Compilation_context.ocaml cctx in + let direct_requires = Lib.Compile.direct_requires compile_info in + let allow_unused_libraries = Lib.Compile.allow_unused_libraries compile_info in + Unused_libs_rules.gen_rules + sctx + toolchain + lib.buildable.loc + ~obj_dir + ~modules + ~dir + ~direct_requires + ~allow_unused_libraries and+ merlin = let+ requires_hidden = Compilation_context.requires_hidden cctx and+ parameters = Compilation_context.parameters cctx in diff --git a/src/dune_rules/mdx.ml b/src/dune_rules/mdx.ml index b2d5d1ce72d..a7512b15294 100644 --- a/src/dune_rules/mdx.ml +++ b/src/dune_rules/mdx.ml @@ -457,6 +457,7 @@ let mdx_prog_gen t ~sctx ~dir ~scope ~mdx_prog = ~allow_overlaps:false ~forbidden_libraries:[] (lib "mdx.test" :: lib "mdx.top" :: t.libraries) + ~allow_unused_libraries:[] ~pps:[] ~dune_version in diff --git a/src/dune_rules/melange/melange_rules.ml b/src/dune_rules/melange/melange_rules.ml index 1b228fabc23..f1ce81cc751 100644 --- a/src/dune_rules/melange/melange_rules.ml +++ b/src/dune_rules/melange/melange_rules.ml @@ -181,6 +181,7 @@ let compile_info ~scope (mel : Melange_stanzas.Emit.t) = ~allow_overlaps:mel.allow_overlapping_dependencies ~forbidden_libraries:[] libraries + ~allow_unused_libraries:[] ~pps ~dune_version ;; diff --git a/src/dune_rules/ocamlobjinfo.mli b/src/dune_rules/ocamlobjinfo.mli index 149c4254013..ead0aa38889 100644 --- a/src/dune_rules/ocamlobjinfo.mli +++ b/src/dune_rules/ocamlobjinfo.mli @@ -10,8 +10,19 @@ val rules : Ocaml_toolchain.t -> dir:Path.Build.t -> sandbox:Sandbox_config.t option - -> unit:Path.t - -> t Action_builder.t + -> units:Path.t list + -> t list Action_builder.t + +(** Run ocamlobjinfo on an archive to extract module names defined in it *) +val archive_rules + : Ocaml_toolchain.t + -> dir:Path.Build.t + -> sandbox:Sandbox_config.t option + -> archive:Path.t + -> Module_name.Unique.Set.t Action_builder.t (** For testing only *) -val parse : string -> t +val parse : string -> t list + +(** Parse archive output to extract module names defined in the archive *) +val parse_archive : string -> Module_name.Unique.Set.t diff --git a/src/dune_rules/ocamlobjinfo.mll b/src/dune_rules/ocamlobjinfo.mll index caa2c247b35..7686b31a028 100644 --- a/src/dune_rules/ocamlobjinfo.mll +++ b/src/dune_rules/ocamlobjinfo.mll @@ -22,23 +22,34 @@ let ws = [' ' '\t']+ let hash = ['0'-'9' 'a'-'z' '-']+ let name = ['A'-'Z'] ['A'-'Z' 'a'-'z' '0'-'9' '_']* -rule ocamlobjinfo acc = parse - | "Interfaces imported:" newline { intfs acc lexbuf } - | "Implementations imported:" newline { impls acc lexbuf } - | _ { ocamlobjinfo acc lexbuf } +rule ocamlobjinfo acc_units acc = parse + | "Interfaces imported:" newline { intfs acc_units acc lexbuf } + | "Implementations imported:" newline { impls acc_units acc lexbuf } + | _ { ocamlobjinfo acc_units acc lexbuf } + | eof { acc :: acc_units } +and intfs acc_units acc = parse + | ws hash ws (name as name) newline { intfs acc_units (add_intf acc name) lexbuf } + | "Implementations imported:" newline { impls acc_units acc lexbuf } + | "File " [^ '\n']+ newline { ocamlobjinfo (acc :: acc_units) empty lexbuf } + | _ | eof { acc :: acc_units } +and impls acc_units acc = parse + | ws hash ws (name as name) newline { impls acc_units (add_impl acc name) lexbuf } + | "File " [^ '\n']+ newline { ocamlobjinfo (acc :: acc_units) empty lexbuf } + | _ | eof { acc :: acc_units } + +and archive acc = parse + | "Unit name:" ws (name as name) { archive (Module_name.Unique.Set.add acc (Module_name.Unique.of_string name)) lexbuf } + | _ { archive acc lexbuf } | eof { acc } -and intfs acc = parse - | ws hash ws (name as name) newline { intfs (add_intf acc name) lexbuf } - | "Implementations imported:" newline { impls acc lexbuf } - | _ | eof { acc } -and impls acc = parse - | ws hash ws (name as name) newline { impls (add_impl acc name) lexbuf } - | _ | eof { acc } { -let parse s = ocamlobjinfo empty (Lexing.from_string s) +let parse s = Lexing.from_string s |> ocamlobjinfo [] empty |> List.rev + +let parse_archive s = + Lexing.from_string s + |> archive Module_name.Unique.Set.empty -let rules (ocaml : Ocaml_toolchain.t) ~dir ~sandbox ~unit = +let rules (ocaml : Ocaml_toolchain.t) ~dir ~sandbox ~units = let no_approx = if Ocaml.Version.ooi_supports_no_approx ocaml.version then [Command.Args.A "-no-approx"] @@ -52,7 +63,9 @@ let rules (ocaml : Ocaml_toolchain.t) ~dir ~sandbox ~unit = [] in let observing_facts = - Dep.Facts.singleton (Dep.file unit) (Dep.Fact.nothing) + List.map units ~f:(fun unit -> + Dep.Facts.singleton (Dep.file unit) (Dep.Fact.nothing)) + |> Dep.Facts.union_all in let open Action_builder.O in let* action = @@ -61,7 +74,7 @@ let rules (ocaml : Ocaml_toolchain.t) ~dir ~sandbox ~unit = (List.concat [ no_approx ; no_code - ; [ Dep unit ] + ; [ Deps units ] ]) in (Dune_engine.Build_system.execute_action_stdout @@ -73,4 +86,24 @@ let rules (ocaml : Ocaml_toolchain.t) ~dir ~sandbox ~unit = } |> Action_builder.of_memo) >>| parse + +let archive_rules (ocaml : Ocaml_toolchain.t) ~dir ~sandbox ~archive = + let observing_facts = + Dep.Facts.singleton (Dep.file archive) (Dep.Fact.nothing) + in + let open Action_builder.O in + let* action = + Command.run' ?sandbox + ~dir:(Path.build dir) ocaml.ocamlobjinfo + [ Dep archive ] + in + (Dune_engine.Build_system.execute_action_stdout + ~observing_facts + { Rule.Anonymous_action.action + ; loc = Loc.none + ; dir + ; alias = None + } + |> Action_builder.of_memo) + >>| parse_archive } diff --git a/src/dune_rules/stanzas/buildable.ml b/src/dune_rules/stanzas/buildable.ml index 4a4596b5c45..1ffcc8f4c2c 100644 --- a/src/dune_rules/stanzas/buildable.ml +++ b/src/dune_rules/stanzas/buildable.ml @@ -20,6 +20,7 @@ type t = ; flags : Ocaml_flags.Spec.t ; js_of_ocaml : Js_of_ocaml.In_buildable.t Js_of_ocaml.Mode.Pair.t ; allow_overlapping_dependencies : bool + ; allow_unused_libraries : (Loc.t * Lib_name.t) list ; ctypes : Ctypes_field.t option } @@ -43,6 +44,22 @@ let decode_modules = Modules_settings.decode let decode_lint = field "lint" Lint.decode ~default:Lint.default let decode_allow_overlapping = field_b "allow_overlapping_dependencies" +let decode_allow_unused_libraries = + let config = + Config.make + ~name:"ALLOW_UNUSED_LIBRARIES" + ~of_string:Config.Toggle.of_string + ~default:`Disabled + in + field + "allow_unused_libraries" + (let* () = return () in + match Config.get config with + | `Disabled -> User_error.raise [ Pp.text "this field is disabled by default" ] + | `Enabled -> repeat (located Lib_name.decode)) + ~default:[] +;; + let decode (for_ : for_) = let use_foreign = Dune_lang.Syntax.deleted_in @@ -119,6 +136,7 @@ let decode (for_ : for_) = >>> Js_of_ocaml.In_buildable.decode ~in_library ~mode:Wasm) ~default:Js_of_ocaml.In_buildable.default and+ allow_overlapping_dependencies = decode_allow_overlapping + and+ allow_unused_libraries = decode_allow_unused_libraries and+ version = Dune_lang.Syntax.get_exn Stanza.syntax and+ ctypes = field_o @@ -182,6 +200,7 @@ let decode (for_ : for_) = ; flags ; js_of_ocaml = { js = js_of_ocaml; wasm = wasm_of_ocaml } ; allow_overlapping_dependencies + ; allow_unused_libraries ; ctypes } ;; diff --git a/src/dune_rules/stanzas/buildable.mli b/src/dune_rules/stanzas/buildable.mli index eaaa3b7db5d..39040375818 100644 --- a/src/dune_rules/stanzas/buildable.mli +++ b/src/dune_rules/stanzas/buildable.mli @@ -19,6 +19,7 @@ type t = ; flags : Ocaml_flags.Spec.t ; js_of_ocaml : Js_of_ocaml.In_buildable.t Js_of_ocaml.Mode.Pair.t ; allow_overlapping_dependencies : bool + ; allow_unused_libraries : (Loc.t * Lib_name.t) list ; ctypes : Ctypes_field.t option } diff --git a/src/dune_rules/stanzas/library.ml b/src/dune_rules/stanzas/library.ml index ffa33627892..f1dd59625fd 100644 --- a/src/dune_rules/stanzas/library.ml +++ b/src/dune_rules/stanzas/library.ml @@ -570,6 +570,7 @@ let to_lib_info in let requires = conf.buildable.libraries in let parameters = conf.parameters in + let allow_unused_libraries = conf.buildable.allow_unused_libraries in let loc = conf.buildable.loc in let kind = conf.kind in let src_dir = dir in @@ -615,6 +616,7 @@ let to_lib_info ~plugins ~archives ~ppx_runtime_deps + ~allow_unused_libraries ~foreign_archives ~native_archives ~foreign_dll_files diff --git a/src/dune_rules/stanzas/parameter.ml b/src/dune_rules/stanzas/parameter.ml index 874089d8635..8f06c9b2b4e 100644 --- a/src/dune_rules/stanzas/parameter.ml +++ b/src/dune_rules/stanzas/parameter.ml @@ -79,6 +79,7 @@ let decode = ; wasm = Js_of_ocaml.In_buildable.default } ; allow_overlapping_dependencies + ; allow_unused_libraries = [] ; ctypes = None } and+ name = field_o "name" Lib_name.Local.decode_loc diff --git a/src/dune_rules/toplevel.ml b/src/dune_rules/toplevel.ml index c6089c5e9e9..cba9e2013a9 100644 --- a/src/dune_rules/toplevel.ml +++ b/src/dune_rules/toplevel.ml @@ -219,6 +219,7 @@ module Stanza = struct ~forbidden_libraries:[] (Lib_dep.Direct (source.loc, compiler_libs) :: List.map toplevel.libraries ~f:(fun d -> Lib_dep.Direct d)) + ~allow_unused_libraries:[] ~pps ~dune_version ~allow_overlaps:false diff --git a/src/dune_rules/unused_libs_rules.ml b/src/dune_rules/unused_libs_rules.ml new file mode 100644 index 00000000000..f97f65aa21e --- /dev/null +++ b/src/dune_rules/unused_libs_rules.ml @@ -0,0 +1,120 @@ +open Import +open Memo.O + +let classify_libs sctx libs = + Memo.parallel_map libs ~f:(fun lib -> + let+ modules = Dir_contents.modules_of_lib sctx lib in + lib, modules) + >>| List.partition_map ~f:(fun (lib, modules) -> + match modules with + | Some modules -> + let module_set = + Modules.With_vlib.obj_map modules + |> Module_name.Unique.Map.keys + |> Module_name.Unique.Set.of_list + in + Left (lib, module_set) + | None -> + (match + let archives = Lib.info lib |> Lib_info.archives in + Mode.Dict.get archives Byte + with + | [] -> Left (lib, Module_name.Unique.Set.empty) + | archive :: _ -> Right (lib, archive))) +;; + +let gen_rules + sctx + toolchain + loc + ~obj_dir + ~modules + ~dir + ~direct_requires + ~allow_unused_libraries + = + match + let modules = + Modules.With_vlib.drop_vlib modules + |> Modules.fold ~init:[] ~f:(fun m acc -> m :: acc) + in + let cmis = Obj_dir.Module.L.cm_files obj_dir modules ~kind:(Ocaml Cmi) in + let cmos = Obj_dir.Module.L.cm_files obj_dir modules ~kind:(Ocaml Cmo) in + cmis @ cmos + with + | [] -> Memo.return () + | units -> + let action = + let open Action_builder.O in + let build_dir = Obj_dir.dir obj_dir in + let* local_modules, external_lib_archives = + let* direct_requires = Resolve.Memo.read direct_requires in + classify_libs sctx direct_requires |> Action_builder.of_memo + in + let* results = + Ocamlobjinfo.rules + toolchain + ~dir:build_dir + ~sandbox:(Some Sandbox_config.needs_sandboxing) + ~units + and* external_modules = + List.map external_lib_archives ~f:(fun (lib, archive) -> + let+ modules = + Ocamlobjinfo.archive_rules + toolchain + ~dir:build_dir + ~sandbox:(Some Sandbox_config.needs_sandboxing) + ~archive + in + lib, modules) + |> Action_builder.all + in + let* allowed_libs = Resolve.Memo.read allow_unused_libraries in + let allowed_set = Lib.Set.of_list allowed_libs in + let unused_libs = + let all_imported = + List.fold_left results ~init:Module_name.Unique.Set.empty ~f:(fun acc result -> + let intf_deps = Ml_kind.Dict.get result Intf in + let impl_deps = Ml_kind.Dict.get result Impl in + Module_name.Unique.Set.union + acc + (Module_name.Unique.Set.union intf_deps impl_deps)) + in + external_modules @ local_modules + |> Lib.Map.of_list_exn + |> Lib.Map.foldi ~init:[] ~f:(fun lib lib_modules acc -> + (* Skip libraries with no modules *) + if Module_name.Unique.Set.is_empty lib_modules + then acc + else ( + (* Check if any module from this library is imported *) + let is_used = + Module_name.Unique.Set.exists lib_modules ~f:(fun mod_name -> + Module_name.Unique.Set.mem all_imported mod_name) + in + (* Check if library is in the allow list *) + let is_allowed = Lib.Set.mem allowed_set lib in + if is_used then acc else if is_allowed then acc else lib :: acc)) + in + match unused_libs with + | [] -> Action_builder.return (Action.progn []) + | libs -> + Action_builder.fail + { fail = + (fun () -> + (* CR-someday rgrinberg: ideally, we'd use the locations of the + unused libraries, but they've already been discarded. *) + User_error.raise + ~loc + [ Pp.text "Unused libraries:" + ; Pp.enumerate libs ~f:(fun lib -> + Lib.name lib |> Lib_name.to_string |> Pp.verbatim) + ]) + } + in + let unused_libs_alias = Alias.make Alias0.unused_libs ~dir in + Rules.Produce.Alias.add_action + unused_libs_alias + ~loc + (action |> Action_builder.map ~f:Action.Full.make) +;; diff --git a/src/dune_rules/unused_libs_rules.mli b/src/dune_rules/unused_libs_rules.mli new file mode 100644 index 00000000000..7451bf2c230 --- /dev/null +++ b/src/dune_rules/unused_libs_rules.mli @@ -0,0 +1,12 @@ +open Import + +val gen_rules + : Super_context.t + -> Ocaml_toolchain.t + -> Loc.t + -> obj_dir:Path.Build.t Obj_dir.t + -> modules:Modules.With_vlib.t + -> dir:Path.Build.t + -> direct_requires:Lib.t list Resolve.Memo.t + -> allow_unused_libraries:Lib.t list Resolve.Memo.t + -> unit Memo.t diff --git a/src/dune_rules/utop.ml b/src/dune_rules/utop.ml index a3fd3330c20..4e3e3173bd8 100644 --- a/src/dune_rules/utop.ml +++ b/src/dune_rules/utop.ml @@ -83,6 +83,7 @@ let add_stanza db ~dir (acc, pps) stanza = db (`Exe exes.names) exes.buildable.libraries + ~allow_unused_libraries:exes.buildable.allow_unused_libraries ~pps ~dune_version ~allow_overlaps:exes.buildable.allow_overlapping_dependencies diff --git a/test/blackbox-tests/test-cases/describe/aliases.t/run.t b/test/blackbox-tests/test-cases/describe/aliases.t/run.t index 64fc81c2882..2c9191716a7 100644 --- a/test/blackbox-tests/test-cases/describe/aliases.t/run.t +++ b/test/blackbox-tests/test-cases/describe/aliases.t/run.t @@ -66,6 +66,7 @@ Adding an OCaml library will introduce OCaml specific aliases: fmt ocaml-index pkg-install + unused-libs Adding a cram test will introduce an alias with the name of the test and also introduce the runtest alias: diff --git a/test/blackbox-tests/test-cases/unused-libs/allow-unused-libraries.t b/test/blackbox-tests/test-cases/unused-libs/allow-unused-libraries.t new file mode 100644 index 00000000000..5219a07cf70 --- /dev/null +++ b/test/blackbox-tests/test-cases/unused-libs/allow-unused-libraries.t @@ -0,0 +1,144 @@ +For now, this field is disabled + + $ export DUNE_CONFIG__ALLOW_UNUSED_LIBRARIES=enabled + +Test allow_unused_libraries field + +The allow_unused_libraries field allows specifying libraries that should not +trigger unused library errors even when they are not used. + + $ cat > dune-project < (lang dune 3.21) + > EOF + +Create three libraries - one will be used, two won't be used: + + $ cat > dune < (library + > (name used_lib) + > (modules used_lib)) + > + > (library + > (name unused_allowed) + > (modules unused_allowed)) + > + > (library + > (name unused_not_allowed) + > (modules unused_not_allowed)) + > + > (executable + > (name main) + > (modules main) + > (libraries used_lib unused_allowed unused_not_allowed) + > (allow_unused_libraries unused_allowed)) + > EOF + + $ cat > used_lib.ml < let helper x = x + 1 + > EOF + + $ cat > unused_allowed.ml < let allowed x = x * 2 + > EOF + + $ cat > unused_not_allowed.ml < let not_allowed x = x * 3 + > EOF + + $ cat > main.ml < (* Only use used_lib, not the other two *) + > let () = print_int (Used_lib.helper 42) + > EOF + +Build the unused-libs alias - should only error on unused_not_allowed: + + $ dune build @unused-libs + File "dune", lines 13-17, characters 0-138: + 13 | (executable + 14 | (name main) + 15 | (modules main) + 16 | (libraries used_lib unused_allowed unused_not_allowed) + 17 | (allow_unused_libraries unused_allowed)) + Error: Unused libraries: + - unused_not_allowed + [1] + +Now allow both unused libraries: + + $ cat > dune < (library + > (name used_lib) + > (modules used_lib)) + > + > (library + > (name unused_allowed) + > (modules unused_allowed)) + > + > (library + > (name unused_not_allowed) + > (modules unused_not_allowed)) + > + > (executable + > (name main) + > (modules main) + > (libraries used_lib unused_allowed unused_not_allowed) + > (allow_unused_libraries unused_allowed unused_not_allowed)) + > EOF + +Build should succeed now: + + $ dune build @unused-libs + +Test with a library that has dependencies: + + $ cat > dune < (library + > (name base_lib) + > (modules base_lib)) + > + > (library + > (name dep_lib) + > (modules dep_lib) + > (libraries base_lib) + > (allow_unused_libraries base_lib)) + > + > (library + > (name unused_dep) + > (modules unused_dep)) + > + > (library + > (name top_lib) + > (modules top_lib) + > (libraries dep_lib unused_dep)) + > EOF + + $ cat > base_lib.ml < let x = 1 + > EOF + + $ cat > dep_lib.ml < (* Intentionally not using base_lib *) + > let y = 2 + > EOF + + $ cat > unused_dep.ml < let z = 3 + > EOF + + $ cat > top_lib.ml < (* Only use dep_lib *) + > let result = Dep_lib.y + > EOF + +Build - dep_lib should not error on base_lib, but top_lib should error on +unused_dep: + + $ dune build @unused-libs + File "dune", lines 15-18, characters 0-76: + 15 | (library + 16 | (name top_lib) + 17 | (modules top_lib) + 18 | (libraries dep_lib unused_dep)) + Error: Unused libraries: + - unused_dep + [1] diff --git a/test/blackbox-tests/test-cases/unused-libs/unused-libs-alias.t b/test/blackbox-tests/test-cases/unused-libs/unused-libs-alias.t new file mode 100644 index 00000000000..8d00aab45e7 --- /dev/null +++ b/test/blackbox-tests/test-cases/unused-libs/unused-libs-alias.t @@ -0,0 +1,42 @@ +Test that the unused-libs alias exists and can be built + + $ cat > dune-project < (lang dune 3.21) + > EOF + + $ cat > dune < (library + > (name used_lib) + > (modules used_lib)) + > + > (library + > (name unused_lib) + > (modules unused_lib)) + > + > (library + > (name mylib) + > (modules mylib) + > (libraries used_lib unused_lib)) + > EOF + + $ cat > used_lib.ml < let helper x = x + 1 + > EOF + + $ cat > unused_lib.ml < let unused_helper x = x * 2 + > EOF + + $ cat > mylib.ml < let compute x = Used_lib.helper x + > EOF + + $ dune build @unused-libs + File "dune", lines 9-12, characters 0-73: + 9 | (library + 10 | (name mylib) + 11 | (modules mylib) + 12 | (libraries used_lib unused_lib)) + Error: Unused libraries: + - unused_lib + [1] diff --git a/test/blackbox-tests/test-cases/unused-libs/unused-libs-exe.t b/test/blackbox-tests/test-cases/unused-libs/unused-libs-exe.t new file mode 100644 index 00000000000..b6236ab1046 --- /dev/null +++ b/test/blackbox-tests/test-cases/unused-libs/unused-libs-exe.t @@ -0,0 +1,54 @@ +Test unused library detection in executables + +The unused-libs alias should detect unused libraries in executables just like +it does for libraries. + + $ cat > dune-project < (lang dune 3.21) + > EOF + +Create two libraries - one that will be used and one that won't: + + $ cat > dune < (library + > (name used_lib) + > (modules used_lib)) + > + > (library + > (name unused_lib) + > (modules unused_lib)) + > + > (executable + > (name main) + > (modules main) + > (libraries used_lib unused_lib)) + > EOF + + $ cat > used_lib.ml < let helper x = x + 1 + > EOF + + $ cat > unused_lib.ml < let other x = x * 2 + > EOF + + $ cat > main.ml < (* Only use used_lib, not unused_lib *) + > let () = print_int (Used_lib.helper 42) + > EOF + +Build the unused-libs alias: + + $ dune build @unused-libs + File "dune", lines 9-12, characters 0-74: + 9 | (executable + 10 | (name main) + 11 | (modules main) + 12 | (libraries used_lib unused_lib)) + Error: Unused libraries: + - unused_lib + [1] + +The executable correctly detects unused_lib as unused. The two "All libraries +are used." messages come from the used_lib and unused_lib libraries themselves, +which have no dependencies so they show all their (zero) dependencies as used. diff --git a/test/blackbox-tests/test-cases/unused-libs/unused-libs-external.t b/test/blackbox-tests/test-cases/unused-libs/unused-libs-external.t new file mode 100644 index 00000000000..470be891d27 --- /dev/null +++ b/test/blackbox-tests/test-cases/unused-libs/unused-libs-external.t @@ -0,0 +1,70 @@ +Test unused library detection with external libraries + +The unused-libs alias should detect unused libraries, including external ones. +Currently, external libraries are not properly handled. + +Setup external libraries in findlib + + $ mkdir -p findlib/ext_used findlib/ext_unused + +Create META files for the external libraries: + + $ cat > findlib/ext_used/META < description = "External library that will be used" + > version = "1.0" + > archive(byte) = "ext_used.cma" + > archive(native) = "ext_used.cmxa" + > EOF + + $ cat > findlib/ext_unused/META < description = "External library that will NOT be used" + > version = "1.0" + > archive(byte) = "ext_unused.cma" + > archive(native) = "ext_unused.cmxa" + > EOF + +Create minimal compiled files for the external libraries: + + $ cat > findlib/ext_used/ext_used.ml < let used_function x = x + 1 + > EOF + + $ cat > findlib/ext_unused/ext_unused.ml < let unused_function x = x * 2 + > EOF + +Compile the external libraries: + + $ (cd findlib/ext_used && ocamlc -a -o ext_used.cma ext_used.ml) + $ (cd findlib/ext_unused && ocamlc -a -o ext_unused.cma ext_unused.ml) + +Create a dune project that uses these external libraries: + + $ cat > dune-project < (lang dune 3.21) + > EOF + + $ cat > dune < (library + > (name mylib) + > (libraries ext_used ext_unused)) + > EOF + + $ cat > mylib.ml < (* Only use ext_used, not ext_unused *) + > let compute x = Ext_used.used_function x + > EOF + +Build the unused-libs alias: + + $ OCAMLPATH=$PWD/findlib dune build @unused-libs + File "dune", lines 1-3, characters 0-56: + 1 | (library + 2 | (name mylib) + 3 | (libraries ext_used ext_unused)) + Error: Unused libraries: + - ext_unused + [1] + +The ext_unused library is correctly detected as unused since mylib.ml only +imports Ext_used, not Ext_unused. diff --git a/test/blackbox-tests/test-cases/unused-libs/unused-libs-limitation.t b/test/blackbox-tests/test-cases/unused-libs/unused-libs-limitation.t new file mode 100644 index 00000000000..0eec2af3710 --- /dev/null +++ b/test/blackbox-tests/test-cases/unused-libs/unused-libs-limitation.t @@ -0,0 +1,37 @@ +Test that the unused-libs alias cannot detect some instances of unused +libraries + + $ cat > dune-project < (lang dune 3.21) + > EOF + + $ cat > dune < (library + > (name used_lib) + > (libraries unused_lib) + > (modules used_lib)) + > + > (library + > (name unused_lib) + > (modules unused_lib)) + > + > (library + > (name mylib) + > (modules mylib) + > (libraries used_lib unused_lib)) + > EOF + + $ cat > used_lib.ml < include Unused_lib + > let helper x = x + 1 + > EOF + + $ cat > unused_lib.ml < let unused_helper x = x * 2 + > EOF + + $ cat > mylib.ml < let compute x = Used_lib.helper x + > EOF + + $ dune build @unused-libs diff --git a/test/expect-tests/ocamlobjinfo_tests.ml b/test/expect-tests/ocamlobjinfo_tests.ml index c58fd03c917..ea832f959e1 100644 --- a/test/expect-tests/ocamlobjinfo_tests.ml +++ b/test/expect-tests/ocamlobjinfo_tests.ml @@ -1,150 +1,227 @@ open Dune_tests_common +open Stdune module Ocamlobjinfo = Dune_rules.For_tests.Ocamlobjinfo +module Module_name = Dune_lang.Module_name let () = init () let fixture = {ocamlobjinfo| -File _build/install/default/lib/dune/_stdune/stdune__Env.cmx -Name: Stdune__Env -CRC of implementation: b678d7aae434ca3158721e3a37a15776 -Globals defined: - Stdune__Env +File _boot/stdune__Env.cmi +Unit name: Stdune__Env Interfaces imported: - 053326e853ce10e1fadf8d891f08f891 Unix - 596c497318b5c3057b47b9d6747ef5d1 Uchar - 3fe6d98e0634486be22d9de07aa0709a Sys - 6339e2b71e8c583a81e808954faf6818 StringLabels - 953d4ea121ff79e9719730997e04436d Stdune__String - 744ae4e7c80910dd9302bf207d11274f Stdune__Sexp_intf - 3a8d88a2c7628492ca76328610a53b04 Stdune__Sexp - 788120b20799b5148dc44c2effd9db08 Stdune__Set_intf - 764302df6c51161e13ccc4553710003d Stdune__Set - 7577d25061f730d87e8e0ab574216d9c Stdune__Result - 4d34756156087d6e6530be0d3275bde4 Stdune__Path - e8eebf0307152a528b7d97ebdf37d1dc Stdune__Ordering - 1fb9ae8c1fb73c55a857f02869cfbeab Stdune__Map_intf - 96daa1ecff8c9bc6c5aadf39201c2f0d Stdune__Map - 5b332ee2108ade34f2abc1448318c5a0 Stdune__Loc - 721c54b94b8c3b89fffc8587041c241a Stdune__List - 0c6e26bc5bc285a87ab7304ee5f3bf0a Stdune__Hashtbl_intf - 8df5a38cb2f28d4cf23f6cd8a6c4c923 Stdune__Hashtbl - 0ff241a400cabb742a8d681c7132c350 Stdune__Hashable - b438dbe8d4c60ca1bb06d04c7bc95652 Stdune__Exn - a1b4f657ccceb16196f104a53a0e199a Stdune__Env - 7643682d2d95acbcf8834a351a1e2779 Stdune__Either - 826226f09894dc48694a431d13b9e541 Stdune__Comparator - 69861cabc5a73becd98269ce298eaa59 Stdune__Bin - cbf0ce887ccceaff96f4a22a8f057799 Stdune__Array - 37cf8dd4fc4be5636ff9f78604107f8b Stdune__ - 28a12def19edf36c317c30fafcc03d6d Set - e5dfd0ca6436c8abad976fc9e914999a Printf - 1b461321ebcc8e419f24eb531c5ac7ac Printexc - 9b04ecdc97e5102c1d342892ef7ad9a2 Pervasives - db5fc31b815ab3040d5a9940a91712c4 MoreLabels - 8b8de381501aa7862270c15619322ee7 Map - f4e829075d9d0bb7de979cfc49c2600b ListLabels - 0971650cdf1fa8e506e733e9a5da2628 Lexing - 0a88e320f172d3413ba0d5e0f9c70ccd Hashtbl - 1a17539924469551f027475153d4d3b5 Format - a4afff2bf4082efda68a6a65cf31f8e2 Stdlib__Result_compat - de733b926f4af640c957c8129aba4139 Stdlib__Result - ba19641102c1711bdb2476bb8b8dbe32 Stdlib__ - 7b10d1bd2d88af9c1da841149c988d94 Stdlib - cd4856c93f21942683ce190142e88396 Complex - 79ae8c0eb753af6b441fe05456c7970b CamlinternalFormatBasics - 4ff98b0650eef9c38ee9c9930e0c3e9b CamlinternalBigarray - 9c9b3639d23d7746c571cdf04646eb29 Buffer - c4974e11dd7c941c002b826edc727de8 ArrayLabels -Implementations imported: - e28bcdad48b0cb1739e47106149016cb Unix - 3c11d6a8ae012d6541b58cecff4809d5 Sys - -------------------------------- Stdune__String - -------------------------------- Stdune__Sexp - -------------------------------- Stdune__Set - -------------------------------- Stdune__Map - -------------------------------- Stdune__List - -------------------------------- Stdune__Exn - -------------------------------- Stdune__Bin - -------------------------------- Stdune__Array - a3cdcb16ec3b460d51a685302e993c0b Printf -Clambda approximation: - _ -Currying functions: 3 2 -Apply functions: 3 2 -Send functions: + bd468c1984f53f5b1d3dadf52a621031 Stdune__Env + ba308f46e67d900971a536229f7f4173 Stdune__String + 421e81602ba8b4117354ae965fbcdb9b Stdune__Set_intf + e71746f44d47934ce121640bcb2f82d1 Stdune__Seq + d5f56ce66eefdb02b6328534c965b2a4 Stdune__Result + 06eac0380458db0b6b9dbf534491bb7d Stdune__Map_intf + 143081f49dab6fc606ae7041a2bb6ab1 Stdune__Hashtbl_intf + 30c8429b4c4dacb8b40f9032dfc90a7c Stdune__Hashtbl + 2f561d28974f1c76974b217015d894ff Stdune__Hashable + f01519520ce7398ca472c40bcdd37916 Stdune__Either + 26c7c23a3f08949c9234b39a8456e0c4 Stdune__Dune_either + 6c4aad0453a73862d11f3e5a6484eb83 Stdune__Comparator + 78d20a1f0a585a77c0b794e2576c5ce1 Stdune__Comparable_intf + 95b5a54d9e83e8c31cf45102cda75e48 Stdune__ + e99839b765b60b06e594103d43eb2fc3 Stdlib__Uchar + 3bc9847742f040684ab499418b7edf05 Stdlib__StringLabels + 22ab37e5d785c94907a48029f32d8e8b Stdlib__Seq + 41f1535996cca328bb845b07f8302854 Stdlib__Format + 46032d55ce31c121a36c0c831eb303d0 Stdlib__Either + 22513dfb5b42771d934b5ab03c3e52e2 Stdlib__Domain + fb4e94053b07bc3831961cf2dd4bfa5f Stdlib__Buffer + b7726274865ffd07290ef11351fb84fe Stdlib + 3d06cb761b7b5c8b1688884de82f6f44 Pp + 95df35f1ae12a6752513bc2d6dd1e2aa Ordering + 40da7899f44eca869bcd9216c9f03eb3 Dyn + 863a7f5288599b181f046f0f55277b0f CamlinternalFormatBasics +File _build/default/otherlibs/stdune/src/.stdune.objs/byte/stdune__Env.cmo +Unit name: Stdune__Env +Interfaces imported: + 03e897aee435213573adf13c80fcb505 Unix + a37d011736ba15fcb93e301d6e9668d2 Stdune__String + b51e9564a437f5dab85ecd7dbeb5fe9e Stdune__Set_intf + 5fa01009dd8a8dc4b5aa020829607a29 Stdune__Seq + c4077e4346d1333fb5a5d86cb1b29d00 Stdune__Result + 533fd86efff7f4531b0b06618b7035fc Stdune__Poly + fb775f8b752e8168d73ada4c504f02b6 Stdune__Map_intf + fae59d6691dc238fd60bd45af4965361 Stdune__Map + 73b7f60f54e3ce7915dc84d6e67ed356 Stdune__Loc0 + e17e834cc6ebc2c4dd1c00550d905672 Stdune__List + dacde49bf9f97ac658c92aed71b8c785 Stdune__Lexbuf + c1d2054a047001a99870331b34bcabfb Stdune__Hashtbl_intf + 2355b12e6f04e2effaeed7fcf7788457 Stdune__Hashtbl + f78523139161a07f69c9bb172c66d2d6 Stdune__Hashable + e658ad11f9a5db6440d11c79d9c50742 Stdune__Env + 9fd88bd5b1dfb61f9d7713cda581d288 Stdune__Either + c6c90e99c836610dda40af3b03120714 Stdune__Dune_either + 99b7fcf93dc56dcc938e4886b46ce3b3 Stdune__Comparator + 4ab9970cf012f4719f898657f92b5780 Stdune__Comparable_intf + ed5bf8d301e6efbb9bb75d08e9fda6ed Stdune__Comparable + b280176bcf92e00bb31e82e836b79478 Stdune__Code_error + 2fdcab3c50cae1145cd9ad5a42fef2b0 Stdune__Array + 56b745aad68436e15c04a81a49a64bc2 Stdune__ + e99839b765b60b06e594103d43eb2fc3 Stdlib__Uchar + 37ffc17421e2ae344263e703f9b97a7f Stdlib__Sys + 3bc9847742f040684ab499418b7edf05 Stdlib__StringLabels + 22ab37e5d785c94907a48029f32d8e8b Stdlib__Seq + 48aae94bed7b9364737dec491dd5504f Stdlib__Printf + 2b62d36b226e41c97dbde849f2884dd5 Stdlib__ListLabels + 79bfb1f841143482726839838f8b6560 Stdlib__Lexing + 41f1535996cca328bb845b07f8302854 Stdlib__Format + 46032d55ce31c121a36c0c831eb303d0 Stdlib__Either + 22513dfb5b42771d934b5ab03c3e52e2 Stdlib__Domain + d3484881d7abcc6fad213e2616c0406e Stdlib__Complex + fb4e94053b07bc3831961cf2dd4bfa5f Stdlib__Buffer + 5bd8a0c0af94a79b082f9c2fdd62d630 Stdlib__Bigarray + c754c627bac3f2948b8da959a4caa4e5 Stdlib__ArrayLabels + b7726274865ffd07290ef11351fb84fe Stdlib + b0a9d2486ded60332f67def162ad115a Pp + 74e26938103410d4bef478dee9a67659 Ordering + b6bda25e76f7a0790682d620ddd2e240 Dyn + 863a7f5288599b181f046f0f55277b0f CamlinternalFormatBasics +Required globals: + Dyn + Stdlib__Printf + Stdlib__Sys + Stdune__Array + Stdune__Code_error + Stdune__Comparable + Stdune__List + Stdune__Poly + Stdune__String + Unix +Uses unsafe features: no Force link: no |ocamlobjinfo} ;; -let parse s = Ocamlobjinfo.parse s |> Ocamlobjinfo.to_dyn |> print_dyn +let parse s = Ocamlobjinfo.parse s |> Dyn.list Ocamlobjinfo.to_dyn |> print_dyn let%expect_test _ = parse fixture; [%expect {| -{ impl = - set - { "printf" - ; "stdune__Array" - ; "stdune__Bin" - ; "stdune__Exn" - ; "stdune__List" - ; "stdune__Map" - ; "stdune__Set" - ; "stdune__Sexp" - ; "stdune__String" - ; "sys" - ; "unix" + [ { impl = set {} + ; intf = + set + { "camlinternalFormatBasics" + ; "dyn" + ; "ordering" + ; "pp" + ; "stdlib" + ; "stdlib__Buffer" + ; "stdlib__Domain" + ; "stdlib__Either" + ; "stdlib__Format" + ; "stdlib__Seq" + ; "stdlib__StringLabels" + ; "stdlib__Uchar" + ; "stdune__" + ; "stdune__Comparable_intf" + ; "stdune__Comparator" + ; "stdune__Dune_either" + ; "stdune__Either" + ; "stdune__Env" + ; "stdune__Hashable" + ; "stdune__Hashtbl" + ; "stdune__Hashtbl_intf" + ; "stdune__Map_intf" + ; "stdune__Result" + ; "stdune__Seq" + ; "stdune__Set_intf" + ; "stdune__String" + } } -; intf = - set - { "arrayLabels" - ; "buffer" - ; "camlinternalBigarray" - ; "camlinternalFormatBasics" - ; "complex" - ; "format" - ; "hashtbl" - ; "lexing" - ; "listLabels" - ; "map" - ; "moreLabels" - ; "pervasives" - ; "printexc" - ; "printf" - ; "set" - ; "stdlib" - ; "stdlib__" - ; "stdlib__Result" - ; "stdlib__Result_compat" - ; "stdune__" - ; "stdune__Array" - ; "stdune__Bin" - ; "stdune__Comparator" - ; "stdune__Either" - ; "stdune__Env" - ; "stdune__Exn" - ; "stdune__Hashable" - ; "stdune__Hashtbl" - ; "stdune__Hashtbl_intf" - ; "stdune__List" - ; "stdune__Loc" - ; "stdune__Map" - ; "stdune__Map_intf" - ; "stdune__Ordering" - ; "stdune__Path" - ; "stdune__Result" - ; "stdune__Set" - ; "stdune__Set_intf" - ; "stdune__Sexp" - ; "stdune__Sexp_intf" - ; "stdune__String" - ; "stringLabels" - ; "sys" - ; "uchar" - ; "unix" + ; { impl = set {} + ; intf = + set + { "camlinternalFormatBasics" + ; "dyn" + ; "ordering" + ; "pp" + ; "stdlib" + ; "stdlib__ArrayLabels" + ; "stdlib__Bigarray" + ; "stdlib__Buffer" + ; "stdlib__Complex" + ; "stdlib__Domain" + ; "stdlib__Either" + ; "stdlib__Format" + ; "stdlib__Lexing" + ; "stdlib__ListLabels" + ; "stdlib__Printf" + ; "stdlib__Seq" + ; "stdlib__StringLabels" + ; "stdlib__Sys" + ; "stdlib__Uchar" + ; "stdune__" + ; "stdune__Array" + ; "stdune__Code_error" + ; "stdune__Comparable" + ; "stdune__Comparable_intf" + ; "stdune__Comparator" + ; "stdune__Dune_either" + ; "stdune__Either" + ; "stdune__Env" + ; "stdune__Hashable" + ; "stdune__Hashtbl" + ; "stdune__Hashtbl_intf" + ; "stdune__Lexbuf" + ; "stdune__List" + ; "stdune__Loc0" + ; "stdune__Map" + ; "stdune__Map_intf" + ; "stdune__Poly" + ; "stdune__Result" + ; "stdune__Seq" + ; "stdune__Set_intf" + ; "stdune__String" + ; "unix" + } } -} -|}] + ] + |}] +;; + +let archive_fixture = + {ocamlobjinfo| +File /path/to/mylib.cma +Force custom: no +Extra C object files: +Extra C options: +Extra dynamically-loaded libraries: +Unit name: Foo +Interfaces imported: + -------------------------------- Bar + -------------------------------- Stdlib +Implementations imported: +Unit name: Bar +Interfaces imported: + -------------------------------- Baz + -------------------------------- Stdlib +Implementations imported: +Unit name: Helper +Interfaces imported: + -------------------------------- Stdlib +Implementations imported: +|ocamlobjinfo} +;; + +let parse_archive s = + let modules = Ocamlobjinfo.parse_archive s in + Module_name.Unique.Set.to_list modules + |> List.map ~f:Module_name.Unique.to_string + |> String.Set.of_list + |> String.Set.to_dyn + |> print_dyn +;; + +let%expect_test "parse_archive extracts unit names" = + parse_archive archive_fixture; + [%expect + {| + set { "bar"; "foo"; "helper" } + |}] ;;