My aggregated monorepo of OCaml code, automaintained
0
fork

Configure Feed

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

Record and validate dep compile layers in doc sidecar

Problem: a doc layer cached from a run with incomplete dep compile
layers produces HTML with unresolved cross-references (e.g.
Stdlib.Format.formatter). A later run reuses the stale layer
because the hash matches, even though the deps are now available.

Fix:
- Record dep compile layer hashes in doc.json deps field
(was always [])
- Add validate_cached_doc: checks that all recorded deps exist
on disk with exit_status=0
- In generate.ml is_cached: invalidate doc layers whose deps
are missing (delete and rebuild)
- In day11_prep.ml Op.build: same validation for OCurrent path
- Add test_xref_resolution.sh: builds docs and checks for
xref-unresolved spans in the HTML output

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

+75 -4
+16 -2
day11/doc/doc_build.ml
··· 89 89 let compile_node : Build.t = 90 90 { hash; pkg; deps = []; 91 91 universe = Day11_solution.Universe.dummy } in 92 + let dep_hashes = List.map (fun d -> 93 + Fpath.basename d) dep_compile_layers in 92 94 let on_extract ~layer_dir ~success:_ = 93 95 let dm : Doc_meta.t = { 94 96 package = OpamPackage.to_string pkg; 95 97 phase = Doc_meta.Compile; 96 - deps = []; 98 + deps = dep_hashes; 97 99 } in 98 100 ignore (Doc_meta.save layer_dir dm) 99 101 in ··· 168 170 let doc_node : Build.t = 169 171 { hash; pkg; deps = []; 170 172 universe = Day11_solution.Universe.dummy } in 173 + let dep_hashes = List.map (fun d -> 174 + Fpath.basename d) dep_compile_layers in 171 175 let on_extract ~layer_dir ~success:_ = 172 176 let dm : Doc_meta.t = { 173 177 package = OpamPackage.to_string pkg; 174 178 phase = Doc_meta.Doc_all; 175 - deps = []; 179 + deps = dep_hashes; 176 180 } in 177 181 ignore (Doc_meta.save layer_dir dm) 178 182 in ··· 191 195 in 192 196 ignore (Day11_exec.Sudo.rm_rf env prep_dir); 193 197 result 198 + 199 + let validate_cached_doc ~os_dir layer_dir = 200 + let doc_json = Fpath.(layer_dir / "doc.json") in 201 + match Doc_meta.load doc_json with 202 + | Error _ -> true (* no doc.json = old format, assume OK *) 203 + | Ok dm -> 204 + List.for_all (fun dep_name -> 205 + let dep_dir = Fpath.(os_dir / dep_name) in 206 + Day11_layer.Layer.is_ok { hash = dep_name; dir = dep_dir } 207 + ) dm.deps
+8
day11/doc/doc_build.mli
··· 76 76 Fpath.t -> bool 77 77 (** [has_documentable_libs layer_dir] returns true if the build layer 78 78 has installed libraries that can be documented. *) 79 + 80 + val validate_cached_doc : 81 + os_dir:Fpath.t -> Fpath.t -> bool 82 + (** [validate_cached_doc ~os_dir layer_dir] checks that a cached doc 83 + layer's dep compile layers (recorded in doc.json) are all present 84 + on disk. Returns [false] if any deps are missing, meaning the 85 + cached layer was built with incomplete inputs and should be 86 + invalidated. *)
+14 -2
day11/doc/generate.ml
··· 535 535 Not_cached 536 536 else begin 537 537 Day11_layer.Last_used.touch (Layer.dir layer); 538 - if Layer.is_ok layer then Cached_ok 539 - else Cached_fail 538 + if not (Layer.is_ok layer) then Cached_fail 539 + (* For doc layers, validate that all dep compile layers are present. 540 + A cached doc layer built with incomplete deps has bad xrefs. *) 541 + else if (Hashtbl.mem plan.compile_set node.hash || 542 + Hashtbl.mem plan.doc_all_set node.hash || 543 + Hashtbl.mem plan.link_set node.hash) && 544 + not (Doc_build.validate_cached_doc ~os_dir (Layer.dir layer)) 545 + then begin 546 + Printf.printf " Invalidating %s: missing dep compile layers\n%!" 547 + (OpamPackage.to_string node.pkg); 548 + ignore (Bos.OS.Dir.delete ~recurse:true (Layer.dir layer)); 549 + Not_cached 550 + end 551 + else Cached_ok 540 552 end 541 553 in 542 554 let dispatch = make_dispatch benv ~os_dir ~plan ~tool_source_dirs
+37
day11/doc/test/test_xref_resolution.sh
··· 1 + #!/bin/bash 2 + # Test that doc generation produces no unresolved cross-references. 3 + # 4 + # This catches the bug where doc layers are cached with incomplete 5 + # dep compile layers, producing HTML with xref-unresolved spans. 6 + # 7 + # Usage: test_xref_resolution.sh [--profile NAME] [PACKAGE] 8 + # Default: base.v0.17.3 with profile ocaml-ci 9 + 10 + set -e 11 + 12 + PROFILE="${PROFILE:-ocaml-ci}" 13 + PACKAGE="${1:-base.v0.17.3}" 14 + OUTPUT_DIR=$(mktemp -d) 15 + 16 + echo "Testing xref resolution for $PACKAGE" 17 + echo "Output: $OUTPUT_DIR" 18 + 19 + # Build with docs 20 + day11 build --profile "$PROFILE" "$PACKAGE" --with-doc "$OUTPUT_DIR" -j 4 21 + 22 + # Check for unresolved xrefs 23 + UNRESOLVED=$(find "$OUTPUT_DIR" -name "*.html" -exec grep -l 'xref-unresolved' {} \; 2>/dev/null) 24 + 25 + if [ -n "$UNRESOLVED" ]; then 26 + echo "FAIL: Found unresolved cross-references in:" 27 + echo "$UNRESOLVED" | head -10 28 + echo "" 29 + echo "Example:" 30 + echo "$UNRESOLVED" | head -1 | xargs grep -o 'class="xref-unresolved">[^<]*<' | head -5 31 + rm -rf "$OUTPUT_DIR" 32 + exit 1 33 + fi 34 + 35 + N_HTML=$(find "$OUTPUT_DIR" -name "*.html" | wc -l) 36 + echo "OK: $N_HTML HTML files, no unresolved xrefs" 37 + rm -rf "$OUTPUT_DIR"