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}