From 3be5fe96098a7914aa884bc6b06187bfae241da5 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: Thu, 5 Nov 2026 15:38:58 +0500 Subject: [PATCH] Install the pinned MSVC toolset from the Manager Modules must be compiled by the core's exact compiler. The Toolchain tab now offers Microsoft's stable-channel Build Tools bootstrapper (VS 18) with the 14.44 toolset, core build tools, Windows SDK 26100 and CMake preselected: downloaded from aka.ms, Authenticode-checked for Microsoft, run in its own UI (modify mode for an existing Build Tools 18). Shows the required and selected cl.exe versions. Co-Authored-By: Claude Opus 5.5 --- .../20261105-install-msvc-for-modules.md | 7 + crates/coa-core/src/lib.rs | 1 + crates/coa-core/src/msvc_setup.rs | 146 ++++++++++++++++++ src-tauri/src/lib.rs | 11 ++ src/lib/api.ts | 6 + src/screens/ModulesPage.tsx | 42 +++++ 6 files changed, 213 insertions(+) create mode 100644 changelog.d/20261105-install-msvc-for-modules.md create mode 100644 crates/coa-core/src/msvc_setup.rs diff --git a/changelog.d/20261105-install-msvc-for-modules.md b/changelog.d/20261105-install-msvc-for-modules.md new file mode 100644 index 0000000..23e865b --- /dev/null +++ b/changelog.d/20261105-install-msvc-for-modules.md @@ -0,0 +1,7 @@ +--- +area: manager +type: added +audience: admins +title: Install the compiler for modules from the Manager +--- +When the C++ compiler that modules need is missing or differs from the one the core was built with, the Toolchain tab offers to install Visual Studio Build Tools with MSVC 14.44. The Manager downloads Microsoft's installer, checks that Microsoft signed it and opens it with the needed components selected; you accept Microsoft's terms there. diff --git a/crates/coa-core/src/lib.rs b/crates/coa-core/src/lib.rs index 440fef5..beac5d8 100644 --- a/crates/coa-core/src/lib.rs +++ b/crates/coa-core/src/lib.rs @@ -43,6 +43,7 @@ pub mod logging; pub mod manifest; pub mod migrations; pub mod modules; +pub mod msvc_setup; pub mod multiworld; mod mysql_snapshot; pub mod net; diff --git a/crates/coa-core/src/msvc_setup.rs b/crates/coa-core/src/msvc_setup.rs new file mode 100644 index 0000000..ebf86df --- /dev/null +++ b/crates/coa-core/src/msvc_setup.rs @@ -0,0 +1,146 @@ +//! Installs the C++ compiler that plugins must be built with. +//! +//! A plugin loads only into a core built by the same MSVC, to the build number (it is part of the ABI +//! fingerprint). The core is built with the MSVC 14.44 toolset of the stable Visual Studio channel, so +//! the Manager installs exactly that: Microsoft's own Build Tools bootstrapper, downloaded from Microsoft +//! and checked to be signed by Microsoft, with the components preselected. The person accepts Microsoft's +//! terms in Microsoft's installer window; nothing is bundled or installed silently. + +use std::path::{Path, PathBuf}; +use std::process::Command; + +use serde::Serialize; + +use crate::build_engine::{detect_toolchain_for, ToolchainStatus}; +use crate::error::{Error, Result}; + +/// The stable-channel Build Tools bootstrapper (Visual Studio 18). Its MSVC 14.44 side-by-side toolset has +/// the same compiler build as the CI image the core is built on. +pub const BOOTSTRAPPER_URL: &str = "https://aka.ms/vs/18/stable/vs_buildtools.exe"; + +/// The toolset alone has no environment script; the core build tools and the Windows SDK complete it. +pub const COMPONENTS: [&str; 4] = [ + "Microsoft.VisualStudio.Component.VC.14.44.17.14.x86.x64", + "Microsoft.VisualStudio.Component.VC.CoreBuildTools", + "Microsoft.VisualStudio.Component.Windows11SDK.26100", + "Microsoft.VisualStudio.Component.VC.CMake.Project", +]; + +#[derive(Debug, Clone, Serialize)] +pub struct MsvcSetupOutcome { + /// The installer finished and the toolchain now matches the core. + pub ready: bool, + pub restart_required: bool, + pub message: String, + pub toolchain: ToolchainStatus, +} + +/// An installed Visual Studio 18 Build Tools instance, which the installer modifies instead of adding a +/// second one. +fn existing_build_tools() -> Option { + let pf86 = std::env::var("ProgramFiles(x86)").unwrap_or_else(|_| "C:\\Program Files (x86)".into()); + let vswhere = Path::new(&pf86).join("Microsoft Visual Studio").join("Installer").join("vswhere.exe"); + let mut cmd = Command::new(vswhere); + cmd.args(["-products", "Microsoft.VisualStudio.Product.BuildTools", "-version", "[18.0,19.0)", + "-property", "installationPath", "-utf8"]); + hide_window(&mut cmd); + let out = cmd.output().ok()?; + String::from_utf8_lossy(&out.stdout).lines().map(str::trim).find(|l| !l.is_empty()).map(PathBuf::from) + .filter(|p| p.is_dir()) +} + +fn hide_window(cmd: &mut Command) { + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + cmd.creation_flags(0x0800_0000); + } + #[cfg(not(windows))] + let _ = cmd; +} + +const SCRIPT: &str = r#"param([string]$Dir, [string]$Url, [string]$InstallPath, [string]$Components) +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' +New-Item -ItemType Directory -Force -Path $Dir | Out-Null +$bt = Join-Path $Dir 'vs_buildtools.exe' +try { Invoke-WebRequest -UseBasicParsing -Uri $Url -OutFile $bt } catch { "RESULT=download-failed $($_.Exception.Message)"; exit 0 } +$sig = Get-AuthenticodeSignature -FilePath $bt +if ($sig.Status -ne 'Valid' -or $sig.SignerCertificate.Subject -notmatch 'O=Microsoft Corporation') { + Remove-Item $bt -Force + "RESULT=bad-signature $($sig.Status)"; exit 0 +} +$a = @() +if ($InstallPath) { $a += @('modify', '--installPath', ('"' + $InstallPath + '"')) } +$a += @('--wait', '--norestart', '--nocache') +foreach ($c in $Components.Split(',')) { $a += @('--add', $c) } +try { $p = Start-Process -FilePath $bt -ArgumentList $a -Wait -PassThru } catch { "RESULT=not-started $($_.Exception.Message)"; exit 0 } +"RESULT=exit $($p.ExitCode)" +"#; + +/// Runs Microsoft's installer and reports whether plugins can now be built for `realm_root`'s core. +/// Blocks until the installer window is closed. +pub fn install_build_tools(realm_root: Option<&Path>) -> Result { + if !cfg!(windows) { + return Err(Error::Invalid("The C++ Build Tools installer is only available on Windows.".into())); + } + let dir = std::env::temp_dir().join("coa-msvc-setup"); + std::fs::create_dir_all(&dir)?; + let script = dir.join("install-build-tools.ps1"); + std::fs::write(&script, SCRIPT)?; + let install_path = existing_build_tools().map(|p| p.to_string_lossy().into_owned()).unwrap_or_default(); + let mut cmd = Command::new("powershell.exe"); + cmd.args(["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File"]) + .arg(&script) + .args(["-Dir"]).arg(dir.join("bootstrapper")) + .args(["-Url", BOOTSTRAPPER_URL, "-InstallPath", &install_path, "-Components", &COMPONENTS.join(",")]); + hide_window(&mut cmd); + let out = cmd.output()?; + let text = String::from_utf8_lossy(&out.stdout); + let result = text.lines().find_map(|l| l.trim().strip_prefix("RESULT=")).unwrap_or("").to_string(); + let (installed, restart, message) = interpret(&result, &String::from_utf8_lossy(&out.stderr)); + let toolchain = detect_toolchain_for(realm_root); + let ready = installed && toolchain.ready; + let message = if installed && !toolchain.ready { + format!("The installer finished, but the compiler still does not match this core: {}", toolchain.diagnostic_message) + } else { + message + }; + Ok(MsvcSetupOutcome { ready, restart_required: restart, message, toolchain }) +} + +/// (installed, restart required, message) for the script's result line. +fn interpret(result: &str, stderr: &str) -> (bool, bool, String) { + let (kind, detail) = result.split_once(' ').unwrap_or((result, "")); + match (kind, detail.trim()) { + ("exit", "0") => (true, false, "Visual Studio Build Tools with MSVC 14.44 are installed.".into()), + ("exit", "3010") => (true, true, "Visual Studio Build Tools are installed; Windows needs a restart before modules can be built.".into()), + ("exit", "1602") | ("exit", "1223") => (false, false, "The installation was cancelled.".into()), + ("exit", "8006") => (false, false, "Close Visual Studio and its installer, then try again.".into()), + ("exit", code) => (false, false, format!("The Visual Studio installer stopped with code {code}.")), + ("bad-signature", s) => (false, false, format!("The downloaded installer is not signed by Microsoft ({s}); it was deleted and not run.")), + ("download-failed", why) => (false, false, format!("The Visual Studio installer could not be downloaded: {why}")), + ("not-started", why) => (false, false, format!("The Visual Studio installer could not be started: {why}")), + _ => (false, false, format!("The installer script failed: {}", stderr.lines().next().unwrap_or("no output"))), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn installer_results_are_explained() { + assert!(interpret("exit 0", "").0); + assert_eq!(interpret("exit 3010", "").1, true); + assert!(!interpret("exit 1602", "").0); + assert!(interpret("bad-signature NotSigned", "").2.contains("not signed by Microsoft")); + assert!(interpret("", "boom").2.contains("boom")); + } + + #[test] + fn the_components_name_the_pinned_toolset() { + assert!(COMPONENTS.iter().any(|c| c.contains("VC.14.44"))); + assert!(BOOTSTRAPPER_URL.starts_with("https://aka.ms/vs/18/stable/")); + } +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ed79bb9..7c6a9cd 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -2964,6 +2964,16 @@ fn preview_toolchain_status( coa_core::build_engine::detect_toolchain_for(realm_root.as_deref()) } +/// Runs Microsoft's Build Tools installer with the MSVC toolset this realm's core was built with. +#[tauri::command] +async fn preview_install_msvc( + state: State<'_, AppState>, + realm_id: Option, +) -> std::result::Result { + let realm_root = realm_id.and_then(|id| path_of(&state, &id).ok()); + blocking(move || coa_core::msvc_setup::install_build_tools(realm_root.as_deref())).await +} + #[tauri::command] async fn preview_register_test_realm( state: State<'_, AppState>, @@ -3380,6 +3390,7 @@ pub fn run() { .invoke_handler(tauri::generate_handler![ release_history, preview_toolchain_status, + preview_install_msvc, preview_register_test_realm, preview_build_module, preview_install_module, diff --git a/src/lib/api.ts b/src/lib/api.ts index c265bd0..51bc12a 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -352,6 +352,10 @@ export interface ToolchainStatus { sdk_available: boolean; sdk_dir?: string | null; sdk_abi_fingerprint?: string | null; + /** MSVC toolset picked for builds, its cl.exe version, and the version this core's SDK requires. */ + msvc_toolset?: string | null; + msvc_cl_version?: string | null; + required_cl_version?: string | null; ready: boolean; diagnostic_message: string; } @@ -850,6 +854,8 @@ export const api = { consoleRisk: (command: string) => invoke<"normal" | "dangerous">("console_risk", { command }), consoleCommand: (id: string, command: string, confirmed: boolean) => invoke("console_command", { id, command, confirmed }), previewPreset: (id: string, scope: Scope, preset: string) => invoke("preview_preset", { id, scope, preset }), + previewInstallMsvc: (realmId?: string) => + invoke<{ ready: boolean; restart_required: boolean; message: string; toolchain: ToolchainStatus }>("preview_install_msvc", { realmId: realmId ?? null }), previewToolchainStatus: (realmId?: string) => invoke("preview_toolchain_status", { realmId: realmId ?? null }), previewRegisterTestRealm: (path?: string) => invoke("preview_register_test_realm", { path: path ?? null }), diff --git a/src/screens/ModulesPage.tsx b/src/screens/ModulesPage.tsx index bc9e715..16e1215 100644 --- a/src/screens/ModulesPage.tsx +++ b/src/screens/ModulesPage.tsx @@ -620,6 +620,8 @@ export function ModulesPage({ // Toolchain state const [toolchain, setToolchain] = useState(null); + const [msvcInstalling, setMsvcInstalling] = useState(false); + const [msvcResult, setMsvcResult] = useState<{ ready: boolean; restart_required: boolean; message: string } | null>(null); // Controlled Build state const [building, setBuilding] = useState(false); @@ -2774,7 +2776,47 @@ export function ModulesPage({ Host SDK ABI Fingerprint {toolchain.sdk_abi_fingerprint || "UNKNOWN"} +
+ {locale === "ru" ? "Компилятор (нужен / выбран)" : "Compiler (required / selected)"} + + {toolchain.required_cl_version ? `cl ${toolchain.required_cl_version}` : "—"} / {toolchain.msvc_cl_version ? `cl ${toolchain.msvc_cl_version} (${toolchain.msvc_toolset ?? ""})` : "NOT FOUND"} + +
+ + {!toolchain.ready && ( +
+

+ {locale === "ru" + ? "Модули собираются на этом ПК тем же компилятором, что и ядро (MSVC 14.44). Менеджер скачает официальный установщик Visual Studio Build Tools с сайта Microsoft, проверит подпись Microsoft и откроет его с уже отмеченными компонентами. Условия Microsoft вы принимаете в окне установщика. Нужно около 4 ГБ на диске и права администратора." + : "Modules are compiled on this PC with the same compiler as the core (MSVC 14.44). The Manager downloads the official Visual Studio Build Tools installer from Microsoft, checks Microsoft's signature and opens it with the components preselected. You accept Microsoft's terms in the installer window. About 4 GB of disk space and administrator rights are needed."} +

+ + {msvcResult && ( +

{msvcResult.message}

+ )} +
+ )} )}