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