My aggregated monorepo of OCaml code, automaintained
at main 133 lines 5.5 kB view raw
1(** Read layer info for packages from day10's cache directory. 2 Uses the [packages/<pkg>/] directory structure with symlinks: 3 {[ 4 build-<hash> -> ../../build-<hash> (all builds) 5 doc-<hash> -> ../../doc-<hash> (all docs) 6 blessed-build -> ../../build-<hash> (canonical build if blessed) 7 blessed-docs -> ../../doc-<hash> (canonical docs if blessed) 8 ]} 9 Falls back to scanning [build-*] directories if no symlinks exist. *) 10 11type layer_info = { 12 package: string; 13 deps: string list; 14 created: float; 15 exit_status: int; 16} 17 18(** Read layer.json from a directory and parse it *) 19let read_layer_json path = 20 if Sys.file_exists path then 21 try 22 let content = In_channel.with_open_text path In_channel.input_all in 23 let json = Yojson.Safe.from_string content in 24 let open Yojson.Safe.Util in 25 (* Handle deps which may have OpamPackage objects or strings *) 26 let deps_list = json |> member "deps" |> to_list in 27 let deps = deps_list |> List.filter_map (fun d -> 28 match d with 29 | `String s -> Some s 30 | _ -> None (* Skip non-string deps *) 31 ) in 32 Some { 33 package = json |> member "package" |> to_string; 34 deps; 35 created = json |> member "created" |> to_float; 36 exit_status = json |> member "exit_status" |> to_int; 37 } 38 with _ -> None 39 else 40 None 41 42(** Follow a symlink and read layer.json from the target directory *) 43let read_layer_via_symlink symlink_path = 44 if Sys.file_exists symlink_path then 45 try 46 let target = Unix.readlink symlink_path in 47 (* Target is relative like "../../build-abc123" *) 48 let layer_dir = Filename.concat (Filename.dirname symlink_path) target in 49 let layer_json = Filename.concat layer_dir "layer.json" in 50 read_layer_json layer_json 51 with Unix.Unix_error _ -> None 52 else 53 None 54 55(** Get layer info for a package. 56 Checks blessed-build first, then falls back to most recent build symlink, 57 then falls back to scanning build-* directories. *) 58let get_package_layer ~cache_dir ~platform ~package = 59 let pkg_dir = Filename.concat cache_dir 60 (Filename.concat platform 61 (Filename.concat "packages" package)) in 62 (* Try blessed-build first *) 63 let blessed_build = Filename.concat pkg_dir "blessed-build" in 64 match read_layer_via_symlink blessed_build with 65 | Some info -> Some info 66 | None -> 67 (* Try to find any build-* symlink in the package directory *) 68 if Sys.file_exists pkg_dir && Sys.is_directory pkg_dir then 69 let build_symlinks = Sys.readdir pkg_dir 70 |> Array.to_list 71 |> List.filter (fun name -> String.length name > 6 && String.sub name 0 6 = "build-") 72 |> List.sort (fun a b -> String.compare b a) (* Most recent first by hash *) 73 in 74 match build_symlinks with 75 | first :: _ -> 76 read_layer_via_symlink (Filename.concat pkg_dir first) 77 | [] -> None 78 else 79 (* No package directory - fall back to scanning build-* directories *) 80 let platform_dir = Filename.concat cache_dir platform in 81 if Sys.file_exists platform_dir && Sys.is_directory platform_dir then 82 Sys.readdir platform_dir 83 |> Array.to_list 84 |> List.filter (fun name -> String.length name > 6 && String.sub name 0 6 = "build-") 85 |> List.find_map (fun build_dir -> 86 let layer_json = Filename.concat platform_dir 87 (Filename.concat build_dir "layer.json") in 88 match read_layer_json layer_json with 89 | Some info when info.package = package -> Some info 90 | _ -> None) 91 else 92 None 93 94(** List all packages with layer info (for computing reverse deps). 95 Returns list of (package_name, layer_info) pairs. 96 Uses packages/ directory structure - each subdirectory is a package. *) 97let list_all_packages ~cache_dir ~platform = 98 let packages_dir = Filename.concat cache_dir 99 (Filename.concat platform "packages") in 100 if Sys.file_exists packages_dir && Sys.is_directory packages_dir then 101 Sys.readdir packages_dir 102 |> Array.to_list 103 |> List.filter (fun name -> 104 (* Each entry should be a directory (package.version) *) 105 let path = Filename.concat packages_dir name in 106 Sys.is_directory path) 107 |> List.filter_map (fun package -> 108 match get_package_layer ~cache_dir ~platform ~package with 109 | Some info -> Some (package, info) 110 | None -> None) 111 else 112 (* Fall back to scanning build-* directories *) 113 let platform_dir = Filename.concat cache_dir platform in 114 if Sys.file_exists platform_dir && Sys.is_directory platform_dir then 115 Sys.readdir platform_dir 116 |> Array.to_list 117 |> List.filter (fun name -> String.length name > 6 && String.sub name 0 6 = "build-") 118 |> List.filter_map (fun build_dir -> 119 let layer_json = Filename.concat platform_dir 120 (Filename.concat build_dir "layer.json") in 121 match read_layer_json layer_json with 122 | Some info -> Some (info.package, info) 123 | None -> None) 124 else 125 [] 126 127(** Compute reverse dependencies: which packages depend on the given package. 128 Returns a list of package names that have this package in their deps. *) 129let get_reverse_deps ~cache_dir ~platform ~package = 130 list_all_packages ~cache_dir ~platform 131 |> List.filter_map (fun (pkg_name, info) -> 132 if List.mem package info.deps then Some pkg_name else None) 133 |> List.sort String.compare