Skip to main content

nix_index/
package.rs

1//! Data types for representing meta information about packages and store paths.
2//!
3//! The main data type in this `StorePath`, which represents a single output of
4//! some nix derivation. We also sometimes call a `StorePath` a package, to avoid
5//! confusion with file paths.
6use std::borrow::Cow;
7use std::fmt::Display;
8use std::io::{self, Write};
9use std::str;
10
11use serde::{Deserialize, Serialize};
12
13/// A type for describing how to reach a given store path.
14///
15/// When building an index, we collect store paths from various sources, such
16/// as the output of nix-env -qa and the references of those store paths.
17///
18/// To show the user how we reached a given store path, each store path tracks
19/// its origin. For example, for top-level store paths, we know which attribute
20/// of nixpkgs builds this store path.
21#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
22pub struct PathOrigin {
23    /// The attribute of nixpkgs that lead to this store path being discovered.
24    ///
25    /// If the store path is a top-level path, then the store path corresponds
26    /// to an output of the derivation assigned to this attribute path.
27    pub attr: String,
28
29    /// The output of the derivation specified by `attr` that we want to refer to.
30    ///
31    /// If a derivation does not support multiple outputs, then this should just be "out",
32    /// the default output.
33    pub output: String,
34
35    /// Indicates that this path is listed in the output of nix-env -qaP --out-name.
36    ///
37    /// We may index paths for which we do not know the exact attribute path. In this
38    /// case, `attr` and `output` will be set to the values for the top-level path that
39    /// contains the path in its closure. (This is also how we discovered the path in the
40    /// first place: through being referenced by another, top-level path). It is unspecified
41    /// which top-level path they will refer to though if there exist multiple ones whose closure
42    /// contains this path.
43    pub toplevel: bool,
44
45    /// Target system
46    pub system: Option<String>,
47}
48
49impl PathOrigin {
50    /// Encodes a path origin as a sequence of bytes, such that it can be decoed using `decode`.
51    ///
52    /// The encoding does not use the bytes `0x00` nor `0x01`, as long as neither `attr` nor `output`
53    /// contain them. This is important since it allows the result to be encoded with [frcode](mod.frcode.html).
54    ///
55    /// # Panics
56    ///
57    /// The `attr` and `output` of the path origin must not contain the byte value `0x02`, otherwise
58    /// this function panics.
59    ///
60    /// # Errors
61    ///
62    /// Returns any errors that were encountered while writing to the supplied `Writer`.
63    pub fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
64        assert!(
65            !self.attr.contains('\x02'),
66            "origin attribute path must not contain the byte value 0x02 anywhere"
67        );
68        assert!(
69            !self.output.contains('\x02'),
70            "origin output name must not contain the byte value 0x02 aynwhere"
71        );
72        write!(
73            writer,
74            "{}\x02{}{}",
75            self.attr,
76            self.output,
77            if self.toplevel { "" } else { "\x02" }
78        )?;
79        Ok(())
80    }
81
82    /// Decodes a path that was encoded by `encode` function of this trait.
83    ///
84    /// Returns the decoded path origin, or `None` if `buf` could not be decoded as path origin.
85    pub fn decode(buf: &[u8]) -> Option<PathOrigin> {
86        let mut iter = buf.splitn(2, |c| *c == b'\x02');
87        iter.next()
88            .and_then(|v| String::from_utf8(v.to_vec()).ok())
89            .and_then(|attr| {
90                iter.next()
91                    .and_then(|v| String::from_utf8(v.to_vec()).ok())
92                    .map(|mut output| {
93                        let mut toplevel = true;
94                        if let Some(l) = output.pop() {
95                            if l == '\x02' {
96                                toplevel = false
97                            } else {
98                                output.push(l)
99                            }
100                        }
101                        PathOrigin {
102                            attr,
103                            output,
104                            toplevel,
105                            system: None,
106                        }
107                    })
108            })
109    }
110}
111
112/// Represents a store path which is something that is produced by `nix-build`.
113///
114/// A store path represents an output in the nix store, matching the pattern
115/// `store_dir/hash-name` (most often, `store_dir` will be `/nix/store`).
116///
117/// Using nix, a store path can be produced by calling `nix-build`.
118///
119/// Note that even if a store path is a directory, the files inside that directory
120/// themselves are *not* store paths. For example, while the following is a store path:
121///
122/// ```text
123/// /nix/store/010yd8jls8w4vcnql4zhjbnyp2yay5pl-bash-4.4-p5
124/// ````
125///
126/// while this is not:
127///
128/// ```text
129/// /nix/store/010yd8jls8w4vcnql4zhjbnyp2yay5pl-bash-4.4-p5/bin/
130/// ```
131///
132/// To avoid any confusion with file paths, we sometimes also refer to a store path as a *package*.
133#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
134pub struct StorePath {
135    store_dir: String,
136    hash: String,
137    name: String,
138    origin: PathOrigin,
139}
140
141impl StorePath {
142    /// Parse a store path from an absolute file path.
143    ///
144    /// Since this function does not know where that path comes from, it takes
145    /// `origin` as an argument.
146    ///
147    /// This function returns `None` if the path could not be parsed as a
148    /// store path. You should not rely on that to check whether a path is a store
149    /// path though, since it only does minimal validation (for one example, it does
150    /// not check the length of the hash).
151    pub fn parse(origin: PathOrigin, path: &str) -> Option<StorePath> {
152        let mut parts = path.splitn(2, '-');
153        parts.next().and_then(|prefix| {
154            parts.next().and_then(|name| {
155                let mut iter = prefix.rsplitn(2, '/');
156                iter.next().map(|hash| {
157                    let store_dir = iter.next().unwrap_or("");
158                    StorePath {
159                        store_dir: store_dir.to_string(),
160                        hash: hash.to_string(),
161                        name: name.to_string(),
162                        origin,
163                    }
164                })
165            })
166        })
167    }
168
169    /// Encodes a store path as a sequence of bytes, so that it can be decoded with `decode`.
170    ///
171    /// The encoding does not use the bytes `0x00` nor `0x01`, as long as none of the fields of
172    /// this path contain those bytes (this includes `store_dir`, `hash`, `name` and `origin`).
173    /// This is important since it allows the result to be encoded with [frcode](mod.frcode.html).
174    ///
175    /// # Panics
176    ///
177    /// The `attr` and `output` of the path origin must not contain the byte value `0x02`, otherwise
178    /// this function panics.
179    pub fn encode(&self) -> io::Result<Vec<u8>> {
180        let mut result = Vec::with_capacity(self.as_str().len());
181        result.extend(self.as_str().bytes());
182        result.push(b'\n');
183        self.origin().encode(&mut result)?;
184        Ok(result)
185    }
186
187    pub fn decode(buf: &[u8]) -> Option<StorePath> {
188        let mut parts = buf.splitn(2, |c| *c == b'\n');
189        parts
190            .next()
191            .and_then(|v| str::from_utf8(v).ok())
192            .and_then(|path| {
193                parts
194                    .next()
195                    .and_then(PathOrigin::decode)
196                    .and_then(|origin| StorePath::parse(origin, path))
197            })
198    }
199
200    /// Returns the name of the store path, which is the part of the file name that
201    /// is not the hash.  In the above example, it would be `bash-4.4-p5`.
202    ///
203    /// # Example
204    ///
205    /// ```
206    /// use nix_index::package::{PathOrigin, StorePath};
207    ///
208    /// let origin = PathOrigin { attr: "dummy".to_string(), output: "out".to_string(), toplevel: true, system: None };
209    /// let store_path = StorePath::parse(origin, "/nix/store/010yd8jls8w4vcnql4zhjbnyp2yay5pl-bash-4.4-p5").unwrap();
210    /// assert_eq!(&store_path.name(), "bash-4.4-p5");
211    /// ```
212    pub fn name(&self) -> Cow<'_, str> {
213        Cow::Borrowed(&self.name)
214    }
215
216    /// The hash of the store path. This is the part just before the name of
217    /// the path.
218    ///
219    /// # Example
220    ///
221    /// ```
222    /// use nix_index::package::{PathOrigin, StorePath};
223    ///
224    /// let origin = PathOrigin { attr: "dummy".to_string(), output: "out".to_string(), toplevel: true, system: None };
225    /// let store_path = StorePath::parse(origin, "/nix/store/010yd8jls8w4vcnql4zhjbnyp2yay5pl-bash-4.4-p5").unwrap();
226    /// assert_eq!(&store_path.name(), "bash-4.4-p5");
227    /// ```
228    pub fn hash(&self) -> Cow<'_, str> {
229        Cow::Borrowed(&self.hash)
230    }
231
232    /// The store dir for which this store path was built.
233    ///
234    /// Currently, this will be `/nix/store` in almost all cases, but
235    /// we include it here anyway for completeness.
236    ///
237    /// # Example
238    ///
239    /// ```
240    /// use nix_index::package::{PathOrigin, StorePath};
241    ///
242    /// let origin = PathOrigin { attr: "dummy".to_string(), output: "out".to_string(), toplevel: true, system: None };
243    /// let store_path = StorePath::parse(origin, "/nix/store/010yd8jls8w4vcnql4zhjbnyp2yay5pl-bash-4.4-p5").unwrap();
244    /// assert_eq!(&store_path.store_dir(), "/nix/store");
245    /// ```
246    pub fn store_dir(&self) -> Cow<'_, str> {
247        Cow::Borrowed(&self.store_dir)
248    }
249
250    /// Converts the store path back into an absolute path.
251    ///
252    /// # Example
253    ///
254    /// ```
255    /// use nix_index::package::{PathOrigin, StorePath};
256    ///
257    /// let origin = PathOrigin { attr: "dummy".to_string(), output: "out".to_string(), toplevel: true, system: None };
258    /// let store_path = StorePath::parse(origin, "/nix/store/010yd8jls8w4vcnql4zhjbnyp2yay5pl-bash-4.4-p5").unwrap();
259    /// assert_eq!(&store_path.as_str(), "/nix/store/010yd8jls8w4vcnql4zhjbnyp2yay5pl-bash-4.4-p5");
260    /// ```
261    pub fn as_str(&self) -> Cow<'_, str> {
262        Cow::Owned(format!("{}/{}-{}", self.store_dir, self.hash, self.name))
263    }
264
265    /// Returns the origin that describes how we discovered this store path.
266    ///
267    /// See the documentation of `PathOrigin` for more information about this field.
268    ///
269    /// # Example
270    ///
271    /// ```
272    /// use nix_index::package::{PathOrigin, StorePath};
273    ///
274    /// let origin = PathOrigin { attr: "dummy".to_string(), output: "out".to_string(), toplevel: true, system: None };
275    /// let store_path = StorePath::parse(origin.clone(), "/nix/store/010yd8jls8w4vcnql4zhjbnyp2yay5pl-bash-4.4-p5").unwrap();
276    /// assert_eq!(store_path.origin().as_ref(), &origin);
277    /// ```
278    pub fn origin(&self) -> Cow<'_, PathOrigin> {
279        Cow::Borrowed(&self.origin)
280    }
281}
282
283impl Display for StorePath {
284    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
285        f.write_str(&self.as_str())
286    }
287}