cli: add streams to rpc, generic 'spawn' command (#179732)

* cli: apply improvements from integrated wsl branch

* cli: add streams to rpc, generic 'spawn' command

For the "exec server" concept, fyi @aeschli.

* update clippy and apply fixes

* fix unused imports :(
This commit is contained in:
Connor Peet
2023-04-12 08:51:29 -07:00
committed by GitHub
parent bb7570f4f8
commit 2d8ff25c85
23 changed files with 572 additions and 184 deletions

View File

@@ -2,29 +2,47 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
use super::errors::{wrap, AnyError, CommandFailed, WrappedError};
use std::{borrow::Cow, ffi::OsStr, process::Stdio};
use super::errors::CodeError;
use std::{
borrow::Cow,
ffi::OsStr,
process::{Output, Stdio},
};
use tokio::process::Command;
pub async fn capture_command_and_check_status(
command_str: impl AsRef<OsStr>,
args: &[impl AsRef<OsStr>],
) -> Result<std::process::Output, AnyError> {
) -> Result<std::process::Output, CodeError> {
let output = capture_command(&command_str, args).await?;
check_output_status(output, || {
format!(
"{} {}",
command_str.as_ref().to_string_lossy(),
args.iter()
.map(|a| a.as_ref().to_string_lossy())
.collect::<Vec<Cow<'_, str>>>()
.join(" ")
)
})
}
pub fn check_output_status(
output: Output,
cmd_str: impl FnOnce() -> String,
) -> Result<std::process::Output, CodeError> {
if !output.status.success() {
return Err(CommandFailed {
command: format!(
"{} {}",
command_str.as_ref().to_string_lossy(),
args.iter()
.map(|a| a.as_ref().to_string_lossy())
.collect::<Vec<Cow<'_, str>>>()
.join(" ")
),
output,
}
.into());
return Err(CodeError::CommandFailed {
command: cmd_str(),
code: output.status.code().unwrap_or(-1),
output: String::from_utf8_lossy(if output.stderr.is_empty() {
&output.stdout
} else {
&output.stderr
})
.into(),
});
}
Ok(output)
@@ -33,7 +51,7 @@ pub async fn capture_command_and_check_status(
pub async fn capture_command<A, I, S>(
command_str: A,
args: I,
) -> Result<std::process::Output, WrappedError>
) -> Result<std::process::Output, CodeError>
where
A: AsRef<OsStr>,
I: IntoIterator<Item = S>,
@@ -45,27 +63,23 @@ where
.stdout(Stdio::piped())
.output()
.await
.map_err(|e| {
wrap(
e,
format!(
"failed to execute command '{}'",
command_str.as_ref().to_string_lossy()
),
)
.map_err(|e| CodeError::CommandFailed {
command: command_str.as_ref().to_string_lossy().to_string(),
code: -1,
output: e.to_string(),
})
}
/// Kills and processes and all of its children.
#[cfg(target_os = "windows")]
pub async fn kill_tree(process_id: u32) -> Result<(), WrappedError> {
pub async fn kill_tree(process_id: u32) -> Result<(), CodeError> {
capture_command("taskkill", &["/t", "/pid", &process_id.to_string()]).await?;
Ok(())
}
/// Kills and processes and all of its children.
#[cfg(not(target_os = "windows"))]
pub async fn kill_tree(process_id: u32) -> Result<(), WrappedError> {
pub async fn kill_tree(process_id: u32) -> Result<(), CodeError> {
use futures::future::join_all;
use tokio::io::{AsyncBufReadExt, BufReader};
@@ -82,7 +96,11 @@ pub async fn kill_tree(process_id: u32) -> Result<(), WrappedError> {
.stdin(Stdio::null())
.stdout(Stdio::piped())
.spawn()
.map_err(|e| wrap(e, "error enumerating process tree"))?;
.map_err(|e| CodeError::CommandFailed {
command: format!("pgrep -P {}", parent_id),
code: -1,
output: e.to_string(),
})?;
let mut kill_futures = vec![tokio::spawn(
async move { kill_single_pid(parent_id).await },

View File

@@ -258,18 +258,6 @@ impl std::fmt::Display for RefreshTokenNotAvailableError {
}
}
#[derive(Debug)]
pub struct UnsupportedPlatformError();
impl std::fmt::Display for UnsupportedPlatformError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"This operation is not supported on your current platform"
)
}
}
#[derive(Debug)]
pub struct NoInstallInUserProvidedPath(pub String);
@@ -419,28 +407,6 @@ impl std::fmt::Display for OAuthError {
}
}
#[derive(Debug)]
pub struct CommandFailed {
pub output: std::process::Output,
pub command: String,
}
impl std::fmt::Display for CommandFailed {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"Failed to run command \"{}\" (code {}): {}",
self.command,
self.output.status,
String::from_utf8_lossy(if self.output.stderr.is_empty() {
&self.output.stdout
} else {
&self.output.stderr
})
)
}
}
// Makes an "AnyError" enum that contains any of the given errors, in the form
// `enum AnyError { FooError(FooError) }` (when given `makeAnyError!(FooError)`).
// Useful to easily deal with application error types without making tons of "From"
@@ -500,6 +466,20 @@ pub enum CodeError {
#[cfg(windows)]
#[error("could not get windows app lock: {0:?}")]
AppLockFailed(std::io::Error),
#[error("failed to run command \"{command}\" (code {code}): {output}")]
CommandFailed {
command: String,
code: i32,
output: String,
},
#[error("platform not currently supported: {0}")]
UnsupportedPlatform(String),
#[error("This machine not meet {name}'s prerequisites, expected either...: {bullets}")]
PrerequisitesFailed { name: &'static str, bullets: String },
#[error("failed to spawn process: {0:?}")]
ProcessSpawnFailed(std::io::Error)
}
makeAnyError!(
@@ -518,7 +498,6 @@ makeAnyError!(
ExtensionInstallFailed,
MismatchedLaunchModeError,
NoAttachedServerError,
UnsupportedPlatformError,
RefreshTokenNotAvailableError,
NoInstallInUserProvidedPath,
UserCancelledInstallation,
@@ -530,7 +509,6 @@ makeAnyError!(
UpdatesNotConfigured,
CorruptDownload,
MissingHomeDirectory,
CommandFailed,
OAuthError,
InvalidRpcDataError,
CodeError

View File

@@ -16,7 +16,7 @@ use hyper::{
HeaderMap, StatusCode,
};
use serde::de::DeserializeOwned;
use std::{io, pin::Pin, str::FromStr, task::Poll};
use std::{io, pin::Pin, str::FromStr, sync::Arc, task::Poll};
use tokio::{
fs,
io::{AsyncRead, AsyncReadExt},
@@ -116,6 +116,8 @@ pub trait SimpleHttp {
) -> Result<SimpleResponse, AnyError>;
}
pub type BoxedHttp = Arc<dyn SimpleHttp + Send + Sync + 'static>;
// Implementation of SimpleHttp that uses a reqwest client.
#[derive(Clone)]
pub struct ReqwestSimpleHttp {
@@ -324,7 +326,6 @@ impl AsyncRead for DelegatedReader {
/// Simple http implementation that falls back to delegated http if
/// making a direct reqwest fails.
#[derive(Clone)]
pub struct FallbackSimpleHttp {
native: ReqwestSimpleHttp,
delegated: DelegatedSimpleHttp,

View File

@@ -7,13 +7,12 @@ use std::cmp::Ordering;
use super::command::capture_command;
use crate::constants::QUALITYLESS_SERVER_NAME;
use crate::update_service::Platform;
use crate::util::errors::SetupError;
use lazy_static::lazy_static;
use regex::bytes::Regex as BinRegex;
use regex::Regex;
use tokio::fs;
use super::errors::AnyError;
use super::errors::CodeError;
lazy_static! {
static ref LDCONFIG_STDC_RE: Regex = Regex::new(r"libstdc\+\+.* => (.+)").unwrap();
@@ -41,19 +40,18 @@ impl PreReqChecker {
}
#[cfg(not(target_os = "linux"))]
pub async fn verify(&self) -> Result<Platform, AnyError> {
use crate::constants::QUALITYLESS_PRODUCT_NAME;
pub async fn verify(&self) -> Result<Platform, CodeError> {
Platform::env_default().ok_or_else(|| {
SetupError(format!(
"{} is not supported on this platform",
QUALITYLESS_PRODUCT_NAME
CodeError::UnsupportedPlatform(format!(
"{} {}",
std::env::consts::OS,
std::env::consts::ARCH
))
.into()
})
}
#[cfg(target_os = "linux")]
pub async fn verify(&self) -> Result<Platform, AnyError> {
pub async fn verify(&self) -> Result<Platform, CodeError> {
let (is_nixos, gnu_a, gnu_b, or_musl) = tokio::join!(
check_is_nixos(),
check_glibc_version(),
@@ -96,10 +94,10 @@ impl PreReqChecker {
.collect::<Vec<String>>()
.join("\n");
Err(AnyError::from(SetupError(format!(
"This machine not meet {}'s prerequisites, expected either...\n{}",
QUALITYLESS_SERVER_NAME, bullets,
))))
Err(CodeError::PrerequisitesFailed {
bullets,
name: QUALITYLESS_SERVER_NAME,
})
}
}

View File

@@ -4,9 +4,11 @@
*--------------------------------------------------------------------------------------------*/
use async_trait::async_trait;
use std::{marker::PhantomData, sync::Arc};
use tokio::sync::{
broadcast, mpsc,
watch::{self, error::RecvError},
use tokio::{
sync::{
broadcast, mpsc,
watch::{self, error::RecvError},
},
};
#[derive(Clone)]