1 | // Copyright 2015 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at |
3 | // http://rust-lang.org/COPYRIGHT. |
4 | // |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
6 | // https://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
7 | // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your |
8 | // option. This file may not be copied, modified, or distributed |
9 | // except according to those terms. |
10 | |
11 | //! A helper module to looking for windows-specific tools: |
12 | //! 1. On Windows host, probe the Windows Registry if needed; |
13 | //! 2. On non-Windows host, check specified environment variables. |
14 | |
15 | #![allow (clippy::upper_case_acronyms)] |
16 | |
17 | use std::{ |
18 | env, |
19 | ffi::{OsStr, OsString}, |
20 | ops::Deref, |
21 | path::PathBuf, |
22 | process::Command, |
23 | sync::Arc, |
24 | }; |
25 | |
26 | use crate::Tool; |
27 | use crate::ToolFamily; |
28 | |
29 | const MSVC_FAMILY: ToolFamily = ToolFamily::Msvc { clang_cl: false }; |
30 | |
31 | /// The target provided by the user. |
32 | #[derive (Copy, Clone, PartialEq, Eq)] |
33 | enum TargetArch { |
34 | X86, |
35 | X64, |
36 | Arm, |
37 | Arm64, |
38 | Arm64ec, |
39 | } |
40 | impl TargetArch { |
41 | /// Parse the `TargetArch` from a str. Returns `None` if the arch is unrecognized. |
42 | fn new(arch: &str) -> Option<Self> { |
43 | // NOTE: Keep up to date with docs in [`find`]. |
44 | match arch { |
45 | "x64" | "x86_64" => Some(Self::X64), |
46 | "arm64" | "aarch64" => Some(Self::Arm64), |
47 | "arm64ec" => Some(Self::Arm64ec), |
48 | "x86" | "i686" | "i586" => Some(Self::X86), |
49 | "arm" | "thumbv7a" => Some(Self::Arm), |
50 | _ => None, |
51 | } |
52 | } |
53 | |
54 | #[cfg (windows)] |
55 | /// Gets the Visual Studio name for the architecture. |
56 | fn as_vs_arch(&self) -> &'static str { |
57 | match self { |
58 | Self::X64 => "x64" , |
59 | Self::Arm64 | Self::Arm64ec => "arm64" , |
60 | Self::X86 => "x86" , |
61 | Self::Arm => "arm" , |
62 | } |
63 | } |
64 | } |
65 | |
66 | pub(crate) enum Env { |
67 | Owned(OsString), |
68 | Arced(Arc<OsStr>), |
69 | } |
70 | |
71 | impl AsRef<OsStr> for Env { |
72 | fn as_ref(&self) -> &OsStr { |
73 | self.deref() |
74 | } |
75 | } |
76 | |
77 | impl Deref for Env { |
78 | type Target = OsStr; |
79 | |
80 | fn deref(&self) -> &Self::Target { |
81 | match self { |
82 | Env::Owned(os_str: &OsString) => os_str, |
83 | Env::Arced(os_str: &Arc) => os_str, |
84 | } |
85 | } |
86 | } |
87 | |
88 | impl From<Env> for PathBuf { |
89 | fn from(env: Env) -> Self { |
90 | match env { |
91 | Env::Owned(os_str: OsString) => PathBuf::from(os_str), |
92 | Env::Arced(os_str: Arc) => PathBuf::from(os_str.deref()), |
93 | } |
94 | } |
95 | } |
96 | |
97 | pub(crate) trait EnvGetter { |
98 | fn get_env(&self, name: &'static str) -> Option<Env>; |
99 | } |
100 | |
101 | struct StdEnvGetter; |
102 | |
103 | impl EnvGetter for StdEnvGetter { |
104 | #[allow (clippy::disallowed_methods)] |
105 | fn get_env(&self, name: &'static str) -> Option<Env> { |
106 | env::var_os(key:name).map(Env::Owned) |
107 | } |
108 | } |
109 | |
110 | /// Attempts to find a tool within an MSVC installation using the Windows |
111 | /// registry as a point to search from. |
112 | /// |
113 | /// The `arch_or_target` argument is the architecture or the Rust target |
114 | /// triple that the tool should work for (e.g. compile or link for). The |
115 | /// supported architecture names are: |
116 | /// - `"x64"` or `"x86_64"` |
117 | /// - `"arm64"` or `"aarch64"` |
118 | /// - `"arm64ec"` |
119 | /// - `"x86"`, `"i586"` or `"i686"` |
120 | /// - `"arm"` or `"thumbv7a"` |
121 | /// |
122 | /// The `tool` argument is the tool to find (e.g. `cl.exe` or `link.exe`). |
123 | /// |
124 | /// This function will return `None` if the tool could not be found, or it will |
125 | /// return `Some(cmd)` which represents a command that's ready to execute the |
126 | /// tool with the appropriate environment variables set. |
127 | /// |
128 | /// Note that this function always returns `None` for non-MSVC targets (if a |
129 | /// full target name was specified). |
130 | pub fn find(arch_or_target: &str, tool: &str) -> Option<Command> { |
131 | find_tool(arch_or_target, tool).map(|c: Tool| c.to_command()) |
132 | } |
133 | |
134 | /// Similar to the `find` function above, this function will attempt the same |
135 | /// operation (finding a MSVC tool in a local install) but instead returns a |
136 | /// `Tool` which may be introspected. |
137 | pub fn find_tool(arch_or_target: &str, tool: &str) -> Option<Tool> { |
138 | let full_arch: &str = if let Some((full_arch: &str, rest: &str)) = arch_or_target.split_once(delimiter:"-" ) { |
139 | // The logic is all tailored for MSVC, if the target is not that then |
140 | // bail out early. |
141 | if !rest.contains("msvc" ) { |
142 | return None; |
143 | } |
144 | full_arch |
145 | } else { |
146 | arch_or_target |
147 | }; |
148 | find_tool_inner(full_arch, tool, &StdEnvGetter) |
149 | } |
150 | |
151 | pub(crate) fn find_tool_inner( |
152 | full_arch: &str, |
153 | tool: &str, |
154 | env_getter: &dyn EnvGetter, |
155 | ) -> Option<Tool> { |
156 | // We only need the arch. |
157 | let target = TargetArch::new(full_arch)?; |
158 | |
159 | // Looks like msbuild isn't located in the same location as other tools like |
160 | // cl.exe and lib.exe. |
161 | if tool.contains("msbuild" ) { |
162 | return impl_::find_msbuild(target, env_getter); |
163 | } |
164 | |
165 | // Looks like devenv isn't located in the same location as other tools like |
166 | // cl.exe and lib.exe. |
167 | if tool.contains("devenv" ) { |
168 | return impl_::find_devenv(target, env_getter); |
169 | } |
170 | |
171 | // Ok, if we're here, now comes the fun part of the probing. Default shells |
172 | // or shells like MSYS aren't really configured to execute `cl.exe` and the |
173 | // various compiler tools shipped as part of Visual Studio. Here we try to |
174 | // first find the relevant tool, then we also have to be sure to fill in |
175 | // environment variables like `LIB`, `INCLUDE`, and `PATH` to ensure that |
176 | // the tool is actually usable. |
177 | |
178 | impl_::find_msvc_environment(tool, target, env_getter) |
179 | .or_else(|| impl_::find_msvc_15plus(tool, target, env_getter)) |
180 | .or_else(|| impl_::find_msvc_14(tool, target, env_getter)) |
181 | } |
182 | |
183 | /// A version of Visual Studio |
184 | #[derive (Debug, PartialEq, Eq, Copy, Clone)] |
185 | #[non_exhaustive ] |
186 | pub enum VsVers { |
187 | /// Visual Studio 12 (2013) |
188 | #[deprecated ( |
189 | note = "Visual Studio 12 is no longer supported. cc will never return this value." |
190 | )] |
191 | Vs12, |
192 | /// Visual Studio 14 (2015) |
193 | Vs14, |
194 | /// Visual Studio 15 (2017) |
195 | Vs15, |
196 | /// Visual Studio 16 (2019) |
197 | Vs16, |
198 | /// Visual Studio 17 (2022) |
199 | Vs17, |
200 | } |
201 | |
202 | /// Find the most recent installed version of Visual Studio |
203 | /// |
204 | /// This is used by the cmake crate to figure out the correct |
205 | /// generator. |
206 | #[allow (clippy::disallowed_methods)] |
207 | pub fn find_vs_version() -> Result<VsVers, String> { |
208 | fn has_msbuild_version(version: &str) -> bool { |
209 | impl_::has_msbuild_version(version, &StdEnvGetter) |
210 | } |
211 | |
212 | match std::env::var("VisualStudioVersion" ) { |
213 | Ok(version) => match &version[..] { |
214 | "17.0" => Ok(VsVers::Vs17), |
215 | "16.0" => Ok(VsVers::Vs16), |
216 | "15.0" => Ok(VsVers::Vs15), |
217 | "14.0" => Ok(VsVers::Vs14), |
218 | vers => Err(format!( |
219 | " \n\n\ |
220 | unsupported or unknown VisualStudio version: {}\n\ |
221 | if another version is installed consider running \ |
222 | the appropriate vcvars script before building this \ |
223 | crate \n\ |
224 | " , |
225 | vers |
226 | )), |
227 | }, |
228 | _ => { |
229 | // Check for the presence of a specific registry key |
230 | // that indicates visual studio is installed. |
231 | if has_msbuild_version("17.0" ) { |
232 | Ok(VsVers::Vs17) |
233 | } else if has_msbuild_version("16.0" ) { |
234 | Ok(VsVers::Vs16) |
235 | } else if has_msbuild_version("15.0" ) { |
236 | Ok(VsVers::Vs15) |
237 | } else if has_msbuild_version("14.0" ) { |
238 | Ok(VsVers::Vs14) |
239 | } else { |
240 | Err(" \n\n\ |
241 | couldn't determine visual studio generator \n\ |
242 | if VisualStudio is installed, however, consider \ |
243 | running the appropriate vcvars script before building \ |
244 | this crate \n\ |
245 | " |
246 | .to_string()) |
247 | } |
248 | } |
249 | } |
250 | } |
251 | |
252 | /// Windows Implementation. |
253 | #[cfg (windows)] |
254 | mod impl_ { |
255 | use crate::windows::com; |
256 | use crate::windows::registry::{RegistryKey, LOCAL_MACHINE}; |
257 | use crate::windows::setup_config::SetupConfiguration; |
258 | use crate::windows::vs_instances::{VsInstances, VswhereInstance}; |
259 | use crate::windows::windows_sys::{ |
260 | GetMachineTypeAttributes, GetProcAddress, LoadLibraryA, UserEnabled, HMODULE, |
261 | IMAGE_FILE_MACHINE_AMD64, MACHINE_ATTRIBUTES, S_OK, |
262 | }; |
263 | use std::convert::TryFrom; |
264 | use std::env; |
265 | use std::ffi::OsString; |
266 | use std::fs::File; |
267 | use std::io::Read; |
268 | use std::iter; |
269 | use std::mem; |
270 | use std::path::{Path, PathBuf}; |
271 | use std::process::Command; |
272 | use std::str::FromStr; |
273 | use std::sync::atomic::{AtomicBool, Ordering}; |
274 | use std::sync::Once; |
275 | |
276 | use super::{EnvGetter, TargetArch, MSVC_FAMILY}; |
277 | use crate::Tool; |
278 | |
279 | struct MsvcTool { |
280 | tool: PathBuf, |
281 | libs: Vec<PathBuf>, |
282 | path: Vec<PathBuf>, |
283 | include: Vec<PathBuf>, |
284 | } |
285 | |
286 | struct LibraryHandle(HMODULE); |
287 | |
288 | impl LibraryHandle { |
289 | fn new(name: &[u8]) -> Option<Self> { |
290 | let handle = unsafe { LoadLibraryA(name.as_ptr() as _) }; |
291 | (!handle.is_null()).then_some(Self(handle)) |
292 | } |
293 | |
294 | /// Get a function pointer to a function in the library. |
295 | /// # SAFETY |
296 | /// |
297 | /// The caller must ensure that the function signature matches the actual function. |
298 | /// The easiest way to do this is to add an entry to `windows_sys_no_link.list` and use the |
299 | /// generated function for `func_signature`. |
300 | /// |
301 | /// The function returned cannot be used after the handle is dropped. |
302 | unsafe fn get_proc_address<F>(&self, name: &[u8]) -> Option<F> { |
303 | let symbol = GetProcAddress(self.0, name.as_ptr() as _); |
304 | symbol.map(|symbol| mem::transmute_copy(&symbol)) |
305 | } |
306 | } |
307 | |
308 | type GetMachineTypeAttributesFuncType = |
309 | unsafe extern "system" fn(u16, *mut MACHINE_ATTRIBUTES) -> i32; |
310 | const _: () = { |
311 | // Ensure that our hand-written signature matches the actual function signature. |
312 | // We can't use `GetMachineTypeAttributes` outside of a const scope otherwise we'll end up statically linking to |
313 | // it, which will fail to load on older versions of Windows. |
314 | let _: GetMachineTypeAttributesFuncType = GetMachineTypeAttributes; |
315 | }; |
316 | |
317 | fn is_amd64_emulation_supported_inner() -> Option<bool> { |
318 | // GetMachineTypeAttributes is only available on Win11 22000+, so dynamically load it. |
319 | let kernel32 = LibraryHandle::new(b"kernel32.dll \0" )?; |
320 | // SAFETY: GetMachineTypeAttributesFuncType is checked to match the real function signature. |
321 | let get_machine_type_attributes = unsafe { |
322 | kernel32 |
323 | .get_proc_address::<GetMachineTypeAttributesFuncType>(b"GetMachineTypeAttributes \0" ) |
324 | }?; |
325 | let mut attributes = Default::default(); |
326 | if unsafe { get_machine_type_attributes(IMAGE_FILE_MACHINE_AMD64, &mut attributes) } == S_OK |
327 | { |
328 | Some((attributes & UserEnabled) != 0) |
329 | } else { |
330 | Some(false) |
331 | } |
332 | } |
333 | |
334 | fn is_amd64_emulation_supported() -> bool { |
335 | // TODO: Replace with a OnceLock once MSRV is 1.70. |
336 | static LOAD_VALUE: Once = Once::new(); |
337 | static IS_SUPPORTED: AtomicBool = AtomicBool::new(false); |
338 | |
339 | // Using Relaxed ordering since the Once is providing synchronization. |
340 | LOAD_VALUE.call_once(|| { |
341 | IS_SUPPORTED.store( |
342 | is_amd64_emulation_supported_inner().unwrap_or(false), |
343 | Ordering::Relaxed, |
344 | ); |
345 | }); |
346 | IS_SUPPORTED.load(Ordering::Relaxed) |
347 | } |
348 | |
349 | impl MsvcTool { |
350 | fn new(tool: PathBuf) -> MsvcTool { |
351 | MsvcTool { |
352 | tool, |
353 | libs: Vec::new(), |
354 | path: Vec::new(), |
355 | include: Vec::new(), |
356 | } |
357 | } |
358 | |
359 | fn into_tool(self, env_getter: &dyn EnvGetter) -> Tool { |
360 | let MsvcTool { |
361 | tool, |
362 | libs, |
363 | path, |
364 | include, |
365 | } = self; |
366 | let mut tool = Tool::with_family(tool, MSVC_FAMILY); |
367 | add_env(&mut tool, "LIB" , libs, env_getter); |
368 | add_env(&mut tool, "PATH" , path, env_getter); |
369 | add_env(&mut tool, "INCLUDE" , include, env_getter); |
370 | tool |
371 | } |
372 | } |
373 | |
374 | /// Checks to see if the target's arch matches the VS environment. Returns `None` if the |
375 | /// environment is unknown. |
376 | fn is_vscmd_target(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> { |
377 | is_vscmd_target_env(target, env_getter).or_else(|| is_vscmd_target_cl(target, env_getter)) |
378 | } |
379 | |
380 | /// Checks to see if the `VSCMD_ARG_TGT_ARCH` environment variable matches the |
381 | /// given target's arch. Returns `None` if the variable does not exist. |
382 | fn is_vscmd_target_env(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> { |
383 | let vscmd_arch = env_getter.get_env("VSCMD_ARG_TGT_ARCH" )?; |
384 | Some(target.as_vs_arch() == vscmd_arch.as_ref()) |
385 | } |
386 | |
387 | /// Checks if the cl.exe target matches the given target's arch. Returns `None` if anything |
388 | /// fails. |
389 | fn is_vscmd_target_cl(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<bool> { |
390 | let cmd_target = vscmd_target_cl(env_getter)?; |
391 | Some(target.as_vs_arch() == cmd_target) |
392 | } |
393 | |
394 | /// Detect the target architecture of `cl.exe` in the current path, and return `None` if this |
395 | /// fails for any reason. |
396 | fn vscmd_target_cl(env_getter: &dyn EnvGetter) -> Option<&'static str> { |
397 | let cl_exe = env_getter.get_env("PATH" ).and_then(|path| { |
398 | env::split_paths(&path) |
399 | .map(|p| p.join("cl.exe" )) |
400 | .find(|p| p.exists()) |
401 | })?; |
402 | let mut cl = Command::new(cl_exe); |
403 | cl.stderr(std::process::Stdio::piped()) |
404 | .stdout(std::process::Stdio::null()); |
405 | |
406 | let out = cl.output().ok()?; |
407 | let cl_arch = out |
408 | .stderr |
409 | .split(|&b| b == b' \n' || b == b' \r' ) |
410 | .next()? |
411 | .rsplit(|&b| b == b' ' ) |
412 | .next()?; |
413 | |
414 | match cl_arch { |
415 | b"x64" => Some("x64" ), |
416 | b"x86" => Some("x86" ), |
417 | b"ARM64" => Some("arm64" ), |
418 | b"ARM" => Some("arm" ), |
419 | _ => None, |
420 | } |
421 | } |
422 | |
423 | /// Attempt to find the tool using environment variables set by vcvars. |
424 | pub(super) fn find_msvc_environment( |
425 | tool: &str, |
426 | target: TargetArch, |
427 | env_getter: &dyn EnvGetter, |
428 | ) -> Option<Tool> { |
429 | // Early return if the environment isn't one that is known to have compiler toolsets in PATH |
430 | // `VCINSTALLDIR` is set from vcvarsall.bat (developer command prompt) |
431 | // `VSTEL_MSBuildProjectFullPath` is set by msbuild when invoking custom build steps |
432 | // NOTE: `VisualStudioDir` used to be used but this isn't set when invoking msbuild from the commandline |
433 | if env_getter.get_env("VCINSTALLDIR" ).is_none() |
434 | && env_getter.get_env("VSTEL_MSBuildProjectFullPath" ).is_none() |
435 | { |
436 | return None; |
437 | } |
438 | |
439 | // If the vscmd target differs from the requested target then |
440 | // attempt to get the tool using the VS install directory. |
441 | if is_vscmd_target(target, env_getter) == Some(false) { |
442 | // We will only get here with versions 15+. |
443 | let vs_install_dir: PathBuf = env_getter.get_env("VSINSTALLDIR" )?.into(); |
444 | tool_from_vs15plus_instance(tool, target, &vs_install_dir, env_getter) |
445 | } else { |
446 | // Fallback to simply using the current environment. |
447 | env_getter |
448 | .get_env("PATH" ) |
449 | .and_then(|path| { |
450 | env::split_paths(&path) |
451 | .map(|p| p.join(tool)) |
452 | .find(|p| p.exists()) |
453 | }) |
454 | .map(|path| Tool::with_family(path, MSVC_FAMILY)) |
455 | } |
456 | } |
457 | |
458 | fn find_msbuild_vs17(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> { |
459 | find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe" , target, "17" , env_getter) |
460 | } |
461 | |
462 | #[allow (bare_trait_objects)] |
463 | fn vs16plus_instances( |
464 | target: TargetArch, |
465 | version: &'static str, |
466 | env_getter: &dyn EnvGetter, |
467 | ) -> Box<Iterator<Item = PathBuf>> { |
468 | let instances = if let Some(instances) = vs15plus_instances(target, env_getter) { |
469 | instances |
470 | } else { |
471 | return Box::new(iter::empty()); |
472 | }; |
473 | Box::new(instances.into_iter().filter_map(move |instance| { |
474 | let installation_name = instance.installation_name()?; |
475 | if installation_name.starts_with(&format!("VisualStudio/{}." , version)) |
476 | || installation_name.starts_with(&format!("VisualStudioPreview/{}." , version)) |
477 | { |
478 | Some(instance.installation_path()?) |
479 | } else { |
480 | None |
481 | } |
482 | })) |
483 | } |
484 | |
485 | fn find_tool_in_vs16plus_path( |
486 | tool: &str, |
487 | target: TargetArch, |
488 | version: &'static str, |
489 | env_getter: &dyn EnvGetter, |
490 | ) -> Option<Tool> { |
491 | vs16plus_instances(target, version, env_getter) |
492 | .filter_map(|path| { |
493 | let path = path.join(tool); |
494 | if !path.is_file() { |
495 | return None; |
496 | } |
497 | let mut tool = Tool::with_family(path, MSVC_FAMILY); |
498 | if target == TargetArch::X64 { |
499 | tool.env.push(("Platform" .into(), "X64" .into())); |
500 | } |
501 | if matches!(target, TargetArch::Arm64 | TargetArch::Arm64ec) { |
502 | tool.env.push(("Platform" .into(), "ARM64" .into())); |
503 | } |
504 | Some(tool) |
505 | }) |
506 | .next() |
507 | } |
508 | |
509 | fn find_msbuild_vs16(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> { |
510 | find_tool_in_vs16plus_path(r"MSBuild\Current\Bin\MSBuild.exe" , target, "16" , env_getter) |
511 | } |
512 | |
513 | // In MSVC 15 (2017) MS once again changed the scheme for locating |
514 | // the tooling. Now we must go through some COM interfaces, which |
515 | // is super fun for Rust. |
516 | // |
517 | // Note that much of this logic can be found [online] wrt paths, COM, etc. |
518 | // |
519 | // [online]: https://blogs.msdn.microsoft.com/vcblog/2017/03/06/finding-the-visual-c-compiler-tools-in-visual-studio-2017/ |
520 | // |
521 | // Returns MSVC 15+ instances (15, 16 right now), the order should be consider undefined. |
522 | // |
523 | // However, on ARM64 this method doesn't work because VS Installer fails to register COM component on ARM64. |
524 | // Hence, as the last resort we try to use vswhere.exe to list available instances. |
525 | fn vs15plus_instances(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<VsInstances> { |
526 | vs15plus_instances_using_com() |
527 | .or_else(|| vs15plus_instances_using_vswhere(target, env_getter)) |
528 | } |
529 | |
530 | fn vs15plus_instances_using_com() -> Option<VsInstances> { |
531 | com::initialize().ok()?; |
532 | |
533 | let config = SetupConfiguration::new().ok()?; |
534 | let enum_setup_instances = config.enum_all_instances().ok()?; |
535 | |
536 | Some(VsInstances::ComBased(enum_setup_instances)) |
537 | } |
538 | |
539 | fn vs15plus_instances_using_vswhere( |
540 | target: TargetArch, |
541 | env_getter: &dyn EnvGetter, |
542 | ) -> Option<VsInstances> { |
543 | let program_files_path = env_getter |
544 | .get_env("ProgramFiles(x86)" ) |
545 | .or_else(|| env_getter.get_env("ProgramFiles" ))?; |
546 | |
547 | let program_files_path = Path::new(program_files_path.as_ref()); |
548 | |
549 | let vswhere_path = |
550 | program_files_path.join(r"Microsoft Visual Studio\Installer\vswhere.exe" ); |
551 | |
552 | if !vswhere_path.exists() { |
553 | return None; |
554 | } |
555 | |
556 | let tools_arch = match target { |
557 | TargetArch::X86 | TargetArch::X64 => Some("x86.x64" ), |
558 | TargetArch::Arm => Some("ARM" ), |
559 | TargetArch::Arm64 | TargetArch::Arm64ec => Some("ARM64" ), |
560 | }; |
561 | |
562 | let vswhere_output = Command::new(vswhere_path) |
563 | .args([ |
564 | "-latest" , |
565 | "-products" , |
566 | "*" , |
567 | "-requires" , |
568 | &format!("Microsoft.VisualStudio.Component.VC.Tools.{}" , tools_arch?), |
569 | "-format" , |
570 | "text" , |
571 | "-nologo" , |
572 | ]) |
573 | .stderr(std::process::Stdio::inherit()) |
574 | .output() |
575 | .ok()?; |
576 | |
577 | let vs_instances = |
578 | VsInstances::VswhereBased(VswhereInstance::try_from(&vswhere_output.stdout).ok()?); |
579 | |
580 | Some(vs_instances) |
581 | } |
582 | |
583 | // Inspired from official microsoft/vswhere ParseVersionString |
584 | // i.e. at most four u16 numbers separated by '.' |
585 | fn parse_version(version: &str) -> Option<Vec<u16>> { |
586 | version |
587 | .split('.' ) |
588 | .map(|chunk| u16::from_str(chunk).ok()) |
589 | .collect() |
590 | } |
591 | |
592 | pub(super) fn find_msvc_15plus( |
593 | tool: &str, |
594 | target: TargetArch, |
595 | env_getter: &dyn EnvGetter, |
596 | ) -> Option<Tool> { |
597 | let iter = vs15plus_instances(target, env_getter)?; |
598 | iter.into_iter() |
599 | .filter_map(|instance| { |
600 | let version = parse_version(&instance.installation_version()?)?; |
601 | let instance_path = instance.installation_path()?; |
602 | let tool = tool_from_vs15plus_instance(tool, target, &instance_path, env_getter)?; |
603 | Some((version, tool)) |
604 | }) |
605 | .max_by(|(a_version, _), (b_version, _)| a_version.cmp(b_version)) |
606 | .map(|(_version, tool)| tool) |
607 | } |
608 | |
609 | // While the paths to Visual Studio 2017's devenv and MSBuild could |
610 | // potentially be retrieved from the registry, finding them via |
611 | // SetupConfiguration has shown to be [more reliable], and is preferred |
612 | // according to Microsoft. To help head off potential regressions though, |
613 | // we keep the registry method as a fallback option. |
614 | // |
615 | // [more reliable]: https://github.com/rust-lang/cc-rs/pull/331 |
616 | fn find_tool_in_vs15_path( |
617 | tool: &str, |
618 | target: TargetArch, |
619 | env_getter: &dyn EnvGetter, |
620 | ) -> Option<Tool> { |
621 | let mut path = match vs15plus_instances(target, env_getter) { |
622 | Some(instances) => instances |
623 | .into_iter() |
624 | .filter_map(|instance| instance.installation_path()) |
625 | .map(|path| path.join(tool)) |
626 | .find(|path| path.is_file()), |
627 | None => None, |
628 | }; |
629 | |
630 | if path.is_none() { |
631 | let key = r"SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7" ; |
632 | path = LOCAL_MACHINE |
633 | .open(key.as_ref()) |
634 | .ok() |
635 | .and_then(|key| key.query_str("15.0" ).ok()) |
636 | .map(|path| PathBuf::from(path).join(tool)) |
637 | .and_then(|path| if path.is_file() { Some(path) } else { None }); |
638 | } |
639 | |
640 | path.map(|path| { |
641 | let mut tool = Tool::with_family(path, MSVC_FAMILY); |
642 | if target == TargetArch::X64 { |
643 | tool.env.push(("Platform" .into(), "X64" .into())); |
644 | } else if matches!(target, TargetArch::Arm64 | TargetArch::Arm64ec) { |
645 | tool.env.push(("Platform" .into(), "ARM64" .into())); |
646 | } |
647 | tool |
648 | }) |
649 | } |
650 | |
651 | fn tool_from_vs15plus_instance( |
652 | tool: &str, |
653 | target: TargetArch, |
654 | instance_path: &Path, |
655 | env_getter: &dyn EnvGetter, |
656 | ) -> Option<Tool> { |
657 | let (root_path, bin_path, host_dylib_path, lib_path, alt_lib_path, include_path) = |
658 | vs15plus_vc_paths(target, instance_path, env_getter)?; |
659 | let tool_path = bin_path.join(tool); |
660 | if !tool_path.exists() { |
661 | return None; |
662 | }; |
663 | |
664 | let mut tool = MsvcTool::new(tool_path); |
665 | tool.path.push(bin_path.clone()); |
666 | tool.path.push(host_dylib_path); |
667 | if let Some(alt_lib_path) = alt_lib_path { |
668 | tool.libs.push(alt_lib_path); |
669 | } |
670 | tool.libs.push(lib_path); |
671 | tool.include.push(include_path); |
672 | |
673 | if let Some((atl_lib_path, atl_include_path)) = atl_paths(target, &root_path) { |
674 | tool.libs.push(atl_lib_path); |
675 | tool.include.push(atl_include_path); |
676 | } |
677 | |
678 | add_sdks(&mut tool, target, env_getter)?; |
679 | |
680 | Some(tool.into_tool(env_getter)) |
681 | } |
682 | |
683 | fn vs15plus_vc_paths( |
684 | target_arch: TargetArch, |
685 | instance_path: &Path, |
686 | env_getter: &dyn EnvGetter, |
687 | ) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf, Option<PathBuf>, PathBuf)> { |
688 | let version = vs15plus_vc_read_version(instance_path)?; |
689 | |
690 | let hosts = match host_arch() { |
691 | X86 => &["X86" ], |
692 | X86_64 => &["X64" ], |
693 | // Starting with VS 17.4, there is a natively hosted compiler on ARM64: |
694 | // https://devblogs.microsoft.com/visualstudio/arm64-visual-studio-is-officially-here/ |
695 | // On older versions of VS, we use x64 if running under emulation is supported, |
696 | // otherwise use x86. |
697 | AARCH64 => { |
698 | if is_amd64_emulation_supported() { |
699 | &["ARM64" , "X64" , "X86" ][..] |
700 | } else { |
701 | &["ARM64" , "X86" ] |
702 | } |
703 | } |
704 | _ => return None, |
705 | }; |
706 | let target_dir = target_arch.as_vs_arch(); |
707 | // The directory layout here is MSVC/bin/Host$host/$target/ |
708 | let path = instance_path.join(r"VC\Tools\MSVC" ).join(version); |
709 | // We use the first available host architecture that can build for the target |
710 | let (host_path, host) = hosts.iter().find_map(|&x| { |
711 | let candidate = path.join("bin" ).join(format!("Host{}" , x)); |
712 | if candidate.join(target_dir).exists() { |
713 | Some((candidate, x)) |
714 | } else { |
715 | None |
716 | } |
717 | })?; |
718 | // This is the path to the toolchain for a particular target, running |
719 | // on a given host |
720 | let bin_path = host_path.join(target_dir); |
721 | // But! we also need PATH to contain the target directory for the host |
722 | // architecture, because it contains dlls like mspdb140.dll compiled for |
723 | // the host architecture. |
724 | let host_dylib_path = host_path.join(host.to_lowercase()); |
725 | let lib_fragment = if use_spectre_mitigated_libs(env_getter) { |
726 | r"lib\spectre" |
727 | } else { |
728 | "lib" |
729 | }; |
730 | let lib_path = path.join(lib_fragment).join(target_dir); |
731 | let alt_lib_path = |
732 | (target_arch == TargetArch::Arm64ec).then(|| path.join(lib_fragment).join("arm64ec" )); |
733 | let include_path = path.join("include" ); |
734 | Some(( |
735 | path, |
736 | bin_path, |
737 | host_dylib_path, |
738 | lib_path, |
739 | alt_lib_path, |
740 | include_path, |
741 | )) |
742 | } |
743 | |
744 | fn vs15plus_vc_read_version(dir: &Path) -> Option<String> { |
745 | // Try to open the default version file. |
746 | let mut version_path: PathBuf = |
747 | dir.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt" ); |
748 | let mut version_file = if let Ok(f) = File::open(&version_path) { |
749 | f |
750 | } else { |
751 | // If the default doesn't exist, search for other version files. |
752 | // These are in the form Microsoft.VCToolsVersion.v143.default.txt |
753 | // where `143` is any three decimal digit version number. |
754 | // This sorts versions by lexical order and selects the highest version. |
755 | let mut version_file = String::new(); |
756 | version_path.pop(); |
757 | for file in version_path.read_dir().ok()? { |
758 | let name = file.ok()?.file_name(); |
759 | let name = name.to_str()?; |
760 | if name.starts_with("Microsoft.VCToolsVersion.v" ) |
761 | && name.ends_with(".default.txt" ) |
762 | && name > &version_file |
763 | { |
764 | version_file.replace_range(.., name); |
765 | } |
766 | } |
767 | if version_file.is_empty() { |
768 | return None; |
769 | } |
770 | version_path.push(version_file); |
771 | File::open(version_path).ok()? |
772 | }; |
773 | |
774 | // Get the version string from the file we found. |
775 | let mut version = String::new(); |
776 | version_file.read_to_string(&mut version).ok()?; |
777 | version.truncate(version.trim_end().len()); |
778 | Some(version) |
779 | } |
780 | |
781 | fn use_spectre_mitigated_libs(env_getter: &dyn EnvGetter) -> bool { |
782 | env_getter |
783 | .get_env("VSCMD_ARG_VCVARS_SPECTRE" ) |
784 | .map(|env| env.as_ref() == "spectre" ) |
785 | .unwrap_or_default() |
786 | } |
787 | |
788 | fn atl_paths(target: TargetArch, path: &Path) -> Option<(PathBuf, PathBuf)> { |
789 | let atl_path = path.join("atlmfc" ); |
790 | let sub = target.as_vs_arch(); |
791 | if atl_path.exists() { |
792 | Some((atl_path.join("lib" ).join(sub), atl_path.join("include" ))) |
793 | } else { |
794 | None |
795 | } |
796 | } |
797 | |
798 | // For MSVC 14 we need to find the Universal CRT as well as either |
799 | // the Windows 10 SDK or Windows 8.1 SDK. |
800 | pub(super) fn find_msvc_14( |
801 | tool: &str, |
802 | target: TargetArch, |
803 | env_getter: &dyn EnvGetter, |
804 | ) -> Option<Tool> { |
805 | let vcdir = get_vc_dir("14.0" )?; |
806 | let mut tool = get_tool(tool, &vcdir, target)?; |
807 | add_sdks(&mut tool, target, env_getter)?; |
808 | Some(tool.into_tool(env_getter)) |
809 | } |
810 | |
811 | fn add_sdks(tool: &mut MsvcTool, target: TargetArch, env_getter: &dyn EnvGetter) -> Option<()> { |
812 | let sub = target.as_vs_arch(); |
813 | let (ucrt, ucrt_version) = get_ucrt_dir()?; |
814 | |
815 | let host = match host_arch() { |
816 | X86 => "x86" , |
817 | X86_64 => "x64" , |
818 | AARCH64 => "arm64" , |
819 | _ => return None, |
820 | }; |
821 | |
822 | tool.path |
823 | .push(ucrt.join("bin" ).join(&ucrt_version).join(host)); |
824 | |
825 | let ucrt_include = ucrt.join("include" ).join(&ucrt_version); |
826 | tool.include.push(ucrt_include.join("ucrt" )); |
827 | |
828 | let ucrt_lib = ucrt.join("lib" ).join(&ucrt_version); |
829 | tool.libs.push(ucrt_lib.join("ucrt" ).join(sub)); |
830 | |
831 | if let Some((sdk, version)) = get_sdk10_dir(env_getter) { |
832 | tool.path.push(sdk.join("bin" ).join(host)); |
833 | let sdk_lib = sdk.join("lib" ).join(&version); |
834 | tool.libs.push(sdk_lib.join("um" ).join(sub)); |
835 | let sdk_include = sdk.join("include" ).join(&version); |
836 | tool.include.push(sdk_include.join("um" )); |
837 | tool.include.push(sdk_include.join("cppwinrt" )); |
838 | tool.include.push(sdk_include.join("winrt" )); |
839 | tool.include.push(sdk_include.join("shared" )); |
840 | } else if let Some(sdk) = get_sdk81_dir() { |
841 | tool.path.push(sdk.join("bin" ).join(host)); |
842 | let sdk_lib = sdk.join("lib" ).join("winv6.3" ); |
843 | tool.libs.push(sdk_lib.join("um" ).join(sub)); |
844 | let sdk_include = sdk.join("include" ); |
845 | tool.include.push(sdk_include.join("um" )); |
846 | tool.include.push(sdk_include.join("winrt" )); |
847 | tool.include.push(sdk_include.join("shared" )); |
848 | } |
849 | |
850 | Some(()) |
851 | } |
852 | |
853 | fn add_env( |
854 | tool: &mut Tool, |
855 | env: &'static str, |
856 | paths: Vec<PathBuf>, |
857 | env_getter: &dyn EnvGetter, |
858 | ) { |
859 | let prev = env_getter.get_env(env); |
860 | let prev = prev.as_ref().map(AsRef::as_ref).unwrap_or_default(); |
861 | let prev = env::split_paths(&prev); |
862 | let new = paths.into_iter().chain(prev); |
863 | tool.env |
864 | .push((env.to_string().into(), env::join_paths(new).unwrap())); |
865 | } |
866 | |
867 | // Given a possible MSVC installation directory, we look for the linker and |
868 | // then add the MSVC library path. |
869 | fn get_tool(tool: &str, path: &Path, target: TargetArch) -> Option<MsvcTool> { |
870 | bin_subdir(target) |
871 | .into_iter() |
872 | .map(|(sub, host)| { |
873 | ( |
874 | path.join("bin" ).join(sub).join(tool), |
875 | path.join("bin" ).join(host), |
876 | ) |
877 | }) |
878 | .filter(|(path, _)| path.is_file()) |
879 | .map(|(path, host)| { |
880 | let mut tool = MsvcTool::new(path); |
881 | tool.path.push(host); |
882 | tool |
883 | }) |
884 | .filter_map(|mut tool| { |
885 | let sub = vc_lib_subdir(target); |
886 | tool.libs.push(path.join("lib" ).join(sub)); |
887 | tool.include.push(path.join("include" )); |
888 | let atlmfc_path = path.join("atlmfc" ); |
889 | if atlmfc_path.exists() { |
890 | tool.libs.push(atlmfc_path.join("lib" ).join(sub)); |
891 | tool.include.push(atlmfc_path.join("include" )); |
892 | } |
893 | Some(tool) |
894 | }) |
895 | .next() |
896 | } |
897 | |
898 | // To find MSVC we look in a specific registry key for the version we are |
899 | // trying to find. |
900 | fn get_vc_dir(ver: &str) -> Option<PathBuf> { |
901 | let key = r"SOFTWARE\Microsoft\VisualStudio\SxS\VC7" ; |
902 | let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; |
903 | let path = key.query_str(ver).ok()?; |
904 | Some(path.into()) |
905 | } |
906 | |
907 | // To find the Universal CRT we look in a specific registry key for where |
908 | // all the Universal CRTs are located and then sort them asciibetically to |
909 | // find the newest version. While this sort of sorting isn't ideal, it is |
910 | // what vcvars does so that's good enough for us. |
911 | // |
912 | // Returns a pair of (root, version) for the ucrt dir if found |
913 | fn get_ucrt_dir() -> Option<(PathBuf, String)> { |
914 | let key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots" ; |
915 | let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; |
916 | let root = key.query_str("KitsRoot10" ).ok()?; |
917 | let readdir = Path::new(&root).join("lib" ).read_dir().ok()?; |
918 | let max_libdir = readdir |
919 | .filter_map(|dir| dir.ok()) |
920 | .map(|dir| dir.path()) |
921 | .filter(|dir| { |
922 | dir.components() |
923 | .last() |
924 | .and_then(|c| c.as_os_str().to_str()) |
925 | .map(|c| c.starts_with("10." ) && dir.join("ucrt" ).is_dir()) |
926 | .unwrap_or(false) |
927 | }) |
928 | .max()?; |
929 | let version = max_libdir.components().last().unwrap(); |
930 | let version = version.as_os_str().to_str().unwrap().to_string(); |
931 | Some((root.into(), version)) |
932 | } |
933 | |
934 | // Vcvars finds the correct version of the Windows 10 SDK by looking |
935 | // for the include `um\Windows.h` because sometimes a given version will |
936 | // only have UCRT bits without the rest of the SDK. Since we only care about |
937 | // libraries and not includes, we instead look for `um\x64\kernel32.lib`. |
938 | // Since the 32-bit and 64-bit libraries are always installed together we |
939 | // only need to bother checking x64, making this code a tiny bit simpler. |
940 | // Like we do for the Universal CRT, we sort the possibilities |
941 | // asciibetically to find the newest one as that is what vcvars does. |
942 | // Before doing that, we check the "WindowsSdkDir" and "WindowsSDKVersion" |
943 | // environment variables set by vcvars to use the environment sdk version |
944 | // if one is already configured. |
945 | fn get_sdk10_dir(env_getter: &dyn EnvGetter) -> Option<(PathBuf, String)> { |
946 | if let (Some(root), Some(version)) = ( |
947 | env_getter.get_env("WindowsSdkDir" ), |
948 | env_getter |
949 | .get_env("WindowsSDKVersion" ) |
950 | .as_ref() |
951 | .and_then(|version| version.as_ref().to_str()), |
952 | ) { |
953 | return Some(( |
954 | PathBuf::from(root), |
955 | version.trim_end_matches(' \\' ).to_string(), |
956 | )); |
957 | } |
958 | |
959 | let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0" ; |
960 | let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; |
961 | let root = key.query_str("InstallationFolder" ).ok()?; |
962 | let readdir = Path::new(&root).join("lib" ).read_dir().ok()?; |
963 | let mut dirs = readdir |
964 | .filter_map(|dir| dir.ok()) |
965 | .map(|dir| dir.path()) |
966 | .collect::<Vec<_>>(); |
967 | dirs.sort(); |
968 | let dir = dirs |
969 | .into_iter() |
970 | .rev() |
971 | .find(|dir| dir.join("um" ).join("x64" ).join("kernel32.lib" ).is_file())?; |
972 | let version = dir.components().last().unwrap(); |
973 | let version = version.as_os_str().to_str().unwrap().to_string(); |
974 | Some((root.into(), version)) |
975 | } |
976 | |
977 | // Interestingly there are several subdirectories, `win7` `win8` and |
978 | // `winv6.3`. Vcvars seems to only care about `winv6.3` though, so the same |
979 | // applies to us. Note that if we were targeting kernel mode drivers |
980 | // instead of user mode applications, we would care. |
981 | fn get_sdk81_dir() -> Option<PathBuf> { |
982 | let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1" ; |
983 | let key = LOCAL_MACHINE.open(key.as_ref()).ok()?; |
984 | let root = key.query_str("InstallationFolder" ).ok()?; |
985 | Some(root.into()) |
986 | } |
987 | |
988 | const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0; |
989 | const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9; |
990 | const PROCESSOR_ARCHITECTURE_ARM64: u16 = 12; |
991 | const X86: u16 = PROCESSOR_ARCHITECTURE_INTEL; |
992 | const X86_64: u16 = PROCESSOR_ARCHITECTURE_AMD64; |
993 | const AARCH64: u16 = PROCESSOR_ARCHITECTURE_ARM64; |
994 | |
995 | // When choosing the tool to use, we have to choose the one which matches |
996 | // the target architecture. Otherwise we end up in situations where someone |
997 | // on 32-bit Windows is trying to cross compile to 64-bit and it tries to |
998 | // invoke the native 64-bit compiler which won't work. |
999 | // |
1000 | // For the return value of this function, the first member of the tuple is |
1001 | // the folder of the tool we will be invoking, while the second member is |
1002 | // the folder of the host toolchain for that tool which is essential when |
1003 | // using a cross linker. We return a Vec since on x64 there are often two |
1004 | // linkers that can target the architecture we desire. The 64-bit host |
1005 | // linker is preferred, and hence first, due to 64-bit allowing it more |
1006 | // address space to work with and potentially being faster. |
1007 | fn bin_subdir(target: TargetArch) -> Vec<(&'static str, &'static str)> { |
1008 | match (target, host_arch()) { |
1009 | (TargetArch::X86, X86) => vec![("" , "" )], |
1010 | (TargetArch::X86, X86_64) => vec![("amd64_x86" , "amd64" ), ("" , "" )], |
1011 | (TargetArch::X64, X86) => vec![("x86_amd64" , "" )], |
1012 | (TargetArch::X64, X86_64) => vec![("amd64" , "amd64" ), ("x86_amd64" , "" )], |
1013 | (TargetArch::Arm, X86) => vec![("x86_arm" , "" )], |
1014 | (TargetArch::Arm, X86_64) => vec![("amd64_arm" , "amd64" ), ("x86_arm" , "" )], |
1015 | _ => vec![], |
1016 | } |
1017 | } |
1018 | |
1019 | // MSVC's x86 libraries are not in a subfolder |
1020 | fn vc_lib_subdir(target: TargetArch) -> &'static str { |
1021 | match target { |
1022 | TargetArch::X86 => "" , |
1023 | TargetArch::X64 => "amd64" , |
1024 | TargetArch::Arm => "arm" , |
1025 | TargetArch::Arm64 | TargetArch::Arm64ec => "arm64" , |
1026 | } |
1027 | } |
1028 | |
1029 | #[allow (bad_style)] |
1030 | fn host_arch() -> u16 { |
1031 | type DWORD = u32; |
1032 | type WORD = u16; |
1033 | type LPVOID = *mut u8; |
1034 | type DWORD_PTR = usize; |
1035 | |
1036 | #[repr (C)] |
1037 | struct SYSTEM_INFO { |
1038 | wProcessorArchitecture: WORD, |
1039 | _wReserved: WORD, |
1040 | _dwPageSize: DWORD, |
1041 | _lpMinimumApplicationAddress: LPVOID, |
1042 | _lpMaximumApplicationAddress: LPVOID, |
1043 | _dwActiveProcessorMask: DWORD_PTR, |
1044 | _dwNumberOfProcessors: DWORD, |
1045 | _dwProcessorType: DWORD, |
1046 | _dwAllocationGranularity: DWORD, |
1047 | _wProcessorLevel: WORD, |
1048 | _wProcessorRevision: WORD, |
1049 | } |
1050 | |
1051 | extern "system" { |
1052 | fn GetNativeSystemInfo(lpSystemInfo: *mut SYSTEM_INFO); |
1053 | } |
1054 | |
1055 | unsafe { |
1056 | let mut info = mem::zeroed(); |
1057 | GetNativeSystemInfo(&mut info); |
1058 | info.wProcessorArchitecture |
1059 | } |
1060 | } |
1061 | |
1062 | // Given a registry key, look at all the sub keys and find the one which has |
1063 | // the maximal numeric value. |
1064 | // |
1065 | // Returns the name of the maximal key as well as the opened maximal key. |
1066 | fn max_version(key: &RegistryKey) -> Option<(OsString, RegistryKey)> { |
1067 | let mut max_vers = 0; |
1068 | let mut max_key = None; |
1069 | for subkey in key.iter().filter_map(|k| k.ok()) { |
1070 | let val = subkey |
1071 | .to_str() |
1072 | .and_then(|s| s.trim_start_matches('v' ).replace('.' , "" ).parse().ok()); |
1073 | let val = match val { |
1074 | Some(s) => s, |
1075 | None => continue, |
1076 | }; |
1077 | if val > max_vers { |
1078 | if let Ok(k) = key.open(&subkey) { |
1079 | max_vers = val; |
1080 | max_key = Some((subkey, k)); |
1081 | } |
1082 | } |
1083 | } |
1084 | max_key |
1085 | } |
1086 | |
1087 | #[inline (always)] |
1088 | pub(super) fn has_msbuild_version(version: &str, env_getter: &dyn EnvGetter) -> bool { |
1089 | match version { |
1090 | "17.0" => { |
1091 | find_msbuild_vs17(TargetArch::X64, env_getter).is_some() |
1092 | || find_msbuild_vs17(TargetArch::X86, env_getter).is_some() |
1093 | || find_msbuild_vs17(TargetArch::Arm64, env_getter).is_some() |
1094 | } |
1095 | "16.0" => { |
1096 | find_msbuild_vs16(TargetArch::X64, env_getter).is_some() |
1097 | || find_msbuild_vs16(TargetArch::X86, env_getter).is_some() |
1098 | || find_msbuild_vs16(TargetArch::Arm64, env_getter).is_some() |
1099 | } |
1100 | "15.0" => { |
1101 | find_msbuild_vs15(TargetArch::X64, env_getter).is_some() |
1102 | || find_msbuild_vs15(TargetArch::X86, env_getter).is_some() |
1103 | || find_msbuild_vs15(TargetArch::Arm64, env_getter).is_some() |
1104 | } |
1105 | "14.0" => LOCAL_MACHINE |
1106 | .open(&OsString::from(format!( |
1107 | "SOFTWARE \\Microsoft \\MSBuild \\ToolsVersions \\{}" , |
1108 | version |
1109 | ))) |
1110 | .is_ok(), |
1111 | _ => false, |
1112 | } |
1113 | } |
1114 | |
1115 | pub(super) fn find_devenv(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> { |
1116 | find_devenv_vs15(target, env_getter) |
1117 | } |
1118 | |
1119 | fn find_devenv_vs15(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> { |
1120 | find_tool_in_vs15_path(r"Common7\IDE\devenv.exe" , target, env_getter) |
1121 | } |
1122 | |
1123 | // see http://stackoverflow.com/questions/328017/path-to-msbuild |
1124 | pub(super) fn find_msbuild(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> { |
1125 | // VS 15 (2017) changed how to locate msbuild |
1126 | if let Some(r) = find_msbuild_vs17(target, env_getter) { |
1127 | Some(r) |
1128 | } else if let Some(r) = find_msbuild_vs16(target, env_getter) { |
1129 | return Some(r); |
1130 | } else if let Some(r) = find_msbuild_vs15(target, env_getter) { |
1131 | return Some(r); |
1132 | } else { |
1133 | find_old_msbuild(target) |
1134 | } |
1135 | } |
1136 | |
1137 | fn find_msbuild_vs15(target: TargetArch, env_getter: &dyn EnvGetter) -> Option<Tool> { |
1138 | find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe" , target, env_getter) |
1139 | } |
1140 | |
1141 | fn find_old_msbuild(target: TargetArch) -> Option<Tool> { |
1142 | let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions" ; |
1143 | LOCAL_MACHINE |
1144 | .open(key.as_ref()) |
1145 | .ok() |
1146 | .and_then(|key| { |
1147 | max_version(&key).and_then(|(_vers, key)| key.query_str("MSBuildToolsPath" ).ok()) |
1148 | }) |
1149 | .map(|path| { |
1150 | let mut path = PathBuf::from(path); |
1151 | path.push("MSBuild.exe" ); |
1152 | let mut tool = Tool::with_family(path, MSVC_FAMILY); |
1153 | if target == TargetArch::X64 { |
1154 | tool.env.push(("Platform" .into(), "X64" .into())); |
1155 | } |
1156 | tool |
1157 | }) |
1158 | } |
1159 | } |
1160 | |
1161 | /// Non-Windows Implementation. |
1162 | #[cfg (not(windows))] |
1163 | mod impl_ { |
1164 | use std::{env, ffi::OsStr}; |
1165 | |
1166 | use super::{EnvGetter, TargetArch, MSVC_FAMILY}; |
1167 | use crate::Tool; |
1168 | |
1169 | /// Finding msbuild.exe tool under unix system is not currently supported. |
1170 | /// Maybe can check it using an environment variable looks like `MSBUILD_BIN`. |
1171 | #[inline (always)] |
1172 | pub(super) fn find_msbuild(_target: TargetArch, _: &dyn EnvGetter) -> Option<Tool> { |
1173 | None |
1174 | } |
1175 | |
1176 | // Finding devenv.exe tool under unix system is not currently supported. |
1177 | // Maybe can check it using an environment variable looks like `DEVENV_BIN`. |
1178 | #[inline (always)] |
1179 | pub(super) fn find_devenv(_target: TargetArch, _: &dyn EnvGetter) -> Option<Tool> { |
1180 | None |
1181 | } |
1182 | |
1183 | /// Attempt to find the tool using environment variables set by vcvars. |
1184 | pub(super) fn find_msvc_environment( |
1185 | tool: &str, |
1186 | _target: TargetArch, |
1187 | env_getter: &dyn EnvGetter, |
1188 | ) -> Option<Tool> { |
1189 | // Early return if the environment doesn't contain a VC install. |
1190 | let vc_install_dir = env_getter.get_env("VCINSTALLDIR" )?; |
1191 | let vs_install_dir = env_getter.get_env("VSINSTALLDIR" )?; |
1192 | |
1193 | let get_tool = |install_dir: &OsStr| { |
1194 | env::split_paths(install_dir) |
1195 | .map(|p| p.join(tool)) |
1196 | .find(|p| p.exists()) |
1197 | .map(|path| Tool::with_family(path, MSVC_FAMILY)) |
1198 | }; |
1199 | |
1200 | // Take the path of tool for the vc install directory. |
1201 | get_tool(vc_install_dir.as_ref()) |
1202 | // Take the path of tool for the vs install directory. |
1203 | .or_else(|| get_tool(vs_install_dir.as_ref())) |
1204 | // Take the path of tool for the current path environment. |
1205 | .or_else(|| { |
1206 | env_getter |
1207 | .get_env("PATH" ) |
1208 | .as_ref() |
1209 | .map(|path| path.as_ref()) |
1210 | .and_then(get_tool) |
1211 | }) |
1212 | } |
1213 | |
1214 | #[inline (always)] |
1215 | pub(super) fn find_msvc_15plus( |
1216 | _tool: &str, |
1217 | _target: TargetArch, |
1218 | _: &dyn EnvGetter, |
1219 | ) -> Option<Tool> { |
1220 | None |
1221 | } |
1222 | |
1223 | // For MSVC 14 we need to find the Universal CRT as well as either |
1224 | // the Windows 10 SDK or Windows 8.1 SDK. |
1225 | #[inline (always)] |
1226 | pub(super) fn find_msvc_14( |
1227 | _tool: &str, |
1228 | _target: TargetArch, |
1229 | _: &dyn EnvGetter, |
1230 | ) -> Option<Tool> { |
1231 | None |
1232 | } |
1233 | |
1234 | #[inline (always)] |
1235 | pub(super) fn has_msbuild_version(_version: &str, _: &dyn EnvGetter) -> bool { |
1236 | false |
1237 | } |
1238 | } |
1239 | |