Build information library for monopam tools.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

monopam-info: extract dune-package library index into monopam-info.index

The dead-lib detection in monopam read dune-package files inline,
duplicating ad-hoc Sexp pattern matching that already had a typed
codec in nox-sexp.codecs.dune.Dune_package. Move that logic into a
new sublib monopam-info.index, which:

- builds the (library -> exposed modules) index from
_opam/lib/<pkg>/dune-package (preferred) and a *.cmi-listing
fallback for packages without dune-package metadata;
- tracks which libraries implement a virtual library so callers can
whitelist them as link-time live.

monopam.lint now consumes the typed Monopam_info_index API instead of
peeking at Sexp values, and monopam-info gains eio/fpath/nox-loc/nox-sexp
deps.

+142
+4
dune-project
··· 18 18 (dune (>= 3.0)) 19 19 (mdx :with-test) 20 20 dune-build-info 21 + eio 22 + fpath 23 + nox-loc 24 + nox-sexp 21 25 (fmt :with-test)) 22 26 )
+4
lib/index/dune
··· 1 + (library 2 + (name monopam_info_index) 3 + (public_name monopam-info.index) 4 + (libraries eio fpath nox-sexp nox-sexp.codecs))
+104
lib/index/monopam_info_index.ml
··· 1 + (* Library-name index for the active opam switch. *) 2 + 3 + module String_set = Set.Make (String) 4 + 5 + type t = { 6 + modules : (string, String_set.t) Hashtbl.t; 7 + virtual_impls : (string, unit) Hashtbl.t; 8 + } 9 + 10 + let load_eio_path eio_path = 11 + try Some (Eio.Path.load eio_path) with Eio.Io _ -> None 12 + 13 + let contains_double_underscore s = 14 + let n = String.length s in 15 + let rec loop i = 16 + if i + 1 >= n then false 17 + else if s.[i] = '_' && s.[i + 1] = '_' then true 18 + else loop (i + 1) 19 + in 20 + loop 0 21 + 22 + let capitalize s = 23 + if s = "" then s 24 + else 25 + String.uppercase_ascii (String.sub s 0 1) 26 + ^ String.sub s 1 (String.length s - 1) 27 + 28 + (* Capitalised basenames of *.cmi files directly in [pkg_dir]. Skips 29 + wrapped-private modules (containing [__]). Fallback for packages that 30 + pre-date dune-package metadata. *) 31 + let cmi_modules_in_dir pkg_dir = 32 + let entries = try Eio.Path.read_dir pkg_dir with Eio.Io _ -> [] in 33 + List.fold_left 34 + (fun acc entry -> 35 + if Filename.check_suffix entry ".cmi" then 36 + let base = Filename.chop_suffix entry ".cmi" in 37 + if contains_double_underscore base then acc 38 + else String_set.add (capitalize base) acc 39 + else acc) 40 + String_set.empty entries 41 + 42 + let parse_libraries content = 43 + match Sexp.Value.parse_string_many content with 44 + | Error _ -> [] 45 + | Ok stanzas -> 46 + List.filter_map 47 + (fun s -> 48 + Sexp.Codec.decode_value Sexp_codecs.Dune.Dune_package.Library.codec s 49 + |> Result.to_option) 50 + stanzas 51 + 52 + let scan_pkg_dir t pkg pkg_dir = 53 + let dune_pkg = Eio.Path.(pkg_dir / "dune-package") in 54 + let covered = ref false in 55 + (match load_eio_path dune_pkg with 56 + | None -> () 57 + | Some content -> 58 + List.iter 59 + (fun (lib : Sexp_codecs.Dune.Dune_package.Library.t) -> 60 + if lib.implements <> None then 61 + Hashtbl.replace t.virtual_impls lib.name (); 62 + if lib.modules <> [] then begin 63 + covered := true; 64 + let existing = 65 + try Hashtbl.find t.modules lib.name 66 + with Not_found -> String_set.empty 67 + in 68 + Hashtbl.replace t.modules lib.name 69 + (String_set.union existing (String_set.of_list lib.modules)) 70 + end) 71 + (parse_libraries content)); 72 + if not !covered then 73 + let mods = cmi_modules_in_dir pkg_dir in 74 + if not (String_set.is_empty mods) then 75 + let existing = 76 + try Hashtbl.find t.modules pkg with Not_found -> String_set.empty 77 + in 78 + Hashtbl.replace t.modules pkg (String_set.union existing mods) 79 + 80 + let build ~fs ~monorepo = 81 + let t = { modules = Hashtbl.create 256; virtual_impls = Hashtbl.create 16 } in 82 + let scan root = 83 + let entries = try Eio.Path.read_dir root with Eio.Io _ -> [] in 84 + List.iter (fun pkg -> scan_pkg_dir t pkg Eio.Path.(root / pkg)) entries 85 + in 86 + let opam_lib = 87 + Eio.Path.(fs / Fpath.to_string Fpath.(monorepo / "_opam" / "lib")) 88 + in 89 + let build_lib = 90 + Eio.Path.( 91 + fs 92 + / Fpath.to_string 93 + Fpath.(monorepo / "_build" / "install" / "default" / "lib")) 94 + in 95 + scan opam_lib; 96 + scan build_lib; 97 + t 98 + 99 + let modules t lib = 100 + match Hashtbl.find_opt t.modules lib with 101 + | None -> [] 102 + | Some s -> String_set.elements s 103 + 104 + let is_virtual_implementation t lib = Hashtbl.mem t.virtual_impls lib
+26
lib/index/monopam_info_index.mli
··· 1 + (** Build-level information about an opam switch's installed libraries. 2 + 3 + For each installed package, dune writes a [dune-package] file at 4 + [_opam/lib/<pkg>/dune-package] (or [_build/install/<context>/lib/...]) 5 + listing every sub-library plus the modules it exposes. This module walks 6 + those metadata files and aggregates them into a single index keyed by 7 + library name. Packages without a [dune-package] (e.g. ones that pre-date 8 + dune, like [zarith] or [asn1-combinators]) fall back to listing [*.cmi] 9 + basenames in the install directory. *) 10 + 11 + type t 12 + (** A library-name index for the active opam switch. *) 13 + 14 + val build : fs:Eio.Fs.dir_ty Eio.Path.t -> monorepo:Fpath.t -> t 15 + (** [build ~fs ~monorepo] scans [<monorepo>/_opam/lib/] and 16 + [<monorepo>/_build/install/default/lib/] and populates the index. *) 17 + 18 + val modules : t -> string -> string list 19 + (** [modules t lib] is the list of top-level module names exposed by [lib], or 20 + [[]] if [lib] is unknown. Module names are capitalised, in the form 21 + [Sexp_codecs.Dune.Dune_package.Library.t.modules] returns. *) 22 + 23 + val is_virtual_implementation : t -> string -> bool 24 + (** [is_virtual_implementation t lib] is [true] iff [lib] has an 25 + [(implements X)] entry in its [dune-package]. Such libraries are link-time 26 + live even when none of their modules appear in source. *)
+4
monopam-info.opam
··· 12 12 "dune" {>= "3.21" & >= "3.0"} 13 13 "mdx" {with-test} 14 14 "dune-build-info" 15 + "eio" 16 + "fpath" 17 + "nox-loc" 18 + "nox-sexp" 15 19 "fmt" {with-test} 16 20 "odoc" {with-doc} 17 21 ]