use std::{
env, fs,
fs::File,
io::{self, BufRead, BufReader, Write},
path::{Path, PathBuf},
sync::Arc,
};
use assembly::{
diagnostics::{IntoDiagnostic, Result},
utils::Serializable,
Assembler, DefaultSourceManager, KernelLibrary, Library, LibraryNamespace,
};
const ASSETS_DIR: &str = "assets";
const ASM_DIR: &str = "asm";
const ASM_MIDEN_DIR: &str = "miden";
const ASM_NOTE_SCRIPTS_DIR: &str = "note_scripts";
const ASM_TX_KERNEL_DIR: &str = "kernels/transaction";
fn main() -> Result<()> {
println!("cargo:rerun-if-changed=asm");
let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let build_dir = env::var("OUT_DIR").unwrap();
let src = Path::new(&crate_dir).join(ASM_DIR);
let dst = Path::new(&build_dir).to_path_buf();
copy_directory(src, &dst);
let source_dir = dst.join(ASM_DIR);
let target_dir = Path::new(&build_dir).join(ASSETS_DIR);
let mut assembler =
compile_tx_kernel(&source_dir.join(ASM_TX_KERNEL_DIR), &target_dir.join("kernels"))?;
let miden_lib = compile_miden_lib(&source_dir, &target_dir, assembler.clone())?;
assembler.add_library(miden_lib)?;
compile_note_scripts(
&source_dir.join(ASM_NOTE_SCRIPTS_DIR),
&target_dir.join(ASM_NOTE_SCRIPTS_DIR),
assembler,
)?;
Ok(())
}
fn compile_tx_kernel(source_dir: &Path, target_dir: &Path) -> Result<Assembler> {
let assembler = build_assembler(None)?;
match env::var("CARGO_FEATURE_TESTING") {
Ok(ref s) if s == "1" => {
let constants = source_dir.join("lib/constants.masm");
let patched = source_dir.join("lib/constants.masm.patched");
{
let read = File::open(&constants).unwrap();
let mut write = File::create(&patched).unwrap();
let modified = BufReader::new(read).lines().map(decrease_pow);
for line in modified {
write.write_all(line.unwrap().as_bytes()).unwrap();
write.write_all(b"\n").unwrap();
}
write.flush().unwrap();
}
fs::remove_file(&constants).unwrap();
fs::rename(&patched, &constants).unwrap();
},
_ => (),
}
let kernel_lib = KernelLibrary::from_dir(
source_dir.join("api.masm"),
Some(source_dir.join("lib")),
assembler,
)?;
let output_file = target_dir.join("tx_kernel").with_extension(Library::LIBRARY_EXTENSION);
kernel_lib.write_to_file(output_file).into_diagnostic()?;
let assembler = build_assembler(Some(kernel_lib))?;
let mut main_assembler = assembler.clone();
let namespace = LibraryNamespace::new("kernel").expect("invalid namespace");
main_assembler.add_modules_from_dir(namespace, &source_dir.join("lib"))?;
let main_file_path = source_dir.join("main.masm").clone();
let kernel_main = main_assembler.assemble_program(main_file_path)?;
let masb_file_path = target_dir.join("tx_kernel.masb");
kernel_main.write_to_file(masb_file_path).into_diagnostic()?;
#[cfg(feature = "testing")]
{
let namespace = "kernel".parse::<LibraryNamespace>().expect("invalid base namespace");
let test_lib =
Library::from_dir(source_dir.join("lib"), namespace, assembler.clone()).unwrap();
let masb_file_path =
target_dir.join("kernel_library").with_extension(Library::LIBRARY_EXTENSION);
test_lib.write_to_file(masb_file_path).into_diagnostic()?;
}
Ok(assembler)
}
fn decrease_pow(line: io::Result<String>) -> io::Result<String> {
let mut line = line?;
if line.starts_with("const.REGULAR_ACCOUNT_SEED_DIGEST_MODULUS") {
line.clear();
line.push_str("const.REGULAR_ACCOUNT_SEED_DIGEST_MODULUS=32 # reduced via build.rs");
} else if line.starts_with("const.FAUCET_ACCOUNT_SEED_DIGEST_MODULUS") {
line.clear();
line.push_str("const.FAUCET_ACCOUNT_SEED_DIGEST_MODULUS=64 # reduced via build.rs");
}
Ok(line)
}
fn compile_miden_lib(
source_dir: &Path,
target_dir: &Path,
assembler: Assembler,
) -> Result<Library> {
let source_dir = source_dir.join(ASM_MIDEN_DIR);
let namespace = "miden".parse::<LibraryNamespace>().expect("invalid base namespace");
let miden_lib = Library::from_dir(source_dir, namespace, assembler)?;
let output_file = target_dir.join("miden").with_extension(Library::LIBRARY_EXTENSION);
miden_lib.write_to_file(output_file).into_diagnostic()?;
Ok(miden_lib)
}
fn compile_note_scripts(source_dir: &Path, target_dir: &Path, assembler: Assembler) -> Result<()> {
if let Err(e) = fs::create_dir_all(target_dir) {
println!("Failed to create note_scripts directory: {}", e);
}
for masm_file_path in get_masm_files(source_dir).unwrap() {
let code = assembler.clone().assemble_program(masm_file_path.clone())?;
let bytes = code.to_bytes();
let masb_file_name = masm_file_path.file_name().unwrap().to_str().unwrap();
let mut masb_file_path = target_dir.join(masb_file_name);
masb_file_path.set_extension("masb");
fs::write(masb_file_path, bytes).unwrap();
}
Ok(())
}
fn build_assembler(kernel: Option<KernelLibrary>) -> Result<Assembler> {
kernel
.map(|kernel| Assembler::with_kernel(Arc::new(DefaultSourceManager::default()), kernel))
.unwrap_or_default()
.with_debug_mode(cfg!(feature = "with-debug-info"))
.with_library(miden_stdlib::StdLibrary::default())
}
fn copy_directory<T: AsRef<Path>, R: AsRef<Path>>(src: T, dst: R) {
let mut prefix = src.as_ref().canonicalize().unwrap();
prefix.pop();
let target_dir = dst.as_ref().join(ASM_DIR);
if !target_dir.exists() {
fs::create_dir_all(target_dir).unwrap();
}
let dst = dst.as_ref();
let mut todo = vec![src.as_ref().to_path_buf()];
while let Some(goal) = todo.pop() {
for entry in fs::read_dir(goal).unwrap() {
let path = entry.unwrap().path();
if path.is_dir() {
let src_dir = path.canonicalize().unwrap();
let dst_dir = dst.join(src_dir.strip_prefix(&prefix).unwrap());
if !dst_dir.exists() {
fs::create_dir_all(&dst_dir).unwrap();
}
todo.push(src_dir);
} else {
let dst_file = dst.join(path.strip_prefix(&prefix).unwrap());
fs::copy(&path, dst_file).unwrap();
}
}
}
}
fn get_masm_files<P: AsRef<Path>>(dir_path: P) -> io::Result<Vec<PathBuf>> {
let mut files = Vec::new();
let path = dir_path.as_ref();
if path.is_dir() {
match fs::read_dir(path) {
Ok(entries) => {
for entry in entries {
match entry {
Ok(file) => {
let file_path = file.path();
if is_masm_file(&file_path)? {
files.push(file_path);
}
},
Err(e) => println!("Error reading directory entry: {}", e),
}
}
},
Err(e) => println!("Error reading directory: {}", e),
}
} else {
println!("cargo:rerun-The specified path is not a directory.");
}
Ok(files)
}
fn is_masm_file(path: &Path) -> io::Result<bool> {
if let Some(extension) = path.extension() {
let extension = extension
.to_str()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid UTF-8 filename"))?
.to_lowercase();
Ok(extension == "masm")
} else {
Ok(false)
}
}