cleanup
This commit is contained in:
parent
4fbf30a076
commit
bce7fe6d24
6 changed files with 529 additions and 184 deletions
|
@ -1,5 +1,85 @@
|
|||
use crate::util::run_nix_cmd;
|
||||
use crate::command::{NixCommand, StdIoInterceptor};
|
||||
use crate::util::{HashExtractor, NixErrorClassifier, NixFileFixer};
|
||||
use std::io::Write;
|
||||
|
||||
pub fn handle_nix_build(args: &[String]) {
|
||||
run_nix_cmd("build", args);
|
||||
pub fn handle_nix_build(
|
||||
args: &[String],
|
||||
hash_extractor: &dyn HashExtractor,
|
||||
fixer: &dyn NixFileFixer,
|
||||
classifier: &dyn NixErrorClassifier,
|
||||
) {
|
||||
let mut cmd = NixCommand::new("build").print_build_logs(true);
|
||||
for arg in args {
|
||||
cmd = cmd.arg(arg);
|
||||
}
|
||||
let status = cmd
|
||||
.run_with_logs(StdIoInterceptor)
|
||||
.expect("failed to run nix build");
|
||||
if status.success() {
|
||||
return;
|
||||
}
|
||||
|
||||
let output = NixCommand::new("build")
|
||||
.print_build_logs(true)
|
||||
.args(args.iter().cloned())
|
||||
.output()
|
||||
.expect("failed to capture output");
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
if let Some(new_hash) = hash_extractor.extract_hash(&stderr) {
|
||||
if fixer.fix_hash_in_files(&new_hash) {
|
||||
eprintln!("\x1b[32m✔ Fixed hash mismatch, retrying...\x1b[0m");
|
||||
let retry_status = NixCommand::new("build")
|
||||
.print_build_logs(true)
|
||||
.args(args.iter().cloned())
|
||||
.run_with_logs(StdIoInterceptor)
|
||||
.unwrap();
|
||||
std::process::exit(retry_status.code().unwrap_or(1));
|
||||
}
|
||||
}
|
||||
|
||||
if classifier.should_retry(&stderr) {
|
||||
if stderr.contains("unfree") {
|
||||
eprintln!(
|
||||
"\x1b[33m⚠ Unfree package detected, retrying with NIXPKGS_ALLOW_UNFREE=1...\x1b[0m"
|
||||
);
|
||||
let retry_status = NixCommand::new("build")
|
||||
.print_build_logs(true)
|
||||
.args(args.iter().cloned())
|
||||
.env("NIXPKGS_ALLOW_UNFREE", "1")
|
||||
.impure(true)
|
||||
.run_with_logs(StdIoInterceptor)
|
||||
.unwrap();
|
||||
std::process::exit(retry_status.code().unwrap_or(1));
|
||||
}
|
||||
if stderr.contains("insecure") {
|
||||
eprintln!(
|
||||
"\x1b[33m⚠ Insecure package detected, retrying with NIXPKGS_ALLOW_INSECURE=1...\x1b[0m"
|
||||
);
|
||||
let retry_status = NixCommand::new("build")
|
||||
.print_build_logs(true)
|
||||
.args(args.iter().cloned())
|
||||
.env("NIXPKGS_ALLOW_INSECURE", "1")
|
||||
.impure(true)
|
||||
.run_with_logs(StdIoInterceptor)
|
||||
.unwrap();
|
||||
std::process::exit(retry_status.code().unwrap_or(1));
|
||||
}
|
||||
if stderr.contains("broken") {
|
||||
eprintln!(
|
||||
"\x1b[33m⚠ Broken package detected, retrying with NIXPKGS_ALLOW_BROKEN=1...\x1b[0m"
|
||||
);
|
||||
let retry_status = NixCommand::new("build")
|
||||
.print_build_logs(true)
|
||||
.args(args.iter().cloned())
|
||||
.env("NIXPKGS_ALLOW_BROKEN", "1")
|
||||
.impure(true)
|
||||
.run_with_logs(StdIoInterceptor)
|
||||
.unwrap();
|
||||
std::process::exit(retry_status.code().unwrap_or(1));
|
||||
}
|
||||
}
|
||||
|
||||
std::io::stderr().write_all(output.stderr.as_ref()).unwrap();
|
||||
std::process::exit(status.code().unwrap_or(1));
|
||||
}
|
||||
|
|
157
eh/src/command.rs
Normal file
157
eh/src/command.rs
Normal file
|
@ -0,0 +1,157 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::process::{Command, ExitStatus, Output, Stdio};
|
||||
|
||||
/// Trait for log interception and output handling.
|
||||
pub trait LogInterceptor: Send {
|
||||
fn on_stderr(&mut self, chunk: &[u8]);
|
||||
fn on_stdout(&mut self, chunk: &[u8]);
|
||||
}
|
||||
|
||||
/// Default log interceptor that just writes to stdio.
|
||||
pub struct StdIoInterceptor;
|
||||
|
||||
impl LogInterceptor for StdIoInterceptor {
|
||||
fn on_stderr(&mut self, chunk: &[u8]) {
|
||||
let _ = io::stderr().write_all(chunk);
|
||||
}
|
||||
fn on_stdout(&mut self, chunk: &[u8]) {
|
||||
let _ = io::stdout().write_all(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder and executor for Nix commands.
|
||||
pub struct NixCommand {
|
||||
subcommand: String,
|
||||
args: Vec<String>,
|
||||
env: Vec<(String, String)>,
|
||||
impure: bool,
|
||||
print_build_logs: bool,
|
||||
}
|
||||
|
||||
impl NixCommand {
|
||||
pub fn new<S: Into<String>>(subcommand: S) -> Self {
|
||||
Self {
|
||||
subcommand: subcommand.into(),
|
||||
args: Vec::new(),
|
||||
env: Vec::new(),
|
||||
impure: false,
|
||||
print_build_logs: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn arg<S: Into<String>>(mut self, arg: S) -> Self {
|
||||
self.args.push(arg.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn args<I, S>(mut self, args: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: Into<String>,
|
||||
{
|
||||
self.args.extend(args.into_iter().map(Into::into));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn env<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
|
||||
self.env.push((key.into(), value.into()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn impure(mut self, yes: bool) -> Self {
|
||||
self.impure = yes;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn print_build_logs(mut self, yes: bool) -> Self {
|
||||
self.print_build_logs = yes;
|
||||
self
|
||||
}
|
||||
|
||||
/// Run the command, streaming output to the provided interceptor.
|
||||
pub fn run_with_logs<I: LogInterceptor + 'static>(
|
||||
&self,
|
||||
mut interceptor: I,
|
||||
) -> io::Result<ExitStatus> {
|
||||
let mut cmd = Command::new("nix");
|
||||
cmd.arg(&self.subcommand);
|
||||
|
||||
if self.print_build_logs && !self.args.iter().any(|a| a == "--no-build-output") {
|
||||
cmd.arg("--print-build-logs");
|
||||
}
|
||||
if self.impure {
|
||||
cmd.arg("--impure");
|
||||
}
|
||||
for (k, v) in &self.env {
|
||||
cmd.env(k, v);
|
||||
}
|
||||
cmd.args(&self.args);
|
||||
cmd.stdout(Stdio::piped());
|
||||
cmd.stderr(Stdio::piped());
|
||||
|
||||
let mut child = cmd.spawn()?;
|
||||
let mut stdout = child.stdout.take().unwrap();
|
||||
let mut stderr = child.stderr.take().unwrap();
|
||||
|
||||
let mut out_buf = [0u8; 4096];
|
||||
let mut err_buf = [0u8; 4096];
|
||||
|
||||
let mut out_queue = VecDeque::new();
|
||||
let mut err_queue = VecDeque::new();
|
||||
|
||||
loop {
|
||||
let mut did_something = false;
|
||||
|
||||
match stdout.read(&mut out_buf) {
|
||||
Ok(0) => {}
|
||||
Ok(n) => {
|
||||
interceptor.on_stdout(&out_buf[..n]);
|
||||
out_queue.push_back(Vec::from(&out_buf[..n]));
|
||||
did_something = true;
|
||||
}
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
|
||||
match stderr.read(&mut err_buf) {
|
||||
Ok(0) => {}
|
||||
Ok(n) => {
|
||||
interceptor.on_stderr(&err_buf[..n]);
|
||||
err_queue.push_back(Vec::from(&err_buf[..n]));
|
||||
did_something = true;
|
||||
}
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
|
||||
if !did_something && child.try_wait()?.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let status = child.wait()?;
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
/// Run the command and capture all output.
|
||||
pub fn output(&self) -> io::Result<Output> {
|
||||
let mut cmd = Command::new("nix");
|
||||
cmd.arg(&self.subcommand);
|
||||
|
||||
if self.print_build_logs && !self.args.iter().any(|a| a == "--no-build-output") {
|
||||
cmd.arg("--print-build-logs");
|
||||
}
|
||||
if self.impure {
|
||||
cmd.arg("--impure");
|
||||
}
|
||||
for (k, v) in &self.env {
|
||||
cmd.env(k, v);
|
||||
}
|
||||
cmd.args(&self.args);
|
||||
cmd.stdout(Stdio::piped());
|
||||
cmd.stderr(Stdio::piped());
|
||||
|
||||
cmd.output()
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ use std::env;
|
|||
use std::path::Path;
|
||||
|
||||
mod build;
|
||||
mod command;
|
||||
mod run;
|
||||
mod shell;
|
||||
mod util;
|
||||
|
@ -45,17 +46,26 @@ fn main() {
|
|||
match app_name {
|
||||
"nr" => {
|
||||
let rest: Vec<String> = args.collect();
|
||||
run::handle_nix_run(&rest);
|
||||
let hash_extractor = util::RegexHashExtractor;
|
||||
let fixer = util::DefaultNixFileFixer;
|
||||
let classifier = util::DefaultNixErrorClassifier;
|
||||
run::handle_nix_run(&rest, &hash_extractor, &fixer, &classifier);
|
||||
return;
|
||||
}
|
||||
"ns" => {
|
||||
let rest: Vec<String> = args.collect();
|
||||
shell::handle_nix_shell(&rest);
|
||||
let hash_extractor = util::RegexHashExtractor;
|
||||
let fixer = util::DefaultNixFileFixer;
|
||||
let classifier = util::DefaultNixErrorClassifier;
|
||||
shell::handle_nix_shell(&rest, &hash_extractor, &fixer, &classifier);
|
||||
return;
|
||||
}
|
||||
"nb" => {
|
||||
let rest: Vec<String> = args.collect();
|
||||
build::handle_nix_build(&rest);
|
||||
let hash_extractor = util::RegexHashExtractor;
|
||||
let fixer = util::DefaultNixFileFixer;
|
||||
let classifier = util::DefaultNixErrorClassifier;
|
||||
build::handle_nix_build(&rest, &hash_extractor, &fixer, &classifier);
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
|
@ -63,10 +73,20 @@ fn main() {
|
|||
|
||||
let cli = Cli::parse();
|
||||
|
||||
let hash_extractor = util::RegexHashExtractor;
|
||||
let fixer = util::DefaultNixFileFixer;
|
||||
let classifier = util::DefaultNixErrorClassifier;
|
||||
|
||||
match cli.command {
|
||||
Some(Command::Run { args }) => run::handle_nix_run(&args),
|
||||
Some(Command::Shell { args }) => shell::handle_nix_shell(&args),
|
||||
Some(Command::Build { args }) => build::handle_nix_build(&args),
|
||||
Some(Command::Run { args }) => {
|
||||
run::handle_nix_run(&args, &hash_extractor, &fixer, &classifier);
|
||||
}
|
||||
Some(Command::Shell { args }) => {
|
||||
shell::handle_nix_shell(&args, &hash_extractor, &fixer, &classifier);
|
||||
}
|
||||
Some(Command::Build { args }) => {
|
||||
build::handle_nix_build(&args, &hash_extractor, &fixer, &classifier);
|
||||
}
|
||||
None => {
|
||||
Cli::command().print_help().unwrap();
|
||||
println!();
|
||||
|
|
|
@ -1,5 +1,85 @@
|
|||
use crate::util::run_nix_cmd;
|
||||
use crate::command::{NixCommand, StdIoInterceptor};
|
||||
use crate::util::{HashExtractor, NixErrorClassifier, NixFileFixer};
|
||||
use std::io::Write;
|
||||
|
||||
pub fn handle_nix_run(args: &[String]) {
|
||||
run_nix_cmd("run", args);
|
||||
pub fn handle_nix_run(
|
||||
args: &[String],
|
||||
hash_extractor: &dyn HashExtractor,
|
||||
fixer: &dyn NixFileFixer,
|
||||
classifier: &dyn NixErrorClassifier,
|
||||
) {
|
||||
let mut cmd = NixCommand::new("run").print_build_logs(true);
|
||||
for arg in args {
|
||||
cmd = cmd.arg(arg);
|
||||
}
|
||||
let status = cmd
|
||||
.run_with_logs(StdIoInterceptor)
|
||||
.expect("failed to run nix run");
|
||||
if status.success() {
|
||||
return;
|
||||
}
|
||||
|
||||
let output = NixCommand::new("run")
|
||||
.print_build_logs(true)
|
||||
.args(args.iter().cloned())
|
||||
.output()
|
||||
.expect("failed to capture output");
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
if let Some(new_hash) = hash_extractor.extract_hash(&stderr) {
|
||||
if fixer.fix_hash_in_files(&new_hash) {
|
||||
eprintln!("\x1b[32m✔ Fixed hash mismatch, retrying...\x1b[0m");
|
||||
let retry_status = NixCommand::new("run")
|
||||
.print_build_logs(true)
|
||||
.args(args.iter().cloned())
|
||||
.run_with_logs(StdIoInterceptor)
|
||||
.unwrap();
|
||||
std::process::exit(retry_status.code().unwrap_or(1));
|
||||
}
|
||||
}
|
||||
|
||||
if classifier.should_retry(&stderr) {
|
||||
if stderr.contains("unfree") {
|
||||
eprintln!(
|
||||
"\x1b[33m⚠ Unfree package detected, retrying with NIXPKGS_ALLOW_UNFREE=1...\x1b[0m"
|
||||
);
|
||||
let retry_status = NixCommand::new("run")
|
||||
.print_build_logs(true)
|
||||
.args(args.iter().cloned())
|
||||
.env("NIXPKGS_ALLOW_UNFREE", "1")
|
||||
.impure(true)
|
||||
.run_with_logs(StdIoInterceptor)
|
||||
.unwrap();
|
||||
std::process::exit(retry_status.code().unwrap_or(1));
|
||||
}
|
||||
if stderr.contains("insecure") {
|
||||
eprintln!(
|
||||
"\x1b[33m⚠ Insecure package detected, retrying with NIXPKGS_ALLOW_INSECURE=1...\x1b[0m"
|
||||
);
|
||||
let retry_status = NixCommand::new("run")
|
||||
.print_build_logs(true)
|
||||
.args(args.iter().cloned())
|
||||
.env("NIXPKGS_ALLOW_INSECURE", "1")
|
||||
.impure(true)
|
||||
.run_with_logs(StdIoInterceptor)
|
||||
.unwrap();
|
||||
std::process::exit(retry_status.code().unwrap_or(1));
|
||||
}
|
||||
if stderr.contains("broken") {
|
||||
eprintln!(
|
||||
"\x1b[33m⚠ Broken package detected, retrying with NIXPKGS_ALLOW_BROKEN=1...\x1b[0m"
|
||||
);
|
||||
let retry_status = NixCommand::new("run")
|
||||
.print_build_logs(true)
|
||||
.args(args.iter().cloned())
|
||||
.env("NIXPKGS_ALLOW_BROKEN", "1")
|
||||
.impure(true)
|
||||
.run_with_logs(StdIoInterceptor)
|
||||
.unwrap();
|
||||
std::process::exit(retry_status.code().unwrap_or(1));
|
||||
}
|
||||
}
|
||||
|
||||
std::io::stderr().write_all(output.stderr.as_ref()).unwrap();
|
||||
std::process::exit(status.code().unwrap_or(1));
|
||||
}
|
||||
|
|
|
@ -1,5 +1,85 @@
|
|||
use crate::util::run_nix_cmd;
|
||||
use crate::command::{NixCommand, StdIoInterceptor};
|
||||
use crate::util::{HashExtractor, NixErrorClassifier, NixFileFixer};
|
||||
use std::io::Write;
|
||||
|
||||
pub fn handle_nix_shell(args: &[String]) {
|
||||
run_nix_cmd("shell", args);
|
||||
pub fn handle_nix_shell(
|
||||
args: &[String],
|
||||
hash_extractor: &dyn HashExtractor,
|
||||
fixer: &dyn NixFileFixer,
|
||||
classifier: &dyn NixErrorClassifier,
|
||||
) {
|
||||
let mut cmd = NixCommand::new("shell").print_build_logs(true);
|
||||
for arg in args {
|
||||
cmd = cmd.arg(arg);
|
||||
}
|
||||
let status = cmd
|
||||
.run_with_logs(StdIoInterceptor)
|
||||
.expect("failed to run nix shell");
|
||||
if status.success() {
|
||||
return;
|
||||
}
|
||||
|
||||
let output = NixCommand::new("shell")
|
||||
.print_build_logs(true)
|
||||
.args(args.iter().cloned())
|
||||
.output()
|
||||
.expect("failed to capture output");
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
if let Some(new_hash) = hash_extractor.extract_hash(&stderr) {
|
||||
if fixer.fix_hash_in_files(&new_hash) {
|
||||
eprintln!("\x1b[32m✔ Fixed hash mismatch, retrying...\x1b[0m");
|
||||
let retry_status = NixCommand::new("shell")
|
||||
.print_build_logs(true)
|
||||
.args(args.iter().cloned())
|
||||
.run_with_logs(StdIoInterceptor)
|
||||
.unwrap();
|
||||
std::process::exit(retry_status.code().unwrap_or(1));
|
||||
}
|
||||
}
|
||||
|
||||
if classifier.should_retry(&stderr) {
|
||||
if stderr.contains("unfree") {
|
||||
eprintln!(
|
||||
"\x1b[33m⚠ Unfree package detected, retrying with NIXPKGS_ALLOW_UNFREE=1...\x1b[0m"
|
||||
);
|
||||
let retry_status = NixCommand::new("shell")
|
||||
.print_build_logs(true)
|
||||
.args(args.iter().cloned())
|
||||
.env("NIXPKGS_ALLOW_UNFREE", "1")
|
||||
.impure(true)
|
||||
.run_with_logs(StdIoInterceptor)
|
||||
.unwrap();
|
||||
std::process::exit(retry_status.code().unwrap_or(1));
|
||||
}
|
||||
if stderr.contains("insecure") {
|
||||
eprintln!(
|
||||
"\x1b[33m⚠ Insecure package detected, retrying with NIXPKGS_ALLOW_INSECURE=1...\x1b[0m"
|
||||
);
|
||||
let retry_status = NixCommand::new("shell")
|
||||
.print_build_logs(true)
|
||||
.args(args.iter().cloned())
|
||||
.env("NIXPKGS_ALLOW_INSECURE", "1")
|
||||
.impure(true)
|
||||
.run_with_logs(StdIoInterceptor)
|
||||
.unwrap();
|
||||
std::process::exit(retry_status.code().unwrap_or(1));
|
||||
}
|
||||
if stderr.contains("broken") {
|
||||
eprintln!(
|
||||
"\x1b[33m⚠ Broken package detected, retrying with NIXPKGS_ALLOW_BROKEN=1...\x1b[0m"
|
||||
);
|
||||
let retry_status = NixCommand::new("shell")
|
||||
.print_build_logs(true)
|
||||
.args(args.iter().cloned())
|
||||
.env("NIXPKGS_ALLOW_BROKEN", "1")
|
||||
.impure(true)
|
||||
.run_with_logs(StdIoInterceptor)
|
||||
.unwrap();
|
||||
std::process::exit(retry_status.code().unwrap_or(1));
|
||||
}
|
||||
}
|
||||
|
||||
std::io::stderr().write_all(output.stderr.as_ref()).unwrap();
|
||||
std::process::exit(status.code().unwrap_or(1));
|
||||
}
|
||||
|
|
266
eh/src/util.rs
266
eh/src/util.rs
|
@ -1,195 +1,123 @@
|
|||
use regex::Regex;
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
use std::process::{Command as StdCommand, Stdio};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub fn extract_hash_from_error(stderr: &str) -> Option<String> {
|
||||
let patterns = [
|
||||
r"got:\s+([a-zA-Z0-9+/=]+)",
|
||||
r"actual:\s+([a-zA-Z0-9+/=]+)",
|
||||
r"have:\s+([a-zA-Z0-9+/=]+)",
|
||||
];
|
||||
|
||||
for pattern in &patterns {
|
||||
if let Ok(re) = Regex::new(pattern) {
|
||||
if let Some(captures) = re.captures(stderr) {
|
||||
if let Some(hash) = captures.get(1) {
|
||||
return Some(hash.as_str().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
pub trait HashExtractor {
|
||||
fn extract_hash(&self, stderr: &str) -> Option<String>;
|
||||
}
|
||||
|
||||
pub fn fix_hash_in_files(new_hash: &str) -> bool {
|
||||
let nix_files = find_nix_files();
|
||||
let mut fixed = false;
|
||||
pub struct RegexHashExtractor;
|
||||
|
||||
for file_path in nix_files {
|
||||
if fix_hash_in_file(&file_path, new_hash) {
|
||||
println!("Updated hash in {file_path}");
|
||||
fixed = true;
|
||||
}
|
||||
}
|
||||
|
||||
fixed
|
||||
}
|
||||
|
||||
pub fn find_nix_files() -> Vec<String> {
|
||||
let mut files = Vec::new();
|
||||
|
||||
let candidates = [
|
||||
"default.nix",
|
||||
"package.nix",
|
||||
"shell.nix",
|
||||
"flake.nix",
|
||||
"nix/default.nix",
|
||||
"nix/package.nix",
|
||||
"nix/site.nix",
|
||||
];
|
||||
|
||||
for candidate in &candidates {
|
||||
if Path::new(*candidate).exists() {
|
||||
files.push((*candidate).to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(entries) = fs::read_dir(".") {
|
||||
for entry in entries.flatten() {
|
||||
if let Some(name) = entry.file_name().to_str() {
|
||||
let path = std::path::Path::new(name);
|
||||
if path
|
||||
.extension()
|
||||
.is_some_and(|ext| ext.eq_ignore_ascii_case("nix"))
|
||||
&& !files.contains(&name.to_string())
|
||||
{
|
||||
files.push(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
files
|
||||
}
|
||||
|
||||
pub fn fix_hash_in_file(file_path: &str, new_hash: &str) -> bool {
|
||||
if let Ok(content) = fs::read_to_string(file_path) {
|
||||
impl HashExtractor for RegexHashExtractor {
|
||||
fn extract_hash(&self, stderr: &str) -> Option<String> {
|
||||
let patterns = [
|
||||
(r#"hash\s*=\s*"[^"]*""#, format!(r#"hash = "{new_hash}""#)),
|
||||
(
|
||||
r#"sha256\s*=\s*"[^"]*""#,
|
||||
format!(r#"sha256 = "{new_hash}""#),
|
||||
),
|
||||
(
|
||||
r#"outputHash\s*=\s*"[^"]*""#,
|
||||
format!(r#"outputHash = "{new_hash}""#),
|
||||
),
|
||||
r"got:\s+([a-zA-Z0-9+/=]+)",
|
||||
r"actual:\s+([a-zA-Z0-9+/=]+)",
|
||||
r"have:\s+([a-zA-Z0-9+/=]+)",
|
||||
];
|
||||
|
||||
for (pattern, replacement) in &patterns {
|
||||
for pattern in &patterns {
|
||||
if let Ok(re) = Regex::new(pattern) {
|
||||
if re.is_match(&content) {
|
||||
let new_content = re.replace_all(&content, replacement);
|
||||
if fs::write(file_path, new_content.as_ref()).is_ok() {
|
||||
return true;
|
||||
if let Some(captures) = re.captures(stderr) {
|
||||
if let Some(hash) = captures.get(1) {
|
||||
return Some(hash.as_str().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn should_retry_nix_error(stderr: &str) -> bool {
|
||||
if extract_hash_from_error(stderr).is_some() {
|
||||
return true;
|
||||
}
|
||||
(stderr.contains("unfree") && stderr.contains("refusing"))
|
||||
|| (stderr.contains("insecure") && stderr.contains("refusing"))
|
||||
|| (stderr.contains("broken") && stderr.contains("refusing"))
|
||||
pub trait NixFileFixer {
|
||||
fn fix_hash_in_files(&self, new_hash: &str) -> bool;
|
||||
fn find_nix_files(&self) -> Vec<PathBuf>;
|
||||
fn fix_hash_in_file(&self, file_path: &Path, new_hash: &str) -> bool;
|
||||
}
|
||||
|
||||
pub fn handle_nix_error(subcommand: &str, args: &[String], stderr: &str) {
|
||||
if let Some(new_hash) = extract_hash_from_error(stderr) {
|
||||
if fix_hash_in_files(&new_hash) {
|
||||
println!("Fixed hash mismatch, retrying...");
|
||||
run_nix_cmd(subcommand, args);
|
||||
return;
|
||||
pub struct DefaultNixFileFixer;
|
||||
|
||||
impl NixFileFixer for DefaultNixFileFixer {
|
||||
fn fix_hash_in_files(&self, new_hash: &str) -> bool {
|
||||
let nix_files = self.find_nix_files();
|
||||
let mut fixed = false;
|
||||
for file_path in nix_files {
|
||||
if self.fix_hash_in_file(&file_path, new_hash) {
|
||||
println!("Updated hash in {}", file_path.display());
|
||||
fixed = true;
|
||||
}
|
||||
}
|
||||
fixed
|
||||
}
|
||||
|
||||
if stderr.contains("unfree") && stderr.contains("refusing") {
|
||||
println!("Unfree package detected, retrying with NIXPKGS_ALLOW_UNFREE=1...");
|
||||
run_nix_cmd_with_env(subcommand, args, "NIXPKGS_ALLOW_UNFREE", "1");
|
||||
return;
|
||||
}
|
||||
|
||||
if stderr.contains("insecure") && stderr.contains("refusing") {
|
||||
println!("Insecure package detected, retrying with NIXPKGS_ALLOW_INSECURE=1...");
|
||||
run_nix_cmd_with_env(subcommand, args, "NIXPKGS_ALLOW_INSECURE", "1");
|
||||
return;
|
||||
}
|
||||
|
||||
if stderr.contains("broken") && stderr.contains("refusing") {
|
||||
println!("Broken package detected, retrying with NIXPKGS_ALLOW_BROKEN=1...");
|
||||
run_nix_cmd_with_env(subcommand, args, "NIXPKGS_ALLOW_BROKEN", "1");
|
||||
return;
|
||||
}
|
||||
|
||||
io::stderr().write_all(stderr.as_bytes()).unwrap();
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
pub fn run_nix_cmd(subcommand: &str, args: &[String]) {
|
||||
let mut cmd = StdCommand::new("nix");
|
||||
cmd.arg(subcommand);
|
||||
|
||||
if !args.iter().any(|arg| arg == "--no-build-output") {
|
||||
cmd.arg("--print-build-logs");
|
||||
}
|
||||
|
||||
cmd.args(args);
|
||||
cmd.stderr(Stdio::piped());
|
||||
cmd.stdout(Stdio::inherit());
|
||||
|
||||
let mut child = cmd.spawn().expect("Failed to start nix command");
|
||||
let stderr = child.stderr.take().unwrap();
|
||||
|
||||
let stderr_handle = std::thread::spawn(move || {
|
||||
let mut buffer = Vec::new();
|
||||
std::io::copy(&mut std::io::BufReader::new(stderr), &mut buffer).unwrap();
|
||||
buffer
|
||||
});
|
||||
|
||||
let exit_status = child.wait().expect("Failed to wait for nix command");
|
||||
let stderr_output = stderr_handle.join().unwrap();
|
||||
|
||||
let stderr_str = String::from_utf8_lossy(&stderr_output);
|
||||
|
||||
if !exit_status.success() {
|
||||
if !should_retry_nix_error(&stderr_str) {
|
||||
io::stderr().write_all(&stderr_output).unwrap();
|
||||
fn find_nix_files(&self) -> Vec<PathBuf> {
|
||||
let mut files = Vec::new();
|
||||
let candidates = [
|
||||
"default.nix",
|
||||
"package.nix",
|
||||
"shell.nix",
|
||||
"flake.nix",
|
||||
"nix/default.nix",
|
||||
"nix/package.nix",
|
||||
"nix/site.nix",
|
||||
];
|
||||
for candidate in &candidates {
|
||||
let path = Path::new(candidate);
|
||||
if path.exists() {
|
||||
files.push(path.to_path_buf());
|
||||
}
|
||||
}
|
||||
handle_nix_error(subcommand, args, &stderr_str);
|
||||
if let Ok(entries) = fs::read_dir(".") {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if let Some(ext) = path.extension() {
|
||||
if ext.eq_ignore_ascii_case("nix") && !files.contains(&path) {
|
||||
files.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
files
|
||||
}
|
||||
|
||||
fn fix_hash_in_file(&self, file_path: &Path, new_hash: &str) -> bool {
|
||||
if let Ok(content) = fs::read_to_string(file_path) {
|
||||
let patterns = [
|
||||
(r#"hash\s*=\s*"[^"]*""#, format!(r#"hash = "{new_hash}""#)),
|
||||
(
|
||||
r#"sha256\s*=\s*"[^"]*""#,
|
||||
format!(r#"sha256 = "{new_hash}""#),
|
||||
),
|
||||
(
|
||||
r#"outputHash\s*=\s*"[^"]*""#,
|
||||
format!(r#"outputHash = "{new_hash}""#),
|
||||
),
|
||||
];
|
||||
for (pattern, replacement) in &patterns {
|
||||
if let Ok(re) = Regex::new(pattern) {
|
||||
if re.is_match(&content) {
|
||||
let new_content = re.replace_all(&content, replacement);
|
||||
if fs::write(file_path, new_content.as_ref()).is_ok() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_nix_cmd_with_env(subcommand: &str, args: &[String], env_key: &str, env_value: &str) {
|
||||
let mut cmd = StdCommand::new("nix");
|
||||
cmd.env(env_key, env_value);
|
||||
cmd.arg(subcommand);
|
||||
|
||||
// Add --impure for env var to take effect
|
||||
cmd.arg("--impure");
|
||||
|
||||
if !args.iter().any(|arg| arg == "--no-build-output") {
|
||||
cmd.arg("--print-build-logs");
|
||||
}
|
||||
|
||||
cmd.args(args);
|
||||
|
||||
let exit_status = cmd.status().expect("Failed to retry nix command");
|
||||
std::process::exit(exit_status.code().unwrap_or(1));
|
||||
pub trait NixErrorClassifier {
|
||||
fn should_retry(&self, stderr: &str) -> bool;
|
||||
}
|
||||
|
||||
pub struct DefaultNixErrorClassifier;
|
||||
|
||||
impl NixErrorClassifier for DefaultNixErrorClassifier {
|
||||
fn should_retry(&self, stderr: &str) -> bool {
|
||||
RegexHashExtractor.extract_hash(stderr).is_some()
|
||||
|| (stderr.contains("unfree") && stderr.contains("refusing"))
|
||||
|| (stderr.contains("insecure") && stderr.contains("refusing"))
|
||||
|| (stderr.contains("broken") && stderr.contains("refusing"))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue