build_solidity_contracts/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3#![deny(missing_docs)]
4
5use std::{path::PathBuf, fs, process::Command};
6
7/// Build contracts from the specified path, outputting the artifacts to the specified path.
8///
9/// Requires solc 0.8.25.
10pub fn build(contracts_path: &str, artifacts_path: &str) -> Result<(), String> {
11  println!("cargo:rerun-if-changed={contracts_path}/*");
12  println!("cargo:rerun-if-changed={artifacts_path}/*");
13
14  for line in String::from_utf8(
15    Command::new("solc")
16      .args(["--version"])
17      .output()
18      .map_err(|_| "couldn't fetch solc output".to_string())?
19      .stdout,
20  )
21  .map_err(|_| "solc stdout wasn't UTF-8")?
22  .lines()
23  {
24    if let Some(version) = line.strip_prefix("Version: ") {
25      let version =
26        version.split('+').next().ok_or_else(|| "no value present on line".to_string())?;
27      if version != "0.8.25" {
28        Err(format!("version was {version}, 0.8.25 required"))?
29      }
30    }
31  }
32
33  #[rustfmt::skip]
34  let args = [
35    "--base-path", ".",
36    "-o", artifacts_path, "--overwrite",
37    "--bin", "--bin-runtime", "--abi",
38    "--via-ir", "--optimize",
39    "--no-color",
40  ];
41  let mut args = args.into_iter().map(str::to_string).collect::<Vec<_>>();
42
43  let mut queue = vec![PathBuf::from(contracts_path)];
44  while let Some(folder) = queue.pop() {
45    for entry in fs::read_dir(folder).map_err(|e| format!("couldn't read directory: {e:?}"))? {
46      let entry = entry.map_err(|e| format!("couldn't read directory in entry: {e:?}"))?;
47      let kind = entry.file_type().map_err(|e| format!("couldn't fetch file type: {e:?}"))?;
48      if kind.is_dir() {
49        queue.push(entry.path());
50      }
51
52      if kind.is_file() &&
53        entry
54          .file_name()
55          .into_string()
56          .map_err(|_| "file name wasn't a valid UTF-8 string".to_string())?
57          .ends_with(".sol")
58      {
59        args.push(
60          entry
61            .path()
62            .into_os_string()
63            .into_string()
64            .map_err(|_| "file path wasn't a valid UTF-8 string".to_string())?,
65        );
66      }
67
68      // We on purposely ignore symlinks to avoid recursive structures
69    }
70  }
71
72  let solc = Command::new("solc")
73    .args(args)
74    .output()
75    .map_err(|_| "couldn't fetch solc output".to_string())?;
76  let stderr =
77    String::from_utf8(solc.stderr).map_err(|_| "solc stderr wasn't UTF-8".to_string())?;
78  if !solc.status.success() {
79    Err(format!("solc didn't successfully execute: {stderr}"))?;
80  }
81  for line in stderr.lines() {
82    if line.contains("Error:") {
83      Err(format!("solc output had error: {stderr}"))?;
84    }
85  }
86
87  Ok(())
88}