From 4ebf86d22277e24f197fed2500d130af2ebada00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Tue, 3 Nov 2026 17:00:18 +0500 Subject: [PATCH] fix(builder): preserve module tree structure, sanitize cmake include paths, and suppress console windows --- crates/coa-core/src/analyzer.rs | 18 ++++++- crates/coa-core/src/build_engine.rs | 84 ++++++++++++++++++++--------- 2 files changed, 77 insertions(+), 25 deletions(-) diff --git a/crates/coa-core/src/analyzer.rs b/crates/coa-core/src/analyzer.rs index b4500a5..0aff576 100644 --- a/crates/coa-core/src/analyzer.rs +++ b/crates/coa-core/src/analyzer.rs @@ -413,7 +413,13 @@ pub fn analyze_repository_with_contract(repo_path: &Path, contract: &SdkExportCo // Check git commit if available let commit = if repo_path.join(".git").exists() { - std::process::Command::new("git") + let mut git_cmd = std::process::Command::new("git"); + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + git_cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW + } + git_cmd .args(["-C", &repo_path.to_string_lossy(), "rev-parse", "HEAD"]) .output() .ok() @@ -959,6 +965,11 @@ pub fn analyze_github_repository(url_str: &str, cache_base_dir: Option<&Path>) - // 1. Resolve commit SHA via git ls-remote (isolated env, no hooks) let repo_remote_url = format!("https://github.com/{}/{}.git", owner, repo); let mut ls_cmd = std::process::Command::new("git"); + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + ls_cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW + } ls_cmd.env("GIT_CONFIG_NOSYSTEM", "1") .env("GIT_CONFIG_GLOBAL", "") .env("GIT_TERMINAL_PROMPT", "0") @@ -1013,6 +1024,11 @@ pub fn analyze_github_repository(url_str: &str, cache_base_dir: Option<&Path>) - fs::create_dir_all(&staging_dir)?; let mut clone_cmd = std::process::Command::new("git"); + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + clone_cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW + } clone_cmd.env("GIT_CONFIG_NOSYSTEM", "1") .env("GIT_CONFIG_GLOBAL", "") .env("GIT_TERMINAL_PROMPT", "0") diff --git a/crates/coa-core/src/build_engine.rs b/crates/coa-core/src/build_engine.rs index f5c7601..a949da8 100644 --- a/crates/coa-core/src/build_engine.rs +++ b/crates/coa-core/src/build_engine.rs @@ -483,22 +483,26 @@ pub fn build_module_from_source( ))); } + // Helper to strip verbatim extended path prefix (\\?\) so CMake and MSVC can parse include dirs + let clean_path = |p: &Path| -> String { + let s = p.to_string_lossy(); + let stripped = s.strip_prefix(r"\\?\").unwrap_or(&s); + stripped.replace('\\', "/") + }; + let mut staged_cpp_paths = Vec::new(); - staged_cpp_paths.push(adapter_path.to_string_lossy().replace('\\', "/")); + staged_cpp_paths.push(clean_path(&adapter_path)); for cpp in &upstream_cpps { - let file_name = cpp.file_name().unwrap(); - // Avoid naming collision with adapter.cpp - let dest_file_name = if file_name == "adapter.cpp" { - format!("upstream_{}", file_name.to_string_lossy()) - } else { - file_name.to_string_lossy().into_owned() - }; - let dest = staging_src.join(&dest_file_name); + let rel = cpp.strip_prefix(module_source_dir).unwrap_or_else(|_| Path::new(cpp.file_name().unwrap())); + let dest = staging_src.join(rel); + if let Some(parent) = dest.parent() { + fs::create_dir_all(parent)?; + } fs::copy(cpp, &dest)?; // Resilient compatibility patching for older / cached mod-coa-playerbots - if dest_file_name == "BotMgr.cpp" { + if dest.file_name().map(|n| n == "BotMgr.cpp").unwrap_or(false) { if let Ok(content) = fs::read_to_string(&dest) { if content.contains("_GetPurchasedTabsSize") || content.contains("_CreateNewBankTab") { let patched = content @@ -513,20 +517,46 @@ pub fn build_module_from_source( } } - staged_cpp_paths.push(dest.to_string_lossy().replace('\\', "/")); + staged_cpp_paths.push(clean_path(&dest)); } - // Collect include directories from upstream - let mut include_dirs = Vec::new(); - include_dirs.push(staging_src.to_string_lossy().replace('\\', "/")); - include_dirs.push(module_source_dir.to_string_lossy().replace('\\', "/")); - + // Also copy all headers (.h and .hpp) preserving directory structure let upstream_headers = collect_files_with_ext(module_source_dir, "h"); - for h in upstream_headers { + for h in &upstream_headers { + let rel = h.strip_prefix(module_source_dir).unwrap_or_else(|_| Path::new(h.file_name().unwrap())); + let dest = staging_src.join(rel); + if let Some(parent) = dest.parent() { + let _ = fs::create_dir_all(parent); + } + let _ = fs::copy(h, &dest); + } + let upstream_hpps = collect_files_with_ext(module_source_dir, "hpp"); + for h in &upstream_hpps { + let rel = h.strip_prefix(module_source_dir).unwrap_or_else(|_| Path::new(h.file_name().unwrap())); + let dest = staging_src.join(rel); + if let Some(parent) = dest.parent() { + let _ = fs::create_dir_all(parent); + } + let _ = fs::copy(h, &dest); + } + + // Collect include directories from upstream and staging + let mut include_dirs = Vec::new(); + include_dirs.push(clean_path(&staging_src)); + include_dirs.push(clean_path(module_source_dir)); + + for h in upstream_headers.iter().chain(upstream_hpps.iter()) { if let Some(parent) = h.parent() { - let p_str = parent.to_string_lossy().replace('\\', "/"); - if !include_dirs.contains(&p_str) { - include_dirs.push(p_str); + let p_clean = clean_path(parent); + if !include_dirs.contains(&p_clean) { + include_dirs.push(p_clean); + } + if let Ok(rel) = parent.strip_prefix(module_source_dir) { + let staged_parent = staging_src.join(rel); + let sp_clean = clean_path(&staged_parent); + if !include_dirs.contains(&sp_clean) { + include_dirs.push(sp_clean); + } } } } @@ -534,7 +564,7 @@ pub fn build_module_from_source( // 3. Write controlled CMakeLists.txt (eliminating D9025 /Zi vs /Z7 conflict) let sources_block = staged_cpp_paths.iter().map(|s| format!(" \"{}\"", s)).collect::>().join("\n"); let includes_block = include_dirs.iter().map(|s| format!(" \"{}\"", s)).collect::>().join("\n"); - let cmake_sdk_path = sdk_dir.replace('\\', "/"); + let cmake_sdk_path = clean_path(Path::new(&sdk_dir)); let controlled_cmake = format!(r#"cmake_minimum_required(VERSION 3.22) project(coa_plugin_{safe_target_name} LANGUAGES CXX) @@ -576,14 +606,20 @@ set "PATH={ninja_dir};%PATH%" if errorlevel 1 exit /b 102 "{cmake}" --build "{build_dir}" -- -j8 if errorlevel 1 exit /b 103 -"#, vcvars64 = vcvars64, ninja_dir = ninja_dir, cmake = cmake, staging_dir = staging_dir.display(), build_dir = build_output_dir.display(), sdk_dir = sdk_dir); +"#, vcvars64 = vcvars64, ninja_dir = ninja_dir, cmake = cmake, staging_dir = clean_path(&staging_dir), build_dir = clean_path(&build_output_dir), sdk_dir = clean_path(Path::new(&sdk_dir))); fs::write(&runner_bat, &runner_content)?; emit_progress("COMPILING", 45, "Compiling C++ sources via MSVC Cl.exe...", None); - // 5. Execute build command - let output = Command::new("cmd.exe") + // 5. Execute build command silently without console popup + let mut cmd = Command::new("cmd.exe"); + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW + } + let output = cmd .args(["/c", &runner_bat.to_string_lossy()]) .output();