this repo has no description
at main 604 lines 19 kB view raw
1(* Packages *) 2 3type dep = string * Digest.t 4 5(* type id = Odoc.id *) 6 7type intf = { mif_hash : string; mif_path : Fpath.t; mif_deps : dep list } 8 9let pp_intf fmt (i : intf) = 10 Format.fprintf fmt "@[<hov>{@,mif_hash: %s;@,mif_path: %a;@,mif_deps: %a@,}@]" 11 i.mif_hash Fpath.pp i.mif_path 12 (Fmt.Dump.list (Fmt.Dump.pair Fmt.string Fmt.string)) 13 i.mif_deps 14 15type src_info = { src_path : Fpath.t } 16 17let pp_src_info fmt i = 18 Format.fprintf fmt "@[<hov>{@,src_path: %a@,}@]" Fpath.pp i.src_path 19 20type impl = { 21 mip_path : Fpath.t; 22 mip_src_info : src_info option; 23 mip_deps : dep list; 24} 25 26let pp_impl fmt i = 27 Format.fprintf fmt 28 "@[<hov>{@,mip_path: %a;@,mip_src_info: %a;@,mip_deps: %a@,}@]" Fpath.pp 29 i.mip_path 30 (Fmt.Dump.option pp_src_info) 31 i.mip_src_info 32 (Fmt.Dump.list (Fmt.Dump.pair Fmt.string Fmt.string)) 33 i.mip_deps 34 35type modulety = { 36 m_name : string; 37 m_intf : intf; 38 m_impl : impl option; 39 m_hidden : bool; 40} 41 42let pp_modulety fmt i = 43 Format.fprintf fmt 44 "@[<hov>{@,m_name: %s;@,m_intf: %a;@,m_impl: %a;@,m_hidden: %b@,}@]" 45 i.m_name pp_intf i.m_intf (Fmt.Dump.option pp_impl) i.m_impl i.m_hidden 46 47type mld = { mld_path : Fpath.t; mld_rel_path : Fpath.t } 48 49type md = { md_path : Fpath.t; md_rel_path : Fpath.t } 50 51let pp_mld fmt m = 52 Format.fprintf fmt "@[<hov>{@,mld_path: %a;@,mld_rel_path: %a@,}@]" Fpath.pp 53 m.mld_path Fpath.pp m.mld_rel_path 54 55let pp_md fmt m = 56 Format.fprintf fmt "@[<hov>{@,md_path: %a;@,md_rel_path: %a@,}@]" Fpath.pp 57 m.md_path Fpath.pp m.md_rel_path 58 59type asset = { asset_path : Fpath.t; asset_rel_path : Fpath.t } 60 61let pp_asset fmt m = 62 Format.fprintf fmt "@[<hov>{@,asset_path: %a;@,asset_rel_path: %a@,}@]" 63 Fpath.pp m.asset_path Fpath.pp m.asset_rel_path 64 65type libty = { 66 lib_name : string; 67 dir : Fpath.t; 68 archive_name : string option; 69 lib_deps : Util.StringSet.t; 70 modules : modulety list; 71 id_override : string option; 72} 73 74let pp_libty fmt l = 75 Format.fprintf fmt 76 "@[<hov>{@,\ 77 lib_name: %s;@,\ 78 dir: %a;@,\ 79 archive_name: %a;@,\ 80 lib_deps: %a;@,\ 81 modules: %a@,\ 82 id_override: %a@,\n\ 83 \ }@]" 84 l.lib_name Fpath.pp l.dir 85 (Fmt.Dump.option Fmt.string) 86 l.archive_name 87 (Fmt.list ~sep:Fmt.comma Fmt.string) 88 (Util.StringSet.elements l.lib_deps) 89 (Fmt.Dump.list pp_modulety) 90 l.modules 91 Fmt.Dump.(option string) 92 l.id_override 93 94type t = { 95 name : string; 96 version : string; 97 libraries : libty list; 98 mlds : mld list; 99 assets : asset list; 100 selected : bool; 101 remaps : (string * string) list; 102 other_docs : md list; 103 pkg_dir : Fpath.t; 104 doc_dir : Fpath.t; 105 config : Global_config.t; 106} 107 108let pp fmt t = 109 Format.fprintf fmt 110 "@[<hov>{@,\ 111 name: %s;@,\ 112 version: %s;@,\ 113 libraries: %a;@,\ 114 mlds: %a;@,\ 115 assets: %a;@,\ 116 selected: %b;@,\ 117 other_docs: %a;@,\ 118 pkg_dir: %a@,\ 119 }@]" 120 t.name t.version (Fmt.Dump.list pp_libty) t.libraries (Fmt.Dump.list pp_mld) 121 t.mlds (Fmt.Dump.list pp_asset) t.assets t.selected (Fmt.Dump.list pp_md) 122 t.other_docs Fpath.pp t.pkg_dir 123 124let maybe_prepend_top top_dir dir = 125 match top_dir with None -> dir | Some d -> Fpath.(d // dir) 126 127let pkg_dir top_dir pkg_name = maybe_prepend_top top_dir Fpath.(v pkg_name) 128 129module Module = struct 130 type t = modulety 131 132 let pp ppf (t : t) = 133 Fmt.pf ppf "name: %s@.intf: %a@.impl: %a@.hidden: %b@." t.m_name Fpath.pp 134 t.m_intf.mif_path (Fmt.option pp_impl) t.m_impl t.m_hidden 135 136 let is_hidden name = Astring.String.is_infix ~affix:"__" name 137 138 let vs libsdir cmtidir modules = 139 let dir = match cmtidir with None -> libsdir | Some dir -> dir in 140 let mk m_name = 141 let exists ext = 142 let p = 143 Fpath.(dir // add_ext ext (v (String.uncapitalize_ascii m_name))) 144 in 145 let upperP = 146 Fpath.(dir // add_ext ext (v (String.capitalize_ascii m_name))) 147 in 148 Logs.debug (fun m -> 149 m "Checking %a (then %a)" Fpath.pp p Fpath.pp upperP); 150 match Bos.OS.File.exists p with 151 | Ok true -> Some p 152 | _ -> ( 153 match Bos.OS.File.exists upperP with 154 | Ok true -> Some upperP 155 | _ -> None) 156 in 157 let mk_intf mif_path = 158 match Odoc.compile_deps mif_path with 159 | Ok { digest; deps } -> 160 { mif_hash = digest; mif_path; mif_deps = deps } 161 | Error _ -> failwith "bad deps" 162 in 163 let mk_impl mip_path = 164 (* Directories in which we should look for source files *) 165 let src_dirs = 166 match cmtidir with None -> [ libsdir ] | Some d2 -> [ libsdir; d2 ] 167 in 168 169 let mip_src_info = 170 match Ocamlobjinfo.get_source mip_path src_dirs with 171 | None -> 172 Logs.debug (fun m -> m "No source found for module %s" m_name); 173 None 174 | Some src_path -> 175 Logs.debug (fun m -> 176 m "Found source file %a for %s" Fpath.pp src_path m_name); 177 Some { src_path } 178 in 179 let mip_deps = 180 match Odoc.compile_deps mip_path with 181 | Ok { digest = _; deps } -> deps 182 | Error _ -> failwith "bad deps" 183 in 184 { mip_src_info; mip_path; mip_deps } 185 in 186 let state = (exists "cmt", exists "cmti") in 187 188 let m_hidden = is_hidden m_name in 189 try 190 let r (m_intf, m_impl) = Some { m_name; m_intf; m_impl; m_hidden } in 191 match state with 192 | Some cmt, Some cmti -> r (mk_intf cmti, Some (mk_impl cmt)) 193 | Some cmt, None -> r (mk_intf cmt, Some (mk_impl cmt)) 194 | None, Some cmti -> r (mk_intf cmti, None) 195 | None, None -> 196 Logs.info (fun m -> m "No files for module: %s" m_name); 197 None 198 with _ -> 199 Logs.err (fun m -> m "Error processing module %s. Ignoring." m_name); 200 None 201 in 202 203 Eio.Fiber.List.filter_map mk modules 204end 205 206module Lib = struct 207 let handle_virtual_lib ~dir ~id_override ~lib_name ~all_lib_deps = 208 let modules = 209 match 210 Bos.OS.Dir.fold_contents 211 (fun p acc -> 212 if Fpath.has_ext "cmti" p then 213 let m_name = Fpath.rem_ext p |> Fpath.basename in 214 m_name :: acc 215 else acc) 216 [] dir 217 with 218 | Ok x -> x 219 | Error (`Msg e) -> 220 Logs.err (fun m -> m "Error reading dir %a: %s" Fpath.pp dir e); 221 [] 222 in 223 let modules = Module.vs dir None modules in 224 let lib_deps = 225 try Util.StringMap.find lib_name all_lib_deps 226 with _ -> Util.StringSet.empty 227 in 228 [ { lib_name; archive_name = None; modules; lib_deps; dir; id_override } ] 229 230 let v ~libname_of_archive ~pkg_name ~dir ~cmtidir ~all_lib_deps ~cmi_only_libs 231 ~id_override = 232 Logs.debug (fun m -> 233 m "Classifying dir %a for package %s" Fpath.pp dir pkg_name); 234 let dirs = 235 match cmtidir with None -> [ dir ] | Some dir2 -> [ dir; dir2 ] 236 in 237 let results = Odoc.classify dirs in 238 match List.length results with 239 | 0 -> ( 240 match 241 List.find_opt (fun dir -> List.mem_assoc dir cmi_only_libs) dirs 242 with 243 | None -> [] 244 | Some dir -> 245 let lib_name = List.assoc dir cmi_only_libs in 246 handle_virtual_lib ~dir ~lib_name ~all_lib_deps ~id_override) 247 | _ -> 248 Logs.debug (fun m -> m "Got %d lines" (List.length results)); 249 List.filter_map 250 (fun (archive_name, modules) -> 251 match 252 Fpath.Map.find Fpath.(dir / archive_name) libname_of_archive 253 with 254 | Some lib_name -> 255 let modules = Module.vs dir cmtidir modules in 256 let lib_deps = 257 try Util.StringMap.find lib_name all_lib_deps 258 with _ -> Util.StringSet.empty 259 in 260 Some 261 { 262 lib_name; 263 archive_name = Some archive_name; 264 modules; 265 lib_deps; 266 dir; 267 id_override; 268 } 269 | None -> 270 Logs.info (fun m -> 271 m "No entry for '%a' in libname_of_archive" Fpath.pp 272 Fpath.(dir / archive_name)); 273 Logs.info (fun m -> 274 m "Unable to determine library of archive %s: Ignoring." 275 archive_name); 276 None) 277 results 278 279 let pp ppf t = 280 Fmt.pf ppf "archive: %a modules: [@[<hov 2>@,%a@]@,]" 281 Fmt.(option string) 282 t.archive_name 283 Fmt.(list ~sep:sp Module.pp) 284 t.modules 285end 286 287(* Construct the list of mlds and assets from a package name and its list of pages *) 288let mk_mlds docs = 289 List.fold_left 290 (fun (mlds, assets, others) (doc : Opam.doc_file) -> 291 match doc.kind with 292 | `Mld -> 293 ( { mld_path = doc.file; mld_rel_path = doc.rel_path } :: mlds, 294 assets, 295 others ) 296 | `Asset -> 297 ( mlds, 298 { asset_path = doc.file; asset_rel_path = doc.rel_path } :: assets, 299 others ) 300 | `Other -> 301 ( mlds, 302 assets, 303 { md_path = doc.file; md_rel_path = doc.rel_path } :: others )) 304 ([], [], []) docs 305 306let fix_missing_deps pkgs = 307 let lib_name_by_hash = 308 List.fold_right 309 (fun pkg acc -> 310 List.fold_left 311 (fun acc lib -> 312 List.fold_left 313 (fun acc m -> 314 Util.StringMap.update m.m_intf.mif_hash 315 (function 316 | None -> Some [ lib.lib_name ] 317 | Some l -> Some (lib.lib_name :: l)) 318 acc) 319 acc lib.modules) 320 acc pkg.libraries) 321 pkgs Util.StringMap.empty 322 in 323 List.map 324 (fun pkg -> 325 let libraries = 326 List.map 327 (fun lib -> 328 let lib_deps = lib.lib_deps in 329 let new_lib_deps = 330 List.fold_left 331 (fun acc m -> 332 let if_deps = 333 Util.StringSet.of_list (List.map snd m.m_intf.mif_deps) 334 in 335 let impl_deps = 336 match m.m_impl with 337 | Some i -> Util.StringSet.of_list (List.map snd i.mip_deps) 338 | None -> Util.StringSet.empty 339 in 340 let deps = Util.StringSet.union if_deps impl_deps in 341 Util.StringSet.fold 342 (fun hash acc -> 343 match Util.StringMap.find hash lib_name_by_hash with 344 | exception Not_found -> acc 345 | deps -> 346 if 347 List.mem lib.lib_name deps 348 || List.exists 349 (fun d -> Util.StringSet.mem d lib_deps) 350 deps 351 then acc 352 else Util.StringSet.add (List.hd deps) acc) 353 deps acc) 354 Util.StringSet.empty lib.modules 355 in 356 if Util.StringSet.cardinal new_lib_deps > 0 then 357 Logs.debug (fun m -> 358 m "Adding missing deps to %s: %a" lib.lib_name 359 Fmt.(list string) 360 (Util.StringSet.elements new_lib_deps)); 361 { lib with lib_deps = Util.StringSet.union new_lib_deps lib_deps }) 362 pkg.libraries 363 in 364 { pkg with libraries }) 365 pkgs 366 367let of_libs ~packages_dir libs = 368 let Ocamlfind.Db. 369 { archives_by_dir; libname_of_archive; cmi_only_libs; all_lib_deps; _ } 370 = 371 Ocamlfind.Db.create libs 372 in 373 374 (* Opam gives us a map of packages to directories, and vice-versa *) 375 let opam_map, opam_rmap = Opam.pkg_to_dir_map () in 376 377 (* Now we can construct the packages *) 378 let packages = 379 Fpath.Map.fold 380 (fun dir archives acc -> 381 match Fpath.Map.find dir opam_rmap with 382 | None -> 383 Logs.debug (fun m -> m "No package for dir %a\n%!" Fpath.pp dir); 384 acc 385 | Some pkg -> 386 let libraries = 387 Lib.v ~libname_of_archive ~pkg_name:pkg.name ~dir ~cmtidir:None 388 ~all_lib_deps ~cmi_only_libs ~id_override:None 389 in 390 let libraries = 391 List.filter 392 (fun l -> 393 match l.archive_name with 394 | None -> true 395 | Some a -> Util.StringSet.mem a archives) 396 libraries 397 in 398 Util.StringMap.update pkg.name 399 (function 400 | Some pkg -> 401 let libraries = libraries @ pkg.libraries in 402 Some { pkg with libraries } 403 | None -> 404 let pkg_dir = pkg_dir packages_dir pkg.name in 405 406 let _, { Opam.docs; odoc_config; _ } = 407 List.find 408 (fun (pkg', _) -> 409 (* Logs.debug (fun m -> 410 m "Checking %s against %s" pkg.Opam.name pkg'.Opam.name); *) 411 pkg = pkg') 412 opam_map 413 in 414 415 let config = 416 match odoc_config with 417 | None -> Global_config.empty 418 | Some f -> Global_config.load f 419 in 420 421 let mlds, assets, _ = mk_mlds docs in 422 Some 423 { 424 name = pkg.name; 425 version = pkg.version; 426 libraries; 427 mlds; 428 assets; 429 selected = false; 430 remaps = []; 431 other_docs = []; 432 pkg_dir; 433 doc_dir = pkg_dir; 434 config; 435 }) 436 acc) 437 archives_by_dir Util.StringMap.empty 438 in 439 let packages = Util.StringMap.bindings packages |> List.map snd in 440 fix_missing_deps packages 441 442let of_packages ~packages_dir packages = 443 Logs.app (fun m -> m "Deciding which packages to build..."); 444 let deps = 445 if packages = [] then Opam.all_opam_packages () else Opam.deps packages 446 in 447 448 let Ocamlfind.Db.{ libname_of_archive; cmi_only_libs; all_lib_deps; _ } = 449 Ocamlfind.Db.create (Ocamlfind.all () |> Util.StringSet.of_list) 450 in 451 452 let opam_map, _opam_rmap = Opam.pkg_to_dir_map () in 453 454 let ps = 455 List.filter_map 456 (fun pkg -> 457 match 458 List.find_opt 459 (fun (pkg', _) -> pkg.Opam.name = pkg'.Opam.name) 460 opam_map 461 with 462 | None -> 463 Logs.warn (fun m -> 464 m "Didn't find package %a in opam_map" Opam.pp pkg); 465 None 466 | x -> x) 467 deps 468 in 469 470 let orig = 471 List.filter_map 472 (fun pkg -> 473 List.find_opt (fun (pkg', _) -> pkg = pkg'.Opam.name) opam_map) 474 packages 475 in 476 477 let all = orig @ ps in 478 let all = 479 List.sort_uniq 480 (fun (a, _) (b, _) -> String.compare a.Opam.name b.Opam.name) 481 all 482 in 483 484 Logs.app (fun m -> m "Performing module-level dependency analysis..."); 485 486 let packages = 487 List.map 488 (fun (pkg, files) -> 489 let libraries = 490 List.fold_left 491 (fun acc dir -> 492 Lib.v ~libname_of_archive ~pkg_name:pkg.Opam.name ~dir 493 ~cmtidir:None ~all_lib_deps ~cmi_only_libs ~id_override:None 494 @ acc) 495 [] 496 (files.Opam.libs |> Fpath.Set.to_list) 497 in 498 let pkg_dir = pkg_dir packages_dir pkg.name in 499 let config = 500 match files.odoc_config with 501 | None -> Global_config.empty 502 | Some f -> Global_config.load f 503 in 504 let mlds, assets, _ = mk_mlds files.docs in 505 let selected = List.mem pkg.name packages in 506 let remaps = 507 if selected then [] 508 else 509 let local_pkg_path = Fpath.to_string (Fpath.to_dir_path pkg_dir) in 510 let pkg_path = 511 Printf.sprintf "https://ocaml.org/p/%s/%s/doc/" pkg.name 512 pkg.version 513 in 514 let lib_paths = 515 List.map 516 (fun libty -> 517 let lib_name = libty.lib_name in 518 let local_lib_path = 519 Printf.sprintf "%s%s/" local_pkg_path lib_name 520 in 521 let lib_path = pkg_path in 522 (local_lib_path, lib_path)) 523 libraries 524 in 525 (local_pkg_path, pkg_path) :: lib_paths 526 in 527 528 { 529 name = pkg.name; 530 version = pkg.version; 531 libraries; 532 mlds; 533 assets; 534 selected; 535 remaps; 536 other_docs = []; 537 pkg_dir; 538 doc_dir = pkg_dir; 539 config; 540 }) 541 all 542 in 543 let res = fix_missing_deps packages in 544 Logs.debug (fun m -> m "Packages: %a" Fmt.Dump.(list pp) res); 545 res 546 547let remap_virtual_interfaces duplicate_hashes pkgs = 548 List.map 549 (fun pkg -> 550 { 551 pkg with 552 libraries = 553 pkg.libraries 554 |> List.map (fun lib -> 555 { 556 lib with 557 modules = 558 lib.modules 559 |> List.map (fun m -> 560 let m_intf = 561 if 562 Util.StringMap.mem m.m_intf.mif_hash 563 duplicate_hashes 564 && Fpath.has_ext "cmt" m.m_intf.mif_path 565 then 566 match 567 List.filter 568 (fun intf -> 569 Fpath.has_ext "cmti" intf.mif_path) 570 (Util.StringMap.find m.m_intf.mif_hash 571 duplicate_hashes) 572 with 573 | [ x ] -> x 574 | _ -> m.m_intf 575 else m.m_intf 576 in 577 { m with m_intf }); 578 }); 579 }) 580 pkgs 581 582let remap_virtual all = 583 let virtual_check = 584 let hashes = 585 List.fold_left 586 (fun acc pkg -> 587 List.fold_left 588 (fun acc lib -> 589 List.fold_left 590 (fun acc m -> 591 let hash = m.m_intf.mif_hash in 592 Util.StringMap.update hash 593 (function 594 | None -> Some [ m.m_intf ] 595 | Some l -> Some (m.m_intf :: l)) 596 acc) 597 acc lib.modules) 598 acc pkg.libraries) 599 Util.StringMap.empty all 600 in 601 Util.StringMap.filter (fun _hash intfs -> List.length intfs > 1) hashes 602 in 603 604 remap_virtual_interfaces virtual_check all