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: deno compile #8539

Merged
merged 17 commits into from
Nov 30, 2020
89 changes: 89 additions & 0 deletions cli/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ pub enum DenoSubcommand {
source_file: String,
out_file: Option<PathBuf>,
},
Compile {
source_file: String,
out_file: Option<String>,
},
Completions {
buf: Box<[u8]>,
},
Expand Down Expand Up @@ -292,6 +296,8 @@ pub fn flags_from_vec_safe(args: Vec<String>) -> clap::Result<Flags> {
doc_parse(&mut flags, m);
} else if let Some(m) = matches.subcommand_matches("lint") {
lint_parse(&mut flags, m);
} else if let Some(m) = matches.subcommand_matches("compile") {
compile_parse(&mut flags, m);
} else {
repl_parse(&mut flags, &matches);
}
Expand Down Expand Up @@ -341,6 +347,7 @@ If the flag is set, restrict these messages to errors.",
)
.subcommand(bundle_subcommand())
.subcommand(cache_subcommand())
.subcommand(compile_subcommand())
.subcommand(completions_subcommand())
.subcommand(doc_subcommand())
.subcommand(eval_subcommand())
Expand Down Expand Up @@ -408,6 +415,18 @@ fn install_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
};
}

fn compile_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
compile_args_parse(flags, matches);

let source_file = matches.value_of("source_file").unwrap().to_string();
let out_file = matches.value_of("out_file").map(|s| s.to_string());

flags.subcommand = DenoSubcommand::Compile {
source_file,
out_file,
};
}

fn bundle_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
compile_args_parse(flags, matches);

Expand Down Expand Up @@ -802,6 +821,32 @@ The installation root is determined, in order of precedence:
These must be added to the path manually if required.")
}

fn compile_subcommand<'a, 'b>() -> App<'a, 'b> {
compile_args(SubCommand::with_name("compile"))
.arg(
Arg::with_name("source_file")
.takes_value(true)
.required(true),
)
.arg(Arg::with_name("out_file").takes_value(true))
.about("Compile the script into a self contained executable")
.long_about(
"Compiles the given script into a self contained executable.
deno compile --unstable https://deno.land/std/http/file_server.ts
deno compile --unstable https://deno.land/std/examples/colors.ts color_util

The executable name is inferred by default:
- Attempt to take the file stem of the URL path. The above example would
become 'file_server'.
- If the file stem is something generic like 'main', 'mod', 'index' or 'cli',
and the path has no parent, take the file name of the parent path. Otherwise
settle with the generic name.
- If the resulting name has an '@...' suffix, strip it.

Cross compiling binaries for different platforms is not currently possible.",
)
}

fn bundle_subcommand<'a, 'b>() -> App<'a, 'b> {
compile_args(SubCommand::with_name("bundle"))
.arg(
Expand Down Expand Up @@ -3200,4 +3245,48 @@ mod tests {
}
);
}

#[test]
fn compile() {
let r = flags_from_vec_safe(svec![
"deno",
"compile",
"https://deno.land/std/examples/colors.ts"
]);
assert_eq!(
r.unwrap(),
Flags {
subcommand: DenoSubcommand::Compile {
source_file: "https://deno.land/std/examples/colors.ts".to_string(),
out_file: None
},
..Flags::default()
}
);
}

#[test]
fn compile_with_flags() {
#[rustfmt::skip]
let r = flags_from_vec_safe(svec!["deno", "compile", "--unstable", "--import-map", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--reload", "--lock", "lock.json", "--lock-write", "--cert", "example.crt", "https://deno.land/std/examples/colors.ts", "colors"]);
assert_eq!(
r.unwrap(),
Flags {
subcommand: DenoSubcommand::Compile {
source_file: "https://deno.land/std/examples/colors.ts".to_string(),
out_file: Some("colors".to_string())
},
unstable: true,
import_map_path: Some("import_map.json".to_string()),
no_remote: true,
config_path: Some("tsconfig.json".to_string()),
no_check: true,
reload: true,
lock: Some(PathBuf::from("lock.json")),
lock_write: true,
ca_file: Some("example.crt".to_string()),
..Flags::default()
}
);
}
}
196 changes: 140 additions & 56 deletions cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ mod resolve_addr;
mod signal;
mod source_maps;
mod specifier_handler;
mod standalone;
mod text_encoding;
mod tokio_util;
mod tools;
Expand All @@ -51,10 +52,16 @@ mod worker;
use crate::file_fetcher::File;
use crate::file_fetcher::FileFetcher;
use crate::file_watcher::ModuleResolutionResult;
use crate::flags::DenoSubcommand;
use crate::flags::Flags;
use crate::import_map::ImportMap;
use crate::media_type::MediaType;
use crate::permissions::Permissions;
use crate::program_state::exit_unstable;
use crate::program_state::ProgramState;
use crate::specifier_handler::FetchHandler;
use crate::standalone::create_standalone_binary;
use crate::tools::installer::infer_name_from_url;
use crate::worker::MainWorker;
use deno_core::error::generic_error;
use deno_core::error::AnyError;
Expand All @@ -66,12 +73,8 @@ use deno_core::v8_set_flags;
use deno_core::ModuleSpecifier;
use deno_doc as doc;
use deno_doc::parser::DocFileLoader;
use flags::DenoSubcommand;
use flags::Flags;
use import_map::ImportMap;
use log::Level;
use log::LevelFilter;
use program_state::exit_unstable;
use std::cell::RefCell;
use std::env;
use std::io::Read;
Expand Down Expand Up @@ -149,6 +152,56 @@ fn get_types(unstable: bool) -> String {
types
}

async fn compile_command(
flags: Flags,
source_file: String,
out_file: Option<String>,
) -> Result<(), AnyError> {
if !flags.unstable {
exit_unstable("compile");
}

let debug = flags.log_level == Some(log::Level::Debug);

let module_specifier = ModuleSpecifier::resolve_url_or_path(&source_file)?;
let program_state = ProgramState::new(flags.clone())?;

let out_file =
out_file.or_else(|| infer_name_from_url(module_specifier.as_url()));
let out_file = match out_file {
Some(out_file) => out_file,
None => return Err(generic_error(
"An executable name was not provided. One could not be inferred from the URL. Aborting.",
)),
};

let module_graph = create_module_graph_and_maybe_check(
module_specifier.clone(),
program_state.clone(),
debug,
)
.await?;

info!(
"{} {}",
colors::green("Bundle"),
module_specifier.to_string()
);
let bundle_str = bundle_module_graph(module_graph, flags, debug)?;

info!(
"{} {}",
colors::green("Compile"),
module_specifier.to_string()
);
create_standalone_binary(bundle_str.as_bytes().to_vec(), out_file.clone())
.await?;

info!("{} {}", colors::green("Emit"), out_file);

Ok(())
}

async fn info_command(
flags: Flags,
maybe_specifier: Option<String>,
Expand Down Expand Up @@ -299,6 +352,73 @@ async fn eval_command(
Ok(())
}

async fn create_module_graph_and_maybe_check(
module_specifier: ModuleSpecifier,
program_state: Arc<ProgramState>,
debug: bool,
) -> Result<module_graph::Graph, AnyError> {
let handler = Rc::new(RefCell::new(FetchHandler::new(
&program_state,
// when bundling, dynamic imports are only access for their type safety,
// therefore we will allow the graph to access any module.
Permissions::allow_all(),
)?));
let mut builder = module_graph::GraphBuilder::new(
handler,
program_state.maybe_import_map.clone(),
program_state.lockfile.clone(),
);
builder.add(&module_specifier, false).await?;
let module_graph = builder.get_graph();

if !program_state.flags.no_check {
// TODO(@kitsonk) support bundling for workers
let lib = if program_state.flags.unstable {
module_graph::TypeLib::UnstableDenoWindow
} else {
module_graph::TypeLib::DenoWindow
};
let result_info =
module_graph.clone().check(module_graph::CheckOptions {
debug,
emit: false,
lib,
maybe_config_path: program_state.flags.config_path.clone(),
reload: program_state.flags.reload,
})?;

debug!("{}", result_info.stats);
if let Some(ignored_options) = result_info.maybe_ignored_options {
eprintln!("{}", ignored_options);
}
if !result_info.diagnostics.is_empty() {
return Err(generic_error(result_info.diagnostics.to_string()));
}
}

Ok(module_graph)
}

fn bundle_module_graph(
module_graph: module_graph::Graph,
flags: Flags,
debug: bool,
) -> Result<String, AnyError> {
let (bundle, stats, maybe_ignored_options) =
module_graph.bundle(module_graph::BundleOptions {
debug,
maybe_config_path: flags.config_path,
})?;
match maybe_ignored_options {
Some(ignored_options) if flags.no_check => {
eprintln!("{}", ignored_options);
}
_ => {}
}
debug!("{}", stats);
Ok(bundle)
}

async fn bundle_command(
flags: Flags,
source_file: String,
Expand All @@ -323,44 +443,12 @@ async fn bundle_command(
module_specifier.to_string()
);

let handler = Rc::new(RefCell::new(FetchHandler::new(
&program_state,
// when bundling, dynamic imports are only access for their type safety,
// therefore we will allow the graph to access any module.
Permissions::allow_all(),
)?));
let mut builder = module_graph::GraphBuilder::new(
handler,
program_state.maybe_import_map.clone(),
program_state.lockfile.clone(),
);
builder.add(&module_specifier, false).await?;
let module_graph = builder.get_graph();

if !flags.no_check {
// TODO(@kitsonk) support bundling for workers
let lib = if flags.unstable {
module_graph::TypeLib::UnstableDenoWindow
} else {
module_graph::TypeLib::DenoWindow
};
let result_info =
module_graph.clone().check(module_graph::CheckOptions {
debug,
emit: false,
lib,
maybe_config_path: flags.config_path.clone(),
reload: flags.reload,
})?;

debug!("{}", result_info.stats);
if let Some(ignored_options) = result_info.maybe_ignored_options {
eprintln!("{}", ignored_options);
}
if !result_info.diagnostics.is_empty() {
return Err(generic_error(result_info.diagnostics.to_string()));
}
}
let module_graph = create_module_graph_and_maybe_check(
module_specifier,
program_state.clone(),
debug,
)
.await?;

let mut paths_to_watch: Vec<PathBuf> = module_graph
.get_modules()
Expand Down Expand Up @@ -392,19 +480,7 @@ async fn bundle_command(
let flags = flags.clone();
let out_file = out_file.clone();
async move {
let (output, stats, maybe_ignored_options) =
module_graph.bundle(module_graph::BundleOptions {
debug,
maybe_config_path: flags.config_path,
})?;

match maybe_ignored_options {
Some(ignored_options) if flags.no_check => {
eprintln!("{}", ignored_options);
}
_ => {}
}
debug!("{}", stats);
let output = bundle_module_graph(module_graph, flags, debug)?;

debug!(">>>>> bundle END");

Expand Down Expand Up @@ -898,6 +974,10 @@ fn get_subcommand(
DenoSubcommand::Cache { files } => {
cache_command(flags, files).boxed_local()
}
DenoSubcommand::Compile {
source_file,
out_file,
} => compile_command(flags, source_file, out_file).boxed_local(),
DenoSubcommand::Fmt {
check,
files,
Expand Down Expand Up @@ -968,8 +1048,12 @@ pub fn main() {
colors::enable_ansi(); // For Windows 10

let args: Vec<String> = env::args().collect();
let flags = flags::flags_from_vec(args);
if let Err(err) = standalone::try_run_standalone_binary(args.clone()) {
eprintln!("{}: {}", colors::red_bold("error"), err.to_string());
std::process::exit(1);
}

let flags = flags::flags_from_vec(args);
if let Some(ref v8_flags) = flags.v8_flags {
init_v8_flags(v8_flags);
}
Expand Down
Loading