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 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://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 probe the Windows Registry when looking for
12 //! windows-specific tools.
13
14 use std::process::Command;
15
16 use crate::Tool;
17 #[cfg(windows)]
18 use crate::ToolFamily;
19
20 #[cfg(windows)]
21 const MSVC_FAMILY: ToolFamily = ToolFamily::Msvc { clang_cl: false };
22
23 /// Attempts to find a tool within an MSVC installation using the Windows
24 /// registry as a point to search from.
25 ///
26 /// The `target` argument is the target that the tool should work for (e.g.
27 /// compile or link for) and the `tool` argument is the tool to find (e.g.
28 /// `cl.exe` or `link.exe`).
29 ///
30 /// This function will return `None` if the tool could not be found, or it will
31 /// return `Some(cmd)` which represents a command that's ready to execute the
32 /// tool with the appropriate environment variables set.
33 ///
34 /// Note that this function always returns `None` for non-MSVC targets.
find(target: &str, tool: &str) -> Option<Command>35 pub fn find(target: &str, tool: &str) -> Option<Command> {
36 find_tool(target, tool).map(|c| c.to_command())
37 }
38
39 /// Similar to the `find` function above, this function will attempt the same
40 /// operation (finding a MSVC tool in a local install) but instead returns a
41 /// `Tool` which may be introspected.
42 #[cfg(not(windows))]
find_tool(_target: &str, _tool: &str) -> Option<Tool>43 pub fn find_tool(_target: &str, _tool: &str) -> Option<Tool> {
44 None
45 }
46
47 /// Documented above.
48 #[cfg(windows)]
find_tool(target: &str, tool: &str) -> Option<Tool>49 pub fn find_tool(target: &str, tool: &str) -> Option<Tool> {
50 use std::env;
51
52 // This logic is all tailored for MSVC, if we're not that then bail out
53 // early.
54 if !target.contains("msvc") {
55 return None;
56 }
57
58 // Looks like msbuild isn't located in the same location as other tools like
59 // cl.exe and lib.exe. To handle this we probe for it manually with
60 // dedicated registry keys.
61 if tool.contains("msbuild") {
62 return impl_::find_msbuild(target);
63 }
64
65 if tool.contains("devenv") {
66 return impl_::find_devenv(target);
67 }
68
69 // If VCINSTALLDIR is set, then someone's probably already run vcvars and we
70 // should just find whatever that indicates.
71 if env::var_os("VCINSTALLDIR").is_some() {
72 return env::var_os("PATH")
73 .and_then(|path| {
74 env::split_paths(&path)
75 .map(|p| p.join(tool))
76 .find(|p| p.exists())
77 })
78 .map(|path| Tool::with_family(path.into(), MSVC_FAMILY));
79 }
80
81 // Ok, if we're here, now comes the fun part of the probing. Default shells
82 // or shells like MSYS aren't really configured to execute `cl.exe` and the
83 // various compiler tools shipped as part of Visual Studio. Here we try to
84 // first find the relevant tool, then we also have to be sure to fill in
85 // environment variables like `LIB`, `INCLUDE`, and `PATH` to ensure that
86 // the tool is actually usable.
87
88 return impl_::find_msvc_15plus(tool, target)
89 .or_else(|| impl_::find_msvc_14(tool, target))
90 .or_else(|| impl_::find_msvc_12(tool, target))
91 .or_else(|| impl_::find_msvc_11(tool, target));
92 }
93
94 /// A version of Visual Studio
95 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
96 pub enum VsVers {
97 /// Visual Studio 12 (2013)
98 Vs12,
99 /// Visual Studio 14 (2015)
100 Vs14,
101 /// Visual Studio 15 (2017)
102 Vs15,
103 /// Visual Studio 16 (2019)
104 Vs16,
105
106 /// Hidden variant that should not be matched on. Callers that want to
107 /// handle an enumeration of `VsVers` instances should always have a default
108 /// case meaning that it's a VS version they don't understand.
109 #[doc(hidden)]
110 #[allow(bad_style)]
111 __Nonexhaustive_do_not_match_this_or_your_code_will_break,
112 }
113
114 /// Find the most recent installed version of Visual Studio
115 ///
116 /// This is used by the cmake crate to figure out the correct
117 /// generator.
118 #[cfg(not(windows))]
find_vs_version() -> Result<VsVers, String>119 pub fn find_vs_version() -> Result<VsVers, String> {
120 Err(format!("not windows"))
121 }
122
123 /// Documented above
124 #[cfg(windows)]
find_vs_version() -> Result<VsVers, String>125 pub fn find_vs_version() -> Result<VsVers, String> {
126 use std::env;
127
128 match env::var("VisualStudioVersion") {
129 Ok(version) => match &version[..] {
130 "16.0" => Ok(VsVers::Vs16),
131 "15.0" => Ok(VsVers::Vs15),
132 "14.0" => Ok(VsVers::Vs14),
133 "12.0" => Ok(VsVers::Vs12),
134 vers => Err(format!(
135 "\n\n\
136 unsupported or unknown VisualStudio version: {}\n\
137 if another version is installed consider running \
138 the appropriate vcvars script before building this \
139 crate\n\
140 ",
141 vers
142 )),
143 },
144 _ => {
145 // Check for the presense of a specific registry key
146 // that indicates visual studio is installed.
147 if impl_::has_msbuild_version("16.0") {
148 Ok(VsVers::Vs16)
149 } else if impl_::has_msbuild_version("15.0") {
150 Ok(VsVers::Vs15)
151 } else if impl_::has_msbuild_version("14.0") {
152 Ok(VsVers::Vs14)
153 } else if impl_::has_msbuild_version("12.0") {
154 Ok(VsVers::Vs12)
155 } else {
156 Err(format!(
157 "\n\n\
158 couldn't determine visual studio generator\n\
159 if VisualStudio is installed, however, consider \
160 running the appropriate vcvars script before building \
161 this crate\n\
162 "
163 ))
164 }
165 }
166 }
167 }
168
169 #[cfg(windows)]
170 mod impl_ {
171 use crate::com;
172 use crate::registry::{RegistryKey, LOCAL_MACHINE};
173 use crate::setup_config::{EnumSetupInstances, SetupConfiguration, SetupInstance};
174 use std::env;
175 use std::ffi::OsString;
176 use std::fs::File;
177 use std::io::Read;
178 use std::iter;
179 use std::mem;
180 use std::path::{Path, PathBuf};
181 use std::str::FromStr;
182
183 use super::MSVC_FAMILY;
184 use crate::Tool;
185
186 struct MsvcTool {
187 tool: PathBuf,
188 libs: Vec<PathBuf>,
189 path: Vec<PathBuf>,
190 include: Vec<PathBuf>,
191 }
192
193 impl MsvcTool {
new(tool: PathBuf) -> MsvcTool194 fn new(tool: PathBuf) -> MsvcTool {
195 MsvcTool {
196 tool: tool,
197 libs: Vec::new(),
198 path: Vec::new(),
199 include: Vec::new(),
200 }
201 }
202
into_tool(self) -> Tool203 fn into_tool(self) -> Tool {
204 let MsvcTool {
205 tool,
206 libs,
207 path,
208 include,
209 } = self;
210 let mut tool = Tool::with_family(tool.into(), MSVC_FAMILY);
211 add_env(&mut tool, "LIB", libs);
212 add_env(&mut tool, "PATH", path);
213 add_env(&mut tool, "INCLUDE", include);
214 tool
215 }
216 }
217
218 #[allow(bare_trait_objects)]
vs16_instances() -> Box<Iterator<Item = PathBuf>>219 fn vs16_instances() -> Box<Iterator<Item = PathBuf>> {
220 let instances = if let Some(instances) = vs15plus_instances() {
221 instances
222 } else {
223 return Box::new(iter::empty());
224 };
225 Box::new(instances.filter_map(|instance| {
226 let instance = instance.ok()?;
227 let installation_name = instance.installation_name().ok()?;
228 if installation_name.to_str()?.starts_with("VisualStudio/16.") {
229 Some(PathBuf::from(instance.installation_path().ok()?))
230 } else if installation_name
231 .to_str()?
232 .starts_with("VisualStudioPreview/16.")
233 {
234 Some(PathBuf::from(instance.installation_path().ok()?))
235 } else {
236 None
237 }
238 }))
239 }
240
find_tool_in_vs16_path(tool: &str, target: &str) -> Option<Tool>241 fn find_tool_in_vs16_path(tool: &str, target: &str) -> Option<Tool> {
242 vs16_instances()
243 .filter_map(|path| {
244 let path = path.join(tool);
245 if !path.is_file() {
246 return None;
247 }
248 let mut tool = Tool::with_family(path, MSVC_FAMILY);
249 if target.contains("x86_64") {
250 tool.env.push(("Platform".into(), "X64".into()));
251 }
252 Some(tool)
253 })
254 .next()
255 }
256
find_msbuild_vs16(target: &str) -> Option<Tool>257 fn find_msbuild_vs16(target: &str) -> Option<Tool> {
258 find_tool_in_vs16_path(r"MSBuild\Current\Bin\MSBuild.exe", target)
259 }
260
261 // In MSVC 15 (2017) MS once again changed the scheme for locating
262 // the tooling. Now we must go through some COM interfaces, which
263 // is super fun for Rust.
264 //
265 // Note that much of this logic can be found [online] wrt paths, COM, etc.
266 //
267 // [online]: https://blogs.msdn.microsoft.com/vcblog/2017/03/06/finding-the-visual-c-compiler-tools-in-visual-studio-2017/
268 //
269 // Returns MSVC 15+ instances (15, 16 right now), the order should be consider undefined.
vs15plus_instances() -> Option<EnumSetupInstances>270 fn vs15plus_instances() -> Option<EnumSetupInstances> {
271 com::initialize().ok()?;
272
273 let config = SetupConfiguration::new().ok()?;
274 config.enum_all_instances().ok()
275 }
276
277 // Inspired from official microsoft/vswhere ParseVersionString
278 // i.e. at most four u16 numbers separated by '.'
parse_version(version: &str) -> Option<Vec<u16>>279 fn parse_version(version: &str) -> Option<Vec<u16>> {
280 version
281 .split('.')
282 .map(|chunk| u16::from_str(chunk).ok())
283 .collect()
284 }
285
find_msvc_15plus(tool: &str, target: &str) -> Option<Tool>286 pub fn find_msvc_15plus(tool: &str, target: &str) -> Option<Tool> {
287 let iter = vs15plus_instances()?;
288 iter.filter_map(|instance| {
289 let instance = instance.ok()?;
290 let version = parse_version(instance.installation_version().ok()?.to_str()?)?;
291 let tool = tool_from_vs15plus_instance(tool, target, &instance)?;
292 Some((version, tool))
293 })
294 .max_by(|(a_version, _), (b_version, _)| a_version.cmp(b_version))
295 .map(|(_version, tool)| tool)
296 }
297
298 // While the paths to Visual Studio 2017's devenv and MSBuild could
299 // potentially be retrieved from the registry, finding them via
300 // SetupConfiguration has shown to be [more reliable], and is preferred
301 // according to Microsoft. To help head off potential regressions though,
302 // we keep the registry method as a fallback option.
303 //
304 // [more reliable]: https://github.com/alexcrichton/cc-rs/pull/331
find_tool_in_vs15_path(tool: &str, target: &str) -> Option<Tool>305 fn find_tool_in_vs15_path(tool: &str, target: &str) -> Option<Tool> {
306 let mut path = match vs15plus_instances() {
307 Some(instances) => instances
308 .filter_map(|instance| {
309 instance
310 .ok()
311 .and_then(|instance| instance.installation_path().ok())
312 })
313 .map(|path| PathBuf::from(path).join(tool))
314 .find(|ref path| path.is_file()),
315 None => None,
316 };
317
318 if path.is_none() {
319 let key = r"SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7";
320 path = LOCAL_MACHINE
321 .open(key.as_ref())
322 .ok()
323 .and_then(|key| key.query_str("15.0").ok())
324 .map(|path| PathBuf::from(path).join(tool))
325 .and_then(|path| if path.is_file() { Some(path) } else { None });
326 }
327
328 path.map(|path| {
329 let mut tool = Tool::with_family(path, MSVC_FAMILY);
330 if target.contains("x86_64") {
331 tool.env.push(("Platform".into(), "X64".into()));
332 }
333 tool
334 })
335 }
336
tool_from_vs15plus_instance( tool: &str, target: &str, instance: &SetupInstance, ) -> Option<Tool>337 fn tool_from_vs15plus_instance(
338 tool: &str,
339 target: &str,
340 instance: &SetupInstance,
341 ) -> Option<Tool> {
342 let (bin_path, host_dylib_path, lib_path, include_path) =
343 vs15plus_vc_paths(target, instance)?;
344 let tool_path = bin_path.join(tool);
345 if !tool_path.exists() {
346 return None;
347 };
348
349 let mut tool = MsvcTool::new(tool_path);
350 tool.path.push(bin_path.clone());
351 tool.path.push(host_dylib_path);
352 tool.libs.push(lib_path);
353 tool.include.push(include_path);
354
355 if let Some((atl_lib_path, atl_include_path)) = atl_paths(target, &bin_path) {
356 tool.libs.push(atl_lib_path);
357 tool.include.push(atl_include_path);
358 }
359
360 add_sdks(&mut tool, target)?;
361
362 Some(tool.into_tool())
363 }
364
vs15plus_vc_paths( target: &str, instance: &SetupInstance, ) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf)>365 fn vs15plus_vc_paths(
366 target: &str,
367 instance: &SetupInstance,
368 ) -> Option<(PathBuf, PathBuf, PathBuf, PathBuf)> {
369 let instance_path: PathBuf = instance.installation_path().ok()?.into();
370 let version_path =
371 instance_path.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt");
372 let mut version_file = File::open(version_path).ok()?;
373 let mut version = String::new();
374 version_file.read_to_string(&mut version).ok()?;
375 let version = version.trim();
376 let host = match host_arch() {
377 X86 => "X86",
378 X86_64 => "X64",
379 // There is no natively hosted compiler on ARM64.
380 // Instead, use the x86 toolchain under emulation (there is no x64 emulation).
381 AARCH64 => "X86",
382 _ => return None,
383 };
384 let target = lib_subdir(target)?;
385 // The directory layout here is MSVC/bin/Host$host/$target/
386 let path = instance_path.join(r"VC\Tools\MSVC").join(version);
387 // This is the path to the toolchain for a particular target, running
388 // on a given host
389 let bin_path = path
390 .join("bin")
391 .join(&format!("Host{}", host))
392 .join(&target);
393 // But! we also need PATH to contain the target directory for the host
394 // architecture, because it contains dlls like mspdb140.dll compiled for
395 // the host architecture.
396 let host_dylib_path = path
397 .join("bin")
398 .join(&format!("Host{}", host))
399 .join(&host.to_lowercase());
400 let lib_path = path.join("lib").join(&target);
401 let include_path = path.join("include");
402 Some((bin_path, host_dylib_path, lib_path, include_path))
403 }
404
atl_paths(target: &str, path: &Path) -> Option<(PathBuf, PathBuf)>405 fn atl_paths(target: &str, path: &Path) -> Option<(PathBuf, PathBuf)> {
406 let atl_path = path.join("atlfmc");
407 let sub = lib_subdir(target)?;
408 if atl_path.exists() {
409 Some((atl_path.join("lib").join(sub), atl_path.join("include")))
410 } else {
411 None
412 }
413 }
414
415 // For MSVC 14 we need to find the Universal CRT as well as either
416 // the Windows 10 SDK or Windows 8.1 SDK.
find_msvc_14(tool: &str, target: &str) -> Option<Tool>417 pub fn find_msvc_14(tool: &str, target: &str) -> Option<Tool> {
418 let vcdir = get_vc_dir("14.0")?;
419 let mut tool = get_tool(tool, &vcdir, target)?;
420 add_sdks(&mut tool, target)?;
421 Some(tool.into_tool())
422 }
423
add_sdks(tool: &mut MsvcTool, target: &str) -> Option<()>424 fn add_sdks(tool: &mut MsvcTool, target: &str) -> Option<()> {
425 let sub = lib_subdir(target)?;
426 let (ucrt, ucrt_version) = get_ucrt_dir()?;
427
428 let host = match host_arch() {
429 X86 => "x86",
430 X86_64 => "x64",
431 AARCH64 => "arm64",
432 _ => return None,
433 };
434
435 tool.path
436 .push(ucrt.join("bin").join(&ucrt_version).join(host));
437
438 let ucrt_include = ucrt.join("include").join(&ucrt_version);
439 tool.include.push(ucrt_include.join("ucrt"));
440
441 let ucrt_lib = ucrt.join("lib").join(&ucrt_version);
442 tool.libs.push(ucrt_lib.join("ucrt").join(sub));
443
444 if let Some((sdk, version)) = get_sdk10_dir() {
445 tool.path.push(sdk.join("bin").join(host));
446 let sdk_lib = sdk.join("lib").join(&version);
447 tool.libs.push(sdk_lib.join("um").join(sub));
448 let sdk_include = sdk.join("include").join(&version);
449 tool.include.push(sdk_include.join("um"));
450 tool.include.push(sdk_include.join("cppwinrt"));
451 tool.include.push(sdk_include.join("winrt"));
452 tool.include.push(sdk_include.join("shared"));
453 } else if let Some(sdk) = get_sdk81_dir() {
454 tool.path.push(sdk.join("bin").join(host));
455 let sdk_lib = sdk.join("lib").join("winv6.3");
456 tool.libs.push(sdk_lib.join("um").join(sub));
457 let sdk_include = sdk.join("include");
458 tool.include.push(sdk_include.join("um"));
459 tool.include.push(sdk_include.join("winrt"));
460 tool.include.push(sdk_include.join("shared"));
461 }
462
463 Some(())
464 }
465
466 // For MSVC 12 we need to find the Windows 8.1 SDK.
find_msvc_12(tool: &str, target: &str) -> Option<Tool>467 pub fn find_msvc_12(tool: &str, target: &str) -> Option<Tool> {
468 let vcdir = get_vc_dir("12.0")?;
469 let mut tool = get_tool(tool, &vcdir, target)?;
470 let sub = lib_subdir(target)?;
471 let sdk81 = get_sdk81_dir()?;
472 tool.path.push(sdk81.join("bin").join(sub));
473 let sdk_lib = sdk81.join("lib").join("winv6.3");
474 tool.libs.push(sdk_lib.join("um").join(sub));
475 let sdk_include = sdk81.join("include");
476 tool.include.push(sdk_include.join("shared"));
477 tool.include.push(sdk_include.join("um"));
478 tool.include.push(sdk_include.join("winrt"));
479 Some(tool.into_tool())
480 }
481
482 // For MSVC 11 we need to find the Windows 8 SDK.
find_msvc_11(tool: &str, target: &str) -> Option<Tool>483 pub fn find_msvc_11(tool: &str, target: &str) -> Option<Tool> {
484 let vcdir = get_vc_dir("11.0")?;
485 let mut tool = get_tool(tool, &vcdir, target)?;
486 let sub = lib_subdir(target)?;
487 let sdk8 = get_sdk8_dir()?;
488 tool.path.push(sdk8.join("bin").join(sub));
489 let sdk_lib = sdk8.join("lib").join("win8");
490 tool.libs.push(sdk_lib.join("um").join(sub));
491 let sdk_include = sdk8.join("include");
492 tool.include.push(sdk_include.join("shared"));
493 tool.include.push(sdk_include.join("um"));
494 tool.include.push(sdk_include.join("winrt"));
495 Some(tool.into_tool())
496 }
497
add_env(tool: &mut Tool, env: &str, paths: Vec<PathBuf>)498 fn add_env(tool: &mut Tool, env: &str, paths: Vec<PathBuf>) {
499 let prev = env::var_os(env).unwrap_or(OsString::new());
500 let prev = env::split_paths(&prev);
501 let new = paths.into_iter().chain(prev);
502 tool.env
503 .push((env.to_string().into(), env::join_paths(new).unwrap()));
504 }
505
506 // Given a possible MSVC installation directory, we look for the linker and
507 // then add the MSVC library path.
get_tool(tool: &str, path: &Path, target: &str) -> Option<MsvcTool>508 fn get_tool(tool: &str, path: &Path, target: &str) -> Option<MsvcTool> {
509 bin_subdir(target)
510 .into_iter()
511 .map(|(sub, host)| {
512 (
513 path.join("bin").join(sub).join(tool),
514 path.join("bin").join(host),
515 )
516 })
517 .filter(|&(ref path, _)| path.is_file())
518 .map(|(path, host)| {
519 let mut tool = MsvcTool::new(path);
520 tool.path.push(host);
521 tool
522 })
523 .filter_map(|mut tool| {
524 let sub = vc_lib_subdir(target)?;
525 tool.libs.push(path.join("lib").join(sub));
526 tool.include.push(path.join("include"));
527 let atlmfc_path = path.join("atlmfc");
528 if atlmfc_path.exists() {
529 tool.libs.push(atlmfc_path.join("lib").join(sub));
530 tool.include.push(atlmfc_path.join("include"));
531 }
532 Some(tool)
533 })
534 .next()
535 }
536
537 // To find MSVC we look in a specific registry key for the version we are
538 // trying to find.
get_vc_dir(ver: &str) -> Option<PathBuf>539 fn get_vc_dir(ver: &str) -> Option<PathBuf> {
540 let key = r"SOFTWARE\Microsoft\VisualStudio\SxS\VC7";
541 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
542 let path = key.query_str(ver).ok()?;
543 Some(path.into())
544 }
545
546 // To find the Universal CRT we look in a specific registry key for where
547 // all the Universal CRTs are located and then sort them asciibetically to
548 // find the newest version. While this sort of sorting isn't ideal, it is
549 // what vcvars does so that's good enough for us.
550 //
551 // Returns a pair of (root, version) for the ucrt dir if found
get_ucrt_dir() -> Option<(PathBuf, String)>552 fn get_ucrt_dir() -> Option<(PathBuf, String)> {
553 let key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots";
554 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
555 let root = key.query_str("KitsRoot10").ok()?;
556 let readdir = Path::new(&root).join("lib").read_dir().ok()?;
557 let max_libdir = readdir
558 .filter_map(|dir| dir.ok())
559 .map(|dir| dir.path())
560 .filter(|dir| {
561 dir.components()
562 .last()
563 .and_then(|c| c.as_os_str().to_str())
564 .map(|c| c.starts_with("10.") && dir.join("ucrt").is_dir())
565 .unwrap_or(false)
566 })
567 .max()?;
568 let version = max_libdir.components().last().unwrap();
569 let version = version.as_os_str().to_str().unwrap().to_string();
570 Some((root.into(), version))
571 }
572
573 // Vcvars finds the correct version of the Windows 10 SDK by looking
574 // for the include `um\Windows.h` because sometimes a given version will
575 // only have UCRT bits without the rest of the SDK. Since we only care about
576 // libraries and not includes, we instead look for `um\x64\kernel32.lib`.
577 // Since the 32-bit and 64-bit libraries are always installed together we
578 // only need to bother checking x64, making this code a tiny bit simpler.
579 // Like we do for the Universal CRT, we sort the possibilities
580 // asciibetically to find the newest one as that is what vcvars does.
get_sdk10_dir() -> Option<(PathBuf, String)>581 fn get_sdk10_dir() -> Option<(PathBuf, String)> {
582 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0";
583 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
584 let root = key.query_str("InstallationFolder").ok()?;
585 let readdir = Path::new(&root).join("lib").read_dir().ok()?;
586 let mut dirs = readdir
587 .filter_map(|dir| dir.ok())
588 .map(|dir| dir.path())
589 .collect::<Vec<_>>();
590 dirs.sort();
591 let dir = dirs
592 .into_iter()
593 .rev()
594 .filter(|dir| dir.join("um").join("x64").join("kernel32.lib").is_file())
595 .next()?;
596 let version = dir.components().last().unwrap();
597 let version = version.as_os_str().to_str().unwrap().to_string();
598 Some((root.into(), version))
599 }
600
601 // Interestingly there are several subdirectories, `win7` `win8` and
602 // `winv6.3`. Vcvars seems to only care about `winv6.3` though, so the same
603 // applies to us. Note that if we were targetting kernel mode drivers
604 // instead of user mode applications, we would care.
get_sdk81_dir() -> Option<PathBuf>605 fn get_sdk81_dir() -> Option<PathBuf> {
606 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1";
607 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
608 let root = key.query_str("InstallationFolder").ok()?;
609 Some(root.into())
610 }
611
get_sdk8_dir() -> Option<PathBuf>612 fn get_sdk8_dir() -> Option<PathBuf> {
613 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.0";
614 let key = LOCAL_MACHINE.open(key.as_ref()).ok()?;
615 let root = key.query_str("InstallationFolder").ok()?;
616 Some(root.into())
617 }
618
619 const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0;
620 const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9;
621 const PROCESSOR_ARCHITECTURE_ARM64: u16 = 12;
622 const X86: u16 = PROCESSOR_ARCHITECTURE_INTEL;
623 const X86_64: u16 = PROCESSOR_ARCHITECTURE_AMD64;
624 const AARCH64: u16 = PROCESSOR_ARCHITECTURE_ARM64;
625
626 // When choosing the tool to use, we have to choose the one which matches
627 // the target architecture. Otherwise we end up in situations where someone
628 // on 32-bit Windows is trying to cross compile to 64-bit and it tries to
629 // invoke the native 64-bit compiler which won't work.
630 //
631 // For the return value of this function, the first member of the tuple is
632 // the folder of the tool we will be invoking, while the second member is
633 // the folder of the host toolchain for that tool which is essential when
634 // using a cross linker. We return a Vec since on x64 there are often two
635 // linkers that can target the architecture we desire. The 64-bit host
636 // linker is preferred, and hence first, due to 64-bit allowing it more
637 // address space to work with and potentially being faster.
bin_subdir(target: &str) -> Vec<(&'static str, &'static str)>638 fn bin_subdir(target: &str) -> Vec<(&'static str, &'static str)> {
639 let arch = target.split('-').next().unwrap();
640 match (arch, host_arch()) {
641 ("i586", X86) | ("i686", X86) => vec![("", "")],
642 ("i586", X86_64) | ("i686", X86_64) => vec![("amd64_x86", "amd64"), ("", "")],
643 ("x86_64", X86) => vec![("x86_amd64", "")],
644 ("x86_64", X86_64) => vec![("amd64", "amd64"), ("x86_amd64", "")],
645 ("arm", X86) | ("thumbv7a", X86) => vec![("x86_arm", "")],
646 ("arm", X86_64) | ("thumbv7a", X86_64) => vec![("amd64_arm", "amd64"), ("x86_arm", "")],
647 _ => vec![],
648 }
649 }
650
lib_subdir(target: &str) -> Option<&'static str>651 fn lib_subdir(target: &str) -> Option<&'static str> {
652 let arch = target.split('-').next().unwrap();
653 match arch {
654 "i586" | "i686" => Some("x86"),
655 "x86_64" => Some("x64"),
656 "arm" | "thumbv7a" => Some("arm"),
657 "aarch64" => Some("arm64"),
658 _ => None,
659 }
660 }
661
662 // MSVC's x86 libraries are not in a subfolder
vc_lib_subdir(target: &str) -> Option<&'static str>663 fn vc_lib_subdir(target: &str) -> Option<&'static str> {
664 let arch = target.split('-').next().unwrap();
665 match arch {
666 "i586" | "i686" => Some(""),
667 "x86_64" => Some("amd64"),
668 "arm" | "thumbv7a" => Some("arm"),
669 "aarch64" => Some("arm64"),
670 _ => None,
671 }
672 }
673
674 #[allow(bad_style)]
host_arch() -> u16675 fn host_arch() -> u16 {
676 type DWORD = u32;
677 type WORD = u16;
678 type LPVOID = *mut u8;
679 type DWORD_PTR = usize;
680
681 #[repr(C)]
682 struct SYSTEM_INFO {
683 wProcessorArchitecture: WORD,
684 _wReserved: WORD,
685 _dwPageSize: DWORD,
686 _lpMinimumApplicationAddress: LPVOID,
687 _lpMaximumApplicationAddress: LPVOID,
688 _dwActiveProcessorMask: DWORD_PTR,
689 _dwNumberOfProcessors: DWORD,
690 _dwProcessorType: DWORD,
691 _dwAllocationGranularity: DWORD,
692 _wProcessorLevel: WORD,
693 _wProcessorRevision: WORD,
694 }
695
696 extern "system" {
697 fn GetNativeSystemInfo(lpSystemInfo: *mut SYSTEM_INFO);
698 }
699
700 unsafe {
701 let mut info = mem::zeroed();
702 GetNativeSystemInfo(&mut info);
703 info.wProcessorArchitecture
704 }
705 }
706
707 // Given a registry key, look at all the sub keys and find the one which has
708 // the maximal numeric value.
709 //
710 // Returns the name of the maximal key as well as the opened maximal key.
max_version(key: &RegistryKey) -> Option<(OsString, RegistryKey)>711 fn max_version(key: &RegistryKey) -> Option<(OsString, RegistryKey)> {
712 let mut max_vers = 0;
713 let mut max_key = None;
714 for subkey in key.iter().filter_map(|k| k.ok()) {
715 let val = subkey
716 .to_str()
717 .and_then(|s| s.trim_left_matches("v").replace(".", "").parse().ok());
718 let val = match val {
719 Some(s) => s,
720 None => continue,
721 };
722 if val > max_vers {
723 if let Ok(k) = key.open(&subkey) {
724 max_vers = val;
725 max_key = Some((subkey, k));
726 }
727 }
728 }
729 max_key
730 }
731
has_msbuild_version(version: &str) -> bool732 pub fn has_msbuild_version(version: &str) -> bool {
733 match version {
734 "16.0" => {
735 find_msbuild_vs16("x86_64-pc-windows-msvc").is_some()
736 || find_msbuild_vs16("i686-pc-windows-msvc").is_some()
737 }
738 "15.0" => {
739 find_msbuild_vs15("x86_64-pc-windows-msvc").is_some()
740 || find_msbuild_vs15("i686-pc-windows-msvc").is_some()
741 }
742 "12.0" | "14.0" => LOCAL_MACHINE
743 .open(&OsString::from(format!(
744 "SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions\\{}",
745 version
746 )))
747 .is_ok(),
748 _ => false,
749 }
750 }
751
find_devenv(target: &str) -> Option<Tool>752 pub fn find_devenv(target: &str) -> Option<Tool> {
753 find_devenv_vs15(&target)
754 }
755
find_devenv_vs15(target: &str) -> Option<Tool>756 fn find_devenv_vs15(target: &str) -> Option<Tool> {
757 find_tool_in_vs15_path(r"Common7\IDE\devenv.exe", target)
758 }
759
760 // see http://stackoverflow.com/questions/328017/path-to-msbuild
find_msbuild(target: &str) -> Option<Tool>761 pub fn find_msbuild(target: &str) -> Option<Tool> {
762 // VS 15 (2017) changed how to locate msbuild
763 if let Some(r) = find_msbuild_vs16(target) {
764 return Some(r);
765 } else if let Some(r) = find_msbuild_vs15(target) {
766 return Some(r);
767 } else {
768 find_old_msbuild(target)
769 }
770 }
771
find_msbuild_vs15(target: &str) -> Option<Tool>772 fn find_msbuild_vs15(target: &str) -> Option<Tool> {
773 find_tool_in_vs15_path(r"MSBuild\15.0\Bin\MSBuild.exe", target)
774 }
775
find_old_msbuild(target: &str) -> Option<Tool>776 fn find_old_msbuild(target: &str) -> Option<Tool> {
777 let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions";
778 LOCAL_MACHINE
779 .open(key.as_ref())
780 .ok()
781 .and_then(|key| {
782 max_version(&key).and_then(|(_vers, key)| key.query_str("MSBuildToolsPath").ok())
783 })
784 .map(|path| {
785 let mut path = PathBuf::from(path);
786 path.push("MSBuild.exe");
787 let mut tool = Tool::with_family(path, MSVC_FAMILY);
788 if target.contains("x86_64") {
789 tool.env.push(("Platform".into(), "X64".into()));
790 }
791 tool
792 })
793 }
794 }
795