Skip to content

Commit

Permalink
Feature/rust msvc (#13)
Browse files Browse the repository at this point in the history
* add support for installing MSVC - Windows SDK and VC Tools dependency
  • Loading branch information
georgik authored Aug 9, 2023
1 parent 67756c5 commit e032b69
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 106 deletions.
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "esp-helm",
"private": true,
"version": "0.0.11",
"version": "0.0.12",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -10,15 +10,16 @@
"tauri": "tauri"
},
"dependencies": {
"@tauri-apps/api": "^1.4.0",
"ansi_up": "^5.2.1",
"vue": "^3.3.4",
"vue-router": "^4.2.4",
"@tauri-apps/api": "^1.4.0"
"vue-router": "^4.2.4"
},
"devDependencies": {
"@tauri-apps/cli": "^1.4.0",
"@vitejs/plugin-vue": "^4.2.3",
"typescript": "^5.0.2",
"vite": "^4.4.4",
"vue-tsc": "^1.8.5",
"@tauri-apps/cli": "^1.4.0"
"vue-tsc": "^1.8.5"
}
}
4 changes: 2 additions & 2 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ futures = "0.3.28"
reqwest = { version = "0.11", features = ["blocking"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tauri = { version = "1.4", features = [ "os-all", "updater", "path-all", "dialog-ask", "dialog-confirm", "dialog-open", "shell-open"] }
tokio = { version = "1.29.1" }
tauri = { version = "1.4", features = [ "updater", "path-all", "dialog-ask", "dialog-confirm", "dialog-open", "shell-open"] }
tokio = { version = "1.29.1", features = [ "io-util", "macros", "process" ] }
thiserror = "1.0.44"
zip = "0.6.6"
walkdir = "2.3.3"
Expand Down
112 changes: 61 additions & 51 deletions src-tauri/src/external_command.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
use std::io::BufReader;
use std::io::BufRead;
use std::process::Command;
use std::process::Stdio;
use std::thread;
use std::path::Path;
Expand Down Expand Up @@ -31,58 +28,71 @@ pub fn emit_rust_console(window: &Window, message: String) {
window.emit("rust-console", event).unwrap();
}

pub fn run_external_command_with_progress(
window: Window,
app: tauri::AppHandle,
cmd_name: &str,
cmd_args: &[&str],
progress_event: &str
) -> Result<String, ()> {
use tokio::process::{Command, ChildStdout, ChildStderr};
use tokio::io::{AsyncBufReadExt, BufReader};

// Convert the references to owned data
let cmd_name_owned = cmd_name.to_string();
let cmd_args_owned: Vec<String> = cmd_args.iter().map(|&s| s.to_string()).collect();

let child_handle = thread::spawn(move || {
// Launch the command
let mut child = Command::new(&cmd_name_owned) // Use the owned data here
.args(&cmd_args_owned) // And here
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("Failed to launch command");

let stdout = child.stderr.take().unwrap();
let reader = BufReader::new(stdout);

// Read the command's output line by line
for line in reader.lines() {
let line = line.expect("Failed to read line");
println!("{}", line);
// window.emit(progress_event, line).unwrap();
emit_rust_console(&window, line);

// If is_abort_state is true, kill the command
if is_abort_state(app.clone()) {
child.kill().expect("Failed to kill command");
break;
}
}

// window.emit(progress_event, "Done".to_string()).unwrap();
emit_rust_console(&window, "Done".to_string());

// Wait for the child to exit completely
child.wait().expect("Failed to wait on child");
});

// Wait for the child process to finish
child_handle.join().unwrap();

Ok("Success".to_string())
pub async fn run_external_command_with_progress(
window: Window,
app: tauri::AppHandle,
cmd_name: &str,
cmd_args: &[&str],
progress_event: &str
) -> Result<String, ()> {
let cmd_name_owned = cmd_name.to_string();
let cmd_args_owned: Vec<String> = cmd_args.iter().map(|&s| s.to_string()).collect();

emit_rust_console(&window.clone(), format!("Command: {} {}", cmd_name_owned, cmd_args_owned.join(" ")));

let mut child = Command::new(&cmd_name_owned)
.args(&cmd_args_owned)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("Failed to launch command");

let stdout = child.stdout.take().unwrap();
let stderr = child.stderr.take().unwrap();

let window_clone_std = window.clone();
let window_clone_err = window.clone();

let stdout_task = tokio::spawn(async move {
let reader = BufReader::new(stdout);
let mut lines = reader.lines();
while let Some(line) = lines.next_line().await.expect("Failed to read line from stdout") {
emit_rust_console(&window_clone_std, line);
}
});

let stderr_task = tokio::spawn(async move {
let reader = BufReader::new(stderr);
let mut lines = reader.lines();
while let Some(line) = lines.next_line().await.expect("Failed to read line from stderr") {
emit_rust_console(&window_clone_err, line);
}
});

let child_task = tokio::spawn(async move {
child.wait().await.expect("Child process encountered an error")
});

tokio::select! {
_ = stdout_task => {},
_ = stderr_task => {},
status = child_task => {
if let Err(err) = status {
emit_rust_console(&window, format!("Child process exited with {:?}", err));
return Err(());
}
}
}

emit_rust_console(&window, "Done".to_string());
Ok("Child process completed successfully".to_string())
}



#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;

Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/flasher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ pub async fn flash_file(window: Window, app: AppHandle, port: String, file_path:
let serial_port_info = get_serial_port_info(port.as_str()).unwrap();
let port_info = match &serial_port_info.port_type {
serialport::SerialPortType::UsbPort(info) => Some(info.clone()),
_ => return Err(("Port is not a USB port".to_string() ))
_ => return Err("Port is not a USB port".to_string() )
};
let mut serial = Interface::new(&serial_port_info, dtr, rts).unwrap();

Expand Down
5 changes: 4 additions & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ use esp_idf::run_install_script;
mod external_command;
mod flasher;
mod monitor;
mod os;
use os::get_platform;
mod rust;
use rust::{check_rust_support, install_rust_support};

Expand Down Expand Up @@ -311,7 +313,8 @@ fn main() {
start_flash, stop_flash,
start_monitor, stop_monitor,
check_rust_support,
install_rust_support
install_rust_support,
get_platform
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
Expand Down
14 changes: 14 additions & 0 deletions src-tauri/src/os.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@


#[tauri::command]
pub fn get_platform() -> String {
if cfg!(target_os = "windows") {
"win32".to_string()
} else if cfg!(target_os = "macos") {
"darwin".to_string()
} else if cfg!(target_os = "linux") {
"linux".to_string()
} else {
"unknown".to_string()
}
}
98 changes: 69 additions & 29 deletions src-tauri/src/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@ use tauri::Window;

use external_command::{run_external_command_with_progress, emit_rust_console};

#[cfg(windows)]
use std::os::windows::process::CommandExt;
#[cfg(windows)]
const CREATE_NO_WINDOW: u32 = 0x08000000; // Windows specific constant to hide console window

pub fn get_tool_version(command: &str, flags: &[&str], keyword: Option<&str>) -> Option<String> {
let mut cmd = Command::new(command);
for flag in flags {
cmd.arg(flag);
}

#[cfg(windows)]
cmd.creation_flags(CREATE_NO_WINDOW);

let output = cmd.output().ok()?;

if !output.status.success() {
Expand Down Expand Up @@ -93,22 +100,36 @@ pub fn check_rust_support() -> Result<RustSupportResponse, String> {
})
}

#[derive(serde::Serialize, serde::Deserialize)]
pub struct RustInstallOptions {
selected_variant: Option<String>,
install_msvc: bool,
install_mingw: bool,
}

#[tauri::command]
pub async fn install_rust_support(window: Window, app: AppHandle, selected_variant: Option<String>) -> Result<String, String> {
install_rustup(window.clone(), selected_variant.as_ref()).await?;
pub async fn install_rust_support(window: Window, app: AppHandle, install_options: RustInstallOptions) -> Result<String, String> {
let selected_variant = install_options.selected_variant;
#[cfg(target_os = "windows")]
{
if install_options.install_msvc {
install_vc_tools_and_sdk(window.clone(), app.clone()).await?;
}
}

install_rustup(window.clone(), app.clone(), selected_variant.as_ref()).await?;
install_espup(window.clone(), app.clone(), selected_variant.as_ref()).await?;
install_rust_toolchain(window, app, selected_variant.as_ref());
install_rust_toolchain(window, app, selected_variant.as_ref()).await?;
Ok("Success".into())
}

pub async fn install_rustup(window: Window, selected_variant: Option<&String>) -> Result<String, String> {
pub async fn install_rustup(window: Window, app: tauri::AppHandle, selected_variant: Option<&String>) -> Result<String, String> {

// Check if rustup is already installed
match Command::new("rustup").arg("--version").output() {
Ok(output) => {
if output.status.success() {
emit_rust_console(&window, "Rustup already installed".into());
emit_rust_console(&window.clone(), "Rustup already installed".into());
return Ok("Rustup already installed".into());
}
},
Expand All @@ -117,36 +138,22 @@ pub async fn install_rustup(window: Window, selected_variant: Option<&String>) -

emit_rust_console(&window, "Installing rustup...".into());

// Install rustup based on the OS
#[cfg(target_os = "windows")]
{
let mut cmd = Command::new("rustup-init.exe");
cmd.arg("install").arg("-y");

if let Some(variant) = &selected_variant {
let host = match variant.as_str() {
"msvc" => "--default-host x86_64-pc-windows-msvc",
"mingw" => "--default-host x86_64-pc-windows-gnu",
_ => return Err("Invalid variant".into()),
};
cmd.arg(host);
let mut args = vec!["install", "-y"];

if let Some(variant) = selected_variant {
args.push("--default-host");
args.push(variant);
}

let output = cmd.output().map_err(|_| "Failed to run rustup-init.exe".to_string())?;
let stdout_str = String::from_utf8_lossy(&output.stdout).into_owned();
emit_rust_console(&window, stdout_str);
run_external_command_with_progress(window.clone(), app, "rustup-init.exe", &args, "PROGRESS_EVENT").await;
}

#[cfg(unix)]
{
let output = Command::new("sh")
.arg("./rustup-init.sh")
.arg("-y")
.output()
.map_err(|_| "Failed to run rustup-init.sh".to_string())?;

let stdout_str = String::from_utf8_lossy(&output.stdout).into_owned();
emit_rust_console(&window, stdout_str);
let args = vec!["-y"];
run_external_command_with_progress(window.clone(), app, "./rustup-init.sh", &args, "PROGRESS_EVENT").await;
}

emit_rust_console(&window, "Rustup installed or already present".into());
Expand All @@ -155,6 +162,7 @@ pub async fn install_rustup(window: Window, selected_variant: Option<&String>) -




use tokio::fs;
use tokio::io::AsyncWriteExt;

Expand Down Expand Up @@ -218,7 +226,7 @@ async fn install_espup(window: Window, app: AppHandle, selected_variant: Option<
}


fn install_rust_toolchain(window: Window, app: AppHandle, selected_variant: Option<&String>) -> Result<String, String> {
async fn install_rust_toolchain(window: Window, app: AppHandle, selected_variant: Option<&String>) -> Result<String, String> {
emit_rust_console(&window, "Installing Rust toolchain via espup... (this might take a while)".into());

let espup_path = dirs::home_dir().ok_or("Failed to get home directory")?.join(".cargo/bin/espup").to_str().unwrap().to_string();
Expand All @@ -227,6 +235,7 @@ fn install_rust_toolchain(window: Window, app: AppHandle, selected_variant: Opti
// If there's a variant specified for Windows, pass it as a parameter
#[cfg(target_os = "windows")]
if let Some(variant) = selected_variant {
args.push("--default-host");
args.push(variant);
}

Expand All @@ -236,7 +245,7 @@ fn install_rust_toolchain(window: Window, app: AppHandle, selected_variant: Opti
&espup_path,
&args,
"PROGRESS_EVENT"
);
).await;

match result {
Ok(_) => {
Expand All @@ -249,3 +258,34 @@ fn install_rust_toolchain(window: Window, app: AppHandle, selected_variant: Opti
}
}
}


#[cfg(target_os = "windows")]
async fn install_vc_tools_and_sdk(window: Window, app: tauri::AppHandle) -> Result<String, String> {
emit_rust_console(&window.clone(), "Downloading Visual Studio Build Tools and Windows SDK...".into());

// Download vs_buildtools.exe
let url = "https://aka.ms/vs/17/release/vs_buildtools.exe";
let response = reqwest::get(url).await.map_err(|e| format!("Failed to download VS Build Tools: {}", e))?;
let bytes = response.bytes().await.map_err(|e| format!("Failed to read response bytes: {}", e))?;

// Save to a temporary location
use std::env;
let tmp_dir = env::temp_dir();
let file_path = tmp_dir.join("vs_buildtools.exe");
fs::write(&file_path, &bytes).await;
emit_rust_console(&window, format!("Starting installer at {:?}", &file_path.display()));

// Run the installer with the necessary components
let args = [
"--passive",
"--wait",
"--add", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
"--add", "Microsoft.VisualStudio.Component.Windows11SDK.22621"
];
run_external_command_with_progress(window.clone(), app, &file_path.to_string_lossy(), &args, "Installing Visual Studio Build Tools and Windows SDK...").await;

emit_rust_console(&window, "Visual Studio Build Tools and Windows SDK installed successfully!".into());

Ok("Visual Studio Build Tools and Windows SDK installed successfully!".into())
}
Loading

0 comments on commit e032b69

Please sign in to comment.