Make flake resolution (slightly) less terrible

Instead of using `path:` which always copies the entire directory,
we now try to resolve the Flake URI using `nix flake metadata` which
may give us a `git+file:`.
This commit is contained in:
Zhaofeng Li 2021-10-25 23:38:10 -07:00
parent b48753239a
commit 0e0a1e84f0
10 changed files with 137 additions and 48 deletions

View file

@ -115,7 +115,7 @@ pub fn subcommand() -> App<'static, 'static> {
}
pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) {
let hive = util::hive_from_args(local_args).unwrap();
let hive = util::hive_from_args(local_args).await.unwrap();
log::info!("Enumerating nodes...");
let all_nodes = hive.deployment_info().await.unwrap();

View file

@ -89,7 +89,7 @@ pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) {
}
}
let hive = util::hive_from_args(local_args).unwrap();
let hive = util::hive_from_args(local_args).await.unwrap();
let hostname = if local_args.is_present("node") {
local_args.value_of("node").unwrap().to_owned()
} else {

View file

@ -55,7 +55,7 @@ It's recommended to use -- to separate Colmena options from the command to run.
}
pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) {
let hive = util::hive_from_args(local_args).unwrap();
let hive = util::hive_from_args(local_args).await.unwrap();
log::info!("Enumerating nodes...");
let all_nodes = hive.deployment_info().await.unwrap();

View file

@ -28,7 +28,7 @@ For example, to retrieve the configuration of one node, you may write something
}
pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) {
let hive = util::hive_from_args(local_args).unwrap();
let hive = util::hive_from_args(local_args).await.unwrap();
if !(local_args.is_present("expression") ^ local_args.is_present("expression_file")) {
log::error!("Either an expression (-E) or a .nix file containing an expression should be specified, not both.");

92
src/nix/flake.rs Normal file
View file

@ -0,0 +1,92 @@
//! Nix Flake utilities.
use std::convert::AsRef;
use std::path::{Path, PathBuf};
use std::process::Stdio;
use serde::Deserialize;
use tokio::process::Command;
use super::{NixCheck, NixError, NixResult};
/// A Nix Flake.
#[derive(Debug)]
pub struct Flake {
/// The Flake URI.
uri: String,
/// The directory the flake lives in, if it's a local flake.
local_dir: Option<PathBuf>,
}
impl Flake {
/// Creates a flake from the given directory.
///
/// This will try to retrieve the resolved URL of the local flake
/// in the specified directory.
pub async fn from_dir<P: AsRef<Path>>(dir: P) -> NixResult<Self> {
NixCheck::require_flake_support().await?;
let flake = dir.as_ref().as_os_str().to_str()
.expect("Flake directory path contains non-UTF-8 characters");
let info = FlakeMetadata::resolve(flake).await?;
Ok(Self {
uri: info.resolved_url,
local_dir: Some(dir.as_ref().to_owned()),
})
}
/// Creates a flake from a Flake URI.
pub async fn from_uri(uri: String) -> NixResult<Self> {
NixCheck::require_flake_support().await?;
Ok(Self {
uri,
local_dir: None,
})
}
/// Returns the URI.
pub fn uri(&self) -> &str {
&self.uri
}
/// Returns the local directory, if it exists.
pub fn local_dir(&self) -> Option<&Path> {
self.local_dir.as_ref().map(|d| d.as_path())
}
}
/// A `nix flake metadata --json` invocation.
#[derive(Deserialize, Debug)]
struct FlakeMetadata {
/// The resolved URL of the flake.
#[serde(rename = "resolvedUrl")]
resolved_url: String,
}
impl FlakeMetadata {
/// Resolves a flake.
async fn resolve(flake: &str) -> NixResult<Self> {
let child = Command::new("nix")
.args(&["flake", "metadata", "--json"])
.args(&["--experimental-features", "nix-command flakes"])
.arg(flake)
.stdout(Stdio::piped())
.spawn()?;
let output = child.wait_with_output().await?;
if !output.status.success() {
return Err(output.status.into());
}
serde_json::from_slice::<FlakeMetadata>(&output.stdout)
.map_err(|_| {
let output = String::from_utf8_lossy(&output.stdout).to_string();
NixError::BadOutput { output }
})
}
}

View file

@ -10,13 +10,13 @@ use serde::Serialize;
use validator::Validate;
use super::{
Flake,
StoreDerivation,
NixResult,
NixError,
NodeConfig,
ProfileMap,
};
use super::{NixCommand, NixCheck};
use super::NixCommand;
use crate::util::CommandExecution;
use crate::progress::TaskProgress;
@ -24,49 +24,38 @@ const HIVE_EVAL: &'static [u8] = include_bytes!("eval.nix");
#[derive(Debug)]
pub enum HivePath {
/// A Nix Flake URI.
/// A Nix Flake.
///
/// The flake must contain the `colmena` output.
Flake(String),
Flake(Flake),
/// A regular .nix file
Legacy(PathBuf),
}
impl HivePath {
pub fn from_path<P: AsRef<Path>>(path: P) -> Self {
pub async fn from_path<P: AsRef<Path>>(path: P) -> NixResult<Self> {
let path = path.as_ref();
if let Some(osstr) = path.file_name() {
if osstr == "flake.nix" {
let parent = path.parent().unwrap().canonicalize().unwrap();
let parent = parent.to_str().unwrap();
let uri = format!("path:{}", parent);
return Self::Flake(uri);
let parent = path.parent().unwrap();
let flake = Flake::from_dir(parent).await?;
return Ok(Self::Flake(flake));
}
}
Self::Legacy(path.to_owned())
Ok(Self::Legacy(path.to_owned()))
}
fn context_dir(&self) -> Option<PathBuf> {
match self {
Self::Legacy(p) => {
if let Some(parent) = p.parent() {
return Some(parent.to_owned());
}
None
p.parent().map(|d| d.to_owned())
}
Self::Flake(flake) => {
flake.local_dir().map(|d| d.to_owned())
}
_ => None,
}
}
fn is_flake(&self) -> bool {
if let Self::Flake(_) = self {
true
} else {
false
}
}
}
@ -128,13 +117,6 @@ impl Hive {
/// Retrieve deployment info for all nodes.
pub async fn deployment_info(&self) -> NixResult<HashMap<String, NodeConfig>> {
let nix_check = NixCheck::detect().await;
if self.path.is_flake() && !nix_check.flakes_supported() {
nix_check.print_flakes_info(true);
return Err(NixError::NoFlakesSupport);
}
// FIXME: Really ugly :(
let s: String = self.nix_instantiate("hive.deploymentConfigJson").eval_with_builders().await?
.capture_json().await?;
@ -281,7 +263,7 @@ impl<'hive> NixInstantiate<'hive> {
self.expression,
));
}
HivePath::Flake(uri) => {
HivePath::Flake(flake) => {
command
.args(&["--experimental-features", "flakes"])
.arg("--no-gc-warning")
@ -289,7 +271,7 @@ impl<'hive> NixInstantiate<'hive> {
.arg(format!(
"with builtins; let eval = import {}; hive = eval {{ flakeUri = \"{}\"; }}; in {}",
self.hive.eval_nix.to_str().unwrap(),
&uri,
flake.uri(),
self.expression,
));
}

View file

@ -3,9 +3,10 @@ use std::process::Stdio;
use log::Level;
use regex::Regex;
use tokio::process::Command;
use super::{NixError, NixResult};
struct NixVersion {
major: usize,
minor: usize,
@ -88,6 +89,17 @@ impl NixCheck {
}
}
pub async fn require_flake_support() -> NixResult<()> {
let check = Self::detect().await;
if !check.flakes_supported() {
check.print_flakes_info(true);
Err(NixError::NoFlakesSupport)
} else {
Ok(())
}
}
pub fn print_version_info(&self) {
if let Some(v) = &self.version {
log::info!("Nix Version: {}", v);

View file

@ -36,6 +36,9 @@ pub use deployment::{Goal, Target, Deployment};
pub mod info;
pub use info::NixCheck;
pub mod flake;
pub use flake::Flake;
#[cfg(test)]
mod tests;

View file

@ -34,7 +34,7 @@ impl TempHive {
let mut temp_file = NamedTempFile::new().unwrap();
temp_file.write_all(text.as_bytes()).unwrap();
let hive_path = HivePath::from_path(temp_file.path());
let hive_path = block_on(HivePath::from_path(temp_file.path())).unwrap();
let hive = Hive::new(hive_path).unwrap();
Self {
@ -149,11 +149,10 @@ fn test_parse_simple() {
#[test]
fn test_parse_flake() {
let flake_path = {
let p = PathBuf::from("./src/nix/tests/simple-flake");
p.canonicalize().unwrap()
};
let hive_path = HivePath::Flake(format!("path:{}", flake_path.to_str().unwrap()));
let flake_dir = PathBuf::from("./src/nix/tests/simple-flake");
let flake = block_on(Flake::from_dir(flake_dir)).unwrap();
let hive_path = HivePath::Flake(flake);
let mut hive = Hive::new(hive_path).unwrap();
hive.set_show_trace(true);

View file

@ -9,7 +9,7 @@ use glob::Pattern as GlobPattern;
use tokio::io::{AsyncRead, AsyncBufReadExt, BufReader};
use tokio::process::Command;
use super::nix::{NodeConfig, Hive, HivePath, NixResult};
use super::nix::{Flake, NodeConfig, Hive, HivePath, NixResult};
use super::progress::TaskProgress;
enum NodeFilter {
@ -79,7 +79,7 @@ impl CommandExecution {
}
}
pub fn hive_from_args(args: &ArgMatches<'_>) -> NixResult<Hive> {
pub async fn hive_from_args(args: &ArgMatches<'_>) -> NixResult<Hive> {
let path = match args.occurrences_of("config") {
0 => {
// traverse upwards until we find hive.nix
@ -142,7 +142,8 @@ pub fn hive_from_args(args: &ArgMatches<'_>) -> NixResult<Hive> {
if !fpath.exists() && path.contains(":") {
// Treat as flake URI
let hive_path = HivePath::Flake(path);
let flake = Flake::from_uri(path).await?;
let hive_path = HivePath::Flake(flake);
let mut hive = Hive::new(hive_path)?;
if args.is_present("show-trace") {
@ -156,7 +157,7 @@ pub fn hive_from_args(args: &ArgMatches<'_>) -> NixResult<Hive> {
}
};
let hive_path = HivePath::from_path(path);
let hive_path = HivePath::from_path(path).await?;
let mut hive = Hive::new(hive_path)?;
if args.is_present("show-trace") {