From 605b8db8f61fc4c0c71d11cde873af18d87c49bf Mon Sep 17 00:00:00 2001 From: Rabin Gaire Date: Tue, 11 Jan 2022 10:09:39 +0545 Subject: [PATCH] cli(compile): fix output flag behaviour on compile command (#13299) --- cli/fs_util.rs | 39 +++++++++++++++ cli/main.rs | 9 +++- cli/tests/integration/compile_tests.rs | 68 ++++++++++++++++++++++++++ cli/tools/standalone.rs | 11 ++++- 4 files changed, 124 insertions(+), 3 deletions(-) diff --git a/cli/fs_util.rs b/cli/fs_util.rs index 06bdb689a6..7290d36968 100644 --- a/cli/fs_util.rs +++ b/cli/fs_util.rs @@ -376,6 +376,20 @@ pub fn specifier_parent(specifier: &ModuleSpecifier) -> ModuleSpecifier { specifier } +/// This function checks if input path has trailing slash or not. If input path +/// has trailing slash it will return true else it will return false. +pub fn path_has_trailing_slash(path: &Path) -> bool { + if let Some(path_str) = path.to_str() { + if cfg!(windows) { + path_str.ends_with('\\') + } else { + path_str.ends_with('/') + } + } else { + false + } +} + #[cfg(test)] mod tests { use super::*; @@ -754,4 +768,29 @@ mod tests { assert_eq!(result.to_string(), expected); } } + + #[test] + fn test_path_has_trailing_slash() { + #[cfg(not(windows))] + { + run_test("/Users/johndoe/Desktop/deno-project/target/", true); + run_test(r"/Users/johndoe/deno-project/target//", true); + run_test("/Users/johndoe/Desktop/deno-project", false); + run_test(r"/Users/johndoe/deno-project\", false); + } + + #[cfg(windows)] + { + run_test(r"C:\test\deno-project\", true); + run_test(r"C:\test\deno-project\\", true); + run_test(r"C:\test\file.txt", false); + run_test(r"C:\test\file.txt/", false); + } + + fn run_test(path_str: &str, expected: bool) { + let path = Path::new(path_str); + let result = path_has_trailing_slash(path); + assert_eq!(result, expected); + } + } } diff --git a/cli/main.rs b/cli/main.rs index f4800b72a1..d8e4c4a704 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -378,7 +378,14 @@ async fn compile_command( let ps = ProcState::build(flags.clone()).await?; let deno_dir = &ps.dir; - let output = compile_flags.output.or_else(|| { + let output = compile_flags.output.and_then(|output| { + if fs_util::path_has_trailing_slash(&output) { + let infer_file_name = infer_name_from_url(&module_specifier).map(PathBuf::from)?; + Some(output.join(infer_file_name)) + } else { + Some(output) + } + }).or_else(|| { infer_name_from_url(&module_specifier).map(PathBuf::from) }).ok_or_else(|| generic_error( "An executable name was not provided. One could not be inferred from the URL. Aborting.", diff --git a/cli/tests/integration/compile_tests.rs b/cli/tests/integration/compile_tests.rs index d2abd04d5a..bff03013ff 100644 --- a/cli/tests/integration/compile_tests.rs +++ b/cli/tests/integration/compile_tests.rs @@ -1,5 +1,6 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +use std::fs::File; use std::process::Command; use tempfile::TempDir; use test_util as util; @@ -233,6 +234,73 @@ fn standalone_compiler_ops() { assert_eq!(output.stdout, b"Hello, Compiler API!\n"); } +#[test] +fn compile_with_directory_output_flag() { + let dir = TempDir::new().expect("tempdir fail"); + let output_path = if cfg!(windows) { + dir.path().join(r"args\random\") + } else { + dir.path().join("args/random/") + }; + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("compile") + .arg("--unstable") + .arg("--output") + .arg(&output_path) + .arg("./standalone_compiler_ops.ts") + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + let exe = if cfg!(windows) { + output_path.join("standalone_compiler_ops.exe") + } else { + output_path.join("standalone_compiler_ops") + }; + assert!(&exe.exists()); + let output = Command::new(exe) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, b"Hello, Compiler API!\n"); +} + +#[test] +fn compile_with_file_exists_error() { + let dir = TempDir::new().expect("tempdir fail"); + let output_path = if cfg!(windows) { + dir.path().join(r"args\") + } else { + dir.path().join("args/") + }; + let file_path = dir.path().join("args"); + File::create(&file_path).expect("cannot create file"); + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("compile") + .arg("--unstable") + .arg("--output") + .arg(&output_path) + .arg("./028_args.ts") + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(!output.status.success()); + let expected_stderr = + format!("Could not compile: {:?} is a file.\n", &file_path); + let stderr = String::from_utf8(output.stderr).unwrap(); + assert!(stderr.contains(&expected_stderr)); +} + #[test] fn compile_with_directory_exists_error() { let dir = TempDir::new().expect("tempdir fail"); diff --git a/cli/tools/standalone.rs b/cli/tools/standalone.rs index a29b405bad..a0960d0516 100644 --- a/cli/tools/standalone.rs +++ b/cli/tools/standalone.rs @@ -136,14 +136,14 @@ pub async fn write_standalone_binary( let output = match target { Some(target) => { if target.contains("windows") { - PathBuf::from(output.display().to_string() + ".exe") + output.with_extension("exe") } else { output } } None => { if cfg!(windows) && output.extension().unwrap_or_default() != "exe" { - PathBuf::from(output.display().to_string() + ".exe") + output.with_extension("exe") } else { output } @@ -175,7 +175,14 @@ pub async fn write_standalone_binary( // Remove file if it was indeed a deno compiled binary, to avoid corruption // (see https://github.com/denoland/deno/issues/10310) std::fs::remove_file(&output)?; + } else { + let output_base = &output.parent().unwrap(); + if output_base.exists() && output_base.is_file() { + bail!("Could not compile: {:?} is a file.", &output_base); + } + tokio::fs::create_dir_all(output_base).await?; } + tokio::fs::write(&output, final_bin).await?; #[cfg(unix)] {