compiler.rs 9.1 KB
Newer Older
R
Ryan Dahl 已提交
1
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
2
use crate::diagnostics::Diagnostic;
A
Andy Hayden 已提交
3 4
use crate::msg;
use crate::resources;
5
use crate::startup_data;
6
use crate::state::*;
A
andy finch 已提交
7
use crate::tokio_util;
8 9
use crate::worker::Worker;
use deno::js_check;
10
use deno::Buf;
R
Ryan Dahl 已提交
11
use futures::Future;
12
use futures::Stream;
K
Kitson Kelly 已提交
13
use std::str;
14
use std::sync::atomic::Ordering;
R
Ryan Dahl 已提交
15 16 17

// This corresponds to JS ModuleMetaData.
// TODO Rename one or the other so they correspond.
A
andy finch 已提交
18
#[derive(Debug, Clone)]
K
Kitson Kelly 已提交
19
pub struct ModuleMetaData {
R
Ryan Dahl 已提交
20
  pub module_name: String,
21
  pub module_redirect_source_name: Option<String>, // source of redirect
R
Ryan Dahl 已提交
22 23
  pub filename: String,
  pub media_type: msg::MediaType,
K
Kitson Kelly 已提交
24
  pub source_code: Vec<u8>,
25
  pub maybe_output_code_filename: Option<String>,
K
Kitson Kelly 已提交
26
  pub maybe_output_code: Option<Vec<u8>>,
27
  pub maybe_source_map_filename: Option<String>,
K
Kitson Kelly 已提交
28
  pub maybe_source_map: Option<Vec<u8>>,
R
Ryan Dahl 已提交
29 30
}

K
Kitson Kelly 已提交
31
impl ModuleMetaData {
32 33 34 35
  pub fn has_output_code_and_source_map(&self) -> bool {
    self.maybe_output_code.is_some() && self.maybe_source_map.is_some()
  }

36
  pub fn js_source(&self) -> String {
R
Ryan Dahl 已提交
37
    if self.media_type == msg::MediaType::Json {
K
Kitson Kelly 已提交
38 39 40 41
      return format!(
        "export default {};",
        str::from_utf8(&self.source_code).unwrap()
      );
R
Ryan Dahl 已提交
42 43
    }
    match self.maybe_output_code {
K
Kitson Kelly 已提交
44 45
      None => str::from_utf8(&self.source_code).unwrap().to_string(),
      Some(ref output_code) => str::from_utf8(output_code).unwrap().to_string(),
R
Ryan Dahl 已提交
46 47 48 49
    }
  }
}

R
Ryan Dahl 已提交
50
type CompilerConfig = Option<(String, Vec<u8>)>;
51

R
Ryan Dahl 已提交
52
/// Creates the JSON message send to compiler.ts's onmessage.
K
Kitson Kelly 已提交
53 54 55 56 57
fn req(
  root_names: Vec<String>,
  compiler_config: CompilerConfig,
  bundle: Option<String>,
) -> Buf {
R
Ryan Dahl 已提交
58 59 60
  let j = if let Some((config_path, config_data)) = compiler_config {
    json!({
      "rootNames": root_names,
K
Kitson Kelly 已提交
61
      "bundle": bundle,
R
Ryan Dahl 已提交
62 63 64 65 66 67
      "configPath": config_path,
      "config": str::from_utf8(&config_data).unwrap(),
    })
  } else {
    json!({
      "rootNames": root_names,
K
Kitson Kelly 已提交
68
      "bundle": bundle,
R
Ryan Dahl 已提交
69 70 71
    })
  };
  j.to_string().into_boxed_str().into_boxed_bytes()
R
Ryan Dahl 已提交
72 73
}

74 75 76 77 78 79
/// 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,
R
Ryan Dahl 已提交
80
) -> CompilerConfig {
81 82 83 84 85 86 87 88 89 90
  // 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,
  }
}

K
Kitson Kelly 已提交
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
pub fn bundle_async(
  state: ThreadSafeState,
  module_name: String,
  out_file: String,
) -> impl Future<Item = (), Error = Diagnostic> {
  debug!(
    "Invoking the compiler to bundle. module_name: {}",
    module_name
  );

  let root_names = vec![module_name.clone()];
  let compiler_config = get_compiler_config(&state, "typescript");
  let req_msg = req(root_names, compiler_config, Some(out_file));

  // Count how many times we start the compiler worker.
  state.metrics.compiler_starts.fetch_add(1, Ordering::SeqCst);

  let mut worker = Worker::new(
    "TS".to_string(),
    startup_data::compiler_isolate_init(),
    // TODO(ry) Maybe we should use a separate state for the compiler.
    // as was done previously.
    state.clone(),
  );
  js_check(worker.execute("denoMain()"));
  js_check(worker.execute("workerMain()"));
  js_check(worker.execute("compilerMain()"));

  let resource = worker.state.resource.clone();
  let compiler_rid = resource.rid;
  let first_msg_fut = resources::post_message_to_worker(compiler_rid, req_msg)
    .then(move |_| worker)
    .then(move |result| {
      if let Err(err) = result {
        // TODO(ry) Need to forward the error instead of exiting.
        eprintln!("{}", err.to_string());
        std::process::exit(1);
      }
      debug!("Sent message to worker");
      let stream_future =
        resources::get_message_stream_from_worker(compiler_rid).into_future();
      stream_future.map(|(f, _rest)| f).map_err(|(f, _rest)| f)
    });

  first_msg_fut.map_err(|_| panic!("not handled")).and_then(
    move |maybe_msg: Option<Buf>| {
      debug!("Received message from worker");

      if let Some(msg) = maybe_msg {
        let json_str = std::str::from_utf8(&msg).unwrap();
        debug!("Message: {}", json_str);
        if let Some(diagnostics) = Diagnostic::from_emit_result(json_str) {
          return Err(diagnostics);
        }
      }

      Ok(())
    },
  )
}

A
andy finch 已提交
152
pub fn compile_async(
R
Ryan Dahl 已提交
153
  state: ThreadSafeState,
K
Kitson Kelly 已提交
154
  module_meta_data: &ModuleMetaData,
155
) -> impl Future<Item = ModuleMetaData, Error = Diagnostic> {
K
Kitson Kelly 已提交
156 157
  let module_name = module_meta_data.module_name.clone();

158
  debug!(
K
Kitson Kelly 已提交
159 160
    "Running rust part of compile_sync. module_name: {}",
    &module_name
161 162
  );

K
Kitson Kelly 已提交
163
  let root_names = vec![module_name.clone()];
R
Ryan Dahl 已提交
164
  let compiler_config = get_compiler_config(&state, "typescript");
K
Kitson Kelly 已提交
165
  let req_msg = req(root_names, compiler_config, None);
R
Ryan Dahl 已提交
166 167 168 169 170 171 172 173 174 175 176 177 178 179

  // Count how many times we start the compiler worker.
  state.metrics.compiler_starts.fetch_add(1, Ordering::SeqCst);

  let mut worker = Worker::new(
    "TS".to_string(),
    startup_data::compiler_isolate_init(),
    // TODO(ry) Maybe we should use a separate state for the compiler.
    // as was done previously.
    state.clone(),
  );
  js_check(worker.execute("denoMain()"));
  js_check(worker.execute("workerMain()"));
  js_check(worker.execute("compilerMain()"));
A
andy finch 已提交
180

K
Kitson Kelly 已提交
181
  let compiling_job = state.progress.add(format!("Compiling {}", module_name));
R
Ryan Dahl 已提交
182

R
Ryan Dahl 已提交
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
  let resource = worker.state.resource.clone();
  let compiler_rid = resource.rid;
  let first_msg_fut = resources::post_message_to_worker(compiler_rid, req_msg)
    .then(move |_| worker)
    .then(move |result| {
      if let Err(err) = result {
        // TODO(ry) Need to forward the error instead of exiting.
        eprintln!("{}", err.to_string());
        std::process::exit(1);
      }
      debug!("Sent message to worker");
      let stream_future =
        resources::get_message_stream_from_worker(compiler_rid).into_future();
      stream_future.map(|(f, _rest)| f).map_err(|(f, _rest)| f)
    });

  first_msg_fut
    .map_err(|_| panic!("not handled"))
    .and_then(move |maybe_msg: Option<Buf>| {
      debug!("Received message from worker");

204 205 206 207 208 209 210
      if let Some(msg) = maybe_msg {
        let json_str = std::str::from_utf8(&msg).unwrap();
        debug!("Message: {}", json_str);
        if let Some(diagnostics) = Diagnostic::from_emit_result(json_str) {
          return Err(diagnostics);
        }
      }
R
Ryan Dahl 已提交
211

212 213 214 215 216 217 218 219 220 221 222 223
      Ok(())
    }).and_then(move |_| {
      state.dir.fetch_module_meta_data_async(
        &module_name,
        ".",
        true,
        true,
      ).map_err(|e| {
        // TODO(95th) Instead of panicking, We could translate this error to Diagnostic.
        panic!("{}", e)
      })
    }).and_then(move |module_meta_data_after_compile| {
R
Ryan Dahl 已提交
224 225
      // Explicit drop to keep reference alive until future completes.
      drop(compiling_job);
R
Ryan Dahl 已提交
226

R
Ryan Dahl 已提交
227 228 229 230 231
      Ok(module_meta_data_after_compile)
    }).then(move |r| {
      // TODO(ry) do this in worker's destructor.
      // resource.close();
      r
A
andy finch 已提交
232 233 234 235
    })
}

pub fn compile_sync(
R
Ryan Dahl 已提交
236
  state: ThreadSafeState,
A
andy finch 已提交
237
  module_meta_data: &ModuleMetaData,
238
) -> Result<ModuleMetaData, Diagnostic> {
K
Kitson Kelly 已提交
239
  tokio_util::block_on(compile_async(state, module_meta_data))
R
Ryan Dahl 已提交
240 241 242 243 244 245 246 247
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn test_compile_sync() {
A
andy finch 已提交
248 249
    tokio_util::init(|| {
      let specifier = "./tests/002_hello.ts";
R
Ryan Dahl 已提交
250 251 252 253
      use crate::worker;
      let module_name = worker::root_specifier_to_url(specifier)
        .unwrap()
        .to_string();
A
andy finch 已提交
254 255

      let mut out = ModuleMetaData {
R
Ryan Dahl 已提交
256
        module_name,
A
andy finch 已提交
257 258 259 260 261 262 263 264 265 266
        module_redirect_source_name: None,
        filename: "/tests/002_hello.ts".to_owned(),
        media_type: msg::MediaType::TypeScript,
        source_code: include_bytes!("../tests/002_hello.ts").to_vec(),
        maybe_output_code_filename: None,
        maybe_output_code: None,
        maybe_source_map_filename: None,
        maybe_source_map: None,
      };

K
Kitson Kelly 已提交
267 268 269 270 271 272 273
      out = compile_sync(
        ThreadSafeState::mock(vec![
          String::from("./deno"),
          String::from("hello.js"),
        ]),
        &out,
      ).unwrap();
A
andy finch 已提交
274 275 276 277 278 279 280
      assert!(
        out
          .maybe_output_code
          .unwrap()
          .starts_with("console.log(\"Hello World\");".as_bytes())
      );
    })
281 282
  }

283 284 285
  #[test]
  fn test_get_compiler_config_no_flag() {
    let compiler_type = "typescript";
K
Kitson Kelly 已提交
286 287 288 289
    let state = ThreadSafeState::mock(vec![
      String::from("./deno"),
      String::from("hello.js"),
    ]);
290 291 292
    let out = get_compiler_config(&state, compiler_type);
    assert_eq!(out, None);
  }
K
Kitson Kelly 已提交
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310

  #[test]
  fn test_bundle_async() {
    let specifier = "./tests/002_hello.ts";
    use crate::worker;
    let module_name = worker::root_specifier_to_url(specifier)
      .unwrap()
      .to_string();

    let state = ThreadSafeState::mock(vec![
      String::from("./deno"),
      String::from("./tests/002_hello.ts"),
      String::from("$deno$/bundle.js"),
    ]);
    let out =
      bundle_async(state, module_name, String::from("$deno$/bundle.js"));
    assert_eq!(tokio_util::block_on(out), Ok(()));
  }
R
Ryan Dahl 已提交
311
}