Переглянути джерело

fix: improve vs build tools detection, closes #3113 (#3144)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
Amr Bashir 3 роки тому
батько
коміт
a8e5ad9299
3 змінених файлів з 138 додано та 172 видалено
  1. 23 3
      tooling/cli.rs/Cargo.lock
  2. 115 169
      tooling/cli.rs/src/info.rs
  3. BIN
      tooling/cli.rs/vswhere.exe

+ 23 - 3
tooling/cli.rs/Cargo.lock

@@ -234,7 +234,7 @@ dependencies = [
  "libc",
  "num-integer",
  "num-traits",
- "time",
+ "time 0.1.43",
  "winapi 0.3.9",
 ]
 
@@ -1397,6 +1397,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "num_threads"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "once_cell"
 version = "1.9.0"
@@ -2370,7 +2379,6 @@ dependencies = [
  "ar",
  "attohttpc",
  "bitness",
- "chrono",
  "dirs-next",
  "glob",
  "handlebars",
@@ -2389,6 +2397,7 @@ dependencies = [
  "tempfile",
  "termcolor",
  "thiserror",
+ "time 0.3.7",
  "toml",
  "uuid",
  "walkdir",
@@ -2558,6 +2567,17 @@ dependencies = [
  "winapi 0.3.9",
 ]
 
+[[package]]
+name = "time"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d"
+dependencies = [
+ "itoa 1.0.1",
+ "libc",
+ "num_threads",
+]
+
 [[package]]
 name = "tinyvec"
 version = "1.5.1"
@@ -2962,5 +2982,5 @@ dependencies = [
  "crc32fast",
  "flate2",
  "thiserror",
- "time",
+ "time 0.1.43",
 ]

+ 115 - 169
tooling/cli.rs/src/info.rs

@@ -97,20 +97,25 @@ fn crate_latest_version(name: &str) -> Option<String> {
   }
 }
 
-fn npm_latest_version(pm: &PackageManager, name: &str) -> crate::Result<Option<String>> {
+fn cross_command(bin: &str) -> Command {
   let mut cmd;
+  #[cfg(target_os = "windows")]
+  {
+    cmd = Command::new("cmd");
+    cmd.arg("/c").arg(bin);
+  }
+
+  #[cfg(not(target_os = "windows"))]
+  {
+    cmd = Command::new(bin)
+  }
+  cmd
+}
+
+fn npm_latest_version(pm: &PackageManager, name: &str) -> crate::Result<Option<String>> {
   match pm {
     PackageManager::Yarn => {
-      #[cfg(target_os = "windows")]
-      {
-        cmd = Command::new("cmd");
-        cmd.arg("/c").arg("yarn");
-      }
-
-      #[cfg(not(target_os = "windows"))]
-      {
-        cmd = Command::new("yarn")
-      }
+      let mut cmd = cross_command("yarn");
 
       let output = cmd
         .arg("info")
@@ -126,16 +131,7 @@ fn npm_latest_version(pm: &PackageManager, name: &str) -> crate::Result<Option<S
       }
     }
     PackageManager::Npm => {
-      #[cfg(target_os = "windows")]
-      {
-        cmd = Command::new("cmd");
-        cmd.arg("/c").arg("npm");
-      }
-
-      #[cfg(not(target_os = "windows"))]
-      {
-        cmd = Command::new("npm")
-      }
+      let mut cmd = cross_command("npm");
 
       let output = cmd.arg("show").arg(name).arg("version").output()?;
       if output.status.success() {
@@ -146,16 +142,7 @@ fn npm_latest_version(pm: &PackageManager, name: &str) -> crate::Result<Option<S
       }
     }
     PackageManager::Pnpm => {
-      #[cfg(target_os = "windows")]
-      {
-        cmd = Command::new("cmd");
-        cmd.arg("/c").arg("pnpm");
-      }
-
-      #[cfg(not(target_os = "windows"))]
-      {
-        cmd = Command::new("pnpm")
-      }
+      let mut cmd = cross_command("pnpm");
 
       let output = cmd.arg("info").arg(name).arg("version").output()?;
       if output.status.success() {
@@ -173,65 +160,25 @@ fn npm_package_version<P: AsRef<Path>>(
   name: &str,
   app_dir: P,
 ) -> crate::Result<Option<String>> {
-  let mut cmd;
   let output = match pm {
-    PackageManager::Yarn => {
-      #[cfg(target_os = "windows")]
-      {
-        cmd = Command::new("cmd");
-        cmd.arg("/c").arg("yarn");
-      }
-
-      #[cfg(not(target_os = "windows"))]
-      {
-        cmd = Command::new("yarn")
-      }
-
-      cmd
-        .args(&["list", "--pattern"])
-        .arg(name)
-        .args(&["--depth", "0"])
-        .current_dir(app_dir)
-        .output()?
-    }
-    PackageManager::Npm => {
-      #[cfg(target_os = "windows")]
-      {
-        cmd = Command::new("cmd");
-        cmd.arg("/c").arg("npm");
-      }
-
-      #[cfg(not(target_os = "windows"))]
-      {
-        cmd = Command::new("npm")
-      }
-
-      cmd
-        .arg("list")
-        .arg(name)
-        .args(&["version", "--depth", "0"])
-        .current_dir(app_dir)
-        .output()?
-    }
-    PackageManager::Pnpm => {
-      #[cfg(target_os = "windows")]
-      {
-        cmd = Command::new("cmd");
-        cmd.arg("/c").arg("pnpm");
-      }
-
-      #[cfg(not(target_os = "windows"))]
-      {
-        cmd = Command::new("pnpm")
-      }
-
-      cmd
-        .arg("list")
-        .arg(name)
-        .args(&["--parseable", "--depth", "0"])
-        .current_dir(app_dir)
-        .output()?
-    }
+    PackageManager::Yarn => cross_command("yarn")
+      .args(&["list", "--pattern"])
+      .arg(name)
+      .args(&["--depth", "0"])
+      .current_dir(app_dir)
+      .output()?,
+    PackageManager::Npm => cross_command("npm")
+      .arg("list")
+      .arg(name)
+      .args(&["version", "--depth", "0"])
+      .current_dir(app_dir)
+      .output()?,
+    PackageManager::Pnpm => cross_command("pnpm")
+      .arg("list")
+      .arg(name)
+      .args(&["--parseable", "--depth", "0"])
+      .current_dir(app_dir)
+      .output()?,
   };
   if output.status.success() {
     let stdout = String::from_utf8_lossy(&output.stdout);
@@ -248,19 +195,10 @@ fn npm_package_version<P: AsRef<Path>>(
 }
 
 fn get_version(command: &str, args: &[&str]) -> crate::Result<Option<String>> {
-  let mut cmd;
-  #[cfg(target_os = "windows")]
-  {
-    cmd = Command::new("cmd");
-    cmd.arg("/c").arg(command);
-  }
-
-  #[cfg(not(target_os = "windows"))]
-  {
-    cmd = Command::new(command)
-  }
-
-  let output = cmd.args(args).arg("--version").output()?;
+  let output = cross_command(command)
+    .args(args)
+    .arg("--version")
+    .output()?;
   let version = if output.status.success() {
     Some(
       String::from_utf8_lossy(&output.stdout)
@@ -310,63 +248,66 @@ fn webview2_version() -> crate::Result<Option<String>> {
 }
 
 #[cfg(windows)]
-fn run_vs_setup_instance() -> std::io::Result<std::process::Output> {
-  Command::new("powershell")
-    .args(&["-NoProfile", "-Command"])
-    .arg("Get-VSSetupInstance")
-    .output()
+#[derive(Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+struct VsInstanceInfo {
+  display_name: String,
 }
 
+#[cfg(windows)]
+const VSWHERE: &[u8] = include_bytes!("../vswhere.exe");
+
 #[cfg(windows)]
 fn build_tools_version() -> crate::Result<Option<Vec<String>>> {
-  let mut output = run_vs_setup_instance();
-  if output.is_err() {
-    Command::new("powershell")
-      .args(&["-NoProfile", "-Command"])
-      .arg("Install-Module VSSetup -Scope CurrentUser")
-      .output()?;
-    output = run_vs_setup_instance();
-  }
-  let output = output?;
-  let versions = if output.status.success() {
-    let stdout = String::from_utf8_lossy(&output.stdout);
-    let mut versions = Vec::new();
+  let mut vswhere = std::env::temp_dir();
+  vswhere.push("vswhere.exe");
 
-    let regex = regex::Regex::new(r"Visual Studio Build Tools (?P<version>\d+)").unwrap();
-    for caps in regex.captures_iter(&stdout) {
-      versions.push(caps["version"].to_string());
-    }
-
-    if versions.is_empty() {
-      None
-    } else {
-      Some(versions)
+  if !vswhere.exists() {
+    if let Ok(mut file) = std::fs::File::create(vswhere.clone()) {
+      use std::io::Write;
+      let _ = file.write_all(VSWHERE);
     }
+  }
+  let output = cross_command(vswhere.to_str().unwrap())
+    .args(&[
+      "-prerelease",
+      "-products",
+      "*",
+      "-requiresAny",
+      "-requires",
+      "Microsoft.VisualStudio.Workload.NativeDesktop",
+      "-requires",
+      "Microsoft.VisualStudio.Workload.VCTools",
+      "-format",
+      "json",
+    ])
+    .output()?;
+  Ok(if output.status.success() {
+    let stdout = String::from_utf8_lossy(&output.stdout);
+    let instances: Vec<VsInstanceInfo> = serde_json::from_str(&stdout)?;
+    Some(
+      instances
+        .iter()
+        .map(|i| i.display_name.clone())
+        .collect::<Vec<String>>(),
+    )
   } else {
     None
-  };
-  Ok(versions)
+  })
 }
 
-fn get_active_rust_toolchain() -> crate::Result<Option<String>> {
-  let mut cmd;
-  #[cfg(target_os = "windows")]
-  {
-    cmd = Command::new("cmd");
-    cmd.arg("/c").arg("rustup");
-  }
-
-  #[cfg(not(target_os = "windows"))]
-  {
-    cmd = Command::new("rustup")
-  }
-
-  let output = cmd.args(["show", "active-toolchain"]).output()?;
+fn active_rust_toolchain() -> crate::Result<Option<String>> {
+  let output = cross_command("rustup")
+    .args(["show", "active-toolchain"])
+    .output()?;
   let toolchain = if output.status.success() {
     Some(
       String::from_utf8_lossy(&output.stdout)
         .replace('\n', "")
-        .replace('\r', ""),
+        .replace('\r', "")
+        .split('(')
+        .collect::<Vec<&str>>()[0]
+        .into(),
     )
   } else {
     None
@@ -481,26 +422,35 @@ pub fn command(_options: Options) -> Result<()> {
 
   #[cfg(windows)]
   VersionBlock::new("Webview2", webview2_version().unwrap_or_default()).display();
+
   #[cfg(windows)]
-  VersionBlock::new(
-    "Visual Studio Build Tools",
-    build_tools_version()
-      .map(|r| {
-        let required_string = "(>= 2019 required)";
-        let multiple_string =
-          "(multiple versions might conflict; keep only 2019 if build errors occur)";
-        r.map(|v| match v.len() {
-          1 if v[0].as_str() < "2019" => format!("{} {}", v[0], required_string),
-          1 if v[0].as_str() >= "2019" => v[0].clone(),
-          _ if v.contains(&"2019".into()) => {
-            format!("{} {}", v.join(", "), multiple_string)
-          }
-          _ => format!("{} {} {}", v.join(", "), required_string, multiple_string),
-        })
-      })
-      .unwrap_or_default(),
-  )
-  .display();
+  {
+    let build_tools = build_tools_version()
+      .unwrap_or_default()
+      .unwrap_or_default();
+
+    if build_tools.is_empty() {
+      InfoBlock {
+        section: false,
+        key: "Visual Studio Build Tools - Not installed",
+        value: None,
+        suffix: None,
+      }
+      .display();
+    } else {
+      InfoBlock {
+        section: false,
+        key: "Visual Studio Build Tools:",
+        value: None,
+        suffix: None,
+      }
+      .display();
+
+      for i in build_tools {
+        VersionBlock::new("  ", i).display();
+      }
+    }
+  }
 
   let hook = panic::take_hook();
   panic::set_hook(Box::new(|_info| {
@@ -603,11 +553,7 @@ pub fn command(_options: Options) -> Result<()> {
     }),
   )
   .display();
-  VersionBlock::new(
-    "  toolchain",
-    get_active_rust_toolchain().unwrap_or_default(),
-  )
-  .display();
+  VersionBlock::new("  toolchain", active_rust_toolchain().unwrap_or_default()).display();
 
   if let Some(app_dir) = app_dir {
     InfoBlock::new("App directory structure")

BIN
tooling/cli.rs/vswhere.exe