use std::io::Write;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use sva_ast::variable;
use sva_core::solve::{
Point, Problem, Solved, Stopped, Vary, findings, point_json, solve as solved, solved_data,
};
use sva_core::{CliError, error_envelope_with, success_envelope};
use sva_engine::Abandon;
use crate::args::SolveArgs;
use crate::heard::Heard;
use crate::store::{On, opened, persisted, wait};
pub const CANCELLED: u8 = 130;
struct Flagged(Arc<AtomicBool>);
impl Abandon for Flagged {
fn abandoned(&self) -> Pin<Box<dyn Future<Output = bool> + '_>> {
Box::pin(async { self.0.load(Ordering::Relaxed) })
}
}
pub fn solve(args: &SolveArgs) -> Result<(String, u8), CliError> {
let dir = crate::composition::composition()?;
let heard = Heard::at(&dir)?;
let stop = Arc::new(AtomicBool::new(false));
let mut warnings = Vec::new();
let tier = opened(&args.cache, &mut warnings, On::Flag(&stop))?;
let problem = Problem {
source: &heard,
cost: args.cost.clone(),
vary: args.vary.clone(),
sets: args.sets.clone(),
rate: args.rate,
threads: args.threads,
stops: args.stops,
};
let mut heard_point = |point: &Point| {
let _ = writeln!(std::io::stderr(), "{}", point_json(point));
};
let outcome = wait(solved(&problem, tier, &mut heard_point, &Flagged(stop)));
persisted(tier, &mut warnings);
let solved = outcome?;
warnings.extend(findings(&solved, &args.vary));
let stopped = solved.stopped_by == Stopped::Cancelled;
if stopped {
let data = solved_data(&solved, &args.vary, "");
let message = format!(
"the solve was stopped by a signal after {} render(s); `data` holds the best so far, \
and no file was written",
solved.renders
);
let json = error_envelope_with("cancelled", &message, &data, &warnings);
return Ok((json, CANCELLED));
}
if !args.write {
let data = solved_data(&solved, &args.vary, "");
return Ok((success_envelope(&data, &warnings), 0));
}
let changes = changed(&dir, &solved, &args.vary)?;
let listed = sva_core::json::list(&changes, |c| {
format!(
"{{ \"path\": \"{}\", \"from\": \"{}\", \"to\": \"{}\" }}",
sva_core::json::escape(&c.path),
sva_core::json::escape(&c.from),
sva_core::json::escape(&c.to)
)
});
if !changes.is_empty() && !args.confirm {
let data = solved_data(&solved, &args.vary, &format!(", \"would_write\": {listed}"));
let message = format!(
"`--write` would change {} variable file(s), and replacing a file needs \
`--confirm`; nothing was written",
changes.len()
);
let json = error_envelope_with("conflict", &message, &data, &warnings);
return Ok((json, 4));
}
for change in &changes {
std::fs::write(dir.join(&change.path), &change.text)
.map_err(|e| CliError::Io(format!("could not write `{}`: {e}", change.path)))?;
}
let data = solved_data(&solved, &args.vary, &format!(", \"written\": {listed}"));
Ok((success_envelope(&data, &warnings), 0))
}
struct Change {
path: String,
from: String,
to: String,
text: String,
}
fn changed(dir: &std::path::Path, solved: &Solved, vary: &[Vary]) -> Result<Vec<Change>, CliError> {
let mut out = Vec::new();
for ((name, _, to), _) in solved.best.values.iter().zip(vary) {
let path = variable(name);
let text = std::fs::read_to_string(dir.join(&path))
.map_err(|e| CliError::Io(format!("could not read `{path}`: {e}")))?;
let at = sva_ast::body_at(&text);
let body = &text[at..];
let from = sva_ast::strip_line_comment(body).trim().to_string();
if from == *to {
continue;
}
let comment = body.find(';').map(|c| format!(" {}", body[c..].trim_end()));
out.push(Change {
text: format!("{}{to}{}\n", &text[..at], comment.unwrap_or_default()),
path,
from,
to: to.clone(),
});
}
Ok(out)
}