this repo has no description
1(** Shared CLI terms and helpers *)
2
3open Cmdliner
4
5let os_dir_term =
6 let doc = "OS-specific cache directory (e.g. /cache/linux-x86_64)" in
7 Arg.(required & opt (some string) None & info [ "os-dir" ] ~docv:"DIR" ~doc)
8
9let cache_dir_term =
10 let doc = "Top-level cache directory" in
11 Arg.(required & opt (some string) None & info [ "cache-dir" ] ~docv:"DIR" ~doc)
12
13let opam_repo_term =
14 let doc = "Path to opam-repository (repeatable, layered in order — \
15 later repos override earlier ones)" in
16 Arg.(non_empty & opt_all string [] & info [ "opam-repository" ] ~docv:"DIR" ~doc)
17
18let np_term =
19 let doc = "Number of parallel workers (default 4)" in
20 Arg.(value & opt int 4 & info [ "np"; "j" ] ~docv:"N" ~doc)
21
22let arch_term =
23 let doc = "Architecture (default x86_64)" in
24 Arg.(value & opt string "x86_64" & info [ "arch" ] ~docv:"ARCH" ~doc)
25
26let os_distribution_term =
27 let doc = "OS distribution (default debian)" in
28 Arg.(value & opt string "debian" & info [ "os-distribution" ] ~docv:"DIST" ~doc)
29
30let os_version_term =
31 let doc = "OS version (default bookworm)" in
32 Arg.(value & opt string "bookworm" & info [ "os-version" ] ~docv:"VER" ~doc)
33
34let ocaml_version_term =
35 let doc = "OCaml compiler version (e.g. ocaml-base-compiler.5.2.1 \
36 or ocaml-variants.5.2.0+ox)" in
37 Arg.(value & opt (some string) None & info [ "ocaml-version" ] ~docv:"PKG" ~doc)
38
39let patches_dir_term =
40 let doc = "Directory of patches to apply before building. \
41 Structure: DIR/PKG.VERSION/*.patch" in
42 Arg.(value & opt (some string) None & info [ "patches-dir" ] ~docv:"DIR" ~doc)
43
44let opam_build_repo_term =
45 let doc = "Path to local opam-build source checkout \
46 (builds opam-build from source for the base image)" in
47 Arg.(value & opt (some string) None & info [ "opam-build-repo" ] ~docv:"DIR" ~doc)
48
49let with_eio f =
50 Eio_main.run @@ fun env -> f (env :> Eio_unix.Stdenv.base)
51
52let fpath s = Fpath.v s
53
54let setup_solver ?(arch = "x86_64") ?(os = "linux")
55 ?(os_distribution = "debian") ?(os_family = "debian")
56 ?(os_version = "12") opam_repositories =
57 let repos_with_heads = List.map (fun repo ->
58 (repo, None)
59 ) opam_repositories in
60 let git_packages, repos_with_shas =
61 Day11_opam.Git_packages.of_repositories repos_with_heads in
62 (* ocaml-git corrupts Bos's temp dir setting — reset it *)
63 Bos.OS.Dir.set_default_tmp (Fpath.v (Filename.get_temp_dir_name ()));
64 let opam_env = Day11_opam.Opam_env.std_env
65 ~arch ~os ~os_distribution ~os_family ~os_version () in
66 (git_packages, repos_with_shas, opam_env)
67
68let parse_ocaml_version = function
69 | None -> None
70 | Some s -> Some (OpamPackage.of_string s)
71
72(* ── Profile support ───────────────────────────────────────────── *)
73
74let profile_term =
75 let doc = "Profile name (from ~/.day11/profiles/)" in
76 Arg.(required & opt (some string) None & info [ "profile" ] ~docv:"NAME" ~doc)
77
78let profile_dir_term =
79 let doc = "Profile directory (default ~/.day11)" in
80 Arg.(value & opt string "" & info [ "profile-dir" ] ~docv:"DIR" ~doc)
81
82type paths = {
83 profile_dir : Fpath.t;
84 cache_dir : Fpath.t;
85 os_dir : Fpath.t;
86 snapshots_base : Fpath.t;
87}
88
89let resolve_profile_dir s =
90 if s = "" then Day11_batch.Profile.default_dir ()
91 else Fpath.v s
92
93let load_profile ~profile_dir ~name =
94 let pdir = resolve_profile_dir profile_dir in
95 let profiles_dir = Fpath.(pdir / "profiles") in
96 match Day11_batch.Profile.load ~dir:profiles_dir ~name with
97 | Error e -> Error e
98 | Ok profile ->
99 let cache_dir = Fpath.(pdir / "cache") in
100 let os_dir = Fpath.(cache_dir / Day11_batch.Profile.os_dir_name profile) in
101 let snapshots_base = Fpath.(pdir / "snapshots" / name) in
102 Ok (profile, { profile_dir = pdir; cache_dir; os_dir; snapshots_base })
103
104let ensure_paths (paths : paths) =
105 ignore (Bos.OS.Dir.create ~path:true paths.cache_dir);
106 ignore (Bos.OS.Dir.create ~path:true paths.os_dir);
107 ignore (Bos.OS.Dir.create ~path:true paths.snapshots_base)
108
109let latest_snapshot_dir (paths : paths) =
110 match Bos.OS.Dir.contents paths.snapshots_base with
111 | Error _ -> None
112 | Ok entries ->
113 let dirs = entries
114 |> List.filter (fun p -> Bos.OS.Dir.exists p |> Result.get_ok)
115 |> List.filter_map (fun p ->
116 try
117 let stat = Unix.stat (Fpath.to_string p) in
118 Some (p, stat.Unix.st_mtime)
119 with Unix.Unix_error _ -> None)
120 |> List.sort (fun (_, t1) (_, t2) -> compare t2 t1)
121 in
122 match dirs with
123 | (p, _) :: _ -> Some p
124 | [] -> None
125
126let snapshot_dirs_by_recency (paths : paths) =
127 match Bos.OS.Dir.contents paths.snapshots_base with
128 | Error _ -> []
129 | Ok entries ->
130 entries
131 |> List.filter (fun p -> Bos.OS.Dir.exists p |> Result.get_ok)
132 |> List.filter_map (fun p ->
133 try
134 let stat = Unix.stat (Fpath.to_string p) in
135 Some (p, stat.Unix.st_mtime)
136 with Unix.Unix_error _ -> None)
137 |> List.sort (fun (_, t1) (_, t2) -> compare t2 t1)
138 |> List.map fst
139
140let read_pins_from_dir dir =
141 let opam_files = Sys.readdir dir |> Array.to_list
142 |> List.filter (fun f -> Filename.check_suffix f ".opam") in
143 List.fold_left (fun acc filename ->
144 let name = Filename.chop_suffix filename ".opam" in
145 let path = Filename.concat dir filename in
146 try
147 let opam = OpamFile.OPAM.read
148 (OpamFile.make (OpamFilename.raw path)) in
149 OpamPackage.Name.Map.add
150 (OpamPackage.Name.of_string name)
151 (OpamPackage.Version.of_string "dev", opam) acc
152 with _ -> acc
153 ) OpamPackage.Name.Map.empty opam_files