diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2c117d6..b4e580d 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -105,11 +105,11 @@ jobs: - platform: macos-latest target: x86_64-apple-darwin name: macos-x86_64 - features: wgpu + features: default - platform: macos-latest target: aarch64-apple-darwin name: macos-arm64 - features: wgpu + features: default fail-fast: false runs-on: ${{ matrix.platform }} steps: diff --git a/Cargo.lock b/Cargo.lock index 4a2f6e1..20c5400 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2867,6 +2867,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", + "shell-escape", "similar", "tempfile", "thiserror", @@ -3719,6 +3720,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shell-escape" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" + [[package]] name = "signal-hook-registry" version = "1.4.1" diff --git a/Cargo.toml b/Cargo.toml index 1fbd55c..f4a6147 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "objdiff" -version = "0.6.1" +version = "0.7.0" edition = "2021" rust-version = "1.70" authors = ["Luke Street "] @@ -19,7 +19,7 @@ lto = "thin" strip = "debuginfo" [features] -default = [] +default = ["wgpu", "wsl"] wgpu = ["eframe/wgpu"] wsl = [] @@ -53,6 +53,7 @@ semver = "1.0.20" serde = { version = "1", features = ["derive"] } serde_json = "1.0.108" serde_yaml = "0.9.27" +shell-escape = "0.1.5" similar = "2.3.0" tempfile = "3.8.1" thiserror = "1.0.50" diff --git a/src/jobs/objdiff.rs b/src/jobs/objdiff.rs index eda9051..ae6366e 100644 --- a/src/jobs/objdiff.rs +++ b/src/jobs/objdiff.rs @@ -17,7 +17,20 @@ use crate::{ pub struct BuildStatus { pub success: bool, - pub log: String, + pub cmdline: String, + pub stdout: String, + pub stderr: String, +} + +impl Default for BuildStatus { + fn default() -> Self { + BuildStatus { + success: true, + cmdline: String::new(), + stdout: String::new(), + stderr: String::new(), + } + } } pub struct ObjDiffConfig { @@ -88,16 +101,24 @@ fn run_make(cwd: &Path, arg: &Path, config: &ObjDiffConfig) -> BuildStatus { command.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW); command }; + let mut cmdline = + shell_escape::escape(command.get_program().to_string_lossy()).into_owned(); + for arg in command.get_args() { + cmdline.push(' '); + cmdline.push_str(shell_escape::escape(arg.to_string_lossy()).as_ref()); + } let output = command.output().context("Failed to execute build")?; let stdout = from_utf8(&output.stdout).context("Failed to process stdout")?; let stderr = from_utf8(&output.stderr).context("Failed to process stderr")?; Ok(BuildStatus { success: output.status.code().unwrap_or(-1) == 0, - log: format!("{stdout}\n{stderr}"), + cmdline, + stdout: stdout.to_string(), + stderr: stderr.to_string(), }) })() { Ok(status) => status, - Err(e) => BuildStatus { success: false, log: e.to_string() }, + Err(e) => BuildStatus { success: false, stderr: e.to_string(), ..Default::default() }, } } @@ -150,7 +171,7 @@ fn run_build( )?; run_make(project_dir, target_path_rel, &config) } - _ => BuildStatus { success: true, log: String::new() }, + _ => BuildStatus::default(), }; let second_status = match base_path_rel { @@ -164,7 +185,7 @@ fn run_build( )?; run_make(project_dir, base_path_rel, &config) } - _ => BuildStatus { success: true, log: String::new() }, + _ => BuildStatus::default(), }; let time = OffsetDateTime::now_utc(); diff --git a/src/views/config.rs b/src/views/config.rs index 7ed8f47..8778c38 100644 --- a/src/views/config.rs +++ b/src/views/config.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "wsl")] +#[cfg(all(windows, feature = "wsl"))] use std::string::FromUtf16Error; use std::{ borrow::Cow, @@ -6,7 +6,7 @@ use std::{ path::{PathBuf, MAIN_SEPARATOR}, }; -#[cfg(feature = "wsl")] +#[cfg(all(windows, feature = "wsl"))] use anyhow::{Context, Result}; use const_format::formatcp; use egui::{ @@ -46,7 +46,7 @@ pub struct ConfigViewState { pub object_search: String, pub filter_diffable: bool, pub filter_incomplete: bool, - #[cfg(feature = "wsl")] + #[cfg(all(windows, feature = "wsl"))] pub available_wsl_distros: Option>, pub file_dialog_state: FileDialogState, } @@ -134,7 +134,7 @@ pub const DEFAULT_WATCH_PATTERNS: &[&str] = &[ "*.inc", "*.py", "*.yml", "*.txt", "*.json", ]; -#[cfg(feature = "wsl")] +#[cfg(all(windows, feature = "wsl"))] fn process_utf16(bytes: &[u8]) -> Result { let u16_bytes: Vec = bytes .chunks_exact(2) @@ -143,7 +143,7 @@ fn process_utf16(bytes: &[u8]) -> Result { String::from_utf16(&u16_bytes) } -#[cfg(feature = "wsl")] +#[cfg(all(windows, feature = "wsl"))] fn wsl_cmd(args: &[&str]) -> Result { use std::{os::windows::process::CommandExt, process::Command}; let output = Command::new("wsl") @@ -154,7 +154,7 @@ fn wsl_cmd(args: &[&str]) -> Result { process_utf16(&output.stdout).context("Failed to process stdout") } -#[cfg(feature = "wsl")] +#[cfg(all(windows, feature = "wsl"))] fn fetch_wsl2_distros() -> Vec { wsl_cmd(&["-l", "-q"]) .map(|stdout| { @@ -176,7 +176,6 @@ pub fn config_ui( ) { let mut config_guard = config.write().unwrap(); let AppConfig { - selected_wsl_distro, target_obj_dir, base_obj_dir, selected_obj, @@ -227,27 +226,6 @@ pub fn config_ui( } ui.separator(); - #[cfg(feature = "wsl")] - { - ui.heading("Build"); - if state.available_wsl_distros.is_none() { - state.available_wsl_distros = Some(fetch_wsl2_distros()); - } - egui::ComboBox::from_label("Run in WSL2") - .selected_text(selected_wsl_distro.as_ref().unwrap_or(&"Disabled".to_string())) - .show_ui(ui, |ui| { - ui.selectable_value(selected_wsl_distro, None, "Disabled"); - for distro in state.available_wsl_distros.as_ref().unwrap() { - ui.selectable_value(selected_wsl_distro, Some(distro.clone()), distro); - } - }); - ui.separator(); - } - #[cfg(not(feature = "wsl"))] - { - let _ = selected_wsl_distro; - } - ui.horizontal(|ui| { ui.heading("Project"); if ui.button(RichText::new("Settings")).clicked() { @@ -649,6 +627,24 @@ fn split_obj_config_ui( config.custom_make = Some(custom_make_str); } } + #[cfg(all(windows, feature = "wsl"))] + { + if state.available_wsl_distros.is_none() { + state.available_wsl_distros = Some(fetch_wsl2_distros()); + } + egui::ComboBox::from_label("Run in WSL2") + .selected_text(config.selected_wsl_distro.as_ref().unwrap_or(&"Disabled".to_string())) + .show_ui(ui, |ui| { + ui.selectable_value(&mut config.selected_wsl_distro, None, "Disabled"); + for distro in state.available_wsl_distros.as_ref().unwrap() { + ui.selectable_value( + &mut config.selected_wsl_distro, + Some(distro.clone()), + distro, + ); + } + }); + } ui.separator(); if let Some(project_dir) = config.project_dir.clone() { diff --git a/src/views/symbol_diff.rs b/src/views/symbol_diff.rs index 791b15b..da7173a 100644 --- a/src/views/symbol_diff.rs +++ b/src/views/symbol_diff.rs @@ -262,11 +262,23 @@ fn symbol_list_ui( fn build_log_ui(ui: &mut Ui, status: &BuildStatus, appearance: &Appearance) { ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| { + ui.horizontal(|ui| { + if ui.button("Copy command").clicked() { + ui.output_mut(|output| output.copied_text = status.cmdline.clone()); + } + if ui.button("Copy log").clicked() { + ui.output_mut(|output| { + output.copied_text = format!("{}\n{}", status.stdout, status.stderr) + }); + } + }); ui.scope(|ui| { ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().wrap = Some(false); - ui.colored_label(appearance.replace_color, &status.log); + ui.label(&status.cmdline); + ui.colored_label(appearance.replace_color, &status.stdout); + ui.colored_label(appearance.delete_color, &status.stderr); }); }); }