mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-22 01:29:04 +01:00
move cli to top level
This commit is contained in:
301
cli/src/util/prereqs.rs
Normal file
301
cli/src/util/prereqs.rs
Normal file
@@ -0,0 +1,301 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use super::command::capture_command;
|
||||
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;
|
||||
|
||||
lazy_static! {
|
||||
static ref LDCONFIG_STDC_RE: Regex = Regex::new(r"libstdc\+\+.* => (.+)").unwrap();
|
||||
static ref LDD_VERSION_RE: BinRegex = BinRegex::new(r"^ldd.*(.+)\.(.+)\s").unwrap();
|
||||
static ref LIBSTD_CXX_VERSION_RE: BinRegex =
|
||||
BinRegex::new(r"GLIBCXX_([0-9]+)\.([0-9]+)(?:\.([0-9]+))?").unwrap();
|
||||
static ref MIN_CXX_VERSION: SimpleSemver = SimpleSemver::new(3, 4, 18);
|
||||
static ref MIN_LDD_VERSION: SimpleSemver = SimpleSemver::new(2, 17, 0);
|
||||
}
|
||||
|
||||
pub struct PreReqChecker {}
|
||||
|
||||
impl Default for PreReqChecker {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl PreReqChecker {
|
||||
pub fn new() -> PreReqChecker {
|
||||
PreReqChecker {}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub async fn verify(&self) -> Result<Platform, AnyError> {
|
||||
Platform::env_default().ok_or_else(|| {
|
||||
SetupError("VS Code it not supported on this platform".to_owned()).into()
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn verify(&self) -> Result<Platform, AnyError> {
|
||||
let (gnu_a, gnu_b, or_musl) = tokio::join!(
|
||||
check_glibc_version(),
|
||||
check_glibcxx_version(),
|
||||
check_musl_interpreter()
|
||||
);
|
||||
|
||||
if gnu_a.is_ok() && gnu_b.is_ok() {
|
||||
return Ok(if cfg!(target_arch = "x86_64") {
|
||||
Platform::LinuxX64
|
||||
} else if cfg!(target_arch = "armhf") {
|
||||
Platform::LinuxARM32
|
||||
} else {
|
||||
Platform::LinuxARM64
|
||||
});
|
||||
}
|
||||
|
||||
if or_musl.is_ok() {
|
||||
return Ok(if cfg!(target_arch = "x86_64") {
|
||||
Platform::LinuxAlpineX64
|
||||
} else {
|
||||
Platform::LinuxAlpineARM64
|
||||
});
|
||||
}
|
||||
|
||||
let mut errors: Vec<String> = vec![];
|
||||
if let Err(e) = gnu_a {
|
||||
errors.push(e);
|
||||
} else if let Err(e) = gnu_b {
|
||||
errors.push(e);
|
||||
}
|
||||
|
||||
if let Err(e) = or_musl {
|
||||
errors.push(e);
|
||||
}
|
||||
|
||||
let bullets = errors
|
||||
.iter()
|
||||
.map(|e| format!(" - {}", e))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
Err(AnyError::from(SetupError(format!(
|
||||
"This machine not meet VS Code Server's prerequisites, expected either...\n{}",
|
||||
bullets,
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
async fn check_musl_interpreter() -> Result<(), String> {
|
||||
const MUSL_PATH: &str = if cfg!(target_platform = "aarch64") {
|
||||
"/lib/ld-musl-aarch64.so.1"
|
||||
} else {
|
||||
"/lib/ld-musl-x86_64.so.1"
|
||||
};
|
||||
|
||||
if fs::metadata(MUSL_PATH).await.is_err() {
|
||||
return Err(format!(
|
||||
"find {}, which is required to run the VS Code Server in musl environments",
|
||||
MUSL_PATH
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
async fn check_glibc_version() -> Result<(), String> {
|
||||
let ldd_version = capture_command("ldd", ["--version"])
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|o| extract_ldd_version(&o.stdout));
|
||||
|
||||
if let Some(v) = ldd_version {
|
||||
return if v.gte(&MIN_LDD_VERSION) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!(
|
||||
"find GLIBC >= 2.17 (but found {} instead) for GNU environments",
|
||||
v
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
async fn check_glibcxx_version() -> Result<(), String> {
|
||||
let mut libstdc_path: Option<String> = None;
|
||||
|
||||
const DEFAULT_LIB_PATH: &str = "/usr/lib64/libstdc++.so.6";
|
||||
const LDCONFIG_PATH: &str = "/sbin/ldconfig";
|
||||
|
||||
if fs::metadata(DEFAULT_LIB_PATH).await.is_ok() {
|
||||
libstdc_path = Some(DEFAULT_LIB_PATH.to_owned());
|
||||
} else if fs::metadata(LDCONFIG_PATH).await.is_ok() {
|
||||
libstdc_path = capture_command(LDCONFIG_PATH, ["-p"])
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|o| extract_libstd_from_ldconfig(&o.stdout));
|
||||
}
|
||||
|
||||
match libstdc_path {
|
||||
Some(path) => match fs::read(&path).await {
|
||||
Ok(contents) => check_for_sufficient_glibcxx_versions(contents),
|
||||
Err(e) => Err(format!(
|
||||
"validate GLIBCXX version for GNU environments, but could not: {}",
|
||||
e
|
||||
)),
|
||||
},
|
||||
None => Err("find libstdc++.so or ldconfig for GNU environments".to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn check_for_sufficient_glibcxx_versions(contents: Vec<u8>) -> Result<(), String> {
|
||||
let all_versions: Vec<SimpleSemver> = LIBSTD_CXX_VERSION_RE
|
||||
.captures_iter(&contents)
|
||||
.map(|m| SimpleSemver {
|
||||
major: m.get(1).map_or(0, |s| u32_from_bytes(s.as_bytes())),
|
||||
minor: m.get(2).map_or(0, |s| u32_from_bytes(s.as_bytes())),
|
||||
patch: m.get(3).map_or(0, |s| u32_from_bytes(s.as_bytes())),
|
||||
})
|
||||
.collect();
|
||||
|
||||
if !all_versions.iter().any(|v| MIN_CXX_VERSION.gte(v)) {
|
||||
return Err(format!(
|
||||
"find GLIBCXX >= 3.4.18 (but found {} instead) for GNU environments",
|
||||
all_versions
|
||||
.iter()
|
||||
.map(String::from)
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_ldd_version(output: &[u8]) -> Option<SimpleSemver> {
|
||||
LDD_VERSION_RE.captures(output).map(|m| SimpleSemver {
|
||||
major: m.get(1).map_or(0, |s| u32_from_bytes(s.as_bytes())),
|
||||
minor: m.get(2).map_or(0, |s| u32_from_bytes(s.as_bytes())),
|
||||
patch: 0,
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_libstd_from_ldconfig(output: &[u8]) -> Option<String> {
|
||||
String::from_utf8_lossy(output)
|
||||
.lines()
|
||||
.find_map(|l| LDCONFIG_STDC_RE.captures(l))
|
||||
.and_then(|cap| cap.get(1))
|
||||
.map(|cap| cap.as_str().to_owned())
|
||||
}
|
||||
|
||||
fn u32_from_bytes(b: &[u8]) -> u32 {
|
||||
String::from_utf8_lossy(b).parse::<u32>().unwrap_or(0)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct SimpleSemver {
|
||||
major: u32,
|
||||
minor: u32,
|
||||
patch: u32,
|
||||
}
|
||||
|
||||
impl From<&SimpleSemver> for String {
|
||||
fn from(s: &SimpleSemver) -> Self {
|
||||
format!("v{}.{}.{}", s.major, s.minor, s.patch)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SimpleSemver {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}", String::from(self))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl SimpleSemver {
|
||||
fn new(major: u32, minor: u32, patch: u32) -> SimpleSemver {
|
||||
SimpleSemver {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
}
|
||||
}
|
||||
|
||||
fn gte(&self, other: &SimpleSemver) -> bool {
|
||||
match self.major.cmp(&other.major) {
|
||||
Ordering::Greater => true,
|
||||
Ordering::Less => false,
|
||||
Ordering::Equal => match self.minor.cmp(&other.minor) {
|
||||
Ordering::Greater => true,
|
||||
Ordering::Less => false,
|
||||
Ordering::Equal => self.patch >= other.patch,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_extract_libstd_from_ldconfig() {
|
||||
let actual = "
|
||||
libstoken.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libstoken.so.1
|
||||
libstemmer.so.0d (libc6,x86-64) => /lib/x86_64-linux-gnu/libstemmer.so.0d
|
||||
libstdc++.so.6 (libc6,x86-64) => /lib/x86_64-linux-gnu/libstdc++.so.6
|
||||
libstartup-notification-1.so.0 (libc6,x86-64) => /lib/x86_64-linux-gnu/libstartup-notification-1.so.0
|
||||
libssl3.so (libc6,x86-64) => /lib/x86_64-linux-gnu/libssl3.so
|
||||
".to_owned().into_bytes();
|
||||
|
||||
assert_eq!(
|
||||
extract_libstd_from_ldconfig(&actual),
|
||||
Some("/lib/x86_64-linux-gnu/libstdc++.so.6".to_owned()),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
extract_libstd_from_ldconfig(&"nothing here!".to_owned().into_bytes()),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gte() {
|
||||
assert!(SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(1, 2, 3)));
|
||||
assert!(SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(0, 10, 10)));
|
||||
assert!(SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(1, 1, 10)));
|
||||
|
||||
assert!(!SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(1, 2, 10)));
|
||||
assert!(!SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(1, 3, 1)));
|
||||
assert!(!SimpleSemver::new(1, 2, 3).gte(&SimpleSemver::new(2, 2, 1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_for_sufficient_glibcxx_versions() {
|
||||
let actual = "ldd (Ubuntu GLIBC 2.31-0ubuntu9.7) 2.31
|
||||
Copyright (C) 2020 Free Software Foundation, Inc.
|
||||
This is free software; see the source for copying conditions. There is NO
|
||||
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
Written by Roland McGrath and Ulrich Drepper."
|
||||
.to_owned()
|
||||
.into_bytes();
|
||||
|
||||
assert_eq!(
|
||||
extract_ldd_version(&actual),
|
||||
Some(SimpleSemver::new(2, 31, 0)),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user