276 lines
9.3 KiB
Rust
276 lines
9.3 KiB
Rust
use std::collections::BTreeMap;
|
|
use std::{env, path::PathBuf, process};
|
|
|
|
use aiken_project::{pretty, script::EvalInfo, telemetry, Project};
|
|
use miette::IntoDiagnostic;
|
|
use owo_colors::OwoColorize;
|
|
use uplc::machine::cost_model::ExBudget;
|
|
|
|
pub mod cmd;
|
|
|
|
pub fn with_project<A>(directory: Option<PathBuf>, mut action: A) -> miette::Result<()>
|
|
where
|
|
A: FnMut(&mut Project<Terminal>) -> Result<(), aiken_project::error::Error>,
|
|
{
|
|
let project_path = if let Some(d) = directory {
|
|
d
|
|
} else {
|
|
env::current_dir().into_diagnostic()?
|
|
};
|
|
|
|
let mut project = match Project::new(project_path, Terminal::default()) {
|
|
Ok(p) => p,
|
|
Err(e) => {
|
|
e.report();
|
|
process::exit(1);
|
|
}
|
|
};
|
|
|
|
let build_result = action(&mut project);
|
|
|
|
let warning_count = project.warnings.len();
|
|
|
|
for warning in project.warnings {
|
|
warning.report()
|
|
}
|
|
|
|
if let Err(err) = build_result {
|
|
err.report();
|
|
println!("\n{}", "Summary".purple().bold());
|
|
println!(
|
|
" {} error(s), {}",
|
|
err.len(),
|
|
format!("{warning_count} warning(s)").yellow(),
|
|
);
|
|
process::exit(1);
|
|
} else {
|
|
println!("\n{}", "Summary".purple().bold());
|
|
println!(
|
|
" 0 error, {}",
|
|
format!("{warning_count} warning(s)").yellow(),
|
|
);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone, Copy)]
|
|
pub struct Terminal;
|
|
|
|
impl telemetry::EventListener for Terminal {
|
|
fn handle_event(&self, event: telemetry::Event) {
|
|
match event {
|
|
telemetry::Event::StartingCompilation {
|
|
name,
|
|
version,
|
|
root,
|
|
} => {
|
|
println!(
|
|
"{} {} {} ({})",
|
|
" Compiling".bold().purple(),
|
|
name.bold(),
|
|
version,
|
|
root.display().bright_blue()
|
|
);
|
|
}
|
|
telemetry::Event::BuildingDocumentation {
|
|
name,
|
|
version,
|
|
root,
|
|
} => {
|
|
println!(
|
|
"{} {} {} ({})",
|
|
" Generating documentation".bold().purple(),
|
|
name.bold(),
|
|
version,
|
|
root.to_str().unwrap_or("").bright_blue()
|
|
);
|
|
}
|
|
telemetry::Event::WaitingForBuildDirLock => {
|
|
println!("{}", "Waiting for build directory lock ...".bold().purple());
|
|
}
|
|
telemetry::Event::GeneratingUPLC { output_path, name } => {
|
|
println!(
|
|
"{} {} in {}",
|
|
" Generating".bold().purple(),
|
|
name.bold(),
|
|
output_path.display().bright_blue()
|
|
);
|
|
}
|
|
telemetry::Event::GeneratingDocFiles { output_path } => {
|
|
println!(
|
|
"{} in {}",
|
|
" Generating documentation files".bold().purple(),
|
|
output_path.to_str().unwrap_or("").bright_blue()
|
|
);
|
|
}
|
|
telemetry::Event::GeneratingUPLCFor { name, path } => {
|
|
println!(
|
|
"{} {}.{{{}}}",
|
|
" Generating Untyped Plutus Core for".bold().purple(),
|
|
path.to_str().unwrap_or("").blue(),
|
|
name.bright_blue(),
|
|
);
|
|
}
|
|
telemetry::Event::EvaluatingFunction { results } => {
|
|
println!("{}\n", " Evaluating function ...".bold().purple());
|
|
|
|
let (max_mem, max_cpu) = find_max_execution_units(&results);
|
|
|
|
for eval_info in &results {
|
|
println!(" {}", fmt_eval(eval_info, max_mem, max_cpu))
|
|
}
|
|
}
|
|
telemetry::Event::RunningTests => {
|
|
println!("{} {}\n", " Testing".bold().purple(), "...".bold());
|
|
}
|
|
telemetry::Event::FinishedTests { tests } => {
|
|
let (max_mem, max_cpu) = find_max_execution_units(&tests);
|
|
|
|
for (module, infos) in &group_by_module(&tests) {
|
|
let first = fmt_test(infos.first().unwrap(), max_mem, max_cpu, false).len();
|
|
println!(
|
|
"{} {} {}",
|
|
" ┌──".bright_black(),
|
|
module.bold().blue(),
|
|
pretty::pad_left("".to_string(), first - module.len() - 3, "─")
|
|
.bright_black()
|
|
);
|
|
for eval_info in infos {
|
|
println!(
|
|
" {} {}",
|
|
"│".bright_black(),
|
|
fmt_test(eval_info, max_mem, max_cpu, true)
|
|
)
|
|
}
|
|
let last = fmt_test(infos.last().unwrap(), max_mem, max_cpu, false).len();
|
|
let summary = fmt_test_summary(infos, false).len();
|
|
println!(
|
|
"{} {}\n",
|
|
pretty::pad_right(" └".to_string(), last - summary + 5, "─")
|
|
.bright_black(),
|
|
fmt_test_summary(infos, true),
|
|
);
|
|
}
|
|
}
|
|
telemetry::Event::DownloadingPackage { name } => {
|
|
println!("{} {}", " Downloading".bold().purple(), name.bold())
|
|
}
|
|
telemetry::Event::PackagesDownloaded { start, count } => {
|
|
let elapsed = format!("{:.2}s", start.elapsed().as_millis() as f32 / 1000.);
|
|
|
|
let msg = match count {
|
|
1 => format!("1 package in {}", elapsed),
|
|
_ => format!("{} packages in {}", count, elapsed),
|
|
};
|
|
|
|
println!("{} {}", " Downloaded".bold().purple(), msg.bold())
|
|
}
|
|
telemetry::Event::ResolvingVersions => {
|
|
println!("{}", " Resolving versions".bold().purple(),)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn fmt_test(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize, styled: bool) -> String {
|
|
let EvalInfo {
|
|
success,
|
|
script,
|
|
spent_budget,
|
|
..
|
|
} = eval_info;
|
|
|
|
let ExBudget { mem, cpu } = spent_budget;
|
|
let mem_pad = pretty::pad_left(mem.to_string(), max_mem, " ");
|
|
let cpu_pad = pretty::pad_left(cpu.to_string(), max_cpu, " ");
|
|
|
|
format!(
|
|
"{} [mem: {}, cpu: {}] {}",
|
|
if *success {
|
|
pretty::style_if(styled, "PASS".to_string(), |s| s.bold().green().to_string())
|
|
} else {
|
|
pretty::style_if(styled, "FAIL".to_string(), |s| s.bold().red().to_string())
|
|
},
|
|
pretty::style_if(styled, mem_pad, |s| s.bright_white().to_string()),
|
|
pretty::style_if(styled, cpu_pad, |s| s.bright_white().to_string()),
|
|
pretty::style_if(styled, script.name.clone(), |s| s.bright_blue().to_string()),
|
|
)
|
|
}
|
|
|
|
fn fmt_test_summary(tests: &Vec<&EvalInfo>, styled: bool) -> String {
|
|
let (n_passed, n_failed) = tests
|
|
.iter()
|
|
.fold((0, 0), |(n_passed, n_failed), test_info| {
|
|
if test_info.success {
|
|
(n_passed + 1, n_failed)
|
|
} else {
|
|
(n_passed, n_failed + 1)
|
|
}
|
|
});
|
|
format!(
|
|
"{} | {} | {}",
|
|
pretty::style_if(styled, format!("{} tests", tests.len()), |s| s
|
|
.bold()
|
|
.to_string()),
|
|
pretty::style_if(styled, format!("{} passed", n_passed), |s| s
|
|
.bright_green()
|
|
.bold()
|
|
.to_string()),
|
|
pretty::style_if(styled, format!("{} failed", n_failed), |s| s
|
|
.bright_red()
|
|
.bold()
|
|
.to_string()),
|
|
)
|
|
}
|
|
|
|
fn fmt_eval(eval_info: &EvalInfo, max_mem: usize, max_cpu: usize) -> String {
|
|
let EvalInfo {
|
|
output,
|
|
script,
|
|
spent_budget,
|
|
..
|
|
} = eval_info;
|
|
|
|
let ExBudget { mem, cpu } = spent_budget;
|
|
|
|
format!(
|
|
" {}::{} [mem: {}, cpu: {}]\n │\n ╰─▶ {}",
|
|
script.module.blue(),
|
|
script.name.bright_blue(),
|
|
pretty::pad_left(mem.to_string(), max_mem, " "),
|
|
pretty::pad_left(cpu.to_string(), max_cpu, " "),
|
|
output
|
|
.as_ref()
|
|
.map(|x| format!("{}", x))
|
|
.unwrap_or_else(|| "Error.".to_string()),
|
|
)
|
|
}
|
|
|
|
fn group_by_module(infos: &Vec<EvalInfo>) -> BTreeMap<String, Vec<&EvalInfo>> {
|
|
let mut modules = BTreeMap::new();
|
|
for eval_info in infos {
|
|
let xs: &mut Vec<&EvalInfo> = modules.entry(eval_info.script.module.clone()).or_default();
|
|
xs.push(eval_info);
|
|
}
|
|
modules
|
|
}
|
|
|
|
fn find_max_execution_units(xs: &[EvalInfo]) -> (usize, usize) {
|
|
let (max_mem, max_cpu) = xs.iter().fold(
|
|
(0, 0),
|
|
|(max_mem, max_cpu), EvalInfo { spent_budget, .. }| {
|
|
if spent_budget.mem >= max_mem && spent_budget.cpu >= max_cpu {
|
|
(spent_budget.mem, spent_budget.cpu)
|
|
} else if spent_budget.mem > max_mem {
|
|
(spent_budget.mem, max_cpu)
|
|
} else if spent_budget.cpu > max_cpu {
|
|
(max_mem, spent_budget.cpu)
|
|
} else {
|
|
(max_mem, max_cpu)
|
|
}
|
|
},
|
|
);
|
|
|
|
(max_mem.to_string().len(), max_cpu.to_string().len())
|
|
}
|