use anyhow::Context;
use clap::builder::styling::Style;
use pint_pkg::{
build::{BuiltPkg, BuiltPkgs},
manifest::{ManifestFile, PackageKind},
plan::Plan,
};
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
#[derive(clap::Args, Debug)]
pub struct Args {
#[arg(long = "manifest-path")]
manifest_path: Option<PathBuf>,
#[arg(long, value_parser = parse_hex)]
salt: Option<[u8; 32]>,
#[arg(long = "print-parsed")]
print_parsed: bool,
#[arg(long = "print-flat")]
print_flat: bool,
#[arg(long = "print-optimized")]
print_optimized: bool,
#[arg(long = "print-asm")]
print_asm: bool,
#[arg(long = "skip-optimize", hide = true)]
skip_optimize: bool,
#[arg(long)]
silent: bool,
}
fn parse_hex(value: &str) -> Result<[u8; 32], String> {
if value.len() > 64 || !value.chars().all(|c| c.is_ascii_hexdigit()) {
return Err("Salt must be a hexadecimal number with up to 64 digts (256 bits)".to_string());
}
let padded_value = format!("{:0>64}", value);
let mut salt = [0u8; 32];
for i in 0..32 {
salt[i] = u8::from_str_radix(&padded_value[2 * i..2 * i + 2], 16)
.map_err(|_| "Invalid hexadecimal value")?;
}
Ok(salt)
}
fn find_file(mut dir: PathBuf, file_name: &str) -> Option<PathBuf> {
loop {
let path = dir.join(file_name);
if path.exists() {
return Some(path);
}
if !dir.pop() {
return None;
}
}
}
pub fn cmd(args: Args) -> anyhow::Result<(Plan, BuiltPkgs)> {
let build_start = std::time::Instant::now();
let manifest_path = match args.manifest_path {
Some(path) => path,
None => {
let current_dir = std::env::current_dir()?;
match find_file(current_dir, ManifestFile::FILE_NAME) {
None => anyhow::bail!("no `pint.toml` in the current or parent directories"),
Some(path) => path,
}
}
};
let bold = Style::new().bold();
let manifest = ManifestFile::from_path(&manifest_path).context("failed to load manifest")?;
if let PackageKind::Library = manifest.pkg.kind {
if args.salt.is_some() {
anyhow::bail!("specifying `salt` for a library package is not allowed");
}
}
let name = manifest.pkg.name.to_string();
let members = [(name, manifest.clone())].into_iter().collect();
let plan = pint_pkg::plan::from_members(&members).context("failed to plan compilation")?;
let mut builder = pint_pkg::build::build_plan(&plan);
let options = pint_pkg::build::BuildOptions {
salts: HashMap::from_iter([(manifest.clone(), args.salt.unwrap_or_default())]),
print_parsed: args.print_parsed,
print_flat: args.print_flat,
print_optimized: args.print_optimized,
print_asm: args.print_asm,
skip_optimize: args.skip_optimize,
};
while let Some(prebuilt) = builder.next_pkg() {
let pinned = prebuilt.pinned();
let manifest = &plan.manifests()[&pinned.id()];
let source_str = source_string(pinned, manifest.dir());
if !args.silent {
println!(
" {}Compiling{} {} [{}] ({})",
bold.render(),
bold.render_reset(),
pinned.name,
manifest.pkg.kind,
source_str,
);
}
let _built = match prebuilt.build(&options) {
Ok(built) => {
built.print_warnings();
built
}
Err(err) => {
let msg = format!("{}", err.kind);
err.print_diagnostics();
anyhow::bail!("{msg}");
}
};
}
let built_pkgs = builder.into_built_pkgs();
if let Some(&n) = plan.compilation_order().last() {
let built = &built_pkgs[&n];
let pinned = &plan.graph()[n];
let manifest = &plan.manifests()[&pinned.id()];
let out_dir = manifest.out_dir();
let profile = "debug";
let profile_dir = out_dir.join(profile);
std::fs::create_dir_all(&profile_dir)
.with_context(|| format!("failed to create directory {profile_dir:?}"))?;
built
.write_to_dir(&pinned.name, &profile_dir)
.with_context(|| format!("failed to write output artifacts to {profile_dir:?}"))?;
if !args.silent {
println!(
" {}Finished{} build [{profile}] in {:?}",
bold.render(),
bold.render_reset(),
build_start.elapsed()
);
}
let kind_str = format!("{}", manifest.pkg.kind);
let padded_kind_str = format!("{kind_str:>12}");
let padding = &padded_kind_str[..padded_kind_str.len() - kind_str.len()];
let ca = match built {
BuiltPkg::Contract(contract) => format!("{}", &contract.ca),
_ => "".to_string(),
};
let name_col_w = name_col_w(&pinned.name, built);
if !args.silent {
println!(
"{padding}{}{kind_str}{} {:<name_col_w$} {}",
bold.render(),
bold.render_reset(),
pinned.name,
ca,
);
}
if let BuiltPkg::Contract(contract) = built {
let mut iter = contract.predicate_metadata.iter().peekable();
while let Some(predicate) = iter.next() {
let pred_name = summary_predicate_name(&predicate.name);
let name = format!("{}{}", pinned.name, pred_name);
let pipe = iter.peek().map(|_| "├──").unwrap_or("└──");
if !args.silent {
println!(" {pipe} {:<name_col_w$} {}", name, predicate.ca);
}
}
}
}
Ok((plan, built_pkgs))
}
fn source_string(pinned: &pint_pkg::plan::Pinned, manifest_dir: &Path) -> String {
match pinned.source {
pint_pkg::source::Pinned::Member(_) => {
format!("{}", manifest_dir.display())
}
_ => format!("{}", pinned.source),
}
}
fn summary_predicate_name(pred_name: &str) -> &str {
match pred_name {
"" => " (predicate)",
_ => pred_name,
}
}
fn name_col_w(name: &str, built: &BuiltPkg) -> usize {
let mut name_w = 0;
if let BuiltPkg::Contract(contract) = built {
for predicate in &contract.predicate_metadata {
let w = summary_predicate_name(&predicate.name).chars().count();
name_w = std::cmp::max(name_w, w);
}
}
name.chars().count() + name_w
}