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}