From f54721c6d3faa9965163872d362691b44fd4f69f Mon Sep 17 00:00:00 2001 From: Brian Myers Date: Mon, 20 Jun 2022 23:12:03 -0400 Subject: [PATCH] feature(cli): don't block forever waiting on stdin --- Cargo.lock | 120 ++++++++++++++++++++++-- crates/swc_cli/Cargo.toml | 14 ++- crates/swc_cli/src/commands/compile.rs | 85 ++++++++--------- crates/swc_cli/src/util/io.rs | 122 +++++++++++++++++++++++++ crates/swc_cli/src/util/mod.rs | 1 + 5 files changed, 287 insertions(+), 55 deletions(-) create mode 100644 crates/swc_cli/src/util/io.rs diff --git a/Cargo.lock b/Cargo.lock index 5aa0210d9dfc..3d397a7e5cb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1046,7 +1046,7 @@ dependencies = [ "cfg-if 1.0.0", "js-sys", "libc", - "wasi", + "wasi 0.10.2+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -1602,6 +1602,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.36.1", +] + [[package]] name = "miow" version = "0.3.7" @@ -3065,9 +3077,9 @@ name = "swc_cli" version = "0.64.0" dependencies = [ "anyhow", - "atty", "clap 3.1.0", "glob", + "mio 0.8.4", "path-absolutize", "rayon", "relative-path", @@ -3077,6 +3089,7 @@ dependencies = [ "swc_common", "swc_plugin_runner", "swc_trace_macro", + "thiserror", "tracing", "tracing-chrome", "tracing-futures", @@ -3084,6 +3097,7 @@ dependencies = [ "walkdir", "wasmer", "wasmer-wasi", + "windows", ] [[package]] @@ -4318,18 +4332,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" dependencies = [ "proc-macro2", "quote", @@ -4457,7 +4471,7 @@ dependencies = [ "bytes", "libc", "memchr", - "mio", + "mio 0.7.14", "num_cpus", "pin-project-lite", "winapi", @@ -4718,6 +4732,12 @@ version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.79" @@ -5150,6 +5170,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" +dependencies = [ + "windows_aarch64_msvc 0.37.0", + "windows_i686_gnu 0.37.0", + "windows_i686_msvc 0.37.0", + "windows_x86_64_gnu 0.37.0", + "windows_x86_64_msvc 0.37.0", +] + [[package]] name = "windows-sys" version = "0.32.0" @@ -5176,6 +5209,19 @@ dependencies = [ "windows_x86_64_msvc 0.33.0", ] +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + [[package]] name = "windows_aarch64_msvc" version = "0.32.0" @@ -5188,6 +5234,18 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" + [[package]] name = "windows_i686_gnu" version = "0.32.0" @@ -5200,6 +5258,18 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" + [[package]] name = "windows_i686_msvc" version = "0.32.0" @@ -5212,6 +5282,18 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_i686_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" + [[package]] name = "windows_x86_64_gnu" version = "0.32.0" @@ -5224,6 +5306,18 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" + [[package]] name = "windows_x86_64_msvc" version = "0.32.0" @@ -5236,6 +5330,18 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" + [[package]] name = "winreg" version = "0.7.0" diff --git a/crates/swc_cli/Cargo.toml b/crates/swc_cli/Cargo.toml index d0f61c306b0d..9d4e88e7ead6 100644 --- a/crates/swc_cli/Cargo.toml +++ b/crates/swc_cli/Cargo.toml @@ -24,7 +24,6 @@ plugin = [ [dependencies] anyhow = "1.0.53" -atty = "0.2.14" clap = { version = "3.1.0", features = ["derive", "wrap_help"] } glob = "0.3.0" rayon = "1" @@ -35,6 +34,7 @@ swc = { version = "0.190.0", path = "../swc" } swc_common = { version = "0.18.0", path = "../swc_common" } swc_plugin_runner = { version = "0.56.0", path = "../swc_plugin_runner", default-features = false, optional = true } swc_trace_macro = { version = "0.1.0", path = "../swc_trace_macro" } +thiserror = "1.0.31" tracing = "0.1.32" tracing-chrome = "0.5.0" tracing-futures = "0.2.5" @@ -46,3 +46,15 @@ wasmer-wasi = { version = "2.3.0", optional = true } [dependencies.path-absolutize] features = ["once_cell_cache"] version = "3.0.11" + +[target.'cfg(unix)'.dependencies] +mio = { version = "0.8.4", features = ["os-ext"] } + +[target.'cfg(windows)'.dependencies.windows] +features = [ + "Win32_Foundation", + "Win32_Storage_FileSystem", + "Win32_System_Console", + "Win32_System_Threading", +] +version = "0.37.0" diff --git a/crates/swc_cli/src/commands/compile.rs b/crates/swc_cli/src/commands/compile.rs index 26a68b4af2dd..0c187df5f368 100644 --- a/crates/swc_cli/src/commands/compile.rs +++ b/crates/swc_cli/src/commands/compile.rs @@ -1,8 +1,9 @@ use std::{ fs::{self, File}, - io::{self, BufRead, Write}, + io::Write, path::{Path, PathBuf}, sync::Arc, + time::Duration, }; use anyhow::Context; @@ -21,7 +22,7 @@ use swc_common::{ use swc_trace_macro::swc_trace; use walkdir::WalkDir; -use crate::util::trace::init_trace; +use crate::{util, util::trace::init_trace}; /// Configuration option for transform files. #[derive(Parser)] @@ -238,21 +239,6 @@ fn emit_output( Ok(()) } -fn collect_stdin_input() -> Option { - if atty::is(atty::Stream::Stdin) { - return None; - } - - Some( - io::stdin() - .lock() - .lines() - .map(|line| line.expect("Not able to read stdin")) - .collect::>() - .join("\n"), - ) -} - struct InputContext { options: Options, fm: Arc, @@ -296,34 +282,7 @@ impl CompileOptions { fn collect_inputs(&self) -> anyhow::Result> { let compiler = COMPILER.clone(); - let stdin_input = collect_stdin_input(); - if stdin_input.is_some() && !self.files.is_empty() { - anyhow::bail!("Cannot specify inputs from stdin and files at the same time"); - } - - if let Some(stdin_input) = stdin_input { - let options = self.build_transform_options(&self.filename.as_deref())?; - - let fm = compiler.cm.new_source_file( - if options.filename.is_empty() { - FileName::Anon - } else { - FileName::Real(options.filename.clone().into()) - }, - stdin_input, - ); - - return Ok(vec![InputContext { - options, - fm, - compiler, - file_path: self - .filename - .clone() - .unwrap_or_else(|| PathBuf::from("unknown")), - file_extension: self.out_file_extension.clone().into(), - }]); - } else if !self.files.is_empty() { + if !self.files.is_empty() { let included_extensions = if let Some(extensions) = &self.extensions { extensions.clone() } else { @@ -353,10 +312,42 @@ impl CompileOptions { }) }) }) - .collect::>>(); + .collect(); } - anyhow::bail!("Input is empty"); + let stdin = { + let mut buf = Vec::new(); + util::io::read_from_stdin_with_timeout( + &mut buf, + Duration::from_millis(100), // poll_duration + Duration::from_secs(5), // timeout + )?; + let stdin = + String::from_utf8(buf).context("Input from stdin may not contain invalid utf-8")?; + if stdin.is_empty() { + anyhow::bail!("Input is empty"); + } + stdin + }; + let options = self.build_transform_options(&self.filename.as_deref())?; + let fm = compiler.cm.new_source_file( + if options.filename.is_empty() { + FileName::Anon + } else { + FileName::Real(options.filename.clone().into()) + }, + stdin, + ); + Ok(vec![InputContext { + options, + fm, + compiler, + file_path: self + .filename + .clone() + .unwrap_or_else(|| PathBuf::from("unknown")), + file_extension: self.out_file_extension.clone().into(), + }]) } fn execute_inner(&self) -> anyhow::Result<()> { diff --git a/crates/swc_cli/src/util/io.rs b/crates/swc_cli/src/util/io.rs new file mode 100644 index 000000000000..cd2efefe46fa --- /dev/null +++ b/crates/swc_cli/src/util/io.rs @@ -0,0 +1,122 @@ +use std::{io, time::Duration}; + +#[derive(Debug, thiserror::Error)] +pub(crate) enum ReadFromStdinWithTimeoutError { + #[error("IO error while reading from stdin: {0}")] + Io(#[from] io::Error), + + #[error("Timeout of {0:?} exceeded while reading from stdin")] + Timeout(Duration), + + #[cfg(windows)] + #[error("Invalid timeout. Timeout may not exceed u32::MAX milliseconds")] + TimeoutInvalid, +} + +impl ReadFromStdinWithTimeoutError { + #[cfg(windows)] + fn other(s: S) -> Self + where + S: Into, + { + Self::Io(io::Error::new(io::ErrorKind::Other, s.into())) + } +} + +pub(crate) fn read_from_stdin_with_timeout( + writer: &mut W, + poll_duration: Duration, + timeout: Duration, +) -> Result +where + W: io::Write, +{ + wait_for_stdin_with_timeout(poll_duration, timeout)?; + let mut stdin = io::stdin().lock(); + let nbytes = io::copy(&mut stdin, writer)?; + Ok(nbytes) +} + +#[cfg(unix)] +fn wait_for_stdin_with_timeout( + poll_duration: Duration, + timeout: Duration, +) -> Result<(), ReadFromStdinWithTimeoutError> { + use std::time::Instant; + + use mio::{unix::SourceFd, Events, Interest, Poll, Token}; + + const EVENTS_BUFFER_CAPACITY: usize = 32; + const STDIN: i32 = 0; + const TOKEN: Token = Token(0); + + let now = Instant::now(); + + let mut poll = Poll::new()?; + let mut events = Events::with_capacity(EVENTS_BUFFER_CAPACITY); + let mut source_fd = SourceFd(&STDIN); + poll.registry() + .register(&mut source_fd, TOKEN, Interest::READABLE)?; + + loop { + if now.elapsed() > timeout { + return Err(ReadFromStdinWithTimeoutError::Timeout(timeout)); + } + poll.poll(&mut events, Some(poll_duration))?; + if events.iter().count() > 0 { + return Ok(()); + } + } +} + +#[cfg(windows)] +fn wait_for_stdin_with_timeout( + _poll_duration: Duration, + timeout: Duration, +) -> Result<(), ReadFromStdinWithTimeoutError> { + use windows::Win32::{ + Foundation::{WAIT_FAILED, WAIT_TIMEOUT}, + Storage::FileSystem::FlushFileBuffers, + System::{ + Console::{GetStdHandle, STD_INPUT_HANDLE}, + Threading::{WaitForSingleObject, WAIT_OBJECT_0}, + }, + }; + + let timeout_ms = u32::try_from(timeout.as_millis()) + .map_err(|_| ReadFromStdinWithTimeoutError::TimeoutInvalid)?; + + unsafe { + let handle = GetStdHandle(STD_INPUT_HANDLE).map_err(|e| { + ReadFromStdinWithTimeoutError::other(format!( + "Failed to get HANDLE to stdin. Error: {}", + e + )) + })?; + + let _ = FlushFileBuffers(handle); + + let ret = WaitForSingleObject(handle, timeout_ms); + if ret == WAIT_OBJECT_0 { + Ok(()) + } else if ret == WAIT_TIMEOUT.0 { + Err(ReadFromStdinWithTimeoutError::Timeout(timeout)) + } else if ret == WAIT_FAILED.0 { + Err(ReadFromStdinWithTimeoutError::other( + "Failed to wait on stdin", + )) + } else { + Err(ReadFromStdinWithTimeoutError::other( + "Failed to wait on stdin", + )) + } + } +} + +#[cfg(all(not(unix), not(windows)))] +fn wait_for_stdin_with_timeout( + _poll_duration: Duration, + _timeout: Duration, +) -> Result<(), ReadFromStdinWithTimeoutError> { + Ok(()) +} diff --git a/crates/swc_cli/src/util/mod.rs b/crates/swc_cli/src/util/mod.rs index daa2d4cb3dcd..6791eca19f17 100644 --- a/crates/swc_cli/src/util/mod.rs +++ b/crates/swc_cli/src/util/mod.rs @@ -1 +1,2 @@ +pub(crate) mod io; pub(crate) mod trace;