use std::{path::{Path, PathBuf}, io::Write, sync::{Arc, Mutex}};
use cargo::{
core::compiler::{CompileKind, CompileTarget},
ops::CompileOptions,
util::interning::InternedString,
Config,
};
use crate::error::Error;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
pub(crate) fn random_temp_dir_name() -> PathBuf {
std::env::temp_dir()
.join(
thread_rng()
.sample_iter(&Alphanumeric)
.take(5)
.map(char::from)
.collect::<String>(),
)
.to_path_buf()
}
pub(crate) fn build_contract(
working_folder: &Path,
source_path: &Path,
destination_path: Option<PathBuf>,
locked: bool,
wasm_file: &str,
) -> Result<(), Error> {
let output_path = destination_path.unwrap_or(Path::new(".").to_path_buf());
let use_cargo_lock = locked && source_path.join("Cargo.lock").exists();
let mut config = CargoConfig::new();
config
.configure(0, false, None, false, use_cargo_lock, false, &None, &[], &[])
.unwrap();
let mut compile_configs =
CompileOptions::new(&config, cargo::util::command_prelude::CompileMode::Build).unwrap();
compile_configs.build_config.requested_kinds = vec![CompileKind::Target(
CompileTarget::new("wasm32-unknown-unknown").unwrap(),
)];
compile_configs.build_config.requested_profile = InternedString::new("release");
let ws =
cargo::core::Workspace::new(&source_path.join("Cargo.toml"), &config).map_err(|e| {
Error::BuildFailure(
format!("Error in preparing workspace according to the manifest file in source path:\n\n{:?}\n", e),
)
})?;
if let Err(_) = cargo::ops::compile(&ws, &compile_configs) {
return Err(Error::BuildFailureWithLogs(config.logs()))
}
if locked {
let _ = std::fs::copy(source_path.join("Cargo.lock"), output_path.join("Cargo.lock"));
}
let temp_wasm = working_folder.join("temp.wasm");
wasm_opt::OptimizationOptions::new_optimize_for_size_aggressively()
.run(
source_path
.join("target")
.join("wasm32-unknown-unknown")
.join("release")
.join(wasm_file),
&temp_wasm,
)
.map_err(|e| Error::BuildFailure(format!("Wasm optimization error:\n\n{:?}\n", e)))?;
let temp2_wasm = working_folder.join("temp2.wasm");
let wasm_snip_options = wasm_snip::Options {
snip_rust_fmt_code: true,
snip_rust_panicking_code: true,
..Default::default()
};
let mut module = walrus::ModuleConfig::new()
.parse_file(temp_wasm)
.map_err(|e| Error::BuildFailure(format!("Wasm snip error:\n\n{:?}\n", e)))?;
wasm_snip::snip(&mut module, wasm_snip_options)
.map_err(|e| Error::BuildFailure(format!("Wasm snip error:\n\n{:?}\n", e)))?;
module
.emit_wasm_file(&temp2_wasm)
.map_err(|e| Error::BuildFailure(format!("Wasm snip error:\n\n{:?}\n", e)))?;
let optimized_wasm = output_path.join(wasm_file);
wasm_opt::OptimizationOptions::new_optimize_for_size()
.add_pass(wasm_opt::Pass::Dce)
.run(temp2_wasm, optimized_wasm)
.map_err(|e| Error::BuildFailure(format!("Wasm optimization error:\n\n{:?}\n", e)))?;
Ok(())
}
pub struct CargoConfig {
logs: Arc<Mutex<Vec<String>>>,
config: Config,
}
impl CargoConfig {
pub fn new() -> Self {
let logs = Arc::new(Mutex::new(Vec::<String>::new()));
let log_writer = BuildLogWriter { buffer: logs.clone() };
let shell = cargo::core::Shell::from_write(Box::new(log_writer));
let current_dir = std::env::current_dir().unwrap();
let home_dir = cargo::util::homedir(¤t_dir).unwrap();
let config = Config::new(shell, current_dir, home_dir);
Self {
logs,
config
}
}
pub fn logs(&self) -> String{
self.logs.lock().unwrap().join("")
}
}
impl std::ops::Deref for CargoConfig {
type Target = Config;
fn deref(&self) -> &Self::Target {
&self.config
}
}
impl std::ops::DerefMut for CargoConfig {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.config
}
}
#[derive(Default)]
pub struct BuildLogWriter {
pub buffer: Arc<Mutex<Vec<String>>>
}
impl Write for BuildLogWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
if let Ok(ref mut mutex) = self.buffer.try_lock() {
mutex.push(String::from_utf8_lossy(buf).to_string());
}
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}