ocaml / dune

A composable build system for OCaml.
https://dune.build/
MIT License
1.63k stars 402 forks source link

Have Dune warn us of unnecessary library dependencies #11061

Open nojb opened 11 hours ago

nojb commented 11 hours ago

Listing unnecessary libraries in the (libraries ...) field of executables and libraries is not harmless at the moment due to the way dependencies are computed in Dune: this can cause potentially a lot of unnecessary recompilation, as any change in the "unused" dependency will cause the recompilation of the whole executable/library.

Having some way of knowing if one has included unnecessary libraries in (libraries ...) would thus be quite useful.

alainfrisch commented 3 hours ago

It's rather straightforward to instrument ocamlc/ocamlopt to dump which of the libraries specified on the command-line (when linking an executable) have actually been linked. Or, without patching the compiler, one can replicate the logic with a simple script like:

let required = Hashtbl.create 17
let add_required (name, _crc) = Hashtbl.replace required name ()
let used_libs = ref []

let scan_file obj_name =
  let file_name =
    try Load_path.find obj_name
    with Not_found -> failwith (Printf.sprintf "File not found: %s" obj_name)
  in
  if Filename.check_suffix file_name ".cmx" then begin
    let (info, _) = Compilenv.read_unit_info file_name in
    List.iter add_required info.ui_imports_cmx
  end else if Filename.check_suffix file_name ".cmxa" then begin
    let infos =
      try Compilenv.read_library_info file_name
      with Compilenv.Error(Not_a_unit_info _) ->
        failwith (Printf.sprintf "Not an object file: %s" file_name)
    in
    let trace = lazy (used_libs := file_name :: !used_libs) in
    List.iter
      (fun (info, _) ->
         if info.Cmx_format.ui_force_link || Hashtbl.mem required info.ui_name
         then begin
           Lazy.force trace;
           List.iter add_required info.ui_imports_cmx
         end
      )
      (List.rev infos.lib_units)
  end else
    failwith (Printf.sprintf "Not an object file: %s" file_name)

let () =
  let files = ref [] in
  Arg.parse
    [ "-I", Arg.String Load_path.add_dir, "" ]
    (fun file -> files := file :: !files) "Usage: ...";
  List.iter scan_file !files; (* reverse order *)
  List.iter print_endline !used_libs

Note that even if a library is not considered to be needed by the linker, it could still be needed to consider it as dune library dependency to allowing type-checking the code.