Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): Implement mun new and mun init #246

Merged
merged 4 commits into from
Jul 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion crates/mun/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ default-features = false # Disable features which are enabled by default
features = ["precommit-hook", "run-cargo-test", "run-cargo-fmt", "run-cargo-clippy", "run-for-all"]

[dev-dependencies]
tempdir = "0.3"
tempfile = "3.1"
serial_test = "0.4"
190 changes: 15 additions & 175 deletions crates/mun/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
use std::cell::RefCell;
mod ops;

use std::env;
use std::rc::Rc;
use std::time::Duration;
use std::ffi::OsString;

use anyhow::anyhow;
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
use mun_compiler::{Config, DisplayColor, Target};
use clap::{App, AppSettings, Arg, SubCommand};
use mun_project::MANIFEST_FILENAME;
use mun_runtime::{invoke_fn, ReturnTypeReflection, Runtime, RuntimeBuilder};
use std::ffi::OsString;
use std::path::{Path, PathBuf};

use ops::{build, init, language_server, new, start};

#[derive(Copy, Debug, Clone, PartialEq, Eq)]
pub enum ExitStatus {
Expand Down Expand Up @@ -94,6 +91,13 @@ where
.help("how much to delay received filesystem events (in ms). This allows bundling of identical events, e.g. when several writes to the same file are detected. A high delay will make hot reloading less responsive. (defaults to 10 ms)"),
),
)
.subcommand(
SubCommand::with_name("new")
.arg(Arg::with_name("path").help("the path to create a new project").required(true).index(1))
)
.subcommand(
SubCommand::with_name("init")
)
.subcommand(
SubCommand::with_name("language-server")
)
Expand All @@ -104,6 +108,8 @@ where
("build", Some(matches)) => build(matches),
("language-server", Some(matches)) => language_server(matches),
("start", Some(matches)) => start(matches).map(|_| ExitStatus::Success),
("new", Some(matches)) => new(matches),
("init", Some(_)) => init(),
_ => unreachable!(),
},
Err(e) => {
Expand All @@ -112,169 +118,3 @@ where
}
}
}

/// Find a Mun manifest file in the specified directory or one of its parents.
fn find_manifest(directory: &Path) -> Option<PathBuf> {
let mut current_dir = Some(directory);
while let Some(dir) = current_dir {
let manifest_path = dir.join(MANIFEST_FILENAME);
if manifest_path.exists() {
return Some(manifest_path);
}
current_dir = dir.parent();
}
None
}

/// This method is invoked when the executable is run with the `build` argument indicating that a
/// user requested us to build a project in the current directory or one of its parent directories.
fn build(matches: &ArgMatches) -> Result<ExitStatus, anyhow::Error> {
log::trace!("starting build");

let options = compiler_options(matches)?;

// Locate the manifest
let manifest_path = match matches.value_of("manifest-path") {
None => {
let current_dir =
std::env::current_dir().expect("could not determine currrent working directory");
find_manifest(&current_dir).ok_or_else(|| {
anyhow::anyhow!(
"could not find {} in '{}' or a parent directory",
MANIFEST_FILENAME,
current_dir.display()
)
})?
}
Some(path) => std::fs::canonicalize(Path::new(path))
.map_err(|_| anyhow::anyhow!("'{}' does not refer to a valid manifest path", path))?,
};

log::info!("located build manifest at: {}", manifest_path.display());

if matches.is_present("watch") {
mun_compiler_daemon::compile_and_watch_manifest(&manifest_path, options)
} else {
mun_compiler::compile_manifest(&manifest_path, options)
}
.map(Into::into)
}

/// Starts the runtime with the specified library and invokes function `entry`.
fn start(matches: &ArgMatches) -> Result<ExitStatus, anyhow::Error> {
let runtime = runtime(matches)?;

let borrowed = runtime.borrow();
let entry_point = matches.value_of("entry").unwrap_or("main");
let fn_definition = borrowed
.get_function_definition(entry_point)
.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("Failed to obtain entry point '{}'", entry_point),
)
})?;

if let Some(ret_type) = fn_definition.prototype.signature.return_type() {
let type_guid = &ret_type.guid;
if *type_guid == bool::type_guid() {
let result: bool = invoke_fn!(runtime, entry_point).map_err(|e| anyhow!("{}", e))?;

println!("{}", result)
} else if *type_guid == f64::type_guid() {
let result: f64 = invoke_fn!(runtime, entry_point).map_err(|e| anyhow!("{}", e))?;

println!("{}", result)
} else if *type_guid == i64::type_guid() {
let result: i64 = invoke_fn!(runtime, entry_point).map_err(|e| anyhow!("{}", e))?;

println!("{}", result)
} else {
return Err(anyhow!(
"Only native Mun return types are supported for entry points. Found: {}",
ret_type.name()
));
};
Ok(ExitStatus::Success)
} else {
#[allow(clippy::unit_arg)]
invoke_fn!(runtime, entry_point)
.map(|_: ()| ExitStatus::Success)
.map_err(|e| anyhow!("{}", e))
}
}

fn compiler_options(matches: &ArgMatches) -> Result<mun_compiler::Config, anyhow::Error> {
let optimization_lvl = match matches.value_of("opt-level") {
Some("0") => mun_compiler::OptimizationLevel::None,
Some("1") => mun_compiler::OptimizationLevel::Less,
None | Some("2") => mun_compiler::OptimizationLevel::Default,
Some("3") => mun_compiler::OptimizationLevel::Aggressive,
_ => return Err(anyhow!("Only optimization levels 0-3 are supported")),
};

let display_color = matches
.value_of("color")
.map(ToOwned::to_owned)
.or_else(|| env::var("MUN_TERMINAL_COLOR").ok())
.map(|value| match value.as_str() {
"disable" => DisplayColor::Disable,
"enable" => DisplayColor::Enable,
_ => DisplayColor::Auto,
})
.unwrap_or(DisplayColor::Auto);

Ok(Config {
target: matches
.value_of("target")
.map_or_else(Target::host_target, Target::search)?,
optimization_lvl,
out_dir: None,
display_color,
})
}

fn runtime(matches: &ArgMatches) -> Result<Rc<RefCell<Runtime>>, anyhow::Error> {
let builder = RuntimeBuilder::new(
matches.value_of("LIBRARY").unwrap(), // Safe because its a required arg
);

let builder = if let Some(delay) = matches.value_of("delay") {
let delay: u64 = delay.parse()?;
builder.set_delay(Duration::from_millis(delay))
} else {
builder
};

builder.spawn()
}

/// This function is invoked when the executable is invoked with the `language-server` argument. A
/// Mun language server is started ready to serve language information about one or more projects.
fn language_server(_matches: &ArgMatches) -> Result<ExitStatus, anyhow::Error> {
mun_language_server::run_server().map_err(|e| anyhow::anyhow!("{}", e))?;
Ok(ExitStatus::Success)
}

#[cfg(test)]
mod test {
use crate::find_manifest;
use mun_project::MANIFEST_FILENAME;
use tempdir::TempDir;

#[test]
fn test_find_manifest() {
let dir = TempDir::new("test_find_manifest").unwrap();
let path = dir.path();
let manifest_path = path.join(MANIFEST_FILENAME);

assert_eq!(find_manifest(path), None);

std::fs::write(&manifest_path, "").unwrap();
assert_eq!(find_manifest(path).as_ref(), Some(&manifest_path));

let subdir_path = path.join("some/random/subdir");
std::fs::create_dir_all(&subdir_path).unwrap();
assert_eq!(find_manifest(&subdir_path).as_ref(), Some(&manifest_path));
}
}
11 changes: 11 additions & 0 deletions crates/mun/src/ops.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
mod build;
pub mod init;
mod language_server;
mod new;
mod start;

pub use build::build;
pub use init::init;
pub use language_server::language_server;
pub use new::new;
pub use start::start;
111 changes: 111 additions & 0 deletions crates/mun/src/ops/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use std::env;
use std::path::{Path, PathBuf};

use anyhow::anyhow;
use clap::ArgMatches;
use mun_compiler::{Config, DisplayColor, Target};
use mun_project::MANIFEST_FILENAME;

use crate::ExitStatus;

/// This method is invoked when the executable is run with the `build` argument indicating that a
/// user requested us to build a project in the current directory or one of its parent directories.
pub fn build(matches: &ArgMatches) -> Result<ExitStatus, anyhow::Error> {
log::trace!("starting build");

let options = compiler_options(matches)?;

// Locate the manifest
let manifest_path = match matches.value_of("manifest-path") {
None => {
let current_dir =
std::env::current_dir().expect("could not determine currrent working directory");
find_manifest(&current_dir).ok_or_else(|| {
anyhow::anyhow!(
"could not find {} in '{}' or a parent directory",
MANIFEST_FILENAME,
current_dir.display()
)
})?
}
Some(path) => std::fs::canonicalize(Path::new(path))
.map_err(|_| anyhow::anyhow!("'{}' does not refer to a valid manifest path", path))?,
};

log::info!("located build manifest at: {}", manifest_path.display());

if matches.is_present("watch") {
mun_compiler_daemon::compile_and_watch_manifest(&manifest_path, options)
} else {
mun_compiler::compile_manifest(&manifest_path, options)
}
.map(Into::into)
}

/// Find a Mun manifest file in the specified directory or one of its parents.
fn find_manifest(directory: &Path) -> Option<PathBuf> {
let mut current_dir = Some(directory);
while let Some(dir) = current_dir {
let manifest_path = dir.join(MANIFEST_FILENAME);
if manifest_path.exists() {
return Some(manifest_path);
}
current_dir = dir.parent();
}
None
}

fn compiler_options(matches: &ArgMatches) -> Result<mun_compiler::Config, anyhow::Error> {
let optimization_lvl = match matches.value_of("opt-level") {
Some("0") => mun_compiler::OptimizationLevel::None,
Some("1") => mun_compiler::OptimizationLevel::Less,
None | Some("2") => mun_compiler::OptimizationLevel::Default,
Some("3") => mun_compiler::OptimizationLevel::Aggressive,
_ => return Err(anyhow!("Only optimization levels 0-3 are supported")),
};

let display_color = matches
.value_of("color")
.map(ToOwned::to_owned)
.or_else(|| env::var("MUN_TERMINAL_COLOR").ok())
.map(|value| match value.as_str() {
"disable" => DisplayColor::Disable,
"enable" => DisplayColor::Enable,
_ => DisplayColor::Auto,
})
.unwrap_or(DisplayColor::Auto);

Ok(Config {
target: matches
.value_of("target")
.map_or_else(Target::host_target, Target::search)?,
optimization_lvl,
out_dir: None,
display_color,
})
}

#[cfg(test)]
mod test {
use super::find_manifest;
use mun_project::MANIFEST_FILENAME;

#[test]
fn test_find_manifest() {
let dir = tempfile::Builder::new()
.prefix("test_find_manifest")
.tempdir()
.unwrap();
let path = dir.path();
let manifest_path = path.join(MANIFEST_FILENAME);

assert_eq!(find_manifest(path), None);

std::fs::write(&manifest_path, "").unwrap();
assert_eq!(find_manifest(path).as_ref(), Some(&manifest_path));

let subdir_path = path.join("some/random/subdir");
std::fs::create_dir_all(&subdir_path).unwrap();
assert_eq!(find_manifest(&subdir_path).as_ref(), Some(&manifest_path));
}
}
Loading