1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
|
(*─────────────────────────────────────────────────────────────────────────────┐
│ SPDX-FileCopyrightText: 2025 toastal <https://toast.al/contact/> │
│ SPDX-License-Identifier: GPL-3.0-or-later │
└─────────────────────────────────────────────────────────────────────────────*)
let prefixed_env_info ?doc ?deprecated var =
Cmdliner.Cmd.Env.info ("NIXTAMAL_" ^ var) ?doc ?deprecated
let common_man = [
`S "BUGS";
`P "During alpha, contact the maker directly or join the XMPP MUC.";
`S "SEE ALSO";
`I ("nixtamal-manifest(5)", "manifest.kdl format (TODO: & schema)");
]
module Global = struct
type t = {
style_renderer: Fmt.style_renderer option;
level: Logs.level option;
dir: string option;
jobs: int;
}
let directory_arg =
let open Cmdliner in
let env = prefixed_env_info ~doc: "Directory for Nixtamal" "DIRECTORY" in
Arg.(
value
& opt (some string) None
& info
["directory"]
~env
~doc: "Working directory for Nixtamal-related files (default: $PWD/nix/tamal)"
~docv: "PATH"
)
let jobs_arg =
let open Cmdliner in
let domain_count : int = Stdlib.Domain.recommended_domain_count () in
Arg.(
value
& opt int domain_count
& info
["j"; "jobs"]
~env: (prefixed_env_info "JOBS")
~doc: "Nixtamal’s executor pool domain size."
~docv: "INT"
)
let args =
let open Cmdliner in
let open Term in
ret
(
const (fun style_renderer level dir jobs ->
`Ok {style_renderer; level; dir; jobs}
)
$ Fmt_cli.style_renderer ~env: (prefixed_env_info "OUTPUT_COLOR") ()
$ Logs_cli.level ~env: (prefixed_env_info "LOG_LEVEL") ()
$ directory_arg
$ jobs_arg
)
let run ~env {style_renderer; level; dir; jobs} =
Fmt_tty.setup_std_outputs ?style_renderer ();
Logs.set_level (
match level with
| None -> Some Logs.Info
| Some lvl -> Some lvl
);
Logs.set_reporter (Logs_fmt.reporter ());
Kdl.indent := 1;
let () =
match dir with
| None ->
Nixtamal.Working_directory.set_default ~env ()
| Some d ->
let cwd = Eio.Stdenv.cwd env in
let directory = Eio.Path.(cwd / d) in
Nixtamal.Working_directory.set ~directory
in
fun f -> f ~env ~domain_count: jobs
end
module Set_up = struct
let nixpkgs_mismatch = "Both --nixpkgs-branch & --nixpkgs-ref cannot be used at the same time"
let info =
Cmdliner.Cmd.info
"set-up"
~doc: "Set up working directory for Nixtamal. By default, also adds Nixpkgs from upstream to the project’s inputs."
~man: common_man
let run ~env ~domain_count: _ nixpkgs : unit =
match Nixtamal.set_up ~env ?nixpkgs () with
| Ok() -> ()
| Error err -> failwith (Fmt.str "%a" Nixtamal.Error.pp_error err)
let term ~env =
let open Cmdliner in
let no_nixpkgs_arg =
Arg.(
value
& flag
& info
["no-nixpkgs"]
~env: (prefixed_env_info "NO_NIXPKGS")
~doc: "Do not add Nixpkgs to the pinned inputs list by default."
)
and use_nixpkgs_git_mirrors_arg =
Arg.(
value
& flag
& info
["use-nixpkgs-git-mirrors"]
~env: (prefixed_env_info "USE_NIXPKGS_GIT_MIRRORS")
~doc: "For resiliance, add known Nixpkgs git mirrors to fallback on when the Nixpkgs’s Microsoft GitHub host inevitably goes down again. Off by default as the Git updating is slightly slower & some users might object to TUNA’s hosting origin state."
)
and nixpkgs_branch_arg =
Arg.(
value
& opt (some string) None
& info
["nixpkgs-branch"]
~env: (prefixed_env_info "NIXPKGS_BRANCH")
~doc: (Fmt.str "Nixpkgs Git branch for Nixtamal setup (shorthand for refs/branches/*). %s." nixpkgs_mismatch)
~docv: "BRANCH_NAME"
)
and nixpkgs_ref_arg =
Arg.(
value
& opt (some string) None
& info
["nixpkgs-ref"]
~env: (prefixed_env_info "NIXPKGS_REF")
~doc: (Fmt.str "Nixpkgs Git ref for Nixtamal setup (default: %s). %s." Nixtamal.Input.Nixpkgs.default_ref nixpkgs_mismatch)
~docv: "REF"
)
in
let nixpkgs_reference_arg =
let open Term in
let mk_reference nixpkgs_branch nixpkgs_ref =
match nixpkgs_branch, nixpkgs_ref with
| None, None -> `Ok None
| Some branch, None -> `Ok (Some (`Branch branch))
| None, Some ref -> `Ok (Some (`Ref ref))
| Some _, Some _ -> `Error (true, nixpkgs_mismatch)
in
ret
(
const mk_reference
$ nixpkgs_branch_arg
$ nixpkgs_ref_arg
)
and nixpkgs_revision_arg =
Arg.(
value
& opt (some string) None
& info
["nixpkgs-revision"]
~env: (prefixed_env_info "NIXPKGS_REVISION")
~doc: ("Nixpkgs Git revision for Nixtamal setup. The value will be used as the latest revision/change.")
~docv: "REVISION"
)
in
let nixpkgs_arg =
let open Term in
let open Nixtamal.Input in
let mk_arg no_nixpkgs use_nixpkgs_git_mirrors (reference : Git.Reference.t option) nixpkgs_revision =
if no_nixpkgs then
`Ok None
else if use_nixpkgs_git_mirrors then
let latest_revision = nixpkgs_revision in
let input = Nixpkgs.make_git_with_known_mirrors ?reference ?latest_revision () in
`Ok (Some input)
else
let latest_value = nixpkgs_revision in
let input = Nixpkgs.make_archive ?reference ?latest_value () in
`Ok (Some input)
in
ret
(
const mk_arg
$ no_nixpkgs_arg
$ use_nixpkgs_git_mirrors_arg
$ nixpkgs_reference_arg
$ nixpkgs_revision_arg
)
in
Term.(
const (fun glb -> Global.run ~env glb @@ run)
$ Global.args
$ nixpkgs_arg
)
let cmd ~env = Cmdliner.Cmd.v info (term ~env)
end
module Check_soundness = struct
let info =
Cmdliner.Cmd.info
"check-soundness"
~doc: "Checks that the manifest × lockfile is sound."
~man: common_man
let run ~env ~domain_count: _ : unit =
match Nixtamal.check_soundness ~env () with
| Ok() -> ()
(* TODO: use these errors for error codes *)
| Error err -> failwith (Fmt.str "%a" Nixtamal.Error.pp_error err)
let term ~env =
let open Cmdliner in
Term.(
const (fun glb -> Global.run ~env glb @@ run)
$ Global.args
)
let cmd ~env = Cmdliner.Cmd.v info (term ~env)
end
module Tweak = struct
let info =
Cmdliner.Cmd.info
"tweak"
~doc: "Tweak the manifest file with \\$VISUAL, \\$EDITOR, or vi"
~man: common_man
let run ~env ~domain_count: _ : unit =
match Nixtamal.tweak ~env () with
| Ok() -> ()
(* TODO: use these errors for error codes *)
| Error err -> failwith (Fmt.str "%a" Nixtamal.Error.pp_error err)
let term ~env =
let open Cmdliner in
Term.(
const (fun glb -> Global.run ~env glb @@ run)
$ Global.args
)
let cmd ~env = Cmdliner.Cmd.v info (term ~env)
end
module Show = struct
let info =
Cmdliner.Cmd.info
"show"
~doc: "Shows current inputs as understood by Nixtamal for earthlings."
~man: common_man
let run ~env ~domain_count: _ : unit =
match Nixtamal.show ~env () with
| Ok() -> ()
| Error err -> failwith (Fmt.str "%a" Nixtamal.Error.pp_error err)
let term ~env =
let open Cmdliner in
Term.(
const (fun glb -> Global.run ~env glb @@ run)
$ Global.args
)
let cmd ~env = Cmdliner.Cmd.v info (term ~env)
end
module Lock = struct
let info =
Cmdliner.Cmd.info
"lock"
~doc: "Lock all not-yet-locked inputs."
~man: common_man
let run ~env ~domain_count force names : unit =
let names = List.map Nixtamal.Name.Name.make names in
match Nixtamal.lock ~env ~domain_count ~force ~names () with
| Ok() -> ()
| Error err -> failwith (Fmt.str "%a" Nixtamal.Error.pp_error err)
let term ~env =
let open Cmdliner in
let force_arg =
Arg.(
value
& flag
& info ["f"; "force"] ~doc: "Force input to lock (useful if changing the manifest in a manner that otherwise wouldn’t trigger a lock)."
)
and names_arg =
Arg.(
value
& pos_all string []
& info [] ~docv: "INPUT_NAME" ~doc: "Input names to lock (if already locked, will skip)."
)
in
Term.(
const (fun glb force -> Global.run ~env glb @@ run force)
$ Global.args
$ force_arg
$ names_arg
)
let cmd ~env = Cmdliner.Cmd.v info (term ~env)
end
module List_stale = struct
let info =
Cmdliner.Cmd.info
"list-stale"
~doc: "List stale inputs with latest-cmd, without refreshing"
~man: common_man
let run ~env ~domain_count : unit =
match Nixtamal.list_stale ~env ~domain_count with
| Ok() -> ()
| Error err -> failwith (Fmt.str "%a" Nixtamal.Error.pp_error err)
let term ~env =
let open Cmdliner in
Term.(
const (fun glb -> Global.run ~env glb @@ run)
$ Global.args
)
let cmd ~env = Cmdliner.Cmd.v info (term ~env)
end
module Refresh = struct
let info =
Cmdliner.Cmd.info
"refresh"
~doc: "Refreshes all non-frozen inputs using the latest-cmd — or the default latest-cmd for certain kinds with a reasonable default (Git)."
~man: common_man
let run ~env ~domain_count names : unit =
let names = List.map Nixtamal.Name.Name.make names in
match Nixtamal.refresh ~env ~domain_count ~names () with
| Ok() -> ()
| Error err -> failwith (Fmt.str &quo
|