use std::{ env, fs, io, path::{Path, PathBuf}, process::Command, }; use crate::common::config::DsxConfig; use assembler::prelude::Assembler; use common::build::{BuildError, Builder}; use compiler::Compiler; // ---------- build ---------------------------------------------------------- pub fn build_project(cwd: &Path) -> Result<(), BuildError> { let config: DsxConfig = toml::from_str(&fs::read_to_string(cwd.join("Dsx.toml"))?) .map_err(|deser_err| { io::Error::new(io::ErrorKind::InvalidData, deser_err.to_string()) })?; let src_dir = cwd.join("src"); if !src_dir.exists() { return Err(BuildError::Generic(String::from( "Source Directory does not exist", ))); } // make sure there's a main file to assemble later. if !main_exists(&src_dir)? { return Err(BuildError::Generic(String::from( "No main.dsa or main.dsc file found in top level of src directory.", ))); } // detect src. let (has_dsa, has_dsc) = detect_source_language(&src_dir); // create a build dir and copy all files across let build_dir = cwd.join("build"); fs::create_dir_all(&build_dir)?; env::set_current_dir(&build_dir)?; copy_recursively(&src_dir, &build_dir)?; if has_dsc { build_all_dsc(&build_dir)?; } // Replace .dsc with .dsa only in include statements, recursively for each file. let mut sed_cmd = Command::new("bash"); sed_cmd.args([ "-c", &format!( "find \"{}\" -type f -name '*.dsa' -exec sed -i '/^include/ s/\\.dsc/.dsa/g' {{}} +", build_dir.display() ), ]); run(&mut sed_cmd); // assemble result { fs::create_dir_all(cwd.join("artifacts"))?; let mut asm = Assembler::new("./main.dsa"); asm.start(); asm.write_result("../artifacts/out.dsb")?; } println!("Build finished. Binary at {}/main.dsb", build_dir.display()); Ok(()) } // ----- Helpers ------------------------------- struct BuildStep; impl BuildStep { pub fn compiling(path: &Path) { println!("Compiling {}", path.display()); } pub fn assembling(path: &Path) { println!("Assembling {}", path.display()); } } /// Checks what source languages are used in the project. fn detect_source_language(src_dir: &Path) -> (bool, bool) { let mut contains_dsc = false; let mut contains_dsa = false; for entry in walkdir::WalkDir::new(src_dir).into_iter().flatten() { match entry.path().extension().and_then(|s| s.to_str()) { Some("dsc") => contains_dsc = true, Some("dsa") => contains_dsa = true, _ => {} } } (contains_dsa, contains_dsc) } // Checks if either main.dsa or main.dsc exist in the source directory fn main_exists(src_dir: &Path) -> Result { for entry in fs::read_dir(src_dir).into_iter().flatten() { match entry?.path().file_name().and_then(|s| s.to_str()) { Some("main.dsc") => return Ok(true), Some("main.dsa") => return Ok(true), _ => {} } } Ok(false) } // Copy contents of one directory to another fn copy_recursively(src: &Path, dst: &Path) -> Result<(), std::io::Error> { if src.is_file() { fs::create_dir_all(dst.parent().unwrap())?; fs::copy(src, dst)?; return Ok(()); } if src.is_dir() { for entry in fs::read_dir(src)? { let entry = entry?; let child_src = entry.path(); let child_dst = dst.join(entry.file_name()); copy_recursively(&child_src, &child_dst)?; } } Ok(()) } fn build_all_dsc(path: &Path) -> Result<(), BuildError> { if path.is_dir() { for entry in fs::read_dir(path)? { let entry = entry?; build_all_dsc(&entry.path())?; } return Ok(()); } if path.extension().and_then(|s| s.to_str()) == Some("dsc") { let input_path = path; let output_path = path.with_extension("dsa"); let mut compiler = Compiler::new(input_path); compiler.start(); compiler.write_result(output_path.clone())?; } Ok(()) } /// Run a command and exit on failure. fn run(cmd: &mut Command) { let status = cmd.status().expect("failed to execute command"); if !status.success() { std::process::exit(1); } }