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
//! The real [`Prefetch`]: `nix flake prefetch --json <url>`.
//!
//! The only process `blue lock` starts, and the reason it lives behind
//! `blue_lang_pkg::lock::Prefetch`: every test of the lock supplies canned
//! output instead, so none of them reaches the network. This file therefore
//! has no test of its own that runs nix; its output is read by
//! `blue_lang_pkg::lock::pin_of`, which is tested against the exact bytes this
//! command printed on nix 2.31.5.
//!
//! Direction, stated because it is the point: **blue calls nix, nix never calls
//! blue** — so pinning a source costs no import-from-derivation.
use std::process::{Command, ExitStatus};
use blue_lang_pkg::lock::Prefetch;
/// Pins through the `nix` on `PATH`.
pub struct NixPrefetch;
/// Why the `nix` process gave no usable answer.
#[derive(Debug, thiserror::Error)]
pub enum NixPrefetchError {
#[error("could not start `nix`: {0}")]
Spawn(#[source] std::io::Error),
#[error("`nix flake prefetch --json` exited with {status}: {stderr}")]
Exit { status: ExitStatus, stderr: String },
#[error("`nix flake prefetch --json` printed output that is not UTF-8")]
NotUtf8(#[source] std::string::FromUtf8Error),
}
impl Prefetch for NixPrefetch {
fn prefetch(&self, url: &str) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
// The features named explicitly, so a nix without them enabled in its
// own config still answers rather than refusing the subcommand.
let out = Command::new("nix")
.args([
"--extra-experimental-features",
"nix-command flakes",
"flake",
"prefetch",
"--json",
url,
])
.output()
.map_err(NixPrefetchError::Spawn)?;
if !out.status.success() {
return Err(NixPrefetchError::Exit {
status: out.status,
stderr: String::from_utf8_lossy(&out.stderr).trim().to_string(),
}
.into());
}
Ok(String::from_utf8(out.stdout).map_err(NixPrefetchError::NotUtf8)?)
}
}