nix_daemon/lib.rs
1// SPDX-FileCopyrightText: 2023 embr <git@liclac.eu>
2//
3// SPDX-License-Identifier: EUPL-1.2
4
5//! nix-daemon
6//! ==========
7//!
8//! This library exposes an interface for directly talking to a [Nix](https://nixos.org/)
9//! daemon.
10//!
11//! - To connect to a `nix-daemon`, use [`nix::DaemonStore`] (through the [`Store`] trait).
12//! - To build your own store, and make it compatible with existing tools (eg. `nix-build`),
13//! implement the [`Store`] trait and use [`nix::DaemonProtocolAdapter`].
14//!
15//! The [`Store`] protocol mirrors the interface of the latest protocol we support, and
16//! may receive breaking changes to keep up.
17//!
18//! However, as the Nix Daemon protocol is forward compatible, and will negotiate the
19//! highest protocol version supported by both ends at connection time, there's no
20//! pressure to upgrade; unless compatibility is broken upstream, an old version of this
21//! crate should in theory be able to talk to newer Nix until the end of time.
22//!
23//! The [`nix`] module currently supports Protocol 1.35, and Nix 2.15+. Support for older
24//! versions will be added in the future (in particular, Protocol 1.21, used by Nix 2.3).
25
26pub mod nix;
27
28use chrono::{DateTime, Utc};
29use num_enum::{IntoPrimitive, TryFromPrimitive, TryFromPrimitiveError};
30use std::fmt::Debug;
31use std::{collections::HashMap, future::Future};
32use thiserror::Error;
33use tokio::io::AsyncReadExt;
34
35pub type Result<T, E = Error> = std::result::Result<T, E>;
36
37trait ResultExt<T, E> {
38 fn with_field(self, f: &'static str) -> Result<T>;
39}
40
41impl<T, E: Into<Error>> ResultExt<T, E> for Result<T, E> {
42 fn with_field(self, f: &'static str) -> Result<T> {
43 self.map_err(|err| Error::Field(f, Box::new(err.into())))
44 }
45}
46
47/// Error enum for the library.
48#[derive(Debug, Error)]
49pub enum Error {
50 /// This error was encountered while reading/writing a specific field.
51 #[error("`{0}`: {1}")]
52 Field(&'static str, #[source] Box<Error>),
53 /// An invalid value of some sort was encountered.
54 #[error("invalid value: {0}")]
55 Invalid(String),
56
57 /// Error returned from the nix daemon.
58 #[error("{0}")]
59 NixError(NixError),
60
61 /// IO error.
62 #[error(transparent)]
63 IO(#[from] std::io::Error),
64}
65
66/// A thrown exception from the daemon.
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct NixError {
69 pub level: Verbosity,
70 pub msg: String,
71 pub traces: Vec<String>,
72}
73
74impl std::fmt::Display for NixError {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 write!(f, "[{:?}] {}", self.level, self.msg)?;
77 for trace in self.traces.iter() {
78 write!(f, "\n\t{}", trace)?;
79 }
80 Ok(())
81 }
82}
83
84/// Real-time logging data returned from a [`Progress`].
85#[derive(Debug, Clone, PartialEq, Eq)]
86pub enum Stderr {
87 /// A plain line of stderr output.
88 Next(String),
89 /// An error propagated from Nix.
90 Error(NixError),
91 /// An activity (such as a build) was started.
92 StartActivity(StderrStartActivity),
93 /// An activity (such as a build) finished.
94 StopActivity { act_id: u64 },
95 /// A progress update from an activity.
96 Result(StderrResult),
97}
98
99/// Type of a [`Stderr::StartActivity`].
100#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
101#[repr(u64)]
102pub enum StderrActivityType {
103 Unknown = 0,
104 CopyPath = 100,
105 FileTransfer = 101,
106 Realise = 102,
107 CopyPaths = 103,
108 Builds = 104,
109 Build = 105,
110 OptimiseStore = 106,
111 VerifyPaths = 107,
112 Substitute = 108,
113 QueryPathInfo = 109,
114 PostBuildHook = 110,
115 BuildWaiting = 111,
116}
117impl From<TryFromPrimitiveError<StderrActivityType>> for Error {
118 fn from(value: TryFromPrimitiveError<StderrActivityType>) -> Self {
119 Self::Invalid(format!("StderrActivityType({:x})", value.number))
120 }
121}
122
123/// Notification that an Activity (such as a build) has started.
124///
125/// TODO: Users should not have to fish data out of the .kind-specific .fields.
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub struct StderrStartActivity {
128 /// Activity ID. The same act_id is passed in [`Stderr::StopActivity`] and [`Stderr::Result`].
129 pub act_id: u64,
130 /// Log level of this activity.
131 pub level: Verbosity,
132 /// Type of the activity.
133 pub kind: StderrActivityType,
134 /// Log message.
135 pub s: String,
136 /// Additional fields. The meaning of these depend on the value of .kind.
137 /// TODO: This will be replaced with something more user-friendly in the future.
138 pub fields: Vec<StderrField>,
139 /// Parent activity, or 0 if this is the top-level one
140 pub parent_id: u64,
141}
142
143/// Type of a [`StderrResult`].
144#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
145#[repr(u64)]
146pub enum StderrResultType {
147 FileLinked = 100,
148 BuildLogLine = 101,
149 UntrustedPath = 102,
150 CorruptedPath = 103,
151 SetPhase = 104,
152 Progress = 105,
153 SetExpected = 106,
154 PostBuildLogLine = 107,
155}
156impl From<TryFromPrimitiveError<StderrResultType>> for Error {
157 fn from(value: TryFromPrimitiveError<StderrResultType>) -> Self {
158 Self::Invalid(format!("StderrResultType({:x})", value.number))
159 }
160}
161
162/// Notification that a result of some kind (see [`StderrResultType`]) has been produced.
163///
164/// TODO: Users should not have to fish data out of the .kind-specific .fields.
165#[derive(Debug, Clone, PartialEq, Eq)]
166pub struct StderrResult {
167 /// Activity ID. The same act_id is passed in [`Stderr::StartActivity`] and [`Stderr::StopActivity`].
168 pub act_id: u64,
169 /// Type of the activity.
170 pub kind: StderrResultType,
171 /// Additional fields. The meaning of these depend on the value of .kind.
172 /// TODO: This will be replaced with something more user-friendly in the future.
173 pub fields: Vec<StderrField>,
174}
175
176/// A raw field used in [`StderrStartActivity`] and [`StderrResult`].
177#[derive(Debug, Clone, PartialEq, Eq)]
178pub enum StderrField {
179 Int(u64),
180 String(String),
181}
182
183impl StderrField {
184 /// If this is a Self::Int, return the value, else None.
185 pub fn as_int(&self) -> Option<&u64> {
186 if let Self::Int(v) = self {
187 Some(v)
188 } else {
189 None
190 }
191 }
192 pub fn as_activity_type(&self) -> Option<StderrActivityType> {
193 self.as_int().and_then(|v| (*v).try_into().ok())
194 }
195
196 /// If this is a Self::String, return the value, else None.
197 pub fn as_string(&self) -> Option<&String> {
198 if let Self::String(v) = self {
199 Some(v)
200 } else {
201 None
202 }
203 }
204}
205
206/// Verbosity of a [`Stderr`].
207#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
208#[repr(u64)]
209pub enum Verbosity {
210 Error = 0,
211 Warn,
212 Notice,
213 Info,
214 Talkative,
215 Chatty,
216 Debug,
217 Vomit,
218}
219impl From<TryFromPrimitiveError<Verbosity>> for Error {
220 fn from(value: TryFromPrimitiveError<Verbosity>) -> Self {
221 Self::Invalid(format!("Verbosity({:x})", value.number))
222 }
223}
224
225/// Passed to [`Store::build_paths()`] and [`Store::build_paths_with_results()`].
226#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
227#[repr(u64)]
228pub enum BuildMode {
229 Normal,
230 Repair,
231 Check,
232}
233impl From<TryFromPrimitiveError<BuildMode>> for Error {
234 fn from(value: TryFromPrimitiveError<BuildMode>) -> Self {
235 Self::Invalid(format!("BuildMode({:x})", value.number))
236 }
237}
238
239/// Status code for a [`BuildResult`], returned from [`Store::build_paths_with_results()`].
240#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
241#[repr(u64)]
242pub enum BuildResultStatus {
243 Built = 0,
244 Substituted = 1,
245 AlreadyValid = 2,
246 PermanentFailure = 3,
247 InputRejected = 4,
248 OutputRejected = 5,
249 /// "possibly transient", the CppNix source helpfully points out.
250 TransientFailure = 6,
251 /// No longer used, according to a comment in CppNix 2.19.3.
252 /// TODO: Figure out since when.
253 CachedFailure = 7,
254 TimedOut = 8,
255 MiscFailure = 9,
256 DependencyFailed = 10,
257 LogLimitExceeded = 11,
258 NotDeterministic = 12,
259 ResolvesToAlreadyValid = 13,
260 NoSubstituters = 14,
261}
262impl From<TryFromPrimitiveError<BuildResultStatus>> for Error {
263 fn from(value: TryFromPrimitiveError<BuildResultStatus>) -> Self {
264 Self::Invalid(format!("BuildResultStatus({:x})", value.number))
265 }
266}
267
268/// Returned from [`Store::build_paths_with_results()`].
269#[derive(Debug, Clone, PartialEq, Eq)]
270pub struct BuildResult {
271 /// Status code, see BuildResultStatus.
272 pub status: BuildResultStatus,
273 /// Verbatim error message, or "" if none.
274 pub error_msg: String,
275 /// How many times this derivation was built. Only present on Proto 1.29+.
276 pub times_built: u64, // FIXME: Make Option<>.
277 pub is_non_deterministic: bool, // FIXME: Make Option<>.
278 pub start_time: DateTime<Utc>, // FIXME: Make Option<>.
279 pub stop_time: DateTime<Utc>, // FIXME: Make Option<>.
280 /// Map of (name, path). Only present on Proto 1.28+.
281 pub built_outputs: HashMap<String, String>,
282}
283
284/// Passed to [`Store::set_options()`].
285#[derive(Debug, Clone, PartialEq, Eq)]
286pub struct ClientSettings {
287 /// Whether to keep temporary directories of failed builds.
288 ///
289 /// Default: `false`
290 pub keep_failed: bool,
291
292 /// Whether to keep building derivations when another build fails.
293 ///
294 /// Default: `false`
295 pub keep_going: bool,
296
297 /// Whether to fall back to building from source if a binary substitution fails.
298 ///
299 /// Default: `false`
300 pub try_fallback: bool,
301
302 /// Verbosity.
303 ///
304 /// Default: [`Verbosity::Error`].
305 pub verbosity: Verbosity,
306
307 /// Number of derivations Nix will attempt to build in parallel.
308 ///
309 /// 0 = No local builds will be performed (except `preferLocalBuild` derivations),
310 /// only remote builds and substitutions.
311 ///
312 /// This is different from [`ClientSettings::build_cores`], which affects how many
313 /// cores are used by a single build. In `nix.conf`, this can also have the magic
314 /// value "auto", which uses the number of threads available on the machine, but we
315 /// can only set it to whole numbers using [`Store::set_options`].
316 ///
317 /// Default: `1`
318 pub max_build_jobs: u64,
319
320 /// Number of seconds a build is allowed to produce no stdout or stderr output.
321 ///
322 /// Default: `0`
323 pub max_silent_time: u64,
324
325 /// Whether to show build log output in real time.
326 pub verbose_build: bool,
327 /// How many cores will be used for an individual build. Sets the `NIX_BUILD_CORES`
328 /// environment variable for builders, which is passed to eg. `make -j`.
329 ///
330 /// 0 = Use all available cores on the builder machine.
331 ///
332 /// This is different from [`ClientSettings::max_build_jobs`], which controls ho
333 /// many derivations Nix will attempt to build in parallel.
334 ///
335 /// NOTE: On the daemon, this defaults to the number of threads that were available
336 /// when it started, which we of course have no way of knowing, since we don't even
337 /// know if we're running on the same machine. Thus, we default to 0 instead, which
338 /// in most cases will do the same thing.
339 ///
340 /// If you know you're on the daemon machine, you can get the available threads with:
341 ///
342 /// ```
343 /// use nix_daemon::ClientSettings;
344 ///
345 /// ClientSettings {
346 /// build_cores: std::thread::available_parallelism().unwrap().get() as u64,
347 /// ..Default::default()
348 /// };
349 /// ```
350 ///
351 /// Default: `0`
352 pub build_cores: u64,
353
354 /// Whether to use binary substitutes if available.
355 ///
356 /// Default: `true`
357 pub use_substitutes: bool,
358
359 /// Undocumented. Some additional settings can be set using this field.
360 ///
361 /// Default: [`HashMap::default()`].
362 pub overrides: HashMap<String, String>,
363}
364
365impl Default for ClientSettings {
366 fn default() -> Self {
367 // Defaults taken from CppNix: src/libstore/globals.hh
368 Self {
369 keep_failed: false,
370 keep_going: false,
371 try_fallback: false,
372 verbosity: Verbosity::Error,
373 max_build_jobs: 1,
374 max_silent_time: 0,
375 verbose_build: true,
376 build_cores: 0,
377 use_substitutes: true,
378 overrides: HashMap::default(),
379 }
380 }
381}
382
383/// PathInfo, like `nix path-info` would return.
384#[derive(Debug, Clone, PartialEq, Eq, Default)]
385pub struct PathInfo {
386 /// The first derivation that produced this path.
387 ///
388 /// Note that if a second derivation produces the same output, eg. because an input
389 /// changed, but not in a way which affects the contents of the output, this will
390 /// still point to the first derivation, which may not be in your store (anymore).
391 ///
392 /// If you want to know which derivations actually in your store can produce a path,
393 /// use [`Store::query_valid_derivers()`] instead.
394 pub deriver: Option<String>,
395
396 /// Other paths referenced by this path.
397 pub references: Vec<String>,
398
399 /// NAR hash, in the form: "(algo)-(hash)".
400 pub nar_hash: String,
401 /// NAR size.
402 pub nar_size: u64,
403
404 /// Is this path "ultimately trusted", eg. built locally?
405 pub ultimate: bool,
406 /// Optional signatures, eg. from a binary cache.
407 pub signatures: Vec<String>,
408 /// An assertion that this path is content-addressed, eg. for fixed-output derivations.
409 pub ca: Option<String>,
410
411 /// When the path was registered, eg. placed into the local store.
412 pub registration_time: DateTime<Utc>,
413}
414
415/// An in-progress operation, which may produces a series of [`Stderr`]s before returning.
416///
417/// All functions on the Store trait return these wrappers. If you just want the final result, not
418/// play-by-play updates on eg. log output or paths built:
419///
420/// ```no_run
421/// use nix_daemon::{Store, Progress, nix::DaemonStore};
422/// # async {
423/// # let mut store = DaemonStore::builder()
424/// # .connect_unix("/nix/var/nix/daemon-socket/socket")
425/// # .await?;
426/// #
427/// let is_valid_path = store.is_valid_path("/nix/store/...").result().await?;
428/// # Ok::<_, nix_daemon::Error>(())
429/// # };
430/// ```
431///
432/// Otherwise, if you are interested in (some of) the progress updates:
433///
434/// ```no_run
435/// use nix_daemon::{Store, Progress, nix::DaemonStore};
436/// # async {
437/// # let mut store = DaemonStore::builder()
438/// # .connect_unix("/nix/var/nix/daemon-socket/socket")
439/// # .await?;
440/// #
441/// let mut prog = store.is_valid_path("/nix/store/...");
442/// while let Some(stderr) = prog.next().await? {
443/// match stderr {
444/// _ => todo!(),
445/// }
446/// }
447/// let is_valid_path = prog.result().await?;
448/// # Ok::<_, nix_daemon::Error>(())
449/// # };
450/// ```
451pub trait Progress: Send {
452 type T: Send;
453 type Error: From<Error> + Send + Sync;
454
455 /// Returns the next Stderr message, or None after all have been consumed.
456 /// This must behave like a fused iterator - once None is returned, all further calls
457 /// must immediately return None, without corrupting the underlying datastream, etc.
458 fn next(&mut self) -> impl Future<Output = Result<Option<Stderr>, Self::Error>> + Send;
459
460 /// Discards any further messages from [`Self::next()`] and proceeds.
461 fn result(self) -> impl Future<Output = Result<Self::T, Self::Error>> + Send;
462}
463
464/// Helper methods for [`Progress`].
465pub trait ProgressExt: Progress {
466 /// Calls `f()` for each message returned from [`Progress::next()`], then [`Progress::result()`].
467 fn inspect_each<F: Fn(Stderr) + Send>(
468 self,
469 f: F,
470 ) -> impl Future<Output = Result<Self::T, Self::Error>> + Send;
471
472 /// Returns a tuple of (stderr, [`Progress::result()`]), where stderr is a `Vec<Stderr>` of all
473 /// Stderr returned by [`Progress::next()`].
474 fn split(self) -> impl Future<Output = (Vec<Stderr>, Result<Self::T, Self::Error>)> + Send;
475}
476impl<P: Progress> ProgressExt for P {
477 async fn inspect_each<F: Fn(Stderr)>(mut self, f: F) -> Result<Self::T, Self::Error> {
478 while let Some(stderr) = self.next().await? {
479 f(stderr)
480 }
481 self.result().await
482 }
483
484 async fn split(mut self) -> (Vec<Stderr>, Result<Self::T, Self::Error>) {
485 let mut stderrs = Vec::new();
486 loop {
487 match self.next().await {
488 Ok(Some(stderr)) => stderrs.push(stderr),
489 Err(err) => break (stderrs, Err(err)),
490 Ok(None) => break (stderrs, self.result().await),
491 }
492 }
493 }
494}
495
496/// Generic interface to a Nix store.
497///
498/// See [`nix::DaemonStore`] for an implementation that talks to a CppNix (compatible) daemon.
499pub trait Store {
500 type Error: From<Error> + Send + Sync;
501
502 /// Returns whether a store path is valid.
503 fn is_valid_path<P: AsRef<str> + Send + Sync + Debug>(
504 &mut self,
505 path: P,
506 ) -> impl Progress<T = bool, Error = Self::Error>;
507
508 /// Returns whether `Self::query_substitutable_paths()` would return anything.
509 fn has_substitutes<P: AsRef<str> + Send + Sync + Debug>(
510 &mut self,
511 path: P,
512 ) -> impl Progress<T = bool, Error = Self::Error>;
513
514 /// Adds a file to the store.
515 fn add_to_store<
516 SN: AsRef<str> + Send + Sync + Debug,
517 SC: AsRef<str> + Send + Sync + Debug,
518 Refs,
519 R,
520 >(
521 &mut self,
522 name: SN,
523 cam_str: SC,
524 refs: Refs,
525 repair: bool,
526 source: R,
527 ) -> impl Progress<T = (String, PathInfo), Error = Self::Error>
528 where
529 Refs: IntoIterator + Send + Debug,
530 Refs::IntoIter: ExactSizeIterator + Send,
531 Refs::Item: AsRef<str> + Send + Sync,
532 R: AsyncReadExt + Unpin + Send + Debug;
533
534 /// Builds the specified paths.
535 fn build_paths<Paths>(
536 &mut self,
537 paths: Paths,
538 mode: BuildMode,
539 ) -> impl Progress<T = (), Error = Self::Error>
540 where
541 Paths: IntoIterator + Send + Debug,
542 Paths::IntoIter: ExactSizeIterator + Send,
543 Paths::Item: AsRef<str> + Send + Sync;
544
545 /// Ensure the specified store path exists.
546 fn ensure_path<Path: AsRef<str> + Send + Sync + Debug>(
547 &mut self,
548 path: Path,
549 ) -> impl Progress<T = (), Error = Self::Error>;
550
551 /// Creates a temporary GC root, which persists until the client disconnects.
552 fn add_temp_root<Path: AsRef<str> + Send + Sync + Debug>(
553 &mut self,
554 path: Path,
555 ) -> impl Progress<T = (), Error = Self::Error>;
556
557 /// Creates a persistent GC root. This is what's normally meant by a GC root.
558 fn add_indirect_root<Path: AsRef<str> + Send + Sync + Debug>(
559 &mut self,
560 path: Path,
561 ) -> impl Progress<T = (), Error = Self::Error>;
562
563 /// Returns the `(link, target)` of all known GC roots.
564 fn find_roots(&mut self) -> impl Progress<T = HashMap<String, String>, Error = Self::Error>;
565
566 /// Applies client options. This changes the behaviour of future commands.
567 fn set_options(&mut self, opts: ClientSettings) -> impl Progress<T = (), Error = Self::Error>;
568
569 /// Returns a [`PathInfo`] struct for the given path.
570 fn query_pathinfo<S: AsRef<str> + Send + Sync + Debug>(
571 &mut self,
572 path: S,
573 ) -> impl Progress<T = Option<PathInfo>, Error = Self::Error>;
574
575 /// Returns which of the passed paths are valid.
576 fn query_valid_paths<Paths>(
577 &mut self,
578 paths: Paths,
579 use_substituters: bool,
580 ) -> impl Progress<T = Vec<String>, Error = Self::Error>
581 where
582 Paths: IntoIterator + Send + Debug,
583 Paths::IntoIter: ExactSizeIterator + Send,
584 Paths::Item: AsRef<str> + Send + Sync;
585
586 /// Returns paths which can be substituted.
587 fn query_substitutable_paths<Paths>(
588 &mut self,
589 paths: Paths,
590 ) -> impl Progress<T = Vec<String>, Error = Self::Error>
591 where
592 Paths: IntoIterator + Send + Debug,
593 Paths::IntoIter: ExactSizeIterator + Send,
594 Paths::Item: AsRef<str> + Send + Sync;
595
596 /// Returns a list of valid derivers for a path.
597 /// This is sort of like [`PathInfo::deriver`], but it doesn't lie to you.
598 fn query_valid_derivers<S: AsRef<str> + Send + Sync + Debug>(
599 &mut self,
600 path: S,
601 ) -> impl Progress<T = Vec<String>, Error = Self::Error>;
602
603 /// Takes a list of paths and queries which would be built, substituted or unknown,
604 /// along with an estimate of the cumulative download and NAR sizes.
605 fn query_missing<Ps>(&mut self, paths: Ps) -> impl Progress<T = Missing, Error = Self::Error>
606 where
607 Ps: IntoIterator + Send + Debug,
608 Ps::IntoIter: ExactSizeIterator + Send,
609 Ps::Item: AsRef<str> + Send + Sync;
610
611 /// Returns a map of `(output, store path)` for the given derivation.
612 fn query_derivation_output_map<P: AsRef<str> + Send + Sync + Debug>(
613 &mut self,
614 path: P,
615 ) -> impl Progress<T = HashMap<String, String>, Error = Self::Error>;
616
617 /// Like `Self::build_paths()`, but returns a [`BuildResult`] for each entry in `paths`.
618 fn build_paths_with_results<Ps>(
619 &mut self,
620 paths: Ps,
621 mode: BuildMode,
622 ) -> impl Progress<T = HashMap<String, BuildResult>, Error = Self::Error>
623 where
624 Ps: IntoIterator + Send + Debug,
625 Ps::IntoIter: ExactSizeIterator + Send,
626 Ps::Item: AsRef<str> + Send + Sync;
627}
628
629/// Returned from [`Store::query_missing()`].
630#[derive(Debug, PartialEq, Eq)]
631pub struct Missing {
632 /// Paths that will be built.
633 pub will_build: Vec<String>,
634 /// Paths that will be substituted.
635 pub will_substitute: Vec<String>,
636 /// Paths we don't know what will happen to.
637 pub unknown: Vec<String>,
638 /// Despite the name, the extracted size of all substituted paths.
639 pub download_size: u64,
640 /// Total size of all NARs to download from a substituter.
641 pub nar_size: u64,
642}