Build information library for monopam tools.
0
fork

Configure Feed

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

Regenerate root files

+158 -65
+1
dune-project
··· 20 20 dune-build-info 21 21 eio 22 22 fpath 23 + nox-dune 23 24 nox-loc 24 25 nox-sexp 25 26 (fmt :with-test))
+1 -1
lib/index/dune
··· 1 1 (library 2 2 (name monopam_info_index) 3 3 (public_name monopam-info.index) 4 - (libraries eio fpath nox-sexp nox-sexp.codecs)) 4 + (libraries eio fpath nox-sexp nox-dune))
+96 -49
lib/index/monopam_info_index.ml
··· 1 - (* Library-name index for the active opam switch. *) 1 + (* Hierarchical index: opam package -> dune library -> modules. 2 2 3 - module String_set = Set.Make (String) 3 + Two structures: 4 + - [pkg_libs]: maps each opam package to a per-package [Dune.Lib_index.t] 5 + so we can answer "what libraries does this package provide" and look 6 + up modules scoped to a specific package. 7 + - [lib_to_pkg]: reverse index for fast [package_of] lookups, since 8 + callers (e.g. monopam.lint dead-lib check) only have a library name. *) 9 + 10 + type origin = Local | Opam 4 11 5 12 type t = { 6 - modules : (string, String_set.t) Hashtbl.t; 7 - virtual_impls : (string, unit) Hashtbl.t; 13 + pkg_libs : (string, Dune.Lib_index.t) Hashtbl.t; 14 + pkg_origin : (string, origin) Hashtbl.t; 15 + lib_to_pkg : (string, string) Hashtbl.t; 8 16 } 9 17 10 18 let load_eio_path eio_path = ··· 25 33 String.uppercase_ascii (String.sub s 0 1) 26 34 ^ String.sub s 1 (String.length s - 1) 27 35 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. *) 36 + (* Capitalised basenames of *.cmi files in [pkg_dir], skipping 37 + wrapped-private modules. Fallback for packages installed without a 38 + [dune-package] file. *) 31 39 let cmi_modules_in_dir pkg_dir = 32 40 let entries = try Eio.Path.read_dir pkg_dir with Eio.Io _ -> [] in 33 - List.fold_left 34 - (fun acc entry -> 41 + List.filter_map 42 + (fun entry -> 35 43 if Filename.check_suffix entry ".cmi" then 36 44 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 45 + if contains_double_underscore base then None else Some (capitalize base) 46 + else None) 47 + entries 41 48 42 - let parse_libraries content = 49 + (* Stream stanzas through Library.codec while harvesting library names — 50 + needed for the lib_to_pkg reverse index. Returning the names lets us 51 + register both indexes in one pass. *) 52 + let library_names_in_dune_package content = 43 53 match Sexp.Value.parse_string_many content with 44 54 | Error _ -> [] 45 55 | Ok stanzas -> 46 56 List.filter_map 47 57 (fun s -> 48 - Sexp.Codec.decode_value Sexp_codecs.Dune.Dune_package.Library.codec s 49 - |> Result.to_option) 58 + match Sexp.Codec.decode_value Dune.Package.Library.codec s with 59 + | Ok (lib : Dune.Package.Library.t) -> Some lib.name 60 + | Error _ -> None) 50 61 stanzas 51 62 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 -> () 63 + let pkg_index t pkg = 64 + match Hashtbl.find_opt t.pkg_libs pkg with 65 + | Some i -> i 66 + | None -> 67 + let i = Dune.Lib_index.empty () in 68 + Hashtbl.replace t.pkg_libs pkg i; 69 + i 70 + 71 + let scan_pkg_dir ~origin t pkg pkg_dir = 72 + let registered = ref false in 73 + let register () = 74 + if not !registered then begin 75 + Hashtbl.replace t.pkg_origin pkg origin; 76 + registered := true 77 + end 78 + in 79 + match load_eio_path Eio.Path.(pkg_dir / "dune-package") with 57 80 | Some content -> 81 + register (); 82 + let i = pkg_index t pkg in 83 + ignore (Dune.Lib_index.add_dune_package i content); 58 84 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) 85 + (fun lib_name -> Hashtbl.replace t.lib_to_pkg lib_name pkg) 86 + (library_names_in_dune_package content) 87 + | None -> ( 88 + match cmi_modules_in_dir pkg_dir with 89 + | [] -> () 90 + | modules -> 91 + register (); 92 + let i = pkg_index t pkg in 93 + ignore (Dune.Lib_index.add_cmi_modules i ~pkg ~modules); 94 + Hashtbl.replace t.lib_to_pkg pkg pkg) 79 95 80 96 let build ~fs ~monorepo = 81 - let t = { modules = Hashtbl.create 256; virtual_impls = Hashtbl.create 16 } in 82 - let scan root = 97 + let t = 98 + { 99 + pkg_libs = Hashtbl.create 256; 100 + pkg_origin = Hashtbl.create 256; 101 + lib_to_pkg = Hashtbl.create 512; 102 + } 103 + in 104 + let scan ~origin root = 83 105 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 106 + List.iter 107 + (fun pkg -> scan_pkg_dir ~origin t pkg Eio.Path.(root / pkg)) 108 + entries 85 109 in 86 110 let opam_lib = 87 111 Eio.Path.(fs / Fpath.to_string Fpath.(monorepo / "_opam" / "lib")) ··· 92 116 / Fpath.to_string 93 117 Fpath.(monorepo / "_build" / "install" / "default" / "lib")) 94 118 in 95 - scan opam_lib; 96 - scan build_lib; 119 + scan ~origin:Opam opam_lib; 120 + scan ~origin:Local build_lib; 97 121 t 98 122 123 + let origin t pkg = Hashtbl.find_opt t.pkg_origin pkg 124 + 125 + let packages t = 126 + Hashtbl.fold (fun k _ acc -> k :: acc) t.pkg_libs [] 127 + |> List.sort String.compare 128 + 129 + let libraries t pkg = 130 + Hashtbl.fold 131 + (fun lib p acc -> if p = pkg then lib :: acc else acc) 132 + t.lib_to_pkg [] 133 + |> List.sort String.compare 134 + 135 + let package_of t lib = Hashtbl.find_opt t.lib_to_pkg lib 136 + 99 137 let modules t lib = 100 - match Hashtbl.find_opt t.modules lib with 138 + match package_of t lib with 101 139 | None -> [] 102 - | Some s -> String_set.elements s 140 + | Some pkg -> ( 141 + match Hashtbl.find_opt t.pkg_libs pkg with 142 + | None -> [] 143 + | Some i -> Dune.Lib_index.modules i lib) 103 144 104 - let is_virtual_implementation t lib = Hashtbl.mem t.virtual_impls lib 145 + let is_virtual_implementation t lib = 146 + match package_of t lib with 147 + | None -> false 148 + | Some pkg -> ( 149 + match Hashtbl.find_opt t.pkg_libs pkg with 150 + | None -> false 151 + | Some i -> Dune.Lib_index.is_virtual_implementation i lib)
+59 -15
lib/index/monopam_info_index.mli
··· 1 - (** Build-level information about an opam switch's installed libraries. 1 + (** Build-level snapshot of the libraries and modules an opam switch makes 2 + available, modelled as the natural hierarchy: 3 + [opam package -> dune library -> modules]. 4 + 5 + Each opam package contributes a [_opam/lib/<pkg>/] directory. Inside, one of 6 + two metadata sources tells us which dune libraries the package provides: 7 + 8 + - {b dune-package}: dune writes [_opam/lib/<pkg>/dune-package] for any 9 + package built with dune. The file lists every sub-library plus the modules 10 + each library exposes, with virtual / implements relationships. This is the 11 + precise source. 12 + - {b *.cmi listing} (fallback): for packages not built with dune (e.g. 13 + [zarith], [asn1-combinators], [ounit2]), one library is registered under 14 + the package name with the [*.cmi] basenames in the install directory as 15 + its modules. This loses the sub-library hierarchy ([zarith.top] etc.) but 16 + is enough to answer "is library [L] referenced by name in source?" for the 17 + top-level library. A future pass could read the findlib [META] file to 18 + recover the [pkg.subpkg] tree, but that needs the [Meta] module to not 19 + collide with [compiler-libs.Meta] in mdx-built executables. *) 2 20 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. *) 21 + type origin = 22 + | Local 23 + | Opam 24 + (** Where a package was discovered: 25 + - [Local]: built locally and installed under 26 + [<monorepo>/_build/install/default/lib/]. Takes precedence over an 27 + opam-installed copy of the same package. 28 + - [Opam]: only present in [<monorepo>/_opam/lib/], i.e. an external 29 + opam dependency. *) 10 30 11 31 type t 12 - (** A library-name index for the active opam switch. *) 32 + (** Snapshot of the libraries available in a monorepo's opam switch. *) 13 33 14 34 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. *) 35 + (** [build ~fs ~monorepo] scans both [<monorepo>/_opam/lib/] and 36 + [<monorepo>/_build/install/default/lib/] and aggregates everything into a 37 + single index. The build-install root is scanned second so locally built 38 + packages override opam-installed ones, and their {!origin} comes out as 39 + [Local]. *) 40 + 41 + (** {1 Querying by opam package} *) 42 + 43 + val packages : t -> string list 44 + (** [packages t] is the sorted list of opam package names known to [t]. *) 45 + 46 + val origin : t -> string -> origin option 47 + (** [origin t pkg] is [Some Local] / [Some Opam] for known packages, or [None] 48 + if [pkg] is not in the index. *) 49 + 50 + val libraries : t -> string -> string list 51 + (** [libraries t pkg] is the sorted list of dune library names provided by opam 52 + package [pkg]. Returns [[]] if [pkg] is unknown. *) 53 + 54 + (** {1 Querying by library} *) 55 + 56 + val package_of : t -> string -> string option 57 + (** [package_of t lib] is the opam package name that provides dune library 58 + [lib], or [None] if [lib] is unknown. *) 17 59 18 60 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. *) 61 + (** [modules t lib] is the list of top-level module names exposed by dune 62 + library [lib], or [[]] if [lib] is unknown. Module names are capitalised, in 63 + the form [Dune.Package.Library.t.modules] returns. *) 22 64 23 65 val is_virtual_implementation : t -> string -> bool 24 66 (** [is_virtual_implementation t lib] is [true] iff [lib] has an 25 67 [(implements X)] entry in its [dune-package]. Such libraries are link-time 26 - live even when none of their modules appear in source. *) 68 + live even when none of their modules appear in source. Always [false] for 69 + libraries discovered via META, since findlib has no notion of virtual 70 + libraries. *)
+1
monopam-info.opam
··· 14 14 "dune-build-info" 15 15 "eio" 16 16 "fpath" 17 + "nox-dune" 17 18 "nox-loc" 18 19 "nox-sexp" 19 20 "fmt" {with-test}