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