Monorepo management for opam overlays
0
fork

Configure Feed

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

monopam/lint: detect missing deps in executables stanzas

(executables ...) uses (public_names ...) (plural), but stanza_owner
only checked (public_name ...) (singular), so executable stanzas had no
inferred owner and their (libraries ...) refs were never attributed to
any package — silently masking missing-dep gaps in (executables ...).
Also fall back to the subtree's primary package when the inferred owner
isn't itself a real opam package (mirrors dune's resolution for binary
names like ocurl that share their package with a sibling library), and
skip *.t/ cram fixture dirs whose dune files describe test scaffolding
rather than the package's real build deps.

+36 -10
+36 -10
lib/lint.ml
··· 275 275 (fun entry -> 276 276 if String.starts_with ~prefix:"." entry then [] 277 277 else if entry = "_build" || entry = "_opam" then [] 278 + (* [foo.t/] directories hold cram-test fixtures; their dune files 279 + describe per-test scaffolding, not the package's real build deps. *) 280 + else if Filename.check_suffix entry ".t" then [] 278 281 else 279 282 let child = Fpath.(dir / entry) in 280 283 let eio_child = Eio.Path.(fs / Fpath.to_string child) in ··· 300 303 |> String_set.of_list 301 304 302 305 (** Owning opam package for a single dune stanza. Prefers an explicit 303 - [(package foo)] field, otherwise infers it from [(public_name n)] — using 304 - the part before the first dot when the name is dotted ([foo.bar] -> [foo]). 305 - Returns [None] for private stanzas (no [public_name], no [package]). *) 306 + [(package foo)] field, otherwise infers it from [(public_name n)] or — for 307 + [(executables ...)] stanzas — from the first installable name in 308 + [(public_names ...)] (the [\\] placeholder marks an exec as not public). 309 + Uses the part before the first dot when the name is dotted ([foo.bar] -> 310 + [foo]). Returns [None] for private stanzas. *) 306 311 let stanza_owner fields = 312 + let prefix pn = 313 + match String.index_opt pn '.' with 314 + | Some i -> Some (String.sub pn 0 i) 315 + | None -> Some pn 316 + in 317 + let rec first_public = function 318 + | Sexp.Atom n :: _ when n <> {|\|} -> prefix n 319 + | _ :: rest -> first_public rest 320 + | [] -> None 321 + in 307 322 match field "package" fields with 308 323 | Some (Sexp.Atom p :: _) -> Some p 309 324 | _ -> ( 310 325 match field "public_name" fields with 311 - | Some (Sexp.Atom pn :: _) -> ( 312 - match String.index_opt pn '.' with 313 - | Some i -> Some (String.sub pn 0 i) 314 - | None -> Some pn) 315 - | _ -> None) 326 + | Some (Sexp.Atom pn :: _) -> prefix pn 327 + | _ -> ( 328 + match field "public_names" fields with 329 + | Some names -> first_public names 330 + | _ -> None)) 316 331 317 332 let libs_of_fields fields = 318 333 match field "libraries" fields with ··· 333 348 A first pass collects the [(name X) (public_name Y)] mapping so a sibling 334 349 reference written as the internal OCaml name (e.g. [atproto_handle] for 335 350 public [atproto-handle]) resolves to the public package name. *) 336 - let dune_packages_by_owner ~fs ~index subtree_path = 351 + let dune_packages_by_owner ~fs ~index ~own_set subtree_path = 337 352 let dune_files = dune_files_in ~fs subtree_path in 338 353 let stanzas = 339 354 List.concat_map ··· 342 357 | None -> [] 343 358 | Some content -> parse_sexps content) 344 359 dune_files 360 + in 361 + (* When a stanza's inferred owner doesn't name a real opam package in this 362 + subtree (e.g. an executable's [public_names] is just a binary name), dune 363 + installs the artefact in the project's primary package — match that here 364 + so the stanza's library refs are still attributed somewhere. *) 365 + let resolve_owner owner = 366 + if String_set.mem owner own_set then owner 367 + else match String_set.elements own_set with [ only ] -> only | _ -> owner 345 368 in 346 369 let internal_to_public = Hashtbl.create 16 in 347 370 List.iter ··· 376 399 match stanza_owner fields with 377 400 | None -> () 378 401 | Some owner -> 402 + let owner = resolve_owner owner in 379 403 List.iter 380 404 (fun lib -> 381 405 if not (is_builtin lib) then add owner (resolve lib)) ··· 667 691 incr scanned; 668 692 let own_set = String_set.of_list (List.map (fun (n, _, _) -> n) pkgs) in 669 693 let dune_pkgs = dune_needed_packages ~fs ~index subtree_path in 670 - let dune_owner_pkgs = dune_packages_by_owner ~fs ~index subtree_path in 694 + let dune_owner_pkgs = 695 + dune_packages_by_owner ~fs ~index ~own_set subtree_path 696 + in 671 697 List.iter 672 698 (fun ((_, _, direct_deps) as pkg) -> 673 699 (* Each opam file declares its own depends; `dune build -p <pkg>`