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 8 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: 3 additions & 1 deletion cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1219,7 +1219,9 @@ 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) =
deno_runtime::standalone::try_run_standalone_binary(args.clone())
{
eprintln!("{}: {}", colors::red_bold("error"), err.to_string());
std::process::exit(1);
}
Expand Down
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
4 changes: 4 additions & 0 deletions runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ edition = "2018"
description = "Provides the deno runtime library"
repository = "https://github.com/denoland/deno"

[[bin]]
name = "deno-rt"
path = "main.rs"

[lib]
name = "deno_runtime"
path = "lib.rs"
Expand Down
1 change: 1 addition & 0 deletions runtime/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub mod metrics;
pub mod ops;
pub mod permissions;
pub mod resolve_addr;
pub mod standalone;
pub mod tokio_util;
pub mod web_worker;
pub mod worker;
14 changes: 14 additions & 0 deletions runtime/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
fn main() {
#[cfg(windows)]
deno_runtime::colors::enable_ansi(); // For Windows 10

let args: Vec<String> = std::env::args().collect();
if let Err(err) = deno_runtime::standalone::try_run_standalone_binary(args) {
eprintln!(
"{}: {}",
deno_runtime::colors::red_bold("error"),
err.to_string()
);
std::process::exit(1);
}
}
138 changes: 138 additions & 0 deletions runtime/standalone.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use crate::colors;
use crate::permissions::Permissions;
use crate::tokio_util;
use crate::worker::MainWorker;
use crate::worker::WorkerOptions;
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 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: "Deno/1.6.3".to_string(), // TODO (yos1p) Based on Deno version and 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: "1.6.3".to_string(), // TODO (yos1p) Deno version
ts_version: "4.1.3".to_string(), // TODO (yos1p) TS version
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 {
crate::errors::get_error_class_name(e).unwrap_or_else(|| {
panic!("Error '{}' contains boxed error of unknown type", e);
})
}

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()
}
}