From 610a725ba2abe5c95334be2470b8804f01b78d1b Mon Sep 17 00:00:00 2001 From: Zhaofeng Li Date: Wed, 17 Mar 2021 14:59:05 -0700 Subject: [PATCH] Add --keep-result to create GC roots for profiles This resembles the behavior of morph. Reference: #18 --- src/command/apply.rs | 15 +++++++++++++++ src/nix/deployment.rs | 25 +++++++++++++++++++++---- src/nix/profile.rs | 30 +++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/command/apply.rs b/src/command/apply.rs index b7d2972..5438326 100644 --- a/src/command/apply.rs +++ b/src/command/apply.rs @@ -57,6 +57,15 @@ Set to 0 to disable parallemism limit. Err(_) => Err(String::from("The value must be a valid number")), } })) + .arg(Arg::with_name("keep-result") + .long("keep-result") + .help("Create GC roots for built profiles") + .long_help(r#"Create GC roots for built profiles. + +The built system profiles will be added as GC roots so that they will not be removed by the garbage collector. +The links will be created under .gcroots in the directory the Hive configuration is located. +"#) + .takes_value(false)) .arg(Arg::with_name("verbose") .short("v") .long("verbose") @@ -101,6 +110,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_base = hive.as_path().parent().unwrap().to_owned(); log::info!("Enumerating nodes..."); let all_nodes = hive.deployment_info().await.unwrap(); @@ -176,6 +186,11 @@ pub async fn run(_global_args: &ArgMatches<'_>, local_args: &ArgMatches<'_>) { options.set_gzip(!local_args.is_present("no-gzip")); options.set_progress_bar(!local_args.is_present("verbose")); options.set_upload_keys(!local_args.is_present("no-keys")); + + if local_args.is_present("keep-result") { + options.set_gc_roots(hive_base.join(".gcroots")); + } + deployment.set_options(options); if local_args.is_present("no-keys") && goal_arg == "keys" { diff --git a/src/nix/deployment.rs b/src/nix/deployment.rs index 16bf960..879cdc9 100644 --- a/src/nix/deployment.rs +++ b/src/nix/deployment.rs @@ -1,6 +1,8 @@ use std::cmp::max; -use std::sync::Arc; use std::collections::HashMap; +use std::convert::AsRef; +use std::path::{Path, PathBuf}; +use std::sync::Arc; use futures::future::join_all; use tokio::sync::{Mutex, Semaphore}; @@ -367,8 +369,7 @@ impl Deployment { ); let goal = arc_self.goal; - let arc_self = arc_self.clone(); - let profiles = arc_self.build_profiles(&chunk, drv, bar.clone()).await; + let profiles = arc_self.clone().build_profiles(&chunk, drv, bar.clone()).await; let profiles = match profiles { Some(profiles) => profiles, @@ -385,6 +386,14 @@ impl Deployment { } } + if let Some(base) = &arc_self.options.gc_roots { + // Create GC roots + if let Err(e) = profiles.create_gc_roots(base).await { + let bar = progress.create_task_progress(BATCH_OPERATION_LABEL.to_string()); + bar.failure(&format!("Failed to create GC roots: {:?}", e)); + } + } + drop(permit); profiles }; @@ -582,7 +591,7 @@ impl ParallelismLimit { } } -#[derive(Copy, Clone, Debug)] +#[derive(Clone, Debug)] pub struct DeploymentOptions { /// Whether to show condensed progress bars. /// @@ -597,6 +606,9 @@ pub struct DeploymentOptions { /// Whether to upload keys when deploying. upload_keys: bool, + + /// Directory to create GC roots for node profiles in. + gc_roots: Option, } impl Default for DeploymentOptions { @@ -606,6 +618,7 @@ impl Default for DeploymentOptions { substituters_push: true, gzip: true, upload_keys: true, + gc_roots: None, } } } @@ -627,6 +640,10 @@ impl DeploymentOptions { self.upload_keys = enable; } + pub fn set_gc_roots>(&mut self, path: P) { + self.gc_roots = Some(path.as_ref().to_owned()); + } + fn to_copy_options(&self) -> CopyOptions { let options = CopyOptions::default(); diff --git a/src/nix/profile.rs b/src/nix/profile.rs index 8cc3208..823acac 100644 --- a/src/nix/profile.rs +++ b/src/nix/profile.rs @@ -1,8 +1,11 @@ use std::collections::HashMap; use std::convert::TryFrom; -use std::ops::{Deref, DerefMut}; use std::fs; +use std::ops::{Deref, DerefMut}; use std::path::Path; +use std::process::Stdio; + +use tokio::process::Command; use super::{ Goal, @@ -110,3 +113,28 @@ impl TryFrom> for ProfileMap { } } } + +impl ProfileMap { + /// Create GC roots for all profiles in the map. + /// + /// The created links will be located at `{base}/node-{node_name}`. + pub async fn create_gc_roots(&self, base: &Path) -> NixResult<()> { + // This will actually try to build all profiles, but since they + // already exist only the GC roots will be created. + for (node, profile) in self.0.iter() { + let path = base.join(format!("node-{}", node)); + + let mut command = Command::new("nix-store"); + command.args(&["--no-build-output", "--indirect", "--add-root", path.to_str().unwrap()]); + command.args(&["--realise", profile.as_path().to_str().unwrap()]); + command.stdout(Stdio::null()); + + let status = command.status().await?; + if !status.success() { + return Err(NixError::NixFailure { exit_code: status.code().unwrap() }); + } + } + + Ok(()) + } +}