My aggregated monorepo of OCaml code, automaintained
0
fork

Configure Feed

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

day11/doc: replace odoc-store with compile layers + shared HTML

Replace the per-dep RO bind mount strategy for .odoc files with
regular content-addressed compile layers stacked via overlayfs.

- Compile phase: dep compile layers stacked as overlayfs lowers,
.odoc output captured in layer's fs/home/opam/odoc-out/...
- Link phase: doc-dep compile layers stacked (wider set including
{post} and x-extra-doc-deps), HTML written via RW mount to
shared os_dir/html/
- Doc-all: combines both, captures compile layer + writes HTML

This eliminates:
- Odoc_store compile_mounts/link_mounts/doc_all_mounts
- Per-dep RO bind mounts (PAGE_SIZE scaling issue)
- dep_locs hashtable tracking
- is_compiled empty-directory bug

Odoc_store stripped to just rel_path + container_html constants.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+104 -317
+17 -19
day11/bin/cmd_build.ml
··· 182 182 Printf.printf "\nGenerating docs to %s...\n%!" output_dir; 183 183 let output = Fpath.v output_dir in 184 184 ignore (Bos.OS.Dir.create ~path:true output); 185 - (* Use a temporary os_dir for the odoc-store so we don't 186 - pollute the shared batch cache *) 187 - let doc_os_dir = Bos.OS.Dir.tmp "day11_build_doc_%s" 188 - |> Result.get_ok in 189 - (* Symlink the layer dirs from the shared cache into the temp 190 - os_dir so the doc pipeline can find them *) 191 - let _ = Sys.command (Printf.sprintf "ln -s %s/* %s/ 2>/dev/null" 192 - (Fpath.to_string os_dir) (Fpath.to_string doc_os_dir)) in 193 185 let driver_compiler = OpamPackage.of_string profile.driver_compiler in 194 186 let odoc_repo = profile.odoc_repo in 195 187 let solutions = [ (target, solve_result) ] in ··· 198 190 Day11_lib.Run_log.set_log_base_dir (Fpath.to_string snapshot_dir); 199 191 let run_log = Day11_lib.Run_log.start_run () in 200 192 Day11_doc.Generate.build_tools_and_run env benv ~np 201 - ~os_dir:doc_os_dir 193 + ~os_dir 202 194 ~packages:git_packages ~repos:repos_with_shas 203 195 ~opam_env:_opam_env ~mounts:base_mounts 204 196 ~driver_compiler ~odoc_repo ··· 209 201 | _ -> false) 210 202 ~opam_repositories ~cache ~run_log 211 203 ~nodes ~solutions ~blessing_maps; 212 - (* Move HTML from temp store to user's output dir *) 213 - let html_root = Fpath.(doc_os_dir / "odoc-store" / "html") in 214 - if Bos.OS.Dir.exists html_root |> Result.get_ok then begin 215 - let cmd = Printf.sprintf "cp -a %s/* %s/ 2>/dev/null" 216 - (Fpath.to_string html_root) (Fpath.to_string output) in 217 - ignore (Sys.command cmd); 218 - Printf.printf "HTML written to %s\n%!" output_dir 219 - end; 220 - (* Clean up temp store *) 221 - ignore (Day11_exec.Sudo.rm_rf env doc_os_dir)); 204 + (* Copy HTML for packages in this solution to the output dir *) 205 + let html_root = Fpath.(os_dir / "html" / "p") in 206 + let n_copied = ref 0 in 207 + OpamPackage.Map.iter (fun pkg _ -> 208 + let name = OpamPackage.Name.to_string (OpamPackage.name pkg) in 209 + let ver = OpamPackage.Version.to_string (OpamPackage.version pkg) in 210 + let src = Fpath.(html_root / name / ver) in 211 + if Bos.OS.Dir.exists src |> Result.get_ok then begin 212 + let dst = Fpath.(output / name / ver) in 213 + ignore (Bos.OS.Dir.create ~path:true (Fpath.parent dst)); 214 + ignore (Sys.command (Printf.sprintf "cp -a %s %s" 215 + (Fpath.to_string src) (Fpath.to_string dst))); 216 + incr n_copied 217 + end 218 + ) solution; 219 + Printf.printf "Copied docs for %d packages to %s\n%!" !n_copied output_dir); 222 220 Printf.printf "Done.\n%!"; 223 221 0 224 222 end
+78 -92
day11/doc/generate.ml
··· 114 114 Some (composite_tool_hash, universe, blessed, pkg_loc, mounts, prep_dir) 115 115 | Error _ -> None) 116 116 117 - let compile_package env benv ~os_dir ~store ~driver_tool ~odoc_tools 118 - ~build_hash_blessed ~find_odoc_tool ~dep_locs (node : build) = 117 + let compile_package env benv ~os_dir ~driver_tool ~odoc_tools 118 + ~build_hash_blessed ~find_odoc_tool ~compile_results (node : build) = 119 119 match prepare_package ~os_dir ~driver_tool ~odoc_tools 120 120 ~build_hash_blessed ~find_odoc_tool node with 121 121 | None -> None 122 - | Some (composite_tool_hash, universe, blessed, pkg_loc, mounts, prep_dir) -> 122 + | Some (composite_tool_hash, universe, blessed, _pkg_loc, mounts, prep_dir) -> 123 + (* Collect dep compile layer dirs for overlayfs stacking *) 123 124 let seen = Hashtbl.create 16 in 124 125 List.iter (collect_transitive_deps seen) node.deps; 125 - let deps = Hashtbl.fold (fun bh () acc -> 126 - match Hashtbl.find_opt dep_locs bh with 127 - | Some loc -> loc :: acc | None -> acc 126 + let dep_compile_dirs = Hashtbl.fold (fun bh () acc -> 127 + match Hashtbl.find_opt compile_results bh with 128 + | Some bl -> Build.dir ~os_dir bl :: acc 129 + | None -> acc 128 130 ) seen [] in 129 - let store_mounts, _ = 130 - Odoc_store.compile_mounts store pkg_loc ~deps in 131 131 let cmd = 132 132 "export PATH=/home/opam/doc-tools/bin:$PATH && eval $(opam env) && " ^ 133 133 Command.odoc_driver_voodoo ~pkg:node.pkg ~universe ··· 146 146 in 147 147 let result = 148 148 match Day11_opam_build.Build_layer.build env benv 149 - ~mounts:(mounts @ store_mounts) ~build_dirs:[] 149 + ~mounts ~build_dirs:dep_compile_dirs 150 150 ~prep_upper:(doc_prep_upper env ~uid:benv.uid ~gid:benv.gid) 151 151 ~on_extract 152 152 compile_node 153 153 ~strategy:{ cmd; cleanup = doc_cleanup } () with 154 154 | Day11_opam_build.Types.Success bl -> 155 - Hashtbl.replace dep_locs node.hash pkg_loc; 155 + Hashtbl.replace compile_results node.hash bl; 156 156 Some bl 157 157 | _ -> 158 158 Printf.printf " %s: compile FAILED\n%!" ··· 162 162 ignore (Day11_exec.Sudo.rm_rf env prep_dir); 163 163 result 164 164 165 - let link_package env benv ~os_dir ~store ~driver_tool ~odoc_tools 166 - ~build_hash_blessed ~find_odoc_tool ~compile_results ~dep_locs 165 + let link_package env benv ~os_dir ~driver_tool ~odoc_tools 166 + ~build_hash_blessed ~find_odoc_tool ~compile_results 167 167 ~doc_dep_hashes 168 168 ~build_hash ~universe_hashes 169 169 (node : build) = ··· 173 173 match prepare_package ~os_dir ~driver_tool ~odoc_tools 174 174 ~build_hash_blessed ~find_odoc_tool node with 175 175 | None -> None 176 - | Some (composite_tool_hash, universe, blessed, pkg_loc, mounts, prep_dir) -> 177 - (* Mount odoc output from doc_deps (not build_deps), so forward 178 - deps like odig are available for cross-referencing. *) 176 + | Some (composite_tool_hash, universe, blessed, _pkg_loc, mounts, prep_dir) -> 177 + (* Collect compile layer dirs from doc_deps (wider set) *) 179 178 let doc_dep_bhs = match Hashtbl.find_opt doc_dep_hashes build_hash with 180 179 | Some bhs -> bhs | None -> [] in 181 - let deps = List.filter_map (fun bh -> 182 - Hashtbl.find_opt dep_locs bh 180 + let dep_compile_dirs = List.filter_map (fun bh -> 181 + match Hashtbl.find_opt compile_results bh with 182 + | Some bl -> Some (Build.dir ~os_dir bl) 183 + | None -> None 183 184 ) doc_dep_bhs in 184 - let store_mounts, _ = 185 - Odoc_store.link_mounts store pkg_loc ~deps in 185 + (* Also include own compile layer *) 186 + let own_compile_dir = match Hashtbl.find_opt compile_results build_hash with 187 + | Some bl -> [ Build.dir ~os_dir bl ] 188 + | None -> [] in 189 + let all_compile_dirs = own_compile_dir @ dep_compile_dirs in 190 + (* HTML RW mount — entire html dir *) 191 + let html_base = Fpath.(os_dir / "html") in 192 + let html_mount = Day11_container.Mount.bind_rw 193 + ~src:(Fpath.to_string html_base) 194 + Odoc_store.container_html in 186 195 let dep_compile_layers = 187 196 List.filter_map (fun dep_bh -> 188 197 if String.equal dep_bh build_hash then None ··· 211 220 in 212 221 let result = 213 222 match Day11_opam_build.Build_layer.build env benv 214 - ~mounts:(mounts @ store_mounts) ~build_dirs:[] 223 + ~mounts:(mounts @ [html_mount]) ~build_dirs:all_compile_dirs 215 224 ~prep_upper:(doc_prep_upper env ~uid:benv.uid ~gid:benv.gid) 216 225 ~on_extract 217 226 link_node ··· 228 237 ignore (Day11_exec.Sudo.rm_rf env prep_dir); 229 238 result 230 239 231 - let doc_all_package env benv ~os_dir ~store ~driver_tool ~odoc_tools 232 - ~build_hash_blessed ~find_odoc_tool ~compile_results ~dep_locs 240 + let doc_all_package env benv ~os_dir ~driver_tool ~odoc_tools 241 + ~build_hash_blessed ~find_odoc_tool ~compile_results 233 242 ~build_hash ~universe_hashes 234 243 (node : build) = 235 244 match prepare_package ~os_dir ~driver_tool ~odoc_tools 236 245 ~build_hash_blessed ~find_odoc_tool node with 237 246 | None -> None 238 - | Some (composite_tool_hash, universe, blessed, pkg_loc, mounts, prep_dir) -> 247 + | Some (composite_tool_hash, universe, blessed, _pkg_loc, mounts, prep_dir) -> 248 + (* Collect dep compile layer dirs for overlayfs stacking *) 239 249 let seen = Hashtbl.create 16 in 240 250 List.iter (collect_transitive_deps seen) node.deps; 241 - let deps = Hashtbl.fold (fun bh () acc -> 242 - match Hashtbl.find_opt dep_locs bh with 243 - | Some loc -> loc :: acc | None -> acc 251 + let dep_compile_dirs = Hashtbl.fold (fun bh () acc -> 252 + match Hashtbl.find_opt compile_results bh with 253 + | Some bl -> Build.dir ~os_dir bl :: acc 254 + | None -> acc 244 255 ) seen [] in 245 - let store_mounts, _, _ = 246 - Odoc_store.doc_all_mounts store pkg_loc ~deps in 256 + (* HTML RW mount — mount entire html dir so global assets (css, js) 257 + and per-package output both land in the shared dir *) 258 + let html_base = Fpath.(os_dir / "html") in 259 + let html_mount = Day11_container.Mount.bind_rw 260 + ~src:(Fpath.to_string html_base) 261 + Odoc_store.container_html in 247 262 let dep_compile_layers = 248 263 List.filter_map (fun dep_bh -> 249 264 if String.equal dep_bh build_hash then None ··· 272 287 in 273 288 let result = 274 289 match Day11_opam_build.Build_layer.build env benv 275 - ~mounts:(mounts @ store_mounts) ~build_dirs:[] 290 + ~mounts:(mounts @ [html_mount]) ~build_dirs:dep_compile_dirs 276 291 ~prep_upper:(doc_prep_upper env ~uid:benv.uid ~gid:benv.gid) 277 292 ~on_extract 278 293 doc_node 279 294 ~strategy:{ cmd; cleanup = doc_cleanup } () with 280 295 | Day11_opam_build.Types.Success bl -> 281 296 Hashtbl.replace compile_results build_hash bl; 282 - Hashtbl.replace dep_locs node.hash pkg_loc; 283 297 Printf.printf " %s: doc-all OK\n%!" 284 298 (OpamPackage.to_string node.pkg); 285 299 Some 0 ··· 295 309 ~tool_source_dirs ~mounts 296 310 ~run_log 297 311 ~build_one ~nodes ~solutions ~blessing_maps:_ = 298 - (* Create shared odoc store for all doc containers *) 299 - let store = Odoc_store.create ~os_dir in 300 - Printf.printf " Odoc store: %s\n%!" (Fpath.to_string (Fpath.(os_dir / "odoc-store"))); 312 + (* Ensure HTML output directory exists *) 313 + ignore (Bos.OS.Dir.create ~path:true Fpath.(os_dir / "html")); 301 314 (* Collect all tool nodes for inclusion in the unified DAG *) 302 315 let tool_nodes = 303 316 let seen = Hashtbl.create 64 in ··· 586 599 Day11_lib.Run_log.write_dag_structure run_log all_doc_nodes; 587 600 (* Track results *) 588 601 let compile_results : (string, build) Hashtbl.t = Hashtbl.create 64 in 589 - (* build_hash -> pkg_loc, for store mount lookups *) 590 - let dep_locs : (string, Odoc_store.pkg_loc) Hashtbl.t = Hashtbl.create 64 in 591 602 let doc_count = Atomic.make 0 in 592 603 let doc_html = Atomic.make 0 in 593 604 let compile_to_build : (string, string) Hashtbl.t = Hashtbl.create 64 in ··· 626 637 else if Hashtbl.mem tool_node_set n.hash then 1 627 638 else 0 628 639 in 629 - (* Pre-compute pkg_locs for all nodes (build + doc) so is_cached 630 - can check the store. Keyed by both build hash and doc node hash. *) 631 - let node_pkg_loc : (string, Odoc_store.pkg_loc) Hashtbl.t = 632 - Hashtbl.create 64 in 633 - let make_loc (node : build) = 634 - let universe = Command.compute_universe_hash [ node.hash ] in 635 - let blessed = match Hashtbl.find_opt build_hash_blessed node.hash with 636 - | Some b -> b 637 - | None -> true in 638 - let loc : Odoc_store.pkg_loc = { pkg = node.pkg; universe; blessed } in 639 - loc 640 - in 641 - (* Map build nodes *) 642 - List.iter (fun (node : build) -> 643 - Hashtbl.replace node_pkg_loc node.hash (make_loc node) 644 - ) nodes; 645 - (* Map doc-all nodes *) 646 - List.iter (fun (dn : build) -> 647 - Hashtbl.replace node_pkg_loc dn.hash (make_loc dn) 648 - ) doc_all_list; 649 - (* Map compile nodes *) 650 - List.iter (fun (cn : build) -> 651 - Hashtbl.replace node_pkg_loc cn.hash (make_loc cn) 652 - ) compile_list; 653 - (* Map link nodes *) 654 - List.iter (fun (ln : build) -> 655 - Hashtbl.replace node_pkg_loc ln.hash (make_loc ln) 656 - ) !link_nodes_list; 657 - (* Pre-populate dep_locs for nodes with compile output in the store *) 658 - Hashtbl.iter (fun build_hash loc -> 659 - if Odoc_store.is_compiled store loc then 660 - Hashtbl.replace dep_locs build_hash loc 661 - ) node_pkg_loc; 640 + (* Pre-populate compile_results for cached compile/doc-all layers *) 641 + List.iter (fun (build_hash, _cn) -> 642 + let cn = Hashtbl.find compile_nodes build_hash in 643 + let layer_dir = Build.dir ~os_dir cn in 644 + let layer_json = Fpath.(layer_dir / "layer.json") in 645 + if Bos.OS.File.exists layer_json |> Result.get_ok then 646 + match Day11_layer.Meta.load layer_json with 647 + | Ok meta when meta.exit_status = 0 -> 648 + Hashtbl.replace compile_results build_hash cn 649 + | _ -> () 650 + ) compile_snapshot; 651 + List.iter (fun (build_hash, _dn) -> 652 + let dn = Hashtbl.find doc_all_nodes build_hash in 653 + let layer_dir = Build.dir ~os_dir dn in 654 + let layer_json = Fpath.(layer_dir / "layer.json") in 655 + if Bos.OS.File.exists layer_json |> Result.get_ok then 656 + match Day11_layer.Meta.load layer_json with 657 + | Ok meta when meta.exit_status = 0 -> 658 + Hashtbl.replace compile_results build_hash dn 659 + | _ -> () 660 + ) doc_all_snapshot; 662 661 let open Day11_opam_build.Dag_executor in 663 662 let is_cached node = 664 663 let layer_dir = Day11_opam_layer.Build.dir ~os_dir node in ··· 672 671 | Error _ -> false 673 672 in 674 673 if not meta_ok then Cached_fail 675 - else if Hashtbl.mem doc_all_set node.hash then 676 - (match Hashtbl.find_opt node_pkg_loc node.hash with 677 - | Some loc when Odoc_store.is_compiled store loc 678 - && Odoc_store.is_linked store loc -> Cached_ok 679 - | _ -> Not_cached) 680 - else if Hashtbl.mem compile_set node.hash then 681 - (match Hashtbl.find_opt node_pkg_loc node.hash with 682 - | Some loc when Odoc_store.is_compiled store loc -> Cached_ok 683 - | _ -> Not_cached) 684 - else if Hashtbl.mem link_set node.hash then 685 - (match Hashtbl.find_opt node_pkg_loc node.hash with 686 - | Some loc when Odoc_store.is_linked store loc -> Cached_ok 687 - | _ -> Not_cached) 688 674 else Cached_ok 689 675 end 690 676 in ··· 736 722 | Some build_hash -> 737 723 let build_node = Hashtbl.find build_by_hash build_hash in 738 724 current_build_hash := build_hash; 739 - (match compile_package env benv ~os_dir ~store ~driver_tool ~odoc_tools 740 - ~build_hash_blessed ~find_odoc_tool ~dep_locs build_node with 741 - | Some bl -> Hashtbl.replace compile_results build_hash bl; true 725 + (match compile_package env benv ~os_dir ~driver_tool ~odoc_tools 726 + ~build_hash_blessed ~find_odoc_tool ~compile_results build_node with 727 + | Some _bl -> true 742 728 | None -> true) 743 729 end else if Hashtbl.mem doc_all_set node.hash then begin 744 730 (* Combined doc-all phase *) ··· 750 736 let universe_hashes = 751 737 match Hashtbl.find_opt build_hash_universe build_hash with 752 738 | Some hs -> hs | None -> [] in 753 - (match doc_all_package env benv ~os_dir ~store ~driver_tool ~odoc_tools 754 - ~build_hash_blessed ~find_odoc_tool ~compile_results ~dep_locs 739 + (match doc_all_package env benv ~os_dir ~driver_tool ~odoc_tools 740 + ~build_hash_blessed ~find_odoc_tool ~compile_results 755 741 ~build_hash ~universe_hashes 756 742 build_node with 757 743 | Some n -> ··· 769 755 let universe_hashes = 770 756 match Hashtbl.find_opt build_hash_universe build_hash with 771 757 | Some hs -> hs | None -> [] in 772 - (match link_package env benv ~os_dir ~store ~driver_tool ~odoc_tools 773 - ~build_hash_blessed ~find_odoc_tool ~compile_results ~dep_locs 758 + (match link_package env benv ~os_dir ~driver_tool ~odoc_tools 759 + ~build_hash_blessed ~find_odoc_tool ~compile_results 774 760 ~doc_dep_hashes 775 761 ~build_hash ~universe_hashes 776 762 build_node with ··· 809 795 in 810 796 List.iter (fun (dn : build) -> count_success dn) doc_all_list; 811 797 List.iter (fun (ln : build) -> count_success ln) !link_nodes_list; 812 - (* Count HTML files from the shared store *) 813 - let store_html = Odoc_store.html_root store in 798 + (* Count HTML files from the output directory *) 799 + let html_root = Fpath.(os_dir / "html") in 814 800 let total_html = 815 - if Bos.OS.Dir.exists store_html |> Result.get_ok then 801 + if Bos.OS.Dir.exists html_root |> Result.get_ok then 816 802 let find_result = Day11_exec.Run.run env 817 - Bos.Cmd.(v "find" % Fpath.to_string store_html 803 + Bos.Cmd.(v "find" % Fpath.to_string html_root 818 804 % "-name" % "*.html" % "-type" % "f") None in 819 805 List.length (String.split_on_char '\n' 820 806 (String.trim find_result.output)
-104
day11/doc/odoc_store.ml
··· 1 - type t = { 2 - root : Fpath.t; 3 - } 4 - 5 1 type pkg_loc = { 6 2 pkg : OpamPackage.t; 7 3 universe : string; 8 4 blessed : bool; 9 5 } 10 6 11 - let create ~os_dir = 12 - let root = Fpath.(os_dir / "odoc-store") in 13 - ignore (Bos.OS.Dir.create ~path:true root); 14 - ignore (Bos.OS.Dir.create ~path:true Fpath.(root / "odoc-out")); 15 - ignore (Bos.OS.Dir.create ~path:true Fpath.(root / "html")); 16 - { root } 17 - 18 7 let rel_path loc = 19 8 let name = OpamPackage.Name.to_string (OpamPackage.name loc.pkg) in 20 9 let version = OpamPackage.Version.to_string (OpamPackage.version loc.pkg) in ··· 23 12 else 24 13 Fpath.(v "u" / loc.universe / name / version) 25 14 26 - let odoc_out_dir t loc = 27 - Fpath.(t.root / "odoc-out" // rel_path loc) 28 - 29 - let html_dir t loc = 30 - Fpath.(t.root / "html" // rel_path loc) 31 - 32 - let container_odoc_out = "/home/opam/odoc-out" 33 15 let container_html = "/home/opam/html" 34 - 35 - 36 - (* Mount strategy: individual per-dep RO mounts for each dependency's 37 - odoc output, plus a RW mount for the package's own output. 38 - The container only sees its actual dependencies, preventing 39 - cross-reference resolution to unrelated packages. *) 40 - 41 - let ensure_pkg_dirs t loc = 42 - ignore (Bos.OS.Dir.create ~path:true (odoc_out_dir t loc)); 43 - ignore (Bos.OS.Dir.create ~path:true (html_dir t loc)) 44 - 45 - let dep_odoc_mounts t deps = 46 - List.filter_map (fun dep_loc -> 47 - let src = odoc_out_dir t dep_loc in 48 - if Bos.OS.Dir.exists src |> Result.get_ok then 49 - let rp = Fpath.to_string (rel_path dep_loc) in 50 - Some (Day11_container.Mount.bind_ro 51 - ~src:(Fpath.to_string src) 52 - (container_odoc_out ^ "/" ^ rp)) 53 - else 54 - None 55 - ) deps 56 - 57 - let compile_mounts t loc ~deps = 58 - ensure_pkg_dirs t loc; 59 - let rp = Fpath.to_string (rel_path loc) in 60 - (* RO mounts for each dep's odoc output *) 61 - let dep_mounts = dep_odoc_mounts t deps in 62 - (* RW mount for this package's odoc output *) 63 - let own_rw = Day11_container.Mount.bind_rw 64 - ~src:(Fpath.to_string (odoc_out_dir t loc)) 65 - (container_odoc_out ^ "/" ^ rp) in 66 - (dep_mounts @ [own_rw], Fpath.v "unused") 67 - 68 - let link_mounts t loc ~deps = 69 - ensure_pkg_dirs t loc; 70 - let rp = Fpath.to_string (rel_path loc) in 71 - (* RO mounts for deps' odoc output (for cross-ref resolution) *) 72 - let dep_mounts = dep_odoc_mounts t deps in 73 - (* Own odoc-out: link reads .odoc (from compile) and writes .odocl 74 - (intermediate). Mount the store dir RW — .odocl files will land 75 - in the store alongside .odoc but are harmless (small, not used 76 - by other packages). *) 77 - let own_odoc_rw = Day11_container.Mount.bind_rw 78 - ~src:(Fpath.to_string (odoc_out_dir t loc)) 79 - (container_odoc_out ^ "/" ^ rp) in 80 - (* RW mount for HTML output *) 81 - let html_rw = Day11_container.Mount.bind_rw 82 - ~src:(Fpath.to_string (html_dir t loc)) 83 - (container_html ^ "/" ^ rp) in 84 - (dep_mounts @ [own_odoc_rw; html_rw], Fpath.v "unused") 85 - 86 - let doc_all_mounts t loc ~deps = 87 - ensure_pkg_dirs t loc; 88 - let rp = Fpath.to_string (rel_path loc) in 89 - (* RO mounts for deps' odoc output *) 90 - let dep_mounts = dep_odoc_mounts t deps in 91 - (* RW mount for this package's odoc output *) 92 - let odoc_rw = Day11_container.Mount.bind_rw 93 - ~src:(Fpath.to_string (odoc_out_dir t loc)) 94 - (container_odoc_out ^ "/" ^ rp) in 95 - (* RW mount for HTML output *) 96 - let html_rw = Day11_container.Mount.bind_rw 97 - ~src:(Fpath.to_string (html_dir t loc)) 98 - (container_html ^ "/" ^ rp) in 99 - (dep_mounts @ [odoc_rw; html_rw], Fpath.v "unused", Fpath.v "unused") 100 - 101 - (* With direct RW mounts, output goes straight to the store. 102 - commit is a no-op — just verify the output landed. *) 103 - 104 - let dir_non_empty path = 105 - match Bos.OS.Dir.exists path with 106 - | Ok false -> false 107 - | Ok true -> 108 - (match Bos.OS.Dir.contents path with 109 - | Ok (_ :: _) -> true 110 - | _ -> false) 111 - | Error _ -> false 112 - 113 - let is_compiled t loc = 114 - dir_non_empty (odoc_out_dir t loc) 115 - 116 - let is_linked t loc = 117 - dir_non_empty (html_dir t loc) 118 - 119 - let html_root t = Fpath.(t.root / "html")
+5 -33
day11/doc/odoc_store.mli
··· 1 - (** Per-package bind-mount based odoc output store. 1 + (** Package location and path utilities for odoc output. 2 2 3 - Provides isolated mounts for doc containers: each package gets a 4 - RW temp dir for its own output, and RO mounts of dependency 5 - outputs for cross-referencing. On success, temp dirs are committed 6 - atomically to the store via rename. *) 7 - 8 - type t 9 - (** The store, rooted at [{os_dir}/odoc-store/]. *) 3 + Used to compute relative paths for HTML output mounts 4 + and container mount points. *) 10 5 11 6 type pkg_loc = { 12 7 pkg : OpamPackage.t; ··· 17 12 blessed uses [p/<Name>/<version>/], non-blessed uses 18 13 [u/<universe>/<Name>/<version>/]. *) 19 14 20 - val create : os_dir:Fpath.t -> t 21 - 22 15 val rel_path : pkg_loc -> Fpath.t 23 16 (** The relative path fragment for a package location. *) 24 17 25 - val compile_mounts : 26 - t -> pkg_loc -> deps:pkg_loc list -> 27 - Day11_container.Mount.t list * Fpath.t 28 - (** [compile_mounts store loc ~deps] returns [(mounts, _)]. 29 - Per-dep RO mounts for each dep's odoc output, plus RW mount 30 - for this package's own odoc output. *) 31 - 32 - val link_mounts : 33 - t -> pkg_loc -> deps:pkg_loc list -> 34 - Day11_container.Mount.t list * Fpath.t 35 - (** [link_mounts store loc ~deps] returns [(mounts, _)]. 36 - Per-dep RO mounts for odoc cross-refs, own odoc-out RO, 37 - plus RW mount for HTML output. *) 38 - 39 - val doc_all_mounts : 40 - t -> pkg_loc -> deps:pkg_loc list -> 41 - Day11_container.Mount.t list * Fpath.t * Fpath.t 42 - (** [doc_all_mounts store loc ~deps] returns [(mounts, _, _)]. 43 - Per-dep RO odoc mounts, RW for own odoc + RW for HTML. *) 44 - 45 - val is_compiled : t -> pkg_loc -> bool 46 - val is_linked : t -> pkg_loc -> bool 47 - val html_root : t -> Fpath.t 18 + val container_html : string 19 + (** Container path for HTML output: ["/home/opam/html"]. *)
+4 -69
day11/doc/test/test_doc.ml
··· 411 411 let p = Odoc_store.rel_path (loc ~blessed:false ~universe:"abc123" "astring.0.8.5") in 412 412 Alcotest.(check string) "non-blessed" "u/abc123/astring/0.8.5" (Fpath.to_string p) 413 413 414 - let test_store_create () = with_tmp_dir @@ fun dir -> 415 - let store = Odoc_store.create ~os_dir:dir in 416 - ignore store; 417 - Alcotest.(check bool) "root dir exists" true 418 - (Bos.OS.Dir.exists Fpath.(dir / "odoc-store") |> Result.get_ok) 419 - 420 - let test_is_compiled_false () = with_tmp_dir @@ fun dir -> 421 - let store = Odoc_store.create ~os_dir:dir in 422 - Alcotest.(check bool) "not compiled" false 423 - (Odoc_store.is_compiled store (loc "astring.0.8.5")) 424 - 425 - let test_is_compiled_true () = with_tmp_dir @@ fun dir -> 426 - let store = Odoc_store.create ~os_dir:dir in 427 - let l = loc "astring.0.8.5" in 428 - (* Manually create the odoc-out dir to simulate a committed compile *) 429 - let odoc_dir = Fpath.(dir / "odoc-store" / "odoc-out" // Odoc_store.rel_path l) in 430 - mkdir odoc_dir; 431 - Alcotest.(check bool) "compiled" true (Odoc_store.is_compiled store l) 432 - 433 - let test_is_linked_false () = with_tmp_dir @@ fun dir -> 434 - let store = Odoc_store.create ~os_dir:dir in 435 - Alcotest.(check bool) "not linked" false 436 - (Odoc_store.is_linked store (loc "astring.0.8.5")) 437 - 438 - let test_is_linked_true () = with_tmp_dir @@ fun dir -> 439 - let store = Odoc_store.create ~os_dir:dir in 440 - let l = loc "astring.0.8.5" in 441 - let html_dir = Fpath.(dir / "odoc-store" / "html" // Odoc_store.rel_path l) in 442 - mkdir html_dir; 443 - Alcotest.(check bool) "linked" true (Odoc_store.is_linked store l) 444 - 445 - let test_compile_mounts () = with_tmp_dir @@ fun dir -> 446 - let store = Odoc_store.create ~os_dir:dir in 447 - let l = loc "astring.0.8.5" in 448 - let dep = loc "fmt.0.11.0" in 449 - (* Create dep's odoc dir so it gets a mount *) 450 - mkdir Fpath.(dir / "odoc-store" / "odoc-out" // Odoc_store.rel_path dep); 451 - let mounts, _ = Odoc_store.compile_mounts store l ~deps:[dep] in 452 - (* 1 RO dep + 1 RW own = 2 mounts *) 453 - Alcotest.(check int) "2 mounts" 2 (List.length mounts); 454 - (* Ensure pkg dir was created in store *) 455 - Alcotest.(check bool) "pkg dir exists" true 456 - (Bos.OS.Dir.exists Fpath.(dir / "odoc-store" / "odoc-out" // Odoc_store.rel_path l) 457 - |> Result.get_ok) 458 - 459 - let test_link_mounts () = with_tmp_dir @@ fun dir -> 460 - let store = Odoc_store.create ~os_dir:dir in 461 - let l = loc "astring.0.8.5" in 462 - let dep = loc "fmt.0.11.0" in 463 - mkdir Fpath.(dir / "odoc-store" / "odoc-out" // Odoc_store.rel_path dep); 464 - mkdir Fpath.(dir / "odoc-store" / "odoc-out" // Odoc_store.rel_path l); 465 - let mounts, _ = Odoc_store.link_mounts store l ~deps:[dep] in 466 - (* 1 RO dep + 1 RO own odoc + 1 RW html = 3 mounts *) 467 - Alcotest.(check int) "3 mounts" 3 (List.length mounts) 468 - 469 - let test_doc_all_mounts () = with_tmp_dir @@ fun dir -> 470 - let store = Odoc_store.create ~os_dir:dir in 471 - let l = loc "astring.0.8.5" in 472 - let mounts, _, _ = Odoc_store.doc_all_mounts store l ~deps:[] in 473 - (* 0 dep mounts + 1 RW odoc + 1 RW html = 2 mounts *) 474 - Alcotest.(check int) "2 mounts" 2 (List.length mounts) 414 + let test_container_html () = 415 + Alcotest.(check string) "container_html" "/home/opam/html" 416 + Odoc_store.container_html 475 417 476 418 (* ── Doc_meta tests ─────────────────────────────────────────────── *) 477 419 ··· 591 533 [ 592 534 Alcotest.test_case "rel_path blessed" `Quick test_rel_path_blessed; 593 535 Alcotest.test_case "rel_path non-blessed" `Quick test_rel_path_non_blessed; 594 - Alcotest.test_case "create" `Quick test_store_create; 595 - Alcotest.test_case "is_compiled false" `Quick test_is_compiled_false; 596 - Alcotest.test_case "is_compiled true" `Quick test_is_compiled_true; 597 - Alcotest.test_case "is_linked false" `Quick test_is_linked_false; 598 - Alcotest.test_case "is_linked true" `Quick test_is_linked_true; 599 - Alcotest.test_case "compile_mounts" `Quick test_compile_mounts; 600 - Alcotest.test_case "link_mounts" `Quick test_link_mounts; 601 - Alcotest.test_case "doc_all_mounts" `Quick test_doc_all_mounts; 536 + Alcotest.test_case "container_html" `Quick test_container_html; 602 537 ] ); 603 538 ( "Doc_meta", 604 539 [