Improved Windows support & simple WSL2 integration

This commit is contained in:
Luke Street 2022-09-11 20:31:58 -04:00
parent b55c919f4d
commit daaa5c86a2
5 changed files with 117 additions and 12 deletions

8
Cargo.lock generated
View File

@ -1576,6 +1576,7 @@ dependencies = [
"log", "log",
"notify", "notify",
"object", "object",
"path-slash",
"ppc750cl", "ppc750cl",
"rabbitizer", "rabbitizer",
"rfd", "rfd",
@ -1583,6 +1584,7 @@ dependencies = [
"thiserror", "thiserror",
"tracing-subscriber", "tracing-subscriber",
"tracing-wasm", "tracing-wasm",
"winapi",
] ]
[[package]] [[package]]
@ -1660,6 +1662,12 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "path-slash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42"
[[package]] [[package]]
name = "peeking_take_while" name = "peeking_take_while"
version = "0.1.2" version = "0.1.2"

View File

@ -27,6 +27,10 @@ egui_extras = "0.19.0"
ppc750cl = { git = "https://github.com/terorie/ppc750cl" } ppc750cl = { git = "https://github.com/terorie/ppc750cl" }
rabbitizer = { git = "https://github.com/encounter/rabbitizer-rs", rev = "10c279b2ef251c62885b1dcdcfe740b0db8e9956" } rabbitizer = { git = "https://github.com/encounter/rabbitizer-rs", rev = "10c279b2ef251c62885b1dcdcfe740b0db8e9956" }
[target.'cfg(windows)'.dependencies]
path-slash = "0.2.0"
winapi = "0.3.9"
# native: # native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tracing-subscriber = "0.3" tracing-subscriber = "0.3"

View File

@ -61,6 +61,11 @@ pub struct ViewState {
#[derive(Default, Clone, serde::Deserialize, serde::Serialize)] #[derive(Default, Clone, serde::Deserialize, serde::Serialize)]
#[serde(default)] #[serde(default)]
pub struct AppConfig { pub struct AppConfig {
pub custom_make: String,
// WSL2 settings
#[serde(skip)]
pub available_wsl_distros: Option<Vec<String>>,
pub selected_wsl_distro: Option<String>,
// Split obj // Split obj
pub project_dir: Option<PathBuf>, pub project_dir: Option<PathBuf>,
pub build_asm_dir: Option<PathBuf>, pub build_asm_dir: Option<PathBuf>,
@ -156,7 +161,6 @@ impl eframe::App for App {
}); });
} else { } else {
egui::SidePanel::left("side_panel").show(ctx, |ui| { egui::SidePanel::left("side_panel").show(ctx, |ui| {
ui.heading("Config");
config_ui(ui, config, view_state); config_ui(ui, config, view_state);
jobs_ui(ui, view_state); jobs_ui(ui, view_state);
}); });
@ -194,7 +198,9 @@ impl eframe::App for App {
ui.separator(); ui.separator();
}); });
if view_state.jobs.iter().any(|job| { // Windows + request_repaint_after breaks dialogs:
// https://github.com/emilk/egui/issues/2003
if cfg!(windows) || view_state.jobs.iter().any(|job| {
if let Some(handle) = &job.handle { if let Some(handle) = &job.handle {
return !handle.is_finished(); return !handle.is_finished();
} }

View File

@ -2,7 +2,7 @@ use std::{
path::Path, path::Path,
process::Command, process::Command,
str::from_utf8, str::from_utf8,
sync::{mpsc::Receiver, Arc, RwLock}, sync::{Arc, mpsc::Receiver, RwLock},
}; };
use anyhow::{Context, Error, Result}; use anyhow::{Context, Error, Result};
@ -11,7 +11,7 @@ use crate::{
app::AppConfig, app::AppConfig,
diff::diff_objs, diff::diff_objs,
elf, elf,
jobs::{queue_job, update_status, Job, JobResult, JobState, Status}, jobs::{Job, JobResult, JobState, queue_job, Status, update_status},
obj::ObjInfo, obj::ObjInfo,
}; };
@ -26,13 +26,36 @@ pub struct BuildResult {
pub second_obj: Option<ObjInfo>, pub second_obj: Option<ObjInfo>,
} }
fn run_make(cwd: &Path, arg: &Path) -> BuildStatus { fn run_make(cwd: &Path, arg: &Path, config: &AppConfig) -> BuildStatus {
match (|| -> Result<BuildStatus> { match (|| -> Result<BuildStatus> {
let output = Command::new("make") let make = if config.custom_make.is_empty() { "make" } else { &config.custom_make };
.current_dir(cwd) #[cfg(not(windows))]
.arg(arg) let mut command = Command::new(make).current_dir(cwd).arg(arg);
.output() #[cfg(windows)]
.context("Failed to execute build")?; let mut command = {
use path_slash::PathExt;
use std::os::windows::process::CommandExt;
let mut command = if config.selected_wsl_distro.is_some() {
Command::new("wsl")
} else {
Command::new(make)
};
if let Some(distro) = &config.selected_wsl_distro {
command
.arg("--cd")
.arg(cwd)
.arg("-d")
.arg(distro)
.arg("--")
.arg(make)
.arg(arg.to_slash_lossy().as_ref());
} else {
command.current_dir(cwd).arg(arg);
}
command.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW);
command
};
let output = command.output().context("Failed to execute build")?;
let stdout = from_utf8(&output.stdout).context("Failed to process stdout")?; let stdout = from_utf8(&output.stdout).context("Failed to process stdout")?;
let stderr = from_utf8(&output.stderr).context("Failed to process stderr")?; let stderr = from_utf8(&output.stderr).context("Failed to process stderr")?;
Ok(BuildStatus { Ok(BuildStatus {
@ -72,10 +95,10 @@ fn run_build(
src_path.strip_prefix(project_dir).context("Failed to create relative src obj path")?; src_path.strip_prefix(project_dir).context("Failed to create relative src obj path")?;
update_status(status, format!("Building asm {}", obj_path), 0, 5, &cancel)?; update_status(status, format!("Building asm {}", obj_path), 0, 5, &cancel)?;
let first_status = run_make(project_dir, asm_path_rel); let first_status = run_make(project_dir, asm_path_rel, &config);
update_status(status, format!("Building src {}", obj_path), 1, 5, &cancel)?; update_status(status, format!("Building src {}", obj_path), 1, 5, &cancel)?;
let second_status = run_make(project_dir, src_path_rel); let second_status = run_make(project_dir, src_path_rel, &config);
let mut first_obj = if first_status.success { let mut first_obj = if first_status.success {
update_status(status, format!("Loading asm {}", obj_path), 2, 5, &cancel)?; update_status(status, format!("Loading asm {}", obj_path), 2, 5, &cancel)?;

View File

@ -1,13 +1,53 @@
use std::process::Command;
use std::string::FromUtf16Error;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use anyhow::{Context, Result};
use crate::{ use crate::{
app::{AppConfig, DiffKind, ViewState}, app::{AppConfig, DiffKind, ViewState},
jobs::{bindiff::queue_bindiff, build::queue_build}, jobs::{bindiff::queue_bindiff, build::queue_build},
}; };
#[cfg(windows)]
fn process_utf16(bytes: &[u8]) -> Result<String, FromUtf16Error> {
let u16_bytes: Vec<u16> = bytes
.chunks_exact(2)
.filter_map(|c| Some(u16::from_ne_bytes(c.try_into().ok()?)))
.collect();
String::from_utf16(&u16_bytes)
}
#[cfg(windows)]
fn wsl_cmd(args: &[&str]) -> Result<String> {
use std::os::windows::process::CommandExt;
let output = Command::new("wsl")
.args(args)
.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW)
.output()
.context("Failed to execute wsl")?;
process_utf16(&output.stdout).context("Failed to process stdout")
}
#[cfg(windows)]
fn fetch_wsl2_distros() -> Vec<String> {
wsl_cmd(&["-l", "-q"])
.map(|stdout| {
stdout
.split('\n')
.filter(|s| !s.trim().is_empty())
.map(|s| s.trim().to_string())
.collect()
})
.unwrap_or_default()
}
pub fn config_ui(ui: &mut egui::Ui, config: &Arc<RwLock<AppConfig>>, view_state: &mut ViewState) { pub fn config_ui(ui: &mut egui::Ui, config: &Arc<RwLock<AppConfig>>, view_state: &mut ViewState) {
let mut config_guard = config.write().unwrap(); let mut config_guard = config.write().unwrap();
let AppConfig { let AppConfig {
custom_make,
available_wsl_distros,
selected_wsl_distro,
project_dir, project_dir,
project_dir_change, project_dir_change,
build_asm_dir, build_asm_dir,
@ -17,6 +57,30 @@ pub fn config_ui(ui: &mut egui::Ui, config: &Arc<RwLock<AppConfig>>, view_state:
right_obj, right_obj,
} = &mut *config_guard; } = &mut *config_guard;
ui.heading("Build config");
#[cfg(windows)]
{
if available_wsl_distros.is_none() {
*available_wsl_distros = Some(fetch_wsl2_distros());
}
egui::ComboBox::from_label("Run in WSL2")
.selected_text(selected_wsl_distro.as_ref().unwrap_or(&"None".to_string()))
.show_ui(ui, |ui| {
ui.selectable_value(selected_wsl_distro, None, "None");
for distro in available_wsl_distros.as_ref().unwrap() {
ui.selectable_value(selected_wsl_distro, Some(distro.clone()), distro);
}
});
}
ui.label("Custom make program:");
ui.text_edit_singleline(custom_make);
ui.separator();
ui.heading("Project config");
if view_state.diff_kind == DiffKind::SplitObj { if view_state.diff_kind == DiffKind::SplitObj {
if ui.button("Select project dir").clicked() { if ui.button("Select project dir").clicked() {
if let Some(path) = rfd::FileDialog::new().pick_folder() { if let Some(path) = rfd::FileDialog::new().pick_folder() {