Monorepo management for opam overlays
0
fork

Configure Feed

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

at main 168 lines 6.2 kB view raw
1(** Package discovery from monorepo subtrees. *) 2 3let src = Logs.Src.create "monopam.monorepo_pkg" 4 5module Log = (val Logs.src_log src) 6 7type t = { 8 pkg_name : string; 9 subtree : string; 10 dev_repo : string; 11 url_src : string; 12 opam_content : string; 13} 14 15let v ~pkg_name ~subtree ~dev_repo ~url_src ~opam_content = 16 { pkg_name; subtree; dev_repo; url_src; opam_content } 17 18let pp ppf t = Fmt.pf ppf "@[<v>%s (subtree: %s)@]" t.pkg_name t.subtree 19let name t = t.pkg_name 20let subtree t = t.subtree 21let matches_name name t = t.pkg_name = name || t.subtree = name 22let dev_repo t = t.dev_repo 23let url_src t = t.url_src 24let opam_content t = t.opam_content 25 26(** Derive dev_repo and url_src for a subtree from various sources. Priority: 27 sources.toml override > dune-project source > default_url_base *) 28let derive_dev_repo ~sources ~subtree dune_proj = 29 let sources_override = Sources_registry.find sources ~subtree in 30 let derive_from_dune () = 31 match 32 ( Dune_project.dev_repo_url dune_proj, 33 Dune_project.url_with_branch dune_proj ) 34 with 35 | Ok dev_repo, Ok url_src -> Some (dev_repo, url_src) 36 | _ -> None 37 in 38 let derive_from_origin () = 39 match Sources_registry.origin sources with 40 | Some base -> 41 let base = 42 if String.ends_with ~suffix:"/" base then 43 String.sub base 0 (String.length base - 1) 44 else base 45 in 46 Some (base ^ "/" ^ subtree) 47 | None -> None 48 in 49 match sources_override with 50 | Some entry -> 51 (* Use the public HTTPS URL (from origin) for opam, 52 falling back to the entry source if no origin is configured. *) 53 let dev_repo, url_src = 54 match derive_from_origin () with 55 | Some url -> 56 (* Origin URL points to where monopam pushes — don't specify 57 branch, let git pick the default *) 58 (url, url) 59 | None -> 60 (* Fallback to entry source — use its branch *) 61 let branch = 62 match entry.Sources_registry.branch with 63 | Some b -> b 64 | None -> ( 65 match dune_proj.Dune_project.source with 66 | Some (Dune_project.Uri { branch = Some b; _ }) -> b 67 | _ -> "main") 68 in 69 ( entry.Sources_registry.source, 70 entry.Sources_registry.source ^ "#" ^ branch ) 71 in 72 Log.debug (fun m -> 73 m "Using sources.toml entry for %s: %s" subtree dev_repo); 74 Some (dev_repo, url_src) 75 | None -> ( 76 match derive_from_dune () with 77 | Some result -> Some result 78 | None -> ( 79 match derive_from_origin () with 80 | Some (dev_repo as url) -> 81 Log.debug (fun m -> m "Using origin for %s: %s" subtree dev_repo); 82 Some (url, url ^ "#main") 83 | None -> 84 Log.warn (fun m -> 85 m 86 "Cannot derive dev-repo for %s (no source in dune-project \ 87 or sources.toml)" 88 subtree); 89 None)) 90 91(** Load opam packages from a subtree directory. *) 92let load_opam_packages ~subtree_path ~subtree ~dev_repo ~url_src = 93 let opam_files = 94 try 95 Eio.Path.read_dir subtree_path 96 |> List.filter (fun name -> Filename.check_suffix name ".opam") 97 with Eio.Io _ -> [] 98 in 99 List.filter_map 100 (fun opam_file -> 101 let pkg_name = Filename.chop_suffix opam_file ".opam" in 102 let opam_path = Eio.Path.(subtree_path / opam_file) in 103 try 104 let raw_content = Eio.Path.load opam_path in 105 let opam_content = 106 Opam_transform.transform ~content:raw_content ~dev_repo ~url_src 107 in 108 Some { pkg_name; subtree; dev_repo; url_src; opam_content } 109 with Eio.Io _ -> None) 110 opam_files 111 112let discover ~fs ~config ?(sources = Sources_registry.empty) () = 113 let monorepo = Config.Paths.monorepo config in 114 let monorepo_eio = Eio.Path.(fs / Fpath.to_string monorepo) in 115 let subdirs = 116 try 117 Eio.Path.read_dir monorepo_eio 118 |> List.filter (fun name -> 119 let child = Eio.Path.(monorepo_eio / name) in 120 match Eio.Path.kind ~follow:false child with 121 | `Directory -> true 122 | _ -> false) 123 with Eio.Io _ -> [] 124 in 125 Log.debug (fun m -> 126 m "Found %d subdirectories in monorepo" (List.length subdirs)); 127 let packages, errors = 128 List.fold_left 129 (fun (pkgs, errs) subtree -> 130 let subtree_path = Eio.Path.(monorepo_eio / subtree) in 131 let dune_project_path = Eio.Path.(subtree_path / "dune-project") in 132 match Eio.Path.kind ~follow:false dune_project_path with 133 | `Regular_file -> ( 134 let content = 135 try Some (Eio.Path.load dune_project_path) with Eio.Io _ -> None 136 in 137 match content with 138 | None -> (pkgs, errs) 139 | Some content -> ( 140 match Dune_project.parse content with 141 | Error msg -> 142 Log.warn (fun m -> 143 m "Failed to parse %s/dune-project: %s" subtree msg); 144 (pkgs, msg :: errs) 145 | Ok dune_proj -> ( 146 match derive_dev_repo ~sources ~subtree dune_proj with 147 | None -> (pkgs, "Cannot derive dev-repo" :: errs) 148 | Some (dev_repo, url_src) -> 149 let new_pkgs = 150 load_opam_packages ~subtree_path ~subtree ~dev_repo 151 ~url_src 152 in 153 Log.debug (fun m -> 154 m "Found %d opam files in %s" (List.length new_pkgs) 155 subtree); 156 (new_pkgs @ pkgs, errs)))) 157 | _ -> 158 Log.debug (fun m -> m "No dune-project in %s, skipping" subtree); 159 (pkgs, errs) 160 | exception Eio.Io _ -> (pkgs, errs)) 161 ([], []) subdirs 162 in 163 if errors <> [] then 164 Log.warn (fun m -> 165 m "Encountered %d errors during monorepo discovery" (List.length errors)); 166 Log.info (fun m -> 167 m "Discovered %d packages from monorepo" (List.length packages)); 168 Ok (List.rev packages)