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: lockfiles #3231

Merged
merged 14 commits into from
Nov 3, 2019
Merged
Next Next commit
First pass at lockfile support
Use --lock-write=lock.json or --lock-check=lock.json on the command
line.
  • Loading branch information
ry committed Oct 29, 2019
commit 9453695d44e10955555fc80c37e8ee8fe38b16d1
20 changes: 20 additions & 0 deletions cli/checksum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use ring;
use std::fmt::Write;

pub fn gen(v: Vec<&[u8]>) -> String {
let mut ctx = ring::digest::Context::new(&ring::digest::SHA256);
for src in v.iter() {
ctx.update(src);
}
let digest = ctx.finish();
let mut out = String::new();
// TODO There must be a better way to do this...
for byte in digest.as_ref() {
write!(&mut out, "{:02x}", byte).unwrap();
}
out
}

pub fn gen2(s: &str) -> String {
gen(vec![s.as_bytes()])
}
18 changes: 1 addition & 17 deletions cli/compilers/ts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ use deno::ModuleSpecifier;
use futures::Future;
use futures::Stream;
use regex::Regex;
use ring;
use std::collections::HashSet;
use std::fmt::Write;
use std::fs;
use std::io;
use std::path::PathBuf;
Expand Down Expand Up @@ -178,28 +176,14 @@ fn req(
j.to_string().into_boxed_str().into_boxed_bytes()
}

fn gen_hash(v: Vec<&[u8]>) -> String {
let mut ctx = ring::digest::Context::new(&ring::digest::SHA256);
for src in v.iter() {
ctx.update(src);
}
let digest = ctx.finish();
let mut out = String::new();
// TODO There must be a better way to do this...
for byte in digest.as_ref() {
write!(&mut out, "{:02x}", byte).unwrap();
}
out
}

/// Emit a SHA256 hash based on source code, deno version and TS config.
/// Used to check if a recompilation for source code is needed.
pub fn source_code_version_hash(
source_code: &[u8],
version: &str,
config_hash: &[u8],
) -> String {
gen_hash(vec![source_code, version.as_bytes(), config_hash])
crate::checksum::gen(vec![source_code, version.as_bytes(), config_hash])
}

pub struct TsCompiler {
Expand Down
45 changes: 45 additions & 0 deletions cli/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ pub struct DenoFlags {
pub v8_flags: Option<Vec<String>>,
// Use tokio::runtime::current_thread
pub current_thread: bool,

pub lock_check: Option<String>,
pub lock_write: Option<String>,
}

static ENV_VARIABLES_HELP: &str = "ENVIRONMENT VARIABLES:
Expand Down Expand Up @@ -223,6 +226,20 @@ Examples: https://github.com/WICG/import-maps#the-import-map",
}
})
.global(true),
).arg(
Arg::with_name("lock-check")
.long("lock-check")
.value_name("FILE")
.help("Check the specified lockfile")
.takes_value(true)
.global(true),
).arg(
Arg::with_name("lock-write")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it may be more user friendly to do deno --lock-write lockfile.json rather than deno --lock lockfile.json --lock-write.
I'd be okay with doing this in in a follow-up PR tho.

.long("lock-write")
.value_name("FILE")
.help("Write a lock file to specified filename")
.takes_value(true)
.global(true),
).arg(
Arg::with_name("v8-options")
.long("v8-options")
Expand Down Expand Up @@ -634,6 +651,14 @@ pub fn parse_flags(
}
}
}
if matches.is_present("lock-check") {
let lockfile = matches.value_of("lock-check").unwrap();
flags.lock_check = Some(lockfile.to_string());
}
if matches.is_present("lock-write") {
let lockfile = matches.value_of("lock-write").unwrap();
flags.lock_write = Some(lockfile.to_string());
}

flags = parse_run_args(flags, matches);
// flags specific to "run" subcommand
Expand Down Expand Up @@ -1890,4 +1915,24 @@ mod tests {
assert_eq!(subcommand, DenoSubcommand::Run);
assert_eq!(argv, svec!["deno", "script.ts"])
}

#[test]
fn test_flags_from_vec_38() {
let (flags, subcommand, argv) = flags_from_vec(svec![
"deno",
"--lock-write=deno_lock.json",
"--lock-check=deno_lock2.json",
"script.ts"
]);
assert_eq!(
flags,
DenoFlags {
lock_write: Some("deno_lock.json".to_string()),
lock_check: Some("deno_lock2.json".to_string()),
..DenoFlags::default()
}
);
assert_eq!(subcommand, DenoSubcommand::Run);
assert_eq!(argv, svec!["deno", "script.ts"])
}
}
9 changes: 9 additions & 0 deletions cli/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ extern crate serde;
extern crate serde_derive;
extern crate url;

mod checksum;
pub mod colors;
pub mod compilers;
pub mod deno_dir;
Expand All @@ -32,6 +33,7 @@ mod http_body;
mod http_util;
mod import_map;
mod js;
mod lockfile;
pub mod msg;
pub mod ops;
pub mod permissions;
Expand Down Expand Up @@ -362,6 +364,13 @@ fn run_script(flags: DenoFlags, argv: Vec<String>) {

worker
.execute_mod_async(&main_module, None, false)
.and_then(move |()| {
if let Some(ref lockfile) = state.lock_write {
let g = lockfile.lock().unwrap();
g.write()?;
}
Ok(())
})
.and_then(move |()| {
js_check(worker.execute("window.dispatchEvent(new Event('load'))"));
worker.then(move |result| {
Expand Down
74 changes: 74 additions & 0 deletions cli/lockfile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use crate::compilers::CompiledModule;
pub use serde_derive::Deserialize;
use serde_json::json;
pub use serde_json::Value;
use std::collections::HashMap;
use std::io::Result;

#[derive(Deserialize)]
struct Map(pub HashMap<String, String>);

pub struct Lockfile {
read: bool,
map: Map,
pub filename: String,
}

impl Lockfile {
pub fn from_flag(flag: &Option<String>) -> Option<Lockfile> {
if let Some(filename) = flag {
Some(Self::new(filename.to_string()))
} else {
None
}
}

pub fn new(filename: String) -> Lockfile {
Lockfile {
map: Map(HashMap::new()),
filename,
read: false,
}
}

pub fn write(&self) -> Result<()> {
let j = json!(self.map.0);
let s = serde_json::to_string_pretty(&j).unwrap();
let mut f = std::fs::OpenOptions::new()
.write(true)
.create(true)
.open(&self.filename)?;
use std::io::Write;
f.write_all(s.as_bytes())?;
debug!("lockfile write {}", self.filename);
Ok(())
}

pub fn read(&mut self) -> Result<()> {
debug!("lockfile read {}", self.filename);
let s = std::fs::read_to_string(&self.filename)?;
self.map.0 = serde_json::from_str(&s)?;
self.read = true;
Ok(())
}

/// Lazily reads the filename, checks the given module is included.
/// Returns Ok(true) if check passed
pub fn check(&mut self, m: &CompiledModule) -> Result<bool> {
if !self.read {
self.read()?;
}

Ok(if let Some(lockfile_checksum) = self.map.0.get(&m.name) {
let compiled_checksum = crate::checksum::gen2(&m.code);
lockfile_checksum == &compiled_checksum
} else {
false
})
}

pub fn insert(&mut self, m: &CompiledModule) -> bool {
let checksum = crate::checksum::gen2(&m.code);
self.map.0.insert(m.name.clone(), checksum).is_none()
}
}
45 changes: 38 additions & 7 deletions cli/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::file_fetcher::SourceFileFetcher;
use crate::flags;
use crate::global_timer::GlobalTimer;
use crate::import_map::ImportMap;
use crate::lockfile::Lockfile;
use crate::msg;
use crate::ops::JsonOp;
use crate::permissions::DenoPermissions;
Expand Down Expand Up @@ -88,6 +89,9 @@ pub struct State {
pub ts_compiler: TsCompiler,

pub include_deno_namespace: bool,

pub lock_check: Option<Mutex<Lockfile>>,
pub lock_write: Option<Mutex<Lockfile>>,
}

impl Clone for ThreadSafeState {
Expand Down Expand Up @@ -255,6 +259,13 @@ impl ThreadSafeState {

let modules = Arc::new(Mutex::new(deno::Modules::new()));

let lock_write = Lockfile::from_flag(&flags.lock_write)
.map(|lockfile| Mutex::new(lockfile));

// Note: reads lazily from disk on first call to lock_check.check()
let lock_check = Lockfile::from_flag(&flags.lock_check)
.map(|lockfile| Mutex::new(lockfile));

let state = State {
main_module,
modules,
Expand All @@ -276,6 +287,8 @@ impl ThreadSafeState {
js_compiler: JsCompiler {},
json_compiler: JsonCompiler {},
include_deno_namespace,
lock_check,
lock_write,
};

Ok(ThreadSafeState(Arc::new(state)))
Expand All @@ -285,31 +298,49 @@ impl ThreadSafeState {
self: &Self,
module_specifier: &ModuleSpecifier,
) -> impl Future<Item = CompiledModule, Error = ErrBox> {
let state_ = self.clone();
let state1 = self.clone();
let state2 = self.clone();

self
.file_fetcher
.fetch_source_file_async(&module_specifier)
.and_then(move |out| match out.media_type {
msg::MediaType::Unknown => {
state_.js_compiler.compile_async(state_.clone(), &out)
state1.js_compiler.compile_async(state1.clone(), &out)
}
msg::MediaType::Json => {
state_.json_compiler.compile_async(state_.clone(), &out)
state1.json_compiler.compile_async(state1.clone(), &out)
}
msg::MediaType::TypeScript
| msg::MediaType::TSX
| msg::MediaType::JSX => {
state_.ts_compiler.compile_async(state_.clone(), &out)
state1.ts_compiler.compile_async(state1.clone(), &out)
}
msg::MediaType::JavaScript => {
if state_.ts_compiler.compile_js {
state_.ts_compiler.compile_async(state_.clone(), &out)
if state1.ts_compiler.compile_js {
state1.ts_compiler.compile_async(state1.clone(), &out)
} else {
state_.js_compiler.compile_async(state_.clone(), &out)
state1.js_compiler.compile_async(state1.clone(), &out)
}
}
})
.and_then(move |compiled_module| {
if let Some(ref lockfile) = state2.lock_check {
let mut g = lockfile.lock().unwrap();
if !g.check(&compiled_module)? {
eprintln!(
"lock file check failed {} {}",
g.filename, compiled_module.name
);
std::process::exit(10);
ry marked this conversation as resolved.
Show resolved Hide resolved
}
}
if let Some(ref lockfile) = state2.lock_write {
let mut g = lockfile.lock().unwrap();
g.insert(&compiled_module);
}
Ok(compiled_module)
})
}

/// Read main module from argv
Expand Down