diff --git a/cli/args/flags.rs b/cli/args/flags.rs index 3f4498dac5..c80b2cdbc8 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -64,6 +64,7 @@ pub struct CompileFlags { pub output: Option, pub args: Vec, pub target: Option, + pub no_terminal: bool, pub include: Vec, } @@ -1031,6 +1032,12 @@ supported in canary. "aarch64-apple-darwin", ]), ) + .arg( + Arg::new("no-terminal") + .long("no-terminal") + .help("Hide terminal on Windows") + .action(ArgAction::SetTrue), + ) .arg(executable_ext_arg()) }) } @@ -2645,6 +2652,7 @@ fn compile_parse(flags: &mut Flags, matches: &mut ArgMatches) { let args = script.collect(); let output = matches.remove_one::("output"); let target = matches.remove_one::("target"); + let no_terminal = matches.get_flag("no-terminal"); let include = match matches.remove_many::("include") { Some(f) => f.collect(), None => vec![], @@ -2656,6 +2664,7 @@ fn compile_parse(flags: &mut Flags, matches: &mut ArgMatches) { output, args, target, + no_terminal, include, }); } @@ -6508,6 +6517,7 @@ mod tests { output: None, args: vec![], target: None, + no_terminal: false, include: vec![] }), type_check_mode: TypeCheckMode::Local, @@ -6519,7 +6529,7 @@ mod tests { #[test] fn compile_with_flags() { #[rustfmt::skip] - let r = flags_from_vec(svec!["deno", "compile", "--import-map", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--unsafely-ignore-certificate-errors", "--reload", "--lock", "lock.json", "--lock-write", "--cert", "example.crt", "--cached-only", "--location", "https:foo", "--allow-read", "--allow-net", "--v8-flags=--help", "--seed", "1", "--output", "colors", "https://deno.land/std/examples/colors.ts", "foo", "bar"]); + let r = flags_from_vec(svec!["deno", "compile", "--import-map", "import_map.json", "--no-remote", "--config", "tsconfig.json", "--no-check", "--unsafely-ignore-certificate-errors", "--reload", "--lock", "lock.json", "--lock-write", "--cert", "example.crt", "--cached-only", "--location", "https:foo", "--allow-read", "--allow-net", "--v8-flags=--help", "--seed", "1", "--no-terminal", "--output", "colors", "https://deno.land/std/examples/colors.ts", "foo", "bar"]); assert_eq!( r.unwrap(), Flags { @@ -6528,6 +6538,7 @@ mod tests { output: Some(PathBuf::from("colors")), args: svec!["foo", "bar"], target: None, + no_terminal: true, include: vec![] }), import_map_path: Some("import_map.json".to_string()), diff --git a/cli/standalone/binary.rs b/cli/standalone/binary.rs index a2fe7e916d..4d964215c1 100644 --- a/cli/standalone/binary.rs +++ b/cli/standalone/binary.rs @@ -1,6 +1,7 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use std::collections::BTreeMap; +use std::env::consts; use std::env::current_exe; use std::io::Read; use std::io::Seek; @@ -10,6 +11,7 @@ use std::path::Path; use std::path::PathBuf; use deno_ast::ModuleSpecifier; +use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; use deno_core::futures::io::AllowStdIo; @@ -383,9 +385,24 @@ impl<'a> DenoCompileBinaryWriter<'a> { cli_options: &CliOptions, ) -> Result<(), AnyError> { // Select base binary based on target - let original_binary = + let mut original_binary = self.get_base_binary(compile_flags.target.clone()).await?; + let target = compile_flags + .target + .clone() + .unwrap_or(consts::OS.to_string()); + + if compile_flags.no_terminal { + if target != "x86_64-pc-windows-msvc" && target != "windows" { + println!("{}", target); + bail!( + "The `--no-terminal` flag is only available when targeting Windows" + ) + } + set_windows_binary_to_gui(&mut original_binary)?; + } + self .write_standalone_binary( writer, @@ -559,3 +576,42 @@ impl<'a> DenoCompileBinaryWriter<'a> { } } } + +/// This function sets the subsystem field in the PE header to 2 (GUI subsystem) +/// For more information about the PE header: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format +fn set_windows_binary_to_gui(bin: &mut [u8]) -> Result<(), AnyError> { + // Get the PE header offset located in an i32 found at offset 60 + // See: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#ms-dos-stub-image-only + let start_pe = u32::from_le_bytes((bin[60..64]).try_into()?); + + // Get image type (PE32 or PE32+) indicates whether the binary is 32 or 64 bit + // The used offset and size values can be found here: + // https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-image-only + let start_32 = start_pe as usize + 28; + let magic_32 = + u16::from_le_bytes(bin[(start_32)..(start_32 + 2)].try_into()?); + + let start_64 = start_pe as usize + 24; + let magic_64 = + u16::from_le_bytes(bin[(start_64)..(start_64 + 2)].try_into()?); + + // Take the standard fields size for the current architecture (32 or 64 bit) + // This is the ofset for the Windows-Specific fields + let standard_fields_size = if magic_32 == 0x10b { + 28 + } else if magic_64 == 0x20b { + 24 + } else { + bail!("Could not find a matching magic field in the PE header") + }; + + // Set the subsystem field (offset 68) to 2 (GUI subsystem) + // For all possible options, see: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-windows-specific-fields-image-only + let subsystem_offset = 68; + let subsystem_start = + start_pe as usize + standard_fields_size + subsystem_offset; + let subsystem: u16 = 2; + bin[(subsystem_start)..(subsystem_start + 2)] + .copy_from_slice(&subsystem.to_le_bytes()); + Ok(()) +} diff --git a/cli/tools/compile.rs b/cli/tools/compile.rs index c53ae4e028..d925b0ea31 100644 --- a/cli/tools/compile.rs +++ b/cli/tools/compile.rs @@ -212,6 +212,7 @@ mod test { output: Some(PathBuf::from("./file")), args: Vec::new(), target: Some("x86_64-unknown-linux-gnu".to_string()), + no_terminal: false, include: vec![], }, &std::env::current_dir().unwrap(), @@ -234,6 +235,7 @@ mod test { args: Vec::new(), target: Some("x86_64-pc-windows-msvc".to_string()), include: vec![], + no_terminal: false, }, &std::env::current_dir().unwrap(), )