My aggregated monorepo of OCaml code, automaintained
0
fork

Configure Feed

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

day10: add Local_repo module for --local-repo package discovery and hashing

Implements the core module for --local-repo support with:
- discover_packages: scan directory for *.opam files
- repo_hash: compute cache hash using git state or opam file contents
- find_for_packages: match requested packages against local repos
- validate: check repo paths exist and detect duplicate packages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+87
+87
day10/bin/local_repo.ml
··· 1 + (** Local repository package discovery and hashing for --local-repo. *) 2 + 3 + (** Discover opam package names in a local directory. 4 + Scans for *.opam files at the root (not recursive). *) 5 + let discover_packages path = 6 + let entries = Sys.readdir path in 7 + Array.to_list entries 8 + |> List.filter_map (fun name -> 9 + match Filename.extension name with 10 + | ".opam" -> Some (Filename.remove_extension name) 11 + | _ -> None) 12 + 13 + (** Compute a cache hash for a local repository path. 14 + Uses git HEAD + dirty state if available, otherwise hashes opam file contents. *) 15 + let repo_hash path = 16 + let git_dir = Filename.concat path ".git" in 17 + if Sys.file_exists git_dir then begin 18 + (* Git repo: use HEAD sha + dirty flag *) 19 + let head = 20 + let ic = Unix.open_process_in (Printf.sprintf "git -C %s rev-parse HEAD 2>/dev/null" (Filename.quote path)) in 21 + let line = try input_line ic with End_of_file -> "unknown" in 22 + let _ = Unix.close_process_in ic in 23 + String.trim line 24 + in 25 + let dirty = 26 + let r = Sys.command (Printf.sprintf "git -C %s diff --quiet 2>/dev/null" (Filename.quote path)) in 27 + r <> 0 28 + in 29 + if dirty then begin 30 + let ic = Unix.open_process_in (Printf.sprintf "git -C %s diff 2>/dev/null | md5sum" (Filename.quote path)) in 31 + let diff_hash = try input_line ic |> String.split_on_char ' ' |> List.hd with End_of_file -> "unknown" in 32 + let _ = Unix.close_process_in ic in 33 + Printf.sprintf "local:%s|%s|dirty-%s" path head diff_hash 34 + end else 35 + Printf.sprintf "local:%s|%s" path head 36 + end else begin 37 + (* Not a git repo: hash opam file contents *) 38 + let packages = discover_packages path in 39 + let contents = List.map (fun pkg -> 40 + let opam_file = Filename.concat path (pkg ^ ".opam") in 41 + In_channel.with_open_text opam_file In_channel.input_all 42 + ) packages in 43 + let combined = String.concat "|" ("local" :: path :: contents) in 44 + "local:" ^ (Digest.string combined |> Digest.to_hex) 45 + end 46 + 47 + (** Find the local repo (if any) that provides at least one of the given package names. 48 + Returns [Some (path, matching_packages)] or [None]. *) 49 + let find_for_packages ~local_repos packages = 50 + List.find_map (fun repo_path -> 51 + let available = discover_packages repo_path in 52 + let matches = List.filter (fun pkg -> List.mem pkg available) packages in 53 + if matches <> [] then Some (repo_path, matches) 54 + else None 55 + ) local_repos 56 + 57 + (** Validate all local repos at startup. 58 + Returns [Ok ()] or [Error msg]. *) 59 + let validate local_repos = 60 + let errors = List.filter_map (fun path -> 61 + if not (Sys.file_exists path) then 62 + Some (Printf.sprintf "--local-repo: directory does not exist: %s" path) 63 + else if not (Sys.is_directory path) then 64 + Some (Printf.sprintf "--local-repo: not a directory: %s" path) 65 + else 66 + let pkgs = discover_packages path in 67 + if pkgs = [] then begin 68 + Printf.eprintf "Warning: --local-repo %s contains no *.opam files\n%!" path; 69 + None 70 + end else 71 + None 72 + ) local_repos in 73 + (* Check for duplicate packages across repos *) 74 + let all_pkgs = List.concat_map (fun path -> 75 + List.map (fun pkg -> (pkg, path)) (discover_packages path) 76 + ) local_repos in 77 + let dup_errors = List.filter_map (fun (pkg, path) -> 78 + let others = List.filter (fun (p, pa) -> p = pkg && pa <> path) all_pkgs in 79 + if others <> [] then 80 + Some (Printf.sprintf "--local-repo: package %s found in multiple repos: %s and %s" pkg path (snd (List.hd others))) 81 + else None 82 + ) all_pkgs in 83 + (* Deduplicate error messages *) 84 + let dup_errors = List.sort_uniq String.compare dup_errors in 85 + match errors @ dup_errors with 86 + | [] -> Ok () 87 + | errs -> Error (String.concat "\n" errs)