pint_pkg/
build.rs

1//! Given a compilation [`Plan`][crate::plan::Plan], build all packages in the graph.
2
3use crate::{
4    manifest,
5    plan::{Graph, NodeIx, Pinned, PinnedManifests, Plan},
6};
7use clap::builder::styling::Style;
8use essential_types::{
9    contract::Contract,
10    predicate::{Predicate as CompiledPredicate, Program},
11    ContentAddress,
12};
13use pint_abi_types::ContractABI;
14use pintc::asm_gen::compile_contract;
15use std::{
16    collections::{BTreeSet, HashMap},
17    path::{Path, PathBuf},
18};
19use thiserror::Error;
20
21/// A context that allows for iteratively compiling packages within a given compilation `Plan`.
22pub struct PlanBuilder<'p> {
23    /// The plan that is being executed by this builder.
24    pub plan: &'p Plan,
25    built_pkgs: BuiltPkgs,
26    order: std::slice::Iter<'p, NodeIx>,
27}
28
29/// A package that is ready to be built.
30pub struct PrebuiltPkg<'p, 'b> {
31    pub plan: &'p Plan,
32    built_pkgs: &'b mut BuiltPkgs,
33    /// The node of the package to be built.
34    n: NodeIx,
35}
36
37#[derive(Default)]
38pub struct BuildOptions {
39    /// A 256-bit unsigned integer in hexadeciaml format that represents the contract "salt". The
40    /// value is left padded with zeros if it has less than 64 hexadecimal digits.
41    ///
42    /// The "salt" is hashed along with the contract's bytecode in order to make the address of the
43    /// contract unique.
44    ///
45    /// If "salt" is provided for a library package, an error is emitted.
46    pub salts: HashMap<manifest::ManifestFile, [u8; 32]>,
47    /// Print the parsed pint program.
48    pub print_parsed: bool,
49    /// Print the flattened pint program.
50    pub print_flat: bool,
51    /// Print the optimized pint program.
52    pub print_optimized: bool,
53    /// Print the assembly.
54    pub print_asm: bool,
55    /// Skip optimizing the pint program.
56    pub skip_optimize: bool,
57}
58
59/// A mapping from the node index to the associated built package.
60pub type BuiltPkgs = HashMap<NodeIx, BuiltPkg>;
61
62/// A successfully built package.
63#[derive(Debug)]
64pub enum BuiltPkg {
65    /// A built contract.
66    Contract(BuiltContract),
67    /// A built library.
68    Library(BuiltLibrary),
69}
70
71/// A successfully built contract package.
72#[derive(Debug)]
73pub struct BuiltContract {
74    /// All the emitted warnings.
75    pub warnings: pintc::warning::Warnings,
76    /// All built predicates.
77    pub predicate_metadata: Vec<PredicateMetadata>,
78    /// The actual contract object
79    pub contract: Contract,
80    /// The individual programs.
81    pub programs: BTreeSet<Program>,
82    /// The content address of the contract.
83    pub ca: ContentAddress,
84    /// The entry-point into the temp library submodules used to provide the CAs.
85    pub lib_entry_point: PathBuf,
86    /// The ABI for the contract.
87    pub abi: ContractABI,
88    /// The optimized contract.
89    pub optimized: pintc::predicate::Contract,
90}
91
92/// An predicate built as a part of a contract.
93#[derive(Debug)]
94pub struct BuiltPredicate {
95    /// The content address of the predicate.
96    pub ca: ContentAddress,
97    /// The name of the predicate from the code.
98    pub name: String,
99    pub predicate: CompiledPredicate,
100}
101
102/// An predicate built as a part of a contract.
103#[derive(Debug)]
104pub struct PredicateMetadata {
105    /// The content address of the predicate.
106    pub ca: ContentAddress,
107    /// The name of the predicate from the code.
108    pub name: String,
109}
110
111/// A successfully built library package.
112#[derive(Debug)]
113pub struct BuiltLibrary {
114    /// All the emitted warnings.
115    pub warnings: pintc::warning::Warnings,
116    /// The compiled contract.
117    pub contract: pintc::predicate::Contract,
118}
119
120/// An error occurred while building according to a compilation plan.
121#[derive(Debug)]
122pub struct BuildError {
123    /// Packages that were successfully built.
124    pub built_pkgs: BuiltPkgs,
125    /// The package error that occurred.
126    pub pkg_err: BuildPkgError,
127}
128
129/// An error was produced while building a package.
130#[derive(Debug)]
131pub struct BuildPkgError {
132    /// The pintc error handler.
133    pub handler: pintc::error::Handler,
134    /// The kind of build error that occurred.
135    pub kind: BuildPkgErrorKind,
136}
137
138#[derive(Debug, Error)]
139pub enum BuildPkgErrorKind {
140    #[error("`pintc` encountered an error: {0}")]
141    Pintc(#[from] PintcError),
142    #[error("failed to create lib providing contract and predicate CAs for {0:?}: {1}")]
143    ContractLibrary(String, std::io::Error),
144}
145
146#[derive(Debug, Error)]
147pub enum PintcError {
148    #[error("parse error")]
149    Parse,
150    #[error("type check error")]
151    TypeCheck,
152    #[error("flattening error")]
153    Flatten,
154    #[error("abi gen")]
155    ABIGen,
156    #[error("asm-gen error")]
157    AsmGen,
158}
159
160/// An error occurred while writing a built package's output artifacts.
161#[derive(Debug, Error)]
162pub enum WriteError {
163    /// Failed to serialize contract or ABI.
164    #[error("failed to serialize contract or ABI: {0}")]
165    SerdeJson(#[from] serde_json::Error),
166    /// An I/O error occurred.
167    #[error("an I/O error occurred: {0}")]
168    Io(#[from] std::io::Error),
169}
170
171impl PlanBuilder<'_> {
172    /// Produce the next package that is to be built.
173    pub fn next_pkg(&mut self) -> Option<PrebuiltPkg> {
174        let &n = self.order.next()?;
175        Some(PrebuiltPkg {
176            plan: self.plan,
177            built_pkgs: &mut self.built_pkgs,
178            n,
179        })
180    }
181
182    /// Access the set of packages that have been built so far.
183    pub fn built_pkgs(&self) -> &BuiltPkgs {
184        &self.built_pkgs
185    }
186
187    /// Build all remaining packages.
188    #[allow(clippy::result_large_err)]
189    pub fn build_all(mut self, options: &BuildOptions) -> Result<BuiltPkgs, BuildError> {
190        while let Some(prebuilt) = self.next_pkg() {
191            if let Err(pkg_err) = prebuilt.build(options) {
192                let built_pkgs = self.built_pkgs;
193                return Err(BuildError {
194                    built_pkgs,
195                    pkg_err,
196                });
197            }
198        }
199        Ok(self.built_pkgs)
200    }
201
202    /// Consume the builder and return the set of `BuiltPkgs` that have already been built.
203    pub fn into_built_pkgs(self) -> BuiltPkgs {
204        self.built_pkgs
205    }
206}
207
208impl<'p, 'b> PrebuiltPkg<'p, 'b> {
209    /// The index of the package within the plan's package graph.
210    pub fn node_ix(&self) -> NodeIx {
211        self.n
212    }
213
214    /// This package's pinned representation from within the compilation graph.
215    pub fn pinned(&self) -> &'p Pinned {
216        &self.plan.graph()[self.n]
217    }
218
219    /// Build this package.
220    pub fn build(self, options: &BuildOptions) -> Result<&'b BuiltPkg, BuildPkgError> {
221        let Self {
222            plan,
223            built_pkgs,
224            n,
225        } = self;
226        let built = build_pkg(plan, built_pkgs, n, options)?;
227        built_pkgs.insert(n, built);
228        Ok(&built_pkgs[&n])
229    }
230}
231
232impl BuildPkgError {
233    /// Consume `self` and print the errors and the warnings.
234    pub fn print_diagnostics(self) {
235        let (errors, warnings) = self.handler.consume();
236        pintc::error::print_errors(&pintc::error::Errors(errors));
237        pintc::warning::print_warnings(&pintc::warning::Warnings(warnings));
238    }
239}
240
241impl BuiltPkg {
242    /// Write the built artifacts for this package to the given directory.
243    pub fn write_to_dir(&self, name: &str, path: &Path) -> Result<(), WriteError> {
244        match self {
245            Self::Library(_) => (),
246            Self::Contract(built) => {
247                // Write the contract.
248                let contract_string =
249                    serde_json::to_string_pretty(&(&built.contract, &built.programs))?;
250                let contract_path = path.join(name).with_extension("json");
251                std::fs::write(contract_path, contract_string)?;
252
253                // Write the ABI.
254                let abi_string = serde_json::to_string_pretty(&built.abi)?;
255                let file_stem = format!("{}-abi", name);
256                let abi_path = path.join(file_stem).with_extension("json");
257                std::fs::write(abi_path, abi_string)?;
258            }
259        }
260        Ok(())
261    }
262
263    /// Print all emitted warnings.
264    pub fn print_warnings(&self) {
265        let (Self::Contract(BuiltContract { warnings, .. })
266        | Self::Library(BuiltLibrary { warnings, .. })) = self;
267
268        pintc::warning::print_warnings(warnings);
269    }
270}
271
272/// Collect the immediate dependencies of the given package.
273fn dependencies<'a>(
274    n: NodeIx,
275    g: &Graph,
276    manifests: &'a PinnedManifests,
277    built_pkgs: &'a BuiltPkgs,
278) -> HashMap<String, PathBuf> {
279    use petgraph::{visit::EdgeRef, Direction};
280    g.edges_directed(n, Direction::Outgoing)
281        .map(|e| {
282            let name = e.weight().name.to_string();
283            let dep_n = e.target();
284            let pinned = &g[dep_n];
285            let manifest = &manifests[&pinned.id()];
286            let entry_point = match &built_pkgs[&dep_n] {
287                BuiltPkg::Library(_lib) => manifest.entry_point(),
288                BuiltPkg::Contract(contract) => contract.lib_entry_point.clone(),
289            };
290            (name, entry_point)
291        })
292        .collect()
293}
294
295/// Given a built contract, generate a library with a module and constant for
296/// each predicate's contract address along with a constant for the contract's
297/// content address.
298///
299/// Returns the entry point to the library.
300fn contract_dep_lib(
301    ca: &ContentAddress,
302    predicates: &[BuiltPredicate],
303) -> std::io::Result<PathBuf> {
304    // Temporary directory for the contract project.
305    let temp_dir = std::env::temp_dir().join(format!("{:x}", ca));
306    std::fs::create_dir_all(&temp_dir)?;
307
308    // Write the contract's CA to the library root.
309    let lib_str = format!("const ADDRESS: b256 = 0x{:x};", ca);
310    let lib_path = temp_dir.join("lib.pnt");
311    std::fs::write(&lib_path, lib_str.as_bytes())?;
312
313    // Write the predicate CAs to submodules.
314    for predicate in predicates {
315        let submod_str = format!("const ADDRESS: b256 = 0x{:x};", predicate.ca);
316
317        // Create the path to the submodule from the predicate name.
318        let mut submod: Vec<&str> = predicate.name.split("::").collect();
319        // The root predicate is nameless when output from pint, so we give it a name.
320        if matches!(&submod[..], &[""]) {
321            submod = vec!["root"];
322        }
323        let mut submod_path = temp_dir.clone();
324        submod_path.extend(submod.clone());
325        submod_path.set_extension("pnt");
326        std::fs::create_dir_all(submod_path.parent().expect("submod has no parent dir"))?;
327        std::fs::write(&submod_path, submod_str.as_bytes())?;
328    }
329
330    Ok(lib_path)
331}
332
333/// Build the package at the given index, assuming all dependencies are already built.
334fn build_pkg(
335    plan: &Plan,
336    built_pkgs: &BuiltPkgs,
337    n: NodeIx,
338    options: &BuildOptions,
339) -> Result<BuiltPkg, BuildPkgError> {
340    let graph = plan.graph();
341    let pinned = &graph[n];
342    let manifest = &plan.manifests()[&pinned.id()];
343    let entry_point = manifest.entry_point();
344    let handler = pintc::error::Handler::default();
345    let deps = dependencies(n, graph, plan.manifests(), built_pkgs);
346    let source_str = match pinned.source {
347        crate::source::Pinned::Member(_) => {
348            format!("{}", manifest.dir().display())
349        }
350        _ => format!("{}", pinned.source),
351    };
352
353    // Parse the package from the entry point.
354    let deps = deps
355        .iter()
356        .map(|(name, path)| (name.as_str(), path.as_path()))
357        .collect();
358    let Ok(parsed) = pintc::parser::parse_project(&handler, &deps, &entry_point) else {
359        let kind = BuildPkgErrorKind::from(PintcError::Parse);
360        return Err(BuildPkgError { handler, kind });
361    };
362
363    let bold = Style::new().bold();
364    if options.print_parsed {
365        println!(
366            "   {}Printing parsed{} {} [{}] ({})",
367            bold.render(),
368            bold.render_reset(),
369            pinned.name,
370            manifest.pkg.kind,
371            source_str,
372        );
373        println!("\n{parsed}");
374    }
375
376    // Type check the package.
377    let Ok(contract) = handler.scope(|handler| parsed.type_check(handler)) else {
378        let kind = BuildPkgErrorKind::from(PintcError::TypeCheck);
379        return Err(BuildPkgError { handler, kind });
380    };
381
382    let built_pkg = match manifest.pkg.kind {
383        manifest::PackageKind::Library => {
384            // TODO: Add checks here to make sure the library is sane.. E.g., the library is
385            // stateless, etc.
386            let lib = BuiltLibrary {
387                warnings: pintc::warning::Warnings(handler.consume().1),
388                contract,
389            };
390            BuiltPkg::Library(lib)
391        }
392        manifest::PackageKind::Contract => {
393            // Flatten the contract to flat pint (the IR).
394            let Ok(flattened) = handler.scope(|handler| contract.flatten(handler)) else {
395                let kind = BuildPkgErrorKind::from(PintcError::Flatten);
396                return Err(BuildPkgError { handler, kind });
397            };
398
399            if options.print_flat {
400                println!(
401                    "   {}Printing flattened{} {} [{}] ({})",
402                    bold.render(),
403                    bold.render_reset(),
404                    pinned.name,
405                    manifest.pkg.kind,
406                    source_str,
407                );
408                println!("\n{flattened}");
409            }
410
411            // Perform optimizations on the flattened contract.
412            let optimized = if options.skip_optimize {
413                flattened
414            } else {
415                flattened.optimize(&handler)
416            };
417
418            if options.print_optimized {
419                println!(
420                    "   {}Printing optimized{} {} [{}] ({})",
421                    bold.render(),
422                    bold.render_reset(),
423                    pinned.name,
424                    manifest.pkg.kind,
425                    source_str,
426                );
427                println!("\n{optimized}");
428            }
429
430            // Produce the ABI for the flattened contract.
431            let Ok(abi) = optimized.abi(&handler) else {
432                let kind = BuildPkgErrorKind::from(PintcError::ABIGen);
433                return Err(BuildPkgError { handler, kind });
434            };
435
436            // Generate the assembly and the predicates.
437            let Ok(contract) = handler.scope(|h| {
438                compile_contract(
439                    h,
440                    *options.salts.get(manifest).unwrap_or(&[0; 32]),
441                    &optimized,
442                )
443            }) else {
444                let kind = BuildPkgErrorKind::from(PintcError::AsmGen);
445                return Err(BuildPkgError { handler, kind });
446            };
447
448            if options.print_asm {
449                println!(
450                    "   {}Printing assembly for{} {} [{}] ({})",
451                    bold.render(),
452                    bold.render_reset(),
453                    pinned.name,
454                    manifest.pkg.kind,
455                    source_str,
456                );
457                println!("\n{contract}");
458            }
459
460            // Collect the predicates alongside their content addresses.
461            let predicates: Vec<_> = contract
462                .contract
463                .predicates
464                .clone()
465                .into_iter()
466                .zip(contract.names)
467                .map(|(predicate, name)| {
468                    let ca = essential_hash::content_addr(&predicate);
469                    BuiltPredicate {
470                        ca,
471                        name,
472                        predicate,
473                    }
474                })
475                .collect();
476
477            // The CA of the contract.
478            let ca = essential_hash::contract_addr::from_predicate_addrs(
479                predicates.iter().map(|predicate| predicate.ca.clone()),
480                &contract.contract.salt,
481            );
482
483            // Generate a temp lib for providing the contract and predicate CAs to dependents.
484            let lib_entry_point = match contract_dep_lib(&ca, &predicates) {
485                Ok(path) => path,
486                Err(e) => {
487                    let kind = BuildPkgErrorKind::ContractLibrary(pinned.name.clone(), e);
488                    return Err(BuildPkgError { handler, kind });
489                }
490            };
491
492            let predicate_metadata = predicates
493                .into_iter()
494                .map(|BuiltPredicate { ca, name, .. }| PredicateMetadata { ca, name })
495                .collect::<Vec<_>>();
496
497            let contract = BuiltContract {
498                warnings: pintc::warning::Warnings(handler.consume().1),
499                ca,
500                predicate_metadata,
501                contract: contract.contract,
502                programs: contract.programs,
503                lib_entry_point,
504                abi,
505                optimized,
506            };
507            BuiltPkg::Contract(contract)
508        }
509    };
510
511    Ok(built_pkg)
512}
513
514/// Given a compilation [`Plan`][crate::plan::Plan], return a [`PlanBuilder`]
515/// that may be used to compile all packages within the graph.
516pub fn build_plan(plan: &Plan) -> PlanBuilder {
517    PlanBuilder {
518        built_pkgs: BuiltPkgs::default(),
519        plan,
520        order: plan.compilation_order().iter(),
521    }
522}