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) - Runtime binary for Deno #8944

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from 12 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
4 changes: 4 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ default-run = "deno"
name = "deno"
path = "main.rs"

[[bin]]
name = "denort"
path = "denort.rs"

[[bench]]
name = "deno_bench"
harness = false
Expand Down
5 changes: 5 additions & 0 deletions cli/bench/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ fn get_binary_sizes(target_dir: &PathBuf) -> Result<Value> {
Value::Number(Number::from(test_util::deno_exe_path().metadata()?.len())),
);

sizes.insert(
"denort".to_string(),
Value::Number(Number::from(test_util::denort_exe_path().metadata()?.len())),
);

// Because cargo's OUT_DIR is not predictable, search the build tree for
// snapshot related files.
for file in walkdir::WalkDir::new(target_dir) {
Expand Down
18 changes: 18 additions & 0 deletions cli/denort.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

mod rt;

fn main() {
#[cfg(windows)]
deno_runtime::colors::enable_ansi(); // For Windows 10

let args: Vec<String> = std::env::args().collect();
if let Err(err) = rt::try_run_standalone_binary(args) {
eprintln!(
"{}: {}",
deno_runtime::colors::red_bold("error"),
err.to_string()
);
std::process::exit(1);
}
}
3 changes: 2 additions & 1 deletion cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ mod module_graph;
mod module_loader;
mod ops;
mod program_state;
mod rt;
mod source_maps;
mod specifier_handler;
mod standalone;
Expand Down Expand Up @@ -1219,7 +1220,7 @@ pub fn main() {
colors::enable_ansi(); // For Windows 10

let args: Vec<String> = env::args().collect();
if let Err(err) = standalone::try_run_standalone_binary(args.clone()) {
if let Err(err) = crate::rt::try_run_standalone_binary(args.clone()) {
eprintln!("{}: {}", colors::red_bold("error"), err.to_string());
std::process::exit(1);
}
Expand Down
147 changes: 147 additions & 0 deletions cli/rt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::futures::FutureExt;
use deno_core::ModuleLoader;
use deno_core::ModuleSpecifier;
use deno_core::OpState;
use deno_runtime::colors;
use deno_runtime::permissions::Permissions;
use deno_runtime::tokio_util;
use deno_runtime::worker::MainWorker;
use deno_runtime::worker::WorkerOptions;
use std::cell::RefCell;
use std::convert::TryInto;
use std::fs::File;
use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
use std::pin::Pin;
use std::rc::Rc;
use std::sync::Arc;

const MAGIC_TRAILER: &[u8; 8] = b"d3n0l4nd";
const SPECIFIER: &str = "file://$deno$/bundle.js";

pub fn try_run_standalone_binary(args: Vec<String>) -> Result<(), AnyError> {
let current_exe_path = std::env::current_exe()?;

let mut current_exe = File::open(current_exe_path)?;
let trailer_pos = current_exe.seek(SeekFrom::End(-16))?;
let mut trailer = [0; 16];
current_exe.read_exact(&mut trailer)?;
let (magic_trailer, bundle_pos_arr) = trailer.split_at(8);
if magic_trailer == MAGIC_TRAILER {
let bundle_pos_arr: &[u8; 8] = bundle_pos_arr.try_into()?;
let bundle_pos = u64::from_be_bytes(*bundle_pos_arr);
current_exe.seek(SeekFrom::Start(bundle_pos))?;

let bundle_len = trailer_pos - bundle_pos;
let mut bundle = String::new();
current_exe.take(bundle_len).read_to_string(&mut bundle)?;
// TODO: check amount of bytes read

let parsed_args: Vec<String> = args[1..].to_vec();
if let Err(err) = tokio_util::run_basic(run(bundle, parsed_args)) {
eprintln!("{}: {}", colors::red_bold("error"), err.to_string());
std::process::exit(1);
}
std::process::exit(0);
} else {
Ok(())
}
}

async fn run(source_code: String, args: Vec<String>) -> Result<(), AnyError> {
let main_module = ModuleSpecifier::resolve_url(SPECIFIER)?;
let permissions = Permissions::allow_all();
let module_loader = Rc::new(EmbeddedModuleLoader(source_code));
let create_web_worker_cb = Arc::new(|_| {
todo!("Worker are currently not supported in standalone binaries");
});

let options = WorkerOptions {
apply_source_maps: false,
args,
debug_flag: false,
user_agent: format!("Deno/{}", deno_version()),
unstable: true,
ca_filepath: None,
seed: None,
js_error_create_fn: None,
create_web_worker_cb,
attach_inspector: false,
maybe_inspector_server: None,
should_break_on_first_statement: false,
module_loader,
runtime_version: deno_version(),
ts_version: env!("TS_VERSION").to_string(),
no_color: !colors::use_color(),
get_error_class_fn: Some(&get_error_class_name),
};
let mut worker =
MainWorker::from_options(main_module.clone(), permissions, &options);
worker.bootstrap(&options);
worker.execute_module(&main_module).await?;
worker.execute("window.dispatchEvent(new Event('load'))")?;
worker.run_event_loop().await?;
worker.execute("window.dispatchEvent(new Event('unload'))")?;
Ok(())
}

fn get_error_class_name(e: &AnyError) -> &'static str {
deno_runtime::errors::get_error_class_name(e).unwrap_or_else(|| {
panic!("Error '{}' contains boxed error of unknown type", e);
})
}

fn deno_version() -> String {
let semver = env!("CARGO_PKG_VERSION");
option_env!("DENO_CANARY").map_or(semver.to_string(), |_| {
format!("{}+{}", semver, env!("GIT_COMMIT_HASH"))
})
}

struct EmbeddedModuleLoader(String);

impl ModuleLoader for EmbeddedModuleLoader {
fn resolve(
&self,
_op_state: Rc<RefCell<OpState>>,
specifier: &str,
_referrer: &str,
_is_main: bool,
) -> Result<ModuleSpecifier, AnyError> {
if specifier != SPECIFIER {
return Err(type_error(
"Self-contained binaries don't support module loading",
));
}
Ok(ModuleSpecifier::resolve_url(specifier)?)
}

fn load(
&self,
_op_state: Rc<RefCell<OpState>>,
module_specifier: &ModuleSpecifier,
_maybe_referrer: Option<ModuleSpecifier>,
_is_dynamic: bool,
) -> Pin<Box<deno_core::ModuleSourceFuture>> {
let module_specifier = module_specifier.clone();
let code = self.0.to_string();
async move {
if module_specifier.to_string() != SPECIFIER {
return Err(type_error(
"Self-contained binaries don't support module loading",
));
}
Ok(deno_core::ModuleSource {
code,
module_url_specified: module_specifier.to_string(),
module_url_found: module_specifier.to_string(),
})
}
.boxed_local()
}
}
140 changes: 0 additions & 140 deletions cli/standalone.rs
Original file line number Diff line number Diff line change
@@ -1,154 +1,14 @@
use crate::colors;
use crate::flags::Flags;
use crate::tokio_util;
use crate::version;
use deno_core::error::bail;
use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::futures::FutureExt;
use deno_core::ModuleLoader;
use deno_core::ModuleSpecifier;
use deno_core::OpState;
use deno_runtime::permissions::Permissions;
use deno_runtime::worker::MainWorker;
use deno_runtime::worker::WorkerOptions;
use std::cell::RefCell;
use std::convert::TryInto;
use std::env::current_exe;
use std::fs::File;
use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
use std::io::Write;
use std::path::PathBuf;
use std::pin::Pin;
use std::rc::Rc;
use std::sync::Arc;

const MAGIC_TRAILER: &[u8; 8] = b"d3n0l4nd";

/// This function will try to run this binary as a standalone binary
/// produced by `deno compile`. It determines if this is a stanalone
/// binary by checking for the magic trailer string `D3N0` at EOF-12.
/// After the magic trailer is a u64 pointer to the start of the JS
/// file embedded in the binary. This file is read, and run. If no
/// magic trailer is present, this function exits with Ok(()).
pub fn try_run_standalone_binary(args: Vec<String>) -> Result<(), AnyError> {
let current_exe_path = current_exe()?;

let mut current_exe = File::open(current_exe_path)?;
let trailer_pos = current_exe.seek(SeekFrom::End(-16))?;
let mut trailer = [0; 16];
current_exe.read_exact(&mut trailer)?;
let (magic_trailer, bundle_pos_arr) = trailer.split_at(8);
if magic_trailer == MAGIC_TRAILER {
let bundle_pos_arr: &[u8; 8] = bundle_pos_arr.try_into()?;
let bundle_pos = u64::from_be_bytes(*bundle_pos_arr);
current_exe.seek(SeekFrom::Start(bundle_pos))?;

let bundle_len = trailer_pos - bundle_pos;
let mut bundle = String::new();
current_exe.take(bundle_len).read_to_string(&mut bundle)?;
// TODO: check amount of bytes read

if let Err(err) = tokio_util::run_basic(run(bundle, args)) {
eprintln!("{}: {}", colors::red_bold("error"), err.to_string());
std::process::exit(1);
}
std::process::exit(0);
} else {
Ok(())
}
}

const SPECIFIER: &str = "file://$deno$/bundle.js";

struct EmbeddedModuleLoader(String);

impl ModuleLoader for EmbeddedModuleLoader {
fn resolve(
&self,
_op_state: Rc<RefCell<OpState>>,
specifier: &str,
_referrer: &str,
_is_main: bool,
) -> Result<ModuleSpecifier, AnyError> {
if specifier != SPECIFIER {
return Err(type_error(
"Self-contained binaries don't support module loading",
));
}
Ok(ModuleSpecifier::resolve_url(specifier)?)
}

fn load(
&self,
_op_state: Rc<RefCell<OpState>>,
module_specifier: &ModuleSpecifier,
_maybe_referrer: Option<ModuleSpecifier>,
_is_dynamic: bool,
) -> Pin<Box<deno_core::ModuleSourceFuture>> {
let module_specifier = module_specifier.clone();
let code = self.0.to_string();
async move {
if module_specifier.to_string() != SPECIFIER {
return Err(type_error(
"Self-contained binaries don't support module loading",
));
}
Ok(deno_core::ModuleSource {
code,
module_url_specified: module_specifier.to_string(),
module_url_found: module_specifier.to_string(),
})
}
.boxed_local()
}
}

async fn run(source_code: String, args: Vec<String>) -> Result<(), AnyError> {
let mut flags = Flags::default();
flags.argv = args[1..].to_vec();
// TODO(lucacasonato): remove once you can specify this correctly through embedded metadata
flags.unstable = true;
yos1p marked this conversation as resolved.
Show resolved Hide resolved
let main_module = ModuleSpecifier::resolve_url(SPECIFIER)?;
let permissions = Permissions::allow_all();
let module_loader = Rc::new(EmbeddedModuleLoader(source_code));
let create_web_worker_cb = Arc::new(|_| {
todo!("Worker are currently not supported in standalone binaries");
});

let options = WorkerOptions {
apply_source_maps: false,
args: flags.argv.clone(),
debug_flag: false,
user_agent: crate::http_util::get_user_agent(),
unstable: true,
ca_filepath: None,
seed: None,
js_error_create_fn: None,
create_web_worker_cb,
attach_inspector: false,
maybe_inspector_server: None,
should_break_on_first_statement: false,
module_loader,
runtime_version: version::deno(),
ts_version: version::TYPESCRIPT.to_string(),
no_color: !colors::use_color(),
get_error_class_fn: Some(&crate::errors::get_error_class_name),
};
let mut worker =
MainWorker::from_options(main_module.clone(), permissions, &options);
worker.bootstrap(&options);
worker.execute_module(&main_module).await?;
worker.execute("window.dispatchEvent(new Event('load'))")?;
worker.run_event_loop().await?;
worker.execute("window.dispatchEvent(new Event('unload'))")?;
Ok(())
}

/// This functions creates a standalone deno binary by appending a bundle
/// and magic trailer to the currently executing binary.
pub async fn create_standalone_binary(
mut source_code: Vec<u8>,
output: PathBuf,
Expand Down
9 changes: 9 additions & 0 deletions test_util/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ pub fn deno_exe_path() -> PathBuf {
p
}

pub fn denort_exe_path() -> PathBuf {
// Something like /Users/rld/src/deno/target/debug/deps/deno
let mut p = target_dir().join("denort");
if cfg!(windows) {
p.set_extension("exe");
}
p
}

pub fn prebuilt_tool_path(tool: &str) -> PathBuf {
let mut exe = tool.to_string();
exe.push_str(if cfg!(windows) { ".exe" } else { "" });
Expand Down