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: Standalone lite binaries and cross compilation #9141

Merged
merged 8 commits into from
Jan 19, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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
28 changes: 25 additions & 3 deletions cli/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub enum DenoSubcommand {
source_file: String,
output: Option<PathBuf>,
args: Vec<String>,
target: Option<String>,
lite: bool,
},
Completions {
buf: Box<[u8]>,
Expand Down Expand Up @@ -447,11 +449,15 @@ fn compile_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
let args = script.split_off(1);
let source_file = script[0].to_string();
let output = matches.value_of("output").map(PathBuf::from);
let lite = matches.is_present("lite");
let target = matches.value_of("target").map(String::from);

flags.subcommand = DenoSubcommand::Compile {
source_file,
output,
args,
lite,
target,
};
}

Expand Down Expand Up @@ -893,11 +899,24 @@ fn compile_subcommand<'a, 'b>() -> App<'a, 'b> {
.help("Output file (defaults to $PWD/<inferred-name>)")
.takes_value(true)
)
.arg(
Arg::with_name("target")
.long("target")
.help("Target OS architecture")
.takes_value(true)
.possible_values(&["x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc", "x86_64-apple-darwin"])
)
.arg(
Arg::with_name("lite")
.long("lite")
.help("Use lite runtime")
)
.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 --output /usr/local/bin/color_util https://deno.land/std/examples/colors.ts
deno compile --unstable --lite --target x86_64-unknown-linux-gnu https://deno.land/std/http/file_server.ts
bartlomieju marked this conversation as resolved.
Show resolved Hide resolved

Any flags passed which affect runtime behavior, such as '--unstable',
'--allow-*', '--v8-flags', etc. are encoded into the output executable and used
Expand All @@ -909,9 +928,7 @@ The executable name is inferred by default:
- 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.",
- If the resulting name has an '@...' suffix, strip it.",
bartlomieju marked this conversation as resolved.
Show resolved Hide resolved
)
}

Expand Down Expand Up @@ -3318,6 +3335,7 @@ mod tests {
let r = flags_from_vec(svec![
"deno",
"compile",
"--lite",
"https://deno.land/std/examples/colors.ts"
]);
assert_eq!(
Expand All @@ -3327,6 +3345,8 @@ mod tests {
source_file: "https://deno.land/std/examples/colors.ts".to_string(),
output: None,
args: vec![],
target: None,
lite: true,
},
..Flags::default()
}
Expand All @@ -3344,6 +3364,8 @@ mod tests {
source_file: "https://deno.land/std/examples/colors.ts".to_string(),
output: Some(PathBuf::from("colors")),
args: svec!["foo", "bar"],
target: None,
lite: false,
},
unstable: true,
import_map_path: Some("import_map.json".to_string()),
Expand Down
22 changes: 17 additions & 5 deletions cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,8 @@ async fn compile_command(
source_file: String,
output: Option<PathBuf>,
args: Vec<String>,
target: Option<String>,
lite: bool,
) -> Result<(), AnyError> {
if !flags.unstable {
exit_unstable("compile");
Expand All @@ -311,6 +313,7 @@ async fn compile_command(

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

let output = output.or_else(|| {
infer_name_from_url(module_specifier.as_url()).map(PathBuf::from)
Expand All @@ -337,15 +340,21 @@ async fn compile_command(
colors::green("Compile"),
module_specifier.to_string()
);
tools::standalone::create_standalone_binary(

// Select base binary based on `target` and `lite` arguments
let original_binary =
tools::standalone::get_base_binary(deno_dir, target, lite).await?;

let final_bin = tools::standalone::create_standalone_binary(
original_binary,
bundle_str,
run_flags,
output.clone(),
)
.await?;
)?;

info!("{} {}", colors::green("Emit"), output.display());

tools::standalone::write_standalone_binary(output.clone(), final_bin).await?;

Ok(())
}

Expand Down Expand Up @@ -1162,7 +1171,10 @@ fn get_subcommand(
source_file,
output,
args,
} => compile_command(flags, source_file, output, args).boxed_local(),
lite,
target,
} => compile_command(flags, source_file, output, args, target, lite)
.boxed_local(),
DenoSubcommand::Fmt {
check,
files,
Expand Down
94 changes: 89 additions & 5 deletions cli/tools/standalone.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,105 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

use crate::deno_dir::DenoDir;
use crate::flags::DenoSubcommand;
use crate::flags::Flags;
use deno_core::error::bail;
use deno_core::error::AnyError;
use deno_core::serde_json;
use deno_runtime::deno_fetch::reqwest::Client;
use std::env;
use std::fs::read;
use std::fs::File;
use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;

use crate::standalone::Metadata;
use crate::standalone::MAGIC_TRAILER;

pub async fn get_base_binary(
deno_dir: &DenoDir,
target: Option<String>,
lite: bool,
) -> Result<Vec<u8>, AnyError> {
if target.is_none() && !lite {
let path = std::env::current_exe()?;
return Ok(tokio::fs::read(path).await?);
}
bartlomieju marked this conversation as resolved.
Show resolved Hide resolved

let target = target.unwrap_or_else(|| env!("TARGET").to_string());
let binary_name = if lite {
format!("denort-{}.zip", target)
} else {
format!("deno-{}.zip", target)
};

let binary_path_suffix = if crate::version::is_canary() {
format!("canary/{}/{}", crate::version::GIT_COMMIT_HASH, binary_name)
} else {
format!("release/v{}/{}", env!("CARGO_PKG_VERSION"), binary_name)
};

let download_directory = deno_dir.root.join("dl");
let binary_path = download_directory.join(&binary_path_suffix);

if !binary_path.exists() {
download_base_binary(&download_directory, &binary_path_suffix).await?;
}

let archive_data = tokio::fs::read(binary_path).await?;
let base_binary_path = crate::tools::upgrade::unpack(archive_data)?;
let base_binary = tokio::fs::read(base_binary_path).await?;
Ok(base_binary)
}

async fn download_base_binary(
output_directory: &Path,
binary_path_suffix: &str,
) -> Result<(), AnyError> {
let download_url = format!("https://dl.deno.land/{}", binary_path_suffix);

let client_builder = Client::builder();

// TODO:
// // If we have been provided a CA Certificate, add it into the HTTP client
// if let Some(ca_file) = ca_file {
// let buf = std::fs::read(ca_file)?;
// let cert = reqwest::Certificate::from_pem(&buf)?;
// client_builder = client_builder.add_root_certificate(cert);
bartlomieju marked this conversation as resolved.
Show resolved Hide resolved
// }

let client = client_builder.build()?;

println!("Checking {}", &download_url);

let res = client.get(&download_url).send().await?;

let binary_content = if res.status().is_success() {
println!("Download has been found");
res.bytes().await?.to_vec()
} else {
println!("Download could not be found, aborting");
std::process::exit(1)
};
bartlomieju marked this conversation as resolved.
Show resolved Hide resolved

std::fs::create_dir_all(&output_directory)?;
let output_path = output_directory.join(binary_path_suffix);
std::fs::create_dir_all(&output_path.parent().unwrap())?;
tokio::fs::write(output_path, binary_content).await?;
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(
pub fn create_standalone_binary(
mut original_bin: Vec<u8>,
source_code: String,
flags: Flags,
output: PathBuf,
) -> Result<(), AnyError> {
) -> Result<Vec<u8>, AnyError> {
let mut source_code = source_code.as_bytes().to_vec();
let ca_data = match &flags.ca_file {
Some(ca_file) => Some(read(ca_file)?),
Expand All @@ -39,8 +116,6 @@ pub async fn create_standalone_binary(
ca_data,
};
let mut metadata = serde_json::to_string(&metadata)?.as_bytes().to_vec();
let original_binary_path = std::env::current_exe()?;
let mut original_bin = tokio::fs::read(original_binary_path).await?;

let bundle_pos = original_bin.len();
let metadata_pos = bundle_pos + source_code.len();
Expand All @@ -55,6 +130,15 @@ pub async fn create_standalone_binary(
final_bin.append(&mut metadata);
final_bin.append(&mut trailer);

Ok(final_bin)
}

/// This function writes out a final binary to specified path. If output path
/// is not already standalone binary it will return error instead.
pub async fn write_standalone_binary(
output: PathBuf,
final_bin: Vec<u8>,
) -> Result<(), AnyError> {
let output =
if cfg!(windows) && output.extension().unwrap_or_default() != "exe" {
PathBuf::from(output.display().to_string() + ".exe")
Expand Down
2 changes: 1 addition & 1 deletion cli/tools/upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ async fn download_package(
}
}

fn unpack(archive_data: Vec<u8>) -> Result<PathBuf, std::io::Error> {
pub fn unpack(archive_data: Vec<u8>) -> Result<PathBuf, std::io::Error> {
// We use into_path so that the tempdir is not automatically deleted. This is
// useful for debugging upgrade, but also so this function can return a path
// to the newly uncompressed file without fear of the tempdir being deleted.
Expand Down