1use anyhow::Context;
4use clap::builder::styling::Style;
5use pint_pkg::{
6 build::{BuiltPkg, BuiltPkgs},
7 manifest::{ManifestFile, PackageKind},
8 plan::Plan,
9};
10use std::{
11 collections::HashMap,
12 path::{Path, PathBuf},
13};
14
15#[derive(clap::Args, Debug)]
17pub struct Args {
18 #[arg(long = "manifest-path")]
23 manifest_path: Option<PathBuf>,
24 #[arg(long, value_parser = parse_hex)]
32 salt: Option<[u8; 32]>,
33 #[arg(long = "print-parsed")]
35 print_parsed: bool,
36 #[arg(long = "print-flat")]
38 print_flat: bool,
39 #[arg(long = "print-optimized")]
41 print_optimized: bool,
42 #[arg(long = "print-asm")]
44 print_asm: bool,
45 #[arg(long = "skip-optimize", hide = true)]
47 skip_optimize: bool,
48 #[arg(long)]
50 silent: bool,
51}
52
53fn parse_hex(value: &str) -> Result<[u8; 32], String> {
58 if value.len() > 64 || !value.chars().all(|c| c.is_ascii_hexdigit()) {
59 return Err("Salt must be a hexadecimal number with up to 64 digts (256 bits)".to_string());
60 }
61
62 let padded_value = format!("{:0>64}", value);
64 let mut salt = [0u8; 32];
65 for i in 0..32 {
66 salt[i] = u8::from_str_radix(&padded_value[2 * i..2 * i + 2], 16)
67 .map_err(|_| "Invalid hexadecimal value")?;
68 }
69 Ok(salt)
70}
71
72fn find_file(mut dir: PathBuf, file_name: &str) -> Option<PathBuf> {
74 loop {
75 let path = dir.join(file_name);
76 if path.exists() {
77 return Some(path);
78 }
79 if !dir.pop() {
80 return None;
81 }
82 }
83}
84
85pub fn cmd(args: Args) -> anyhow::Result<(Plan, BuiltPkgs)> {
89 let build_start = std::time::Instant::now();
90
91 let manifest_path = match args.manifest_path {
93 Some(path) => path,
94 None => {
95 let current_dir = std::env::current_dir()?;
96 match find_file(current_dir, ManifestFile::FILE_NAME) {
97 None => anyhow::bail!("no `pint.toml` in the current or parent directories"),
98 Some(path) => path,
99 }
100 }
101 };
102
103 let bold = Style::new().bold();
105
106 let manifest = ManifestFile::from_path(&manifest_path).context("failed to load manifest")?;
108 if let PackageKind::Library = manifest.pkg.kind {
109 if args.salt.is_some() {
110 anyhow::bail!("specifying `salt` for a library package is not allowed");
111 }
112 }
113
114 let name = manifest.pkg.name.to_string();
115 let members = [(name, manifest.clone())].into_iter().collect();
116 let plan = pint_pkg::plan::from_members(&members).context("failed to plan compilation")?;
118
119 let mut builder = pint_pkg::build::build_plan(&plan);
121 let options = pint_pkg::build::BuildOptions {
122 salts: HashMap::from_iter([(manifest.clone(), args.salt.unwrap_or_default())]),
123 print_parsed: args.print_parsed,
124 print_flat: args.print_flat,
125 print_optimized: args.print_optimized,
126 print_asm: args.print_asm,
127 skip_optimize: args.skip_optimize,
128 };
129
130 while let Some(prebuilt) = builder.next_pkg() {
131 let pinned = prebuilt.pinned();
132 let manifest = &plan.manifests()[&pinned.id()];
133 let source_str = source_string(pinned, manifest.dir());
134
135 if !args.silent {
136 println!(
137 " {}Compiling{} {} [{}] ({})",
138 bold.render(),
139 bold.render_reset(),
140 pinned.name,
141 manifest.pkg.kind,
142 source_str,
143 );
144 }
145
146 let _built = match prebuilt.build(&options) {
148 Ok(built) => {
149 built.print_warnings();
150 built
151 }
152 Err(err) => {
153 let msg = format!("{}", err.kind);
154 err.print_diagnostics();
155 anyhow::bail!("{msg}");
156 }
157 };
158 }
159
160 let built_pkgs = builder.into_built_pkgs();
162
163 if let Some(&n) = plan.compilation_order().last() {
165 let built = &built_pkgs[&n];
166 let pinned = &plan.graph()[n];
167 let manifest = &plan.manifests()[&pinned.id()];
168
169 let out_dir = manifest.out_dir();
172 let profile = "debug";
173 let profile_dir = out_dir.join(profile);
174 std::fs::create_dir_all(&profile_dir)
175 .with_context(|| format!("failed to create directory {profile_dir:?}"))?;
176
177 built
179 .write_to_dir(&pinned.name, &profile_dir)
180 .with_context(|| format!("failed to write output artifacts to {profile_dir:?}"))?;
181
182 if !args.silent {
183 println!(
185 " {}Finished{} build [{profile}] in {:?}",
186 bold.render(),
187 bold.render_reset(),
188 build_start.elapsed()
189 );
190 }
191
192 let kind_str = format!("{}", manifest.pkg.kind);
194 let padded_kind_str = format!("{kind_str:>12}");
195 let padding = &padded_kind_str[..padded_kind_str.len() - kind_str.len()];
196 let ca = match built {
197 BuiltPkg::Contract(contract) => format!("{}", &contract.ca),
198 _ => "".to_string(),
199 };
200 let name_col_w = name_col_w(&pinned.name, built);
201
202 if !args.silent {
203 println!(
204 "{padding}{}{kind_str}{} {:<name_col_w$} {}",
205 bold.render(),
206 bold.render_reset(),
207 pinned.name,
208 ca,
209 );
210 }
211
212 if let BuiltPkg::Contract(contract) = built {
214 let mut iter = contract.predicate_metadata.iter().peekable();
215 while let Some(predicate) = iter.next() {
216 let pred_name = summary_predicate_name(&predicate.name);
217 let name = format!("{}{}", pinned.name, pred_name);
218 let pipe = iter.peek().map(|_| "├──").unwrap_or("└──");
219 if !args.silent {
220 println!(" {pipe} {:<name_col_w$} {}", name, predicate.ca);
221 }
222 }
223 }
224 }
225
226 Ok((plan, built_pkgs))
227}
228
229fn source_string(pinned: &pint_pkg::plan::Pinned, manifest_dir: &Path) -> String {
231 match pinned.source {
232 pint_pkg::source::Pinned::Member(_) => {
233 format!("{}", manifest_dir.display())
234 }
235 _ => format!("{}", pinned.source),
236 }
237}
238
239fn summary_predicate_name(pred_name: &str) -> &str {
241 match pred_name {
242 "" => " (predicate)",
243 _ => pred_name,
244 }
245}
246
247fn name_col_w(name: &str, built: &BuiltPkg) -> usize {
250 let mut name_w = 0;
251 if let BuiltPkg::Contract(contract) = built {
252 for predicate in &contract.predicate_metadata {
253 let w = summary_predicate_name(&predicate.name).chars().count();
254 name_w = std::cmp::max(name_w, w);
255 }
256 }
257 name.chars().count() + name_w
258}