301 lines
11 KiB
Rust
301 lines
11 KiB
Rust
|
|
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<HashMap<String, elmi::Interface>, 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.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::<Vec<&str>>()
|
||
|
|
.join(".");
|
||
|
|
interfaces.insert(module_name, i);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
_ => (),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Ok(interfaces)
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn load_objects(elm_cache_dir: &Path) -> Result<HashMap<elmi::Global, elmi::Node>, 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<elmi::Global, elmi::Node>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl SymbolLoader {
|
||
|
|
pub fn try_load(&mut self, symbol: elmi::Global) -> Result<bool, Problem> {
|
||
|
|
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::<Vec<&str>>().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<HashMap<String, elmi::Interface>, 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::<Vec<&str>>()
|
||
|
|
// .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<S: AsRef<Path>>(filename: S, search: S) -> io::Result<PathBuf> {
|
||
|
|
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<PathBuf> {
|
||
|
|
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<elmi::Name, CompilerError> {
|
||
|
|
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())
|
||
|
|
}
|
||
|
|
}
|