My aggregated monorepo of OCaml code, automaintained

Merge commit '75b19af405bed3161fcdb979e6f36431fe1d7ac3'

+124 -7
+70
monopam/lib/monopam.ml
··· 217 217 218 218 let gitignore_content = {|_build 219 219 *.install 220 + root.opam 220 221 |} 222 + 223 + (** Collect all external dependencies from packages. 224 + Returns a sorted, deduplicated list of package names that are dependencies 225 + but not packages in the repo itself. *) 226 + let collect_external_deps pkgs = 227 + let pkg_names = List.map Package.name pkgs in 228 + let all_deps = 229 + List.concat_map Package.depends pkgs 230 + |> List.sort_uniq String.compare 231 + in 232 + (* Filter out packages that are in the repo *) 233 + List.filter (fun dep -> not (List.mem dep pkg_names)) all_deps 234 + 235 + (** Generate dune-project content for the monorepo root. 236 + Lists all external dependencies as a virtual package. *) 237 + let generate_dune_project pkgs = 238 + let external_deps = collect_external_deps pkgs in 239 + let buf = Buffer.create 1024 in 240 + Buffer.add_string buf "(lang dune 3.20)\n"; 241 + Buffer.add_string buf "(name root)\n"; 242 + Buffer.add_string buf "\n"; 243 + Buffer.add_string buf "(generate_opam_files true)\n"; 244 + Buffer.add_string buf "\n"; 245 + Buffer.add_string buf "(package\n"; 246 + Buffer.add_string buf " (name root)\n"; 247 + Buffer.add_string buf 248 + " (synopsis \"Monorepo root package with external dependencies\")\n"; 249 + Buffer.add_string buf " (allow_empty)\n"; 250 + Buffer.add_string buf " (depends\n"; 251 + List.iter 252 + (fun dep -> Buffer.add_string buf (Printf.sprintf " %s\n" dep)) 253 + external_deps; 254 + Buffer.add_string buf " ))\n"; 255 + Buffer.contents buf 256 + 257 + (** Write dune-project to monorepo with external dependencies. *) 258 + let write_dune_project ~proc ~fs ~config pkgs = 259 + let monorepo = Config.Paths.monorepo config in 260 + let monorepo_eio = Eio.Path.(fs / Fpath.to_string monorepo) in 261 + let dune_project_path = Eio.Path.(monorepo_eio / "dune-project") in 262 + let content = generate_dune_project pkgs in 263 + (* Check if dune-project already exists with same content *) 264 + let needs_update = 265 + match Eio.Path.load dune_project_path with 266 + | existing -> existing <> content 267 + | exception Eio.Io _ -> true 268 + in 269 + if needs_update then begin 270 + Log.info (fun m -> m "Updating dune-project in monorepo"); 271 + Eio.Path.save ~create:(`Or_truncate 0o644) dune_project_path content; 272 + (* Stage and commit the dune-project *) 273 + Eio.Switch.run (fun sw -> 274 + let child = 275 + Eio.Process.spawn proc ~sw ~cwd:monorepo_eio 276 + [ "git"; "add"; "dune-project" ] 277 + in 278 + ignore (Eio.Process.await child)); 279 + Eio.Switch.run (fun sw -> 280 + let child = 281 + Eio.Process.spawn proc ~sw ~cwd:monorepo_eio 282 + [ "git"; "commit"; "-m"; "Update dune-project with external dependencies" ] 283 + in 284 + ignore (Eio.Process.await child)); 285 + Log.app (fun m -> 286 + m "Updated dune-project with %d external dependencies" 287 + (List.length (collect_external_deps pkgs))) 288 + end 221 289 222 290 let ensure_monorepo_initialized ~proc ~fs ~config = 223 291 let monorepo = Config.Paths.monorepo config in ··· 563 631 m "%d repositories unchanged." unchanged); 564 632 (* Update README.md with package summary *) 565 633 write_readme ~proc ~fs:fs_t ~config all_pkgs; 634 + (* Update dune-project with external dependencies *) 635 + write_dune_project ~proc ~fs:fs_t ~config all_pkgs; 566 636 Ok ()) 567 637 end 568 638 end)
+33 -1
monopam/lib/opam_repo.ml
··· 47 47 | _ -> None) 48 48 items 49 49 50 + (** Extract package name from a dependency formula value. 51 + Handles cases like: 52 + - "pkgname" 53 + - "pkgname" {>= "1.0"} 54 + - "pkgname" {with-test} 55 + Returns the package name if found. *) 56 + let rec extract_dep_name (v : OP.value) : string option = 57 + match v.pelem with 58 + | OP.String s -> Some s 59 + | OP.Option (inner, _) -> extract_dep_name inner 60 + | _ -> None 61 + 62 + (** Extract all dependency package names from a depends value. 63 + The depends field is a list of package formulas. *) 64 + let extract_depends_list (v : OP.value) : string list = 65 + match v.pelem with 66 + | OP.List { pelem = items; _ } -> 67 + List.filter_map extract_dep_name items 68 + | _ -> ( 69 + match extract_dep_name v with Some s -> [ s ] | None -> []) 70 + 71 + let find_depends (items : OP.opamfile_item list) : string list = 72 + List.find_map 73 + (fun (item : OP.opamfile_item) -> 74 + match item.pelem with 75 + | OP.Variable (name, value) when name.pelem = "depends" -> 76 + Some (extract_depends_list value) 77 + | _ -> None) 78 + items 79 + |> Option.value ~default:[] 80 + 50 81 let parse_package_path (path : Fpath.t) : (string * string) option = 51 82 let segs = Fpath.segs path in 52 83 let rec find_after_packages = function ··· 76 107 if not (is_git_url url) then Error (Not_git_remote (name, url)) 77 108 else 78 109 let dev_repo = normalize_git_url url in 79 - Ok (Package.create ~name ~version ~dev_repo ()) 110 + let depends = find_depends opamfile.file_contents in 111 + Ok (Package.create ~name ~version ~dev_repo ~depends ()) 80 112 with 81 113 | Eio.Io _ as e -> Error (Io_error (Printexc.to_string e)) 82 114 | exn -> Error (Parse_error (path_str, Printexc.to_string exn)))
+8 -3
monopam/lib/package.ml
··· 3 3 version : string; 4 4 dev_repo : Uri.t; 5 5 branch : string option; 6 + depends : string list; 6 7 } 7 8 8 - let create ~name ~version ~dev_repo ?branch () = 9 - { name; version; dev_repo; branch } 9 + let create ~name ~version ~dev_repo ?branch ?(depends = []) () = 10 + { name; version; dev_repo; branch; depends } 10 11 11 12 let name t = t.name 12 13 let version t = t.version 13 14 let dev_repo t = t.dev_repo 14 15 let branch t = t.branch 16 + let depends t = t.depends 15 17 16 18 let repo_name t = 17 19 (* Extract basename from dev-repo URL, stripping .git suffix *) ··· 29 31 30 32 let pp ppf t = 31 33 Fmt.pf ppf 32 - "@[<hov 2>{ name = %S;@ version = %S;@ dev_repo = %a;@ branch = %a }@]" 34 + "@[<hov 2>{ name = %S;@ version = %S;@ dev_repo = %a;@ branch = %a;@ \ 35 + depends = [%a] }@]" 33 36 t.name t.version Uri.pp t.dev_repo 34 37 Fmt.(option ~none:(any "None") string) 35 38 t.branch 39 + Fmt.(list ~sep:(any ", ") string) 40 + t.depends
+13 -3
monopam/lib/package.mli
··· 12 12 (** {1 Constructors} *) 13 13 14 14 val create : 15 - name:string -> version:string -> dev_repo:Uri.t -> ?branch:string -> unit -> t 16 - (** [create ~name ~version ~dev_repo ?branch ()] creates a new package. 15 + name:string -> 16 + version:string -> 17 + dev_repo:Uri.t -> 18 + ?branch:string -> 19 + ?depends:string list -> 20 + unit -> 21 + t 22 + (** [create ~name ~version ~dev_repo ?branch ?depends ()] creates a new package. 17 23 18 24 @param name The opam package name 19 25 @param version The package version (e.g., "dev") 20 26 @param dev_repo The git remote URL from the opam file's dev-repo field 21 - @param branch Optional branch override; defaults to repository default *) 27 + @param branch Optional branch override; defaults to repository default 28 + @param depends List of opam package names this package depends on *) 22 29 23 30 (** {1 Accessors} *) 24 31 ··· 33 40 34 41 val branch : t -> string option 35 42 (** [branch t] returns the branch to track, if explicitly set. *) 43 + 44 + val depends : t -> string list 45 + (** [depends t] returns the list of opam package names this package depends on. *) 36 46 37 47 val repo_name : t -> string 38 48 (** [repo_name t] returns the repository name extracted from the dev-repo URL.