Monorepo management for opam overlays
0
fork

Configure Feed

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

monopam: scan_opam_files returns a record, lint honours pin-depends

Replace the 4-tuple [(name, runtime, all, pin_depends)] returned by
[scan_opam_files] with a record [opam_pkg] — names beat positional
fields once we got past three. While here, exempt deps already listed
in the consuming package's [pin-depends:] from the missing-pin check
so packages that ship a pin (like [irmin]'s [tw]) stop being flagged.

+84 -34
+84 -34
lib/lint.ml
··· 157 157 158 158 (* ---- Opam file deps parsing ---- *) 159 159 160 + type opam_pkg = { 161 + name : string; 162 + runtime : String_set.t; 163 + (** Runtime deps (no [with-test], [with-doc], [build]). *) 164 + all : String_set.t; (** Every declared dep, regardless of scope. *) 165 + pin_depends : String_set.t; 166 + (** Package names from [pin-depends:], stripped of versions. *) 167 + } 168 + (** What [scan_opam_files] returns for each [.opam] file: package name plus the 169 + three dep sets we care about. *) 170 + 160 171 let rec extract_dep_name (v : Opam.Value.t) = 161 172 match v with 162 173 | Opam.Value.String s -> Some s ··· 179 190 | _ -> false) 180 191 items 181 192 182 - (** Parse an opam file and return [(runtime_deps, all_deps)]. 193 + (** Strip the [".version"] suffix from a [pin-depends] entry name like 194 + ["tw.dev"] or ["wire.0.9.0"]. *) 195 + let pin_pkg_of_string s = 196 + match String.index_opt s '.' with Some i -> String.sub s 0 i | None -> s 183 197 184 - Runtime deps exclude those annotated with [:with-test], [:with-doc], or 185 - [build]. *) 186 - let opam_depends_of_reader ~file r = 198 + (** Extract [pin-depends:] package names from an opam item. The opam grammar 199 + encodes [["pkg.version" "url"]] as a [List [String _; String _]]. *) 200 + let pin_depends_of_value (v : Opam.Value.t) = 201 + match v with 202 + | Opam.Value.List entries -> 203 + List.fold_left 204 + (fun acc entry -> 205 + match entry with 206 + | Opam.Value.List (Opam.Value.String name :: _) -> 207 + String_set.add (pin_pkg_of_string name) acc 208 + | _ -> acc) 209 + String_set.empty entries 210 + | _ -> String_set.empty 211 + 212 + (** Parse an opam file's [depends:] / [pin-depends:] fields. Runtime deps 213 + exclude those annotated with [:with-test], [:with-doc], or [build]. *) 214 + let opam_depends_of_reader ~name ~file r = 215 + let empty = 216 + { 217 + name; 218 + runtime = String_set.empty; 219 + all = String_set.empty; 220 + pin_depends = String_set.empty; 221 + } 222 + in 187 223 try 188 224 let opam = Opam_bytesrw.of_reader ~file r in 189 225 List.fold_left 190 - (fun (runtime, all) (item : Opam.Value.item) -> 226 + (fun (acc : opam_pkg) (item : Opam.Value.item) -> 191 227 match item with 192 228 | Opam.Value.Variable ("depends", value) -> 193 229 let deps = 194 230 match value with Opam.Value.List items -> items | _ -> [] 195 231 in 196 - List.fold_left 197 - (fun (rt, al) dep -> 198 - match extract_dep_name dep with 199 - | None -> (rt, al) 200 - | Some n -> 201 - let al = String_set.add n al in 202 - if is_scoped dep then (rt, al) else (String_set.add n rt, al)) 203 - (runtime, all) deps 204 - | _ -> (runtime, all)) 205 - (String_set.empty, String_set.empty) 206 - opam.contents 232 + let runtime, all = 233 + List.fold_left 234 + (fun (rt, al) dep -> 235 + match extract_dep_name dep with 236 + | None -> (rt, al) 237 + | Some n -> 238 + let al = String_set.add n al in 239 + if is_scoped dep then (rt, al) 240 + else (String_set.add n rt, al)) 241 + (acc.runtime, acc.all) deps 242 + in 243 + { acc with runtime; all } 244 + | Opam.Value.Variable ("pin-depends", value) -> 245 + { 246 + acc with 247 + pin_depends = 248 + String_set.union acc.pin_depends (pin_depends_of_value value); 249 + } 250 + | _ -> acc) 251 + empty opam.contents 207 252 with exn -> 208 253 Log.debug (fun m -> m "opam parse failed: %s" (Printexc.to_string exn)); 209 - (String_set.empty, String_set.empty) 210 - 211 - (** Scan a subtree directory for [*.opam] files. 254 + empty 212 255 213 - Returns [(pkg_name, runtime_deps, all_deps)] for each opam file. *) 214 - let scan_opam_files ~fs subtree_path = 256 + (** Scan a subtree directory for [*.opam] files. *) 257 + let scan_opam_files ~fs subtree_path : opam_pkg list = 215 258 let eio_path = Eio.Path.(fs / Fpath.to_string subtree_path) in 216 259 let entries = try Eio.Path.read_dir eio_path with Eio.Io _ -> [] in 217 260 List.filter_map ··· 222 265 let file = Fpath.to_string opam_path in 223 266 let eio_opam = Eio.Path.(fs / file) in 224 267 try 225 - let runtime, all = 268 + let pkg = 226 269 Eio.Path.with_open_in eio_opam (fun flow -> 227 270 let r = Bytesrw_eio.bytes_reader_of_flow flow in 228 - opam_depends_of_reader ~file r) 271 + opam_depends_of_reader ~name:pkg_name ~file r) 229 272 in 230 - Some (pkg_name, runtime, all) 273 + Some pkg 231 274 with Eio.Io _ -> None 232 275 else None) 233 276 entries ··· 735 778 |> List.sort String.compare 736 779 737 780 let check_package ~index ~build_lib ~dune_pkgs ~dune_owner_pkgs ~own_set 738 - ~all_deps ~subtree (pkg_name, runtime_deps, _all_deps) ~fs = 781 + ~all_deps ~subtree (pkg : opam_pkg) ~fs = 782 + let pkg_name = pkg.name in 783 + let runtime_deps = pkg.runtime in 739 784 let meta_path = Fpath.(build_lib / pkg_name / "META") in 740 785 let meta_pkgs, meta_available = 741 786 match load_meta ~fs meta_path with ··· 849 894 let subtree_path = Fpath.(monorepo / subtree) in 850 895 let pkgs = scan_opam_files ~fs subtree_path in 851 896 List.fold_left 852 - (fun acc (pkg_name, _, _) -> String_set.add pkg_name acc) 897 + (fun acc (pkg : opam_pkg) -> String_set.add pkg.name acc) 853 898 acc pkgs) 854 899 String_set.empty subdirs 855 900 ··· 867 912 | exception Eio.Io _ -> String_set.empty 868 913 869 914 (** A runtime dep is missing_pin when it's neither provided by a monorepo 870 - subtree nor installable from a public opam-repo. We approximate the second 871 - leg by checking whether the dep is currently a pinned overlay 872 - ([_opam/.opam-switch/overlay/<pkg>]) or absent from the active switch 873 - altogether — both cases will trip up [opam install] from a fresh switch. *) 915 + subtree, nor declared in this package's [pin-depends:], nor installable from 916 + a public opam-repo. We approximate the third leg by checking whether the dep 917 + is currently a pinned overlay ([_opam/.opam-switch/overlay/<pkg>]) or absent 918 + from the active switch altogether — both cases will trip up [opam install] 919 + from a fresh switch. *) 874 920 let missing_pin_packages ~pinned_pkgs ~lib_index ~monorepo_pkgs ~subtree 875 - (_pkg_name, runtime_deps, _all_deps) = 921 + (pkg : opam_pkg) = 876 922 String_set.fold 877 923 (fun dep acc -> 878 924 if String_set.mem dep monorepo_pkgs then acc 879 925 else if String_set.mem dep implicit_deps then acc 880 926 else if is_conf_pkg dep then acc 927 + else if String_set.mem dep pkg.pin_depends then acc 881 928 else if String_set.mem dep pinned_pkgs then 882 929 { subtree; kind = Missing_pin; package = dep } :: acc 883 930 else if Monopam_info_index.origin lib_index dep <> None then acc 884 931 else { subtree; kind = Missing_pin; package = dep } :: acc) 885 - runtime_deps [] 932 + pkg.runtime [] 886 933 887 934 let run ~fs ~monorepo () = 888 935 let index = build_library_index ~fs ~monorepo in ··· 908 955 if pkgs = [] then Log.debug (fun m -> m "%s: no opam files" subtree) 909 956 else begin 910 957 incr scanned; 911 - let own_set = String_set.of_list (List.map (fun (n, _, _) -> n) pkgs) in 958 + let own_set = 959 + String_set.of_list (List.map (fun (p : opam_pkg) -> p.name) pkgs) 960 + in 912 961 let dune_pkgs = dune_needed_packages ~fs ~index subtree_path in 913 962 let dune_owner_pkgs = 914 963 dune_packages_by_owner ~fs ~index ~own_set subtree_path 915 964 in 916 965 List.iter 917 - (fun ((_, _, direct_deps) as pkg) -> 966 + (fun (pkg : opam_pkg) -> 967 + let direct_deps = pkg.all in 918 968 (* Each opam file declares its own depends; `dune build -p <pkg>` 919 969 only sees that one file. Expand this package's declared deps 920 970 with whatever they re-export, so consumers don't need to list