1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
//! MSVC toolchain detection for Windows
//!
//! Detects and configures Microsoft Visual C++ for Windows builds.
//! Note: Native MSVC builds are only available on Windows.
use std::path::PathBuf;
#[cfg(target_os = "windows")]
use std::process::Command;
use anyhow::{bail, Result};
use super::Toolchain;
/// MSVC toolchain for Windows builds
pub struct MsvcToolchain {
/// Visual Studio installation path
vs_path: PathBuf,
/// MSVC version
version: String,
/// VC tools version (e.g., 14.29.30133)
vc_tools_version: String,
/// Target architecture ("x86_64", "aarch64")
architecture: String,
}
impl MsvcToolchain {
/// Detect MSVC installation using vswhere (Windows) or xwin (Linux/Docker)
///
/// On Windows: Uses native Visual Studio installation
/// On Linux: Checks for xwin + clang-cl setup (Docker environment)
pub fn detect() -> Result<Self> {
#[cfg(target_os = "windows")]
{
Self::detect_windows()
}
#[cfg(not(target_os = "windows"))]
{
Self::detect_xwin()
}
}
/// Detect xwin-based MSVC setup (Linux/Docker with clang-cl)
#[cfg(not(target_os = "windows"))]
fn detect_xwin() -> Result<Self> {
// Check for xwin SDK directory
let xwin_sdk_path = PathBuf::from("/opt/xwin/sdk");
if !xwin_sdk_path.exists() {
bail!(
"xwin Windows SDK not found at /opt/xwin/sdk\n\
For Docker builds with MSVC toolchain, use: --docker --toolchain msvc\n\
For cross-compilation, use MinGW instead: --toolchain mingw"
);
}
// Check for clang-cl wrapper script
let clang_cl_path = PathBuf::from("/usr/local/bin/clang-cl");
if !clang_cl_path.exists() {
bail!(
"clang-cl wrapper not found at /usr/local/bin/clang-cl\n\
Your Docker image may be outdated. Please rebuild it:\n\
docker rmi ccgo-builder-windows-msvc"
);
}
// Verify LLVM/Clang is available
let clang_output = std::process::Command::new("clang")
.arg("--version")
.output();
if clang_output.is_err() {
bail!("clang not found. xwin requires LLVM/Clang to be installed.");
}
// Get LLVM/Clang version
let version = if let Ok(output) = clang_output {
String::from_utf8_lossy(&output.stdout)
.lines()
.next()
.unwrap_or("unknown")
.to_string()
} else {
"unknown".to_string()
};
Ok(Self {
vs_path: xwin_sdk_path,
version,
vc_tools_version: "xwin".to_string(),
architecture: "x86_64".to_string(),
})
}
#[cfg(target_os = "windows")]
fn detect_windows() -> Result<Self> {
// Try to find vswhere
let vswhere_paths = [
r"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe",
r"C:\Program Files\Microsoft Visual Studio\Installer\vswhere.exe",
];
let vswhere_path = vswhere_paths
.iter()
.find(|p| std::path::Path::new(p).exists())
.ok_or_else(|| {
anyhow::anyhow!(
"Visual Studio not found. Please install Visual Studio 2019 or later."
)
})?;
// Run vswhere to find VS installation
let output = Command::new(vswhere_path)
.args([
"-latest",
"-requires",
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
"-property",
"installationPath",
])
.output()
.map_err(|e| anyhow::anyhow!("Failed to run vswhere: {}", e))?;
let vs_path = String::from_utf8_lossy(&output.stdout).trim().to_string();
if vs_path.is_empty() {
bail!("No Visual Studio installation with C++ tools found");
}
let vs_path = PathBuf::from(&vs_path);
// Get VS version
let version_output = Command::new(vswhere_path)
.args(["-latest", "-property", "installationVersion"])
.output()
.map_err(|e| anyhow::anyhow!("Failed to get VS version: {}", e))?;
let version = String::from_utf8_lossy(&version_output.stdout)
.trim()
.to_string();
// Get VC tools version
let vc_version_file = vs_path
.join("VC")
.join("Auxiliary")
.join("Build")
.join("Microsoft.VCToolsVersion.default.txt");
let vc_tools_version = if vc_version_file.exists() {
std::fs::read_to_string(&vc_version_file)
.unwrap_or_default()
.trim()
.to_string()
} else {
"unknown".to_string()
};
Ok(Self {
vs_path,
version,
vc_tools_version,
architecture: "x86_64".to_string(),
})
}
/// Get the Visual Studio installation path
pub fn vs_path(&self) -> &PathBuf {
&self.vs_path
}
/// Get the Visual Studio version
pub fn version(&self) -> &str {
&self.version
}
/// Get the VC tools version
pub fn vc_tools_version(&self) -> &str {
&self.vc_tools_version
}
/// Get the path to vcvarsall.bat
pub fn vcvarsall_path(&self) -> PathBuf {
self.vs_path
.join("VC")
.join("Auxiliary")
.join("Build")
.join("vcvarsall.bat")
}
/// Get CMake generator for this MSVC version
pub fn cmake_generator(&self) -> &str {
// xwin environment (Linux + clang-cl) uses Ninja
if self.vc_tools_version == "xwin" {
return "Ninja";
}
// Native Windows MSVC uses Visual Studio generator
// Parse major version to determine generator
let major = self
.version
.split('.')
.next()
.and_then(|v| v.parse::<u32>().ok())
.unwrap_or(16);
match major {
17 => "Visual Studio 17 2022",
16 => "Visual Studio 16 2019",
15 => "Visual Studio 15 2017",
_ => "Visual Studio 16 2019", // Default to VS 2019
}
}
/// Detect MSVC for a specific target architecture.
///
/// xwin (Docker) stays x64-only; on native Windows the CMake generator platform
/// is set to `x64` (x86_64) or `ARM64` (aarch64).
pub fn detect_for_arch(arch: &str) -> Result<Self> {
let mut tc = Self::detect()?;
tc.architecture = arch.to_string();
Ok(tc)
}
/// Get the target architecture string
pub fn architecture(&self) -> &str {
&self.architecture
}
/// Map architecture string to `CMAKE_GENERATOR_PLATFORM` value
fn cmake_generator_platform(&self) -> &str {
match self.architecture.as_str() {
"aarch64" | "arm64" => "ARM64",
_ => "x64",
}
}
/// Check if this is an xwin-based setup (Linux + clang-cl)
pub fn is_xwin(&self) -> bool {
self.vc_tools_version == "xwin"
}
}
impl Toolchain for MsvcToolchain {
fn name(&self) -> &str {
"msvc"
}
fn is_available(&self) -> bool {
self.vs_path.exists() && self.vcvarsall_path().exists()
}
fn path(&self) -> Option<PathBuf> {
Some(self.vs_path.clone())
}
fn cmake_variables(&self) -> Vec<(String, String)> {
let mut vars = vec![(
"CMAKE_GENERATOR".to_string(),
self.cmake_generator().to_string(),
)];
// Native Windows MSVC needs platform specification
if !self.is_xwin() {
vars.push(("CMAKE_GENERATOR_PLATFORM".to_string(), self.cmake_generator_platform().to_string()));
} else {
// xwin environment uses a CMake toolchain file extracted to ~/.ccgo/cmake/
// by the embedded cmake_templates system (same as ios/tvos/watchos toolchains)
let home = std::env::var("HOME")
.or_else(|_| std::env::var("USERPROFILE"))
.unwrap_or_default();
let toolchain_path =
PathBuf::from(home).join(".ccgo/cmake/windows-msvc.toolchain.cmake");
vars.push((
"CMAKE_TOOLCHAIN_FILE".to_string(),
toolchain_path.display().to_string(),
));
// Pass target arch so the toolchain file can select the correct
// xwin SDK lib path (crt/lib/x86_64 vs crt/lib/aarch64) and
// Clang target triple (x86_64-pc-windows-msvc vs aarch64-pc-windows-msvc).
vars.push((
"CCGO_XWIN_ARCH".to_string(),
self.architecture.clone(),
));
}
vars
}
fn validate(&self) -> Result<()> {
if !self.vs_path.exists() {
if self.is_xwin() {
bail!(
"xwin Windows SDK not found at: {}\n\
Your Docker image may be missing xwin installation.",
self.vs_path.display()
);
} else {
bail!(
"Visual Studio installation not found at: {}",
self.vs_path.display()
);
}
}
// For native Windows, check vcvarsall.bat
if !self.is_xwin() {
let vcvarsall = self.vcvarsall_path();
if !vcvarsall.exists() {
bail!("vcvarsall.bat not found at: {}", vcvarsall.display());
}
} else {
// For xwin, check clang-cl
let clang_cl = PathBuf::from("/usr/local/bin/clang-cl");
if !clang_cl.exists() {
bail!(
"clang-cl wrapper not found at: {}\n\
Your Docker image may be outdated.",
clang_cl.display()
);
}
}
Ok(())
}
}
/// Check if MSVC is available
pub fn is_msvc_available() -> bool {
MsvcToolchain::detect().is_ok()
}