use crate::reporting::{CompilerError, Problem}; use elmi::DataBinary; use std::collections::HashMap; use std::fs::{self, canonicalize, metadata}; use std::io::{self, BufRead, Error, ErrorKind, Write}; use std::path::{Path, PathBuf}; use std::process::Command; use tracing::info_span; pub fn make(file: &Path, debug: bool, verbosity: u64) -> Result<(), Problem> { let mut command = Command::new("elm"); command.arg("make").arg("--output").arg("/dev/null"); if debug { command.arg("--debug"); } command.arg(file); let span = info_span!("elm make target file"); let timing_guard = span.enter(); match command.output() { Ok(output) => { if !output.status.success() { io::stderr().write_all(&output.stderr).unwrap(); io::stderr().write_all(&output.stdout).unwrap(); return Err(CompilerError::FailedBuildingFixture.into()); } if verbosity > 0 { io::stderr().write_all(&output.stderr).unwrap(); } } Err(_io_err) => { // TODO handle this one return Err(Problem::Wildcard("elm failed".into())); } } drop(timing_guard); Ok(()) } pub fn load_interfaces( elm_cache_dir: &Path, ) -> Result, Problem> { let mut interfaces = HashMap::new(); let entries = fs::read_dir(&elm_cache_dir) .map_err(|io_err| CompilerError::ReadInputFailed(io_err, elm_cache_dir.to_path_buf()))?; for entry in entries { let entry = entry.map_err(|io_err| { CompilerError::ReadInputFailed(io_err, elm_cache_dir.to_path_buf()) })?; let path = entry.path(); if path.is_file() { match path.file_name() { Some(file_name) if file_name == "i.dat" => { let data = std::fs::read(&path) .map_err(|io_err| CompilerError::ReadInputFailed(io_err, path.clone()))?; let (_remaining, i) = ::get(&data) .map_err(|_err| { CompilerError::FailedElmiParse("todo elmi parsing".to_owned()) })?; for (module_name, dep_interface) in i.into_iter() { match dep_interface { elmi::DependencyInterface::Public(interface) => { interfaces.insert(module_name, interface); } elmi::DependencyInterface::Private(package_name, unions, aliases) => { println!("skipping private interface {}", package_name); //for (k, v) in unions { // println!(" {}", k); //} } } } } _ => {} } match path.extension() { Some(ext) if ext == "elmi" => { // step 4 load all the modules let parse_span = info_span!("parse elmi"); let _parse_guard = parse_span.enter(); let load_span = info_span!("file reads"); let load_guard = load_span.enter(); let data = std::fs::read(&path) .map_err(|io_err| CompilerError::ReadInputFailed(io_err, path.clone()))?; drop(load_guard); let (_remaining, i) = elmi::Interface::get(&data).map_err(|_err| { CompilerError::FailedElmiParse("todo elmi parsing".to_owned()) })?; if let Some(stem) = path.file_stem() { // in theory the module name of the interface can be determined by // the filename in elm 0.19.0 and 0.19.1 let module: String = stem .to_string_lossy() .split('-') .collect::>() .join("."); let cannonical_module = elmi::ModuleNameCanonical { package: elmi::PackageName::new("author", "project"), module: elmi::Name(module), }; interfaces.insert(cannonical_module, i); } } _ => (), } } } Ok(interfaces) } pub fn load_objects(elm_cache_dir: &Path) -> Result, Problem> { let mut objects = HashMap::new(); let entries = fs::read_dir(&elm_cache_dir) .map_err(|io_err| CompilerError::ReadInputFailed(io_err, elm_cache_dir.to_path_buf()))?; for entry in entries { let entry = entry.map_err(|io_err| { CompilerError::ReadInputFailed(io_err, elm_cache_dir.to_path_buf()) })?; let path = entry.path(); if path.is_file() { match path.file_name() { Some(file_name) if file_name == "o.dat" => { let data = std::fs::read(&path) .map_err(|io_err| CompilerError::ReadInputFailed(io_err, path.clone()))?; let (_remaining, opt_global_graph) = elmi::OptGlobalGraph::get(&data).map_err(|_err| { CompilerError::FailedElmiParse("todo elmi parsing".to_owned()) })?; objects.extend(opt_global_graph.nodes.into_iter()); } _ => {} } match path.extension() { Some(ext) if ext == "elmo" => { // step 4 load all the modules let parse_span = info_span!("parse elmo"); let _parse_guard = parse_span.enter(); let load_span = info_span!("file reads"); let load_guard = load_span.enter(); let data = std::fs::read(&path) .map_err(|io_err| CompilerError::ReadInputFailed(io_err, path.clone()))?; drop(load_guard); let (_remaining, opt_local_graph) = elmi::OptLocalGraph::get(&data).map_err(|_err| { CompilerError::FailedElmiParse("todo elmi parsing".to_owned()) })?; objects.extend(opt_local_graph.nodes.into_iter()); } _ => (), } } } Ok(objects) } pub struct SymbolLoader { project_cache_directory: PathBuf, global_cache_directory: PathBuf, symbol_cache: HashMap, } impl SymbolLoader { pub fn try_load(&mut self, symbol: elmi::Global) -> Result { if self.symbol_cache.contains_key(&symbol) { return Ok(true); } let elmi::Global( elmi::ModuleNameCanonical { ref package, ref module, }, ref _identifier, ) = symbol; if *package == elmi::PackageName::new("author", "project") { let filename = module.0.split('.').collect::>().join("-"); let mut path = self.project_cache_directory.join(filename); path.set_extension(".elmo"); } else { let mut path = self.global_cache_directory.clone(); path.push(&package.author); path.push(&package.project); // TODO figure out the version required. //path.push(version); path.push("artifacts.dat"); } Ok(false) } } //pub fn load_symbol_table(elm_cache_dir: &Path) -> Result, Problem> { // // given a global symbol, calculate the folder that contains the cache file. // let (_remaining, i) = OptLocalGraph::get(&data).unwrap(); // // let mut interfaces = HashMap::new(); // // let entries = fs::read_dir(&elm_cache_dir) // .map_err(|io_err| CompilerError::ReadInputFailed(io_err, elm_cache_dir.to_path_buf()))?; // // for entry in entries { // let entry = entry.map_err(|io_err| { // CompilerError::ReadInputFailed(io_err, elm_cache_dir.to_path_buf()) // })?; // let path = entry.path(); // if path.is_file() { // match path.extension() { // Some(ext) if ext == "elmi" => { // // step 4 load all the modules // let parse_span = info_span!("parse elmi"); // let _parse_guard = parse_span.enter(); // // let load_span = info_span!("file reads"); // let load_guard = load_span.enter(); // let data = std::fs::read(&path) // .map_err(|io_err| CompilerError::ReadInputFailed(io_err, path.clone()))?; // drop(load_guard); // // let (_remaining, i) = elmi::Interface::get(&data).map_err(|_err| { // CompilerError::FailedElmiParse("todo elmi parsing".to_owned()) // })?; // // if let Some(stem) = path.file_stem() { // // in theory the module name of the interface can be determined by // // the filename in elm 0.19.0 and 0.19.1 // let module_name: String = stem // .to_string_lossy() // .split('-') // .collect::>() // .join("."); // interfaces.insert(module_name, i); // } // } // _ => (), // } // } // } // // Ok(interfaces) //} /// Look for the cargo.toml file that we are building to determine the root directory of the project /// (so that we can locate the source files) pub fn find_project_root>(filename: S, search: S) -> io::Result { if !search.as_ref().is_dir() { return Err(Error::new( ErrorKind::InvalidInput, format!("`{}` is not a directory", search.as_ref().display()), )); } let mut path_buf = canonicalize(&search)?; let error: io::Error = Error::new( ErrorKind::NotFound, format!( "could not find `{}` in `{:?}` or any parent directory", filename.as_ref().display(), path_buf ), ); loop { let test_file = path_buf.as_path().join(&filename); match metadata(test_file) { Ok(_) => return Ok(path_buf), Err(_) => (), } match path_buf.as_path().parent() { Some(_) => (), None => return Err(error), } path_buf.pop(); } } pub fn get_elm_home() -> io::Result { match std::env::var("ELM_HOME") { Ok(elm_home) => Ok(PathBuf::from(elm_home)), Err(_) => { if let Some(mut dir) = home::home_dir() { dir.push(".elm"); Ok(dir) } else { Err(std::io::Error::new( ErrorKind::NotFound, "ELM_HOME not set and home directory could not be calculated by `home` crate", )) } } } } pub fn parse_module_name(data: &[u8]) -> Result { if let Some(Ok(first_line)) = data.lines().next() { let mut tokens = first_line.split_whitespace(); match tokens.next() { Some(token) if token == "port" => { tokens.next(); } Some(token) if token == "module" => (), Some(token) if token == "effect" => { tokens.next(); } _ => { return Err(CompilerError::CantParseModule(first_line.clone()).into()); } }; if let Some(module_name) = tokens.next() { Ok(elmi::Name(module_name.to_string())) } else { Err(CompilerError::CantParseModule(first_line.clone()).into()) } } else { Err(CompilerError::EmptyModule.into()) } }