diff options
Diffstat (limited to 'cli')
-rw-r--r-- | cli/BUILD.gn | 3 | ||||
-rw-r--r-- | cli/compiler.rs | 25 | ||||
-rw-r--r-- | cli/deno_dir.rs | 83 | ||||
-rw-r--r-- | cli/flags.rs | 24 | ||||
-rw-r--r-- | cli/msg.fbs | 11 | ||||
-rw-r--r-- | cli/ops.rs | 38 | ||||
-rw-r--r-- | cli/state.rs | 50 |
7 files changed, 216 insertions, 18 deletions
diff --git a/cli/BUILD.gn b/cli/BUILD.gn index 7bf34dec3..dc11f3b0b 100644 --- a/cli/BUILD.gn +++ b/cli/BUILD.gn @@ -52,9 +52,10 @@ ts_sources = [ "../js/buffer.ts", "../js/build.ts", "../js/chmod.ts", - "../js/console_table.ts", + "../js/colors.ts", "../js/compiler.ts", "../js/console.ts", + "../js/console_table.ts", "../js/copy_file.ts", "../js/core.ts", "../js/custom_event.ts", diff --git a/cli/compiler.rs b/cli/compiler.rs index d327835d3..522002b0b 100644 --- a/cli/compiler.rs +++ b/cli/compiler.rs @@ -158,6 +158,23 @@ fn req(specifier: &str, referrer: &str, cmd_id: u32) -> Buf { .into_boxed_bytes() } +/// Returns an optional tuple which represents the state of the compiler +/// configuration where the first is canonical name for the configuration file +/// and a vector of the bytes of the contents of the configuration file. +pub fn get_compiler_config( + parent_state: &ThreadSafeState, + _compiler_type: &str, +) -> Option<(String, Vec<u8>)> { + // The compiler type is being passed to make it easier to implement custom + // compilers in the future. + match (&parent_state.config_path, &parent_state.config) { + (Some(config_path), Some(config)) => { + Some((config_path.to_string(), config.to_vec())) + } + _ => None, + } +} + pub fn compile_async( parent_state: ThreadSafeState, specifier: &str, @@ -306,4 +323,12 @@ mod tests { assert_eq!(parse_cmd_id(res_json), cmd_id); } + + #[test] + fn test_get_compiler_config_no_flag() { + let compiler_type = "typescript"; + let state = ThreadSafeState::mock(); + let out = get_compiler_config(&state, compiler_type); + assert_eq!(out, None); + } } diff --git a/cli/deno_dir.rs b/cli/deno_dir.rs index 5dc9afed3..e38d454e1 100644 --- a/cli/deno_dir.rs +++ b/cli/deno_dir.rs @@ -51,12 +51,18 @@ pub struct DenoDir { // This splits to http and https deps pub deps_http: PathBuf, pub deps_https: PathBuf, + /// The active configuration file contents (or empty array) which applies to + /// source code cached by `DenoDir`. + pub config: Vec<u8>, } impl DenoDir { // Must be called before using any function from this module. // https://github.com/denoland/deno/blob/golang/deno_dir.go#L99-L111 - pub fn new(custom_root: Option<PathBuf>) -> std::io::Result<Self> { + pub fn new( + custom_root: Option<PathBuf>, + state_config: &Option<Vec<u8>>, + ) -> std::io::Result<Self> { // Only setup once. let home_dir = dirs::home_dir().expect("Could not get home directory."); let fallback = home_dir.join(".deno"); @@ -73,12 +79,22 @@ impl DenoDir { let deps_http = deps.join("http"); let deps_https = deps.join("https"); + // Internally within DenoDir, we use the config as part of the hash to + // determine if a file has been transpiled with the same configuration, but + // we have borrowed the `State` configuration, which we want to either clone + // or create an empty `Vec` which we will use in our hash function. + let config = match state_config { + Some(config) => config.clone(), + _ => b"".to_vec(), + }; + let deno_dir = Self { root, gen, deps, deps_http, deps_https, + config, }; // TODO Lazily create these directories. @@ -102,7 +118,8 @@ impl DenoDir { filename: &str, source_code: &[u8], ) -> (PathBuf, PathBuf) { - let cache_key = source_code_hash(filename, source_code, version::DENO); + let cache_key = + source_code_hash(filename, source_code, version::DENO, &self.config); ( self.gen.join(cache_key.to_string() + ".js"), self.gen.join(cache_key.to_string() + ".js.map"), @@ -156,6 +173,11 @@ impl DenoDir { let gen = self.gen.clone(); + // If we don't clone the config, we then end up creating an implied lifetime + // which gets returned in the future, so we clone here so as to not leak the + // move below when the future is resolving. + let config = self.config.clone(); + Either::B( get_source_code_async( self, @@ -191,8 +213,12 @@ impl DenoDir { return Ok(out); } - let cache_key = - source_code_hash(&out.filename, &out.source_code, version::DENO); + let cache_key = source_code_hash( + &out.filename, + &out.source_code, + version::DENO, + &config, + ); let (output_code_filename, output_source_map_filename) = ( gen.join(cache_key.to_string() + ".js"), gen.join(cache_key.to_string() + ".js.map"), @@ -468,15 +494,19 @@ fn load_cache2( Ok((read_output_code, read_source_map)) } +/// Generate an SHA1 hash for source code, to be used to determine if a cached +/// version of the code is valid or invalid. fn source_code_hash( filename: &str, source_code: &[u8], version: &str, + config: &[u8], ) -> String { let mut ctx = ring::digest::Context::new(&ring::digest::SHA1); ctx.update(version.as_bytes()); ctx.update(filename.as_bytes()); ctx.update(source_code); + ctx.update(config); let digest = ctx.finish(); let mut out = String::new(); // TODO There must be a better way to do this... @@ -860,8 +890,9 @@ mod tests { fn test_setup() -> (TempDir, DenoDir) { let temp_dir = TempDir::new().expect("tempdir fail"); - let deno_dir = - DenoDir::new(Some(temp_dir.path().to_path_buf())).expect("setup fail"); + let config = Some(b"{}".to_vec()); + let deno_dir = DenoDir::new(Some(temp_dir.path().to_path_buf()), &config) + .expect("setup fail"); (temp_dir, deno_dir) } @@ -904,7 +935,8 @@ mod tests { let (temp_dir, deno_dir) = test_setup(); let filename = "hello.js"; let source_code = b"1+2"; - let hash = source_code_hash(filename, source_code, version::DENO); + let config = b"{}"; + let hash = source_code_hash(filename, source_code, version::DENO, config); assert_eq!( ( temp_dir.path().join(format!("gen/{}.js", hash)), @@ -915,6 +947,24 @@ mod tests { } #[test] + fn test_cache_path_config() { + // We are changing the compiler config from the "mock" and so we expect the + // resolved files coming back to not match the calculated hash. + let (temp_dir, deno_dir) = test_setup(); + let filename = "hello.js"; + let source_code = b"1+2"; + let config = b"{\"compilerOptions\":{}}"; + let hash = source_code_hash(filename, source_code, version::DENO, config); + assert_ne!( + ( + temp_dir.path().join(format!("gen/{}.js", hash)), + temp_dir.path().join(format!("gen/{}.js.map", hash)) + ), + deno_dir.cache_path(filename, source_code) + ); + } + + #[test] fn test_code_cache() { let (_temp_dir, deno_dir) = test_setup(); @@ -922,7 +972,8 @@ mod tests { let source_code = b"1+2"; let output_code = b"1+2 // output code"; let source_map = b"{}"; - let hash = source_code_hash(filename, source_code, version::DENO); + let config = b"{}"; + let hash = source_code_hash(filename, source_code, version::DENO, config); let (cache_path, source_map_path) = deno_dir.cache_path(filename, source_code); assert!(cache_path.ends_with(format!("gen/{}.js", hash))); @@ -949,23 +1000,23 @@ mod tests { #[test] fn test_source_code_hash() { assert_eq!( - "7e44de2ed9e0065da09d835b76b8d70be503d276", - source_code_hash("hello.ts", b"1+2", "0.2.11") + "830c8b63ba3194cf2108a3054c176b2bf53aee45", + source_code_hash("hello.ts", b"1+2", "0.2.11", b"{}") ); // Different source_code should result in different hash. assert_eq!( - "57033366cf9db1ef93deca258cdbcd9ef5f4bde1", - source_code_hash("hello.ts", b"1", "0.2.11") + "fb06127e9b2e169bea9c697fa73386ae7c901e8b", + source_code_hash("hello.ts", b"1", "0.2.11", b"{}") ); // Different filename should result in different hash. assert_eq!( - "19657f90b5b0540f87679e2fb362e7bd62b644b0", - source_code_hash("hi.ts", b"1+2", "0.2.11") + "3a17b6a493ff744b6a455071935f4bdcd2b72ec7", + source_code_hash("hi.ts", b"1+2", "0.2.11", b"{}") ); // Different version should result in different hash. assert_eq!( - "e2b4b7162975a02bf2770f16836eb21d5bcb8be1", - source_code_hash("hi.ts", b"1+2", "0.2.0") + "d6b2cfdc39dae9bd3ad5b493ee1544eb22e7475f", + source_code_hash("hi.ts", b"1+2", "0.2.0", b"{}") ); } diff --git a/cli/flags.rs b/cli/flags.rs index 2b0b37b9a..dbd185efb 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -13,6 +13,9 @@ pub struct DenoFlags { pub log_debug: bool, pub version: bool, pub reload: bool, + /// When the `--config`/`-c` flag is used to pass the name, this will be set + /// the path passed on the command line, otherwise `None`. + pub config_path: Option<String>, pub allow_read: bool, pub allow_write: bool, pub allow_net: bool, @@ -80,6 +83,13 @@ pub fn create_cli_app<'a, 'b>() -> App<'a, 'b> { .long("reload") .help("Reload source code cache (recompile TypeScript)"), ).arg( + Arg::with_name("config") + .short("c") + .long("config") + .value_name("FILE") + .help("Load compiler configuration file") + .takes_value(true), + ).arg( Arg::with_name("v8-options") .long("v8-options") .help("Print V8 command line options"), @@ -146,6 +156,7 @@ pub fn parse_flags(matches: ArgMatches) -> DenoFlags { if matches.is_present("reload") { flags.reload = true; } + flags.config_path = matches.value_of("config").map(ToOwned::to_owned); if matches.is_present("allow-read") { flags.allow_read = true; } @@ -353,4 +364,17 @@ mod tests { } ) } + + #[test] + fn test_set_flags_11() { + let flags = + flags_from_vec(svec!["deno", "-c", "tsconfig.json", "script.ts"]); + assert_eq!( + flags, + DenoFlags { + config_path: Some("tsconfig.json".to_owned()), + ..DenoFlags::default() + } + ) + } } diff --git a/cli/msg.fbs b/cli/msg.fbs index d217fc7ba..ff5454a91 100644 --- a/cli/msg.fbs +++ b/cli/msg.fbs @@ -3,6 +3,8 @@ union Any { Chdir, Chmod, Close, + CompilerConfig, + CompilerConfigRes, CopyFile, Cwd, CwdRes, @@ -174,6 +176,15 @@ table StartRes { no_color: bool; } +table CompilerConfig { + compiler_type: string; +} + +table CompilerConfigRes { + path: string; + data: [ubyte]; +} + table FormatError { error: string; } diff --git a/cli/ops.rs b/cli/ops.rs index bc06a2fb7..4ebcf5fdb 100644 --- a/cli/ops.rs +++ b/cli/ops.rs @@ -1,6 +1,7 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. use atty; use crate::ansi; +use crate::compiler::get_compiler_config; use crate::errors; use crate::errors::{DenoError, DenoResult, ErrorKind}; use crate::fs as deno_fs; @@ -146,6 +147,8 @@ pub fn dispatch_all( pub fn op_selector_compiler(inner_type: msg::Any) -> Option<OpCreator> { match inner_type { + msg::Any::CompilerConfig => Some(op_compiler_config), + msg::Any::Cwd => Some(op_cwd), msg::Any::FetchModuleMetaData => Some(op_fetch_module_meta_data), msg::Any::WorkerGetMessage => Some(op_worker_get_message), msg::Any::WorkerPostMessage => Some(op_worker_post_message), @@ -443,6 +446,41 @@ fn op_fetch_module_meta_data( }())) } +/// Retrieve any relevant compiler configuration. +fn op_compiler_config( + state: &ThreadSafeState, + base: &msg::Base<'_>, + data: deno_buf, +) -> Box<OpWithError> { + assert_eq!(data.len(), 0); + let inner = base.inner_as_compiler_config().unwrap(); + let cmd_id = base.cmd_id(); + let compiler_type = inner.compiler_type().unwrap(); + + Box::new(futures::future::result(|| -> OpResult { + let builder = &mut FlatBufferBuilder::new(); + let (path, out) = match get_compiler_config(state, compiler_type) { + Some(val) => val, + _ => ("".to_owned(), "".as_bytes().to_owned()), + }; + let data_off = builder.create_vector(&out); + let msg_args = msg::CompilerConfigResArgs { + path: Some(builder.create_string(&path)), + data: Some(data_off), + }; + let inner = msg::CompilerConfigRes::create(builder, &msg_args); + Ok(serialize_response( + cmd_id, + builder, + msg::BaseArgs { + inner: Some(inner.as_union_value()), + inner_type: msg::Any::CompilerConfigRes, + ..Default::default() + }, + )) + }())) +} + fn op_chdir( _state: &ThreadSafeState, base: &msg::Base<'_>, diff --git a/cli/state.rs b/cli/state.rs index f10f3b7e0..5aefe7d90 100644 --- a/cli/state.rs +++ b/cli/state.rs @@ -15,6 +15,7 @@ use futures::future::Shared; use std; use std::collections::HashMap; use std::env; +use std::fs; use std::ops::Deref; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; @@ -51,6 +52,12 @@ pub struct State { pub argv: Vec<String>, pub permissions: DenoPermissions, pub flags: flags::DenoFlags, + /// When flags contains a `.config_path` option, the content of the + /// configuration file will be resolved and set. + pub config: Option<Vec<u8>>, + /// When flags contains a `.config_path` option, the fully qualified path + /// name of the passed path will be resolved and set. + pub config_path: Option<String>, pub metrics: Metrics, pub worker_channels: Mutex<WorkerChannels>, pub global_timer: Mutex<GlobalTimer>, @@ -97,11 +104,52 @@ impl ThreadSafeState { let external_channels = (worker_in_tx, worker_out_rx); let resource = resources::add_worker(external_channels); + // take the passed flag and resolve the file name relative to the cwd + let config_file = match &flags.config_path { + Some(config_file_name) => { + debug!("Compiler config file: {}", config_file_name); + let cwd = std::env::current_dir().unwrap(); + Some(cwd.join(config_file_name)) + } + _ => None, + }; + + // Convert the PathBuf to a canonicalized string. This is needed by the + // compiler to properly deal with the configuration. + let config_path = match &config_file { + Some(config_file) => Some( + config_file + .canonicalize() + .unwrap() + .to_str() + .unwrap() + .to_owned(), + ), + _ => None, + }; + + // Load the contents of the configuration file + let config = match &config_file { + Some(config_file) => { + debug!("Attempt to load config: {}", config_file.to_str().unwrap()); + match fs::read(&config_file) { + Ok(config_data) => Some(config_data.to_owned()), + _ => panic!( + "Error retrieving compiler config file at \"{}\"", + config_file.to_str().unwrap() + ), + } + } + _ => None, + }; + ThreadSafeState(Arc::new(State { - dir: deno_dir::DenoDir::new(custom_root).unwrap(), + dir: deno_dir::DenoDir::new(custom_root, &config).unwrap(), argv: argv_rest, permissions: DenoPermissions::from_flags(&flags), flags, + config, + config_path, metrics: Metrics::default(), worker_channels: Mutex::new(internal_channels), global_timer: Mutex::new(GlobalTimer::new()), |