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.
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.
11 //! A helper module to probe the Windows Registry when looking for
12 //! windows-specific tools.
14 use std
::process
::Command
;
20 ($expr
:expr
) => (match $expr
{
26 /// Attempts to find a tool within an MSVC installation using the Windows
27 /// registry as a point to search from.
29 /// The `target` argument is the target that the tool should work for (e.g.
30 /// compile or link for) and the `tool` argument is the tool to find (e.g.
31 /// `cl.exe` or `link.exe`).
33 /// This function will return `None` if the tool could not be found, or it will
34 /// return `Some(cmd)` which represents a command that's ready to execute the
35 /// tool with the appropriate environment variables set.
37 /// Note that this function always returns `None` for non-MSVC targets.
38 pub fn find(target
: &str, tool
: &str) -> Option
<Command
> {
39 find_tool(target
, tool
).map(|c
| c
.to_command())
42 /// Similar to the `find` function above, this function will attempt the same
43 /// operation (finding a MSVC tool in a local install) but instead returns a
44 /// `Tool` which may be introspected.
46 pub fn find_tool(_target
: &str, _tool
: &str) -> Option
<Tool
> {
52 pub fn find_tool(target
: &str, tool
: &str) -> Option
<Tool
> {
55 // This logic is all tailored for MSVC, if we're not that then bail out
57 if !target
.contains("msvc") {
61 // Looks like msbuild isn't located in the same location as other tools like
62 // cl.exe and lib.exe. To handle this we probe for it manually with
63 // dedicated registry keys.
64 if tool
.contains("msbuild") {
65 return impl_
::find_msbuild(target
);
68 // If VCINSTALLDIR is set, then someone's probably already run vcvars and we
69 // should just find whatever that indicates.
70 if env
::var_os("VCINSTALLDIR").is_some() {
71 return env
::var_os("PATH")
72 .and_then(|path
| env
::split_paths(&path
).map(|p
| p
.join(tool
)).find(|p
| p
.exists()))
73 .map(|path
| Tool
::new(path
.into()));
76 // Ok, if we're here, now comes the fun part of the probing. Default shells
77 // or shells like MSYS aren't really configured to execute `cl.exe` and the
78 // various compiler tools shipped as part of Visual Studio. Here we try to
79 // first find the relevant tool, then we also have to be sure to fill in
80 // environment variables like `LIB`, `INCLUDE`, and `PATH` to ensure that
81 // the tool is actually usable.
83 return impl_
::find_msvc_15(tool
, target
)
84 .or_else(|| impl_
::find_msvc_14(tool
, target
))
85 .or_else(|| impl_
::find_msvc_12(tool
, target
))
86 .or_else(|| impl_
::find_msvc_11(tool
, target
));
89 /// A version of Visual Studio
90 #[derive(Debug, PartialEq, Eq, Copy, Clone)]
92 /// Visual Studio 12 (2013)
94 /// Visual Studio 14 (2015)
96 /// Visual Studio 15 (2017)
99 /// Hidden variant that should not be matched on. Callers that want to
100 /// handle an enumeration of `VsVers` instances should always have a default
101 /// case meaning that it's a VS version they don't understand.
104 __Nonexhaustive_do_not_match_this_or_your_code_will_break
,
107 /// Find the most recent installed version of Visual Studio
109 /// This is used by the cmake crate to figure out the correct
112 pub fn find_vs_version() -> Result
<VsVers
, String
> {
113 Err(format
!("not windows"))
118 pub fn find_vs_version() -> Result
<VsVers
, String
> {
121 match env
::var("VisualStudioVersion") {
124 "15.0" => Ok(VsVers
::Vs15
),
125 "14.0" => Ok(VsVers
::Vs14
),
126 "12.0" => Ok(VsVers
::Vs12
),
127 vers
=> Err(format
!("\n\n\
128 unsupported or unknown VisualStudio version: {}\n\
129 if another version is installed consider running \
130 the appropriate vcvars script before building this \
136 // Check for the presense of a specific registry key
137 // that indicates visual studio is installed.
138 if impl_
::has_msbuild_version("15.0") {
140 } else if impl_
::has_msbuild_version("14.0") {
142 } else if impl_
::has_msbuild_version("12.0") {
146 couldn't determine visual studio generator\n\
147 if VisualStudio is installed, however, consider \
148 running the appropriate vcvars script before building \
159 use std
::ffi
::OsString
;
161 use std
::path
::{Path, PathBuf}
;
164 use registry
::{RegistryKey, LOCAL_MACHINE}
;
166 use setup_config
::{SetupConfiguration, SetupInstance}
;
174 include
: Vec
<PathBuf
>,
178 fn new(tool
: PathBuf
) -> MsvcTool
{
187 fn into_tool(self) -> Tool
{
188 let MsvcTool { tool, libs, path, include }
= self;
189 let mut tool
= Tool
::new(tool
.into());
190 add_env(&mut tool
, "LIB", libs
);
191 add_env(&mut tool
, "PATH", path
);
192 add_env(&mut tool
, "INCLUDE", include
);
197 // In MSVC 15 (2017) MS once again changed the scheme for locating
198 // the tooling. Now we must go through some COM interfaces, which
199 // is super fun for Rust.
201 // Note that much of this logic can be found [online] wrt paths, COM, etc.
203 // [online]: https://blogs.msdn.microsoft.com/vcblog/2017/03/06/finding-the-visual-c-compiler-tools-in-visual-studio-2017/
204 pub fn find_msvc_15(tool
: &str, target
: &str) -> Option
<Tool
> {
205 otry
!(com
::initialize().ok());
207 let config
= otry
!(SetupConfiguration
::new().ok());
208 let iter
= otry
!(config
.enum_all_instances().ok());
209 for instance
in iter
{
210 let instance
= otry
!(instance
.ok());
211 let tool
= tool_from_vs15_instance(tool
, target
, &instance
);
220 fn tool_from_vs15_instance(tool
: &str, target
: &str,
221 instance
: &SetupInstance
) -> Option
<Tool
> {
222 let (bin_path
, host_dylib_path
, lib_path
, include_path
) = otry
!(vs15_vc_paths(target
, instance
));
223 let tool_path
= bin_path
.join(tool
);
224 if !tool_path
.exists() { return None }
;
226 let mut tool
= MsvcTool
::new(tool_path
);
227 tool
.path
.push(host_dylib_path
);
228 tool
.libs
.push(lib_path
);
229 tool
.include
.push(include_path
);
231 if let Some((atl_lib_path
, atl_include_path
)) = atl_paths(target
, &bin_path
) {
232 tool
.libs
.push(atl_lib_path
);
233 tool
.include
.push(atl_include_path
);
236 otry
!(add_sdks(&mut tool
, target
));
238 Some(tool
.into_tool())
241 fn vs15_vc_paths(target
: &str, instance
: &SetupInstance
) -> Option
<(PathBuf
, PathBuf
, PathBuf
, PathBuf
)> {
242 let instance_path
: PathBuf
= otry
!(instance
.installation_path().ok()).into();
243 let version_path
= instance_path
.join(r
"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt");
244 let mut version_file
= otry
!(File
::open(version_path
).ok());
245 let mut version
= String
::new();
246 otry
!(version_file
.read_to_string(&mut version
).ok());
247 let version
= version
.trim();
248 let host
= match host_arch() {
253 let target
= otry
!(lib_subdir(target
));
254 // The directory layout here is MSVC/bin/Host$host/$target/
255 let path
= instance_path
.join(r
"VC\Tools\MSVC").join(version
);
256 // This is the path to the toolchain for a particular target, running
258 let bin_path
= path
.join("bin").join(&format
!("Host{}", host
)).join(&target
);
259 // But! we also need PATH to contain the target directory for the host
260 // architecture, because it contains dlls like mspdb140.dll compiled for
261 // the host architecture.
262 let host_dylib_path
= path
.join("bin").join(&format
!("Host{}", host
)).join(&host
.to_lowercase());
263 let lib_path
= path
.join("lib").join(&target
);
264 let include_path
= path
.join("include");
265 Some((bin_path
, host_dylib_path
, lib_path
, include_path
))
268 fn atl_paths(target
: &str, path
: &Path
) -> Option
<(PathBuf
, PathBuf
)> {
269 let atl_path
= path
.join("atlfmc");
270 let sub
= otry
!(lib_subdir(target
));
271 if atl_path
.exists() {
272 Some((atl_path
.join("lib").join(sub
), atl_path
.join("include")))
278 // For MSVC 14 we need to find the Universal CRT as well as either
279 // the Windows 10 SDK or Windows 8.1 SDK.
280 pub fn find_msvc_14(tool
: &str, target
: &str) -> Option
<Tool
> {
281 let vcdir
= otry
!(get_vc_dir("14.0"));
282 let mut tool
= otry
!(get_tool(tool
, &vcdir
, target
));
283 otry
!(add_sdks(&mut tool
, target
));
284 Some(tool
.into_tool())
287 fn add_sdks(tool
: &mut MsvcTool
, target
: &str) -> Option
<()> {
288 let sub
= otry
!(lib_subdir(target
));
289 let (ucrt
, ucrt_version
) = otry
!(get_ucrt_dir());
291 tool
.path
.push(ucrt
.join("bin").join(&ucrt_version
).join(sub
));
293 let ucrt_include
= ucrt
.join("include").join(&ucrt_version
);
294 tool
.include
.push(ucrt_include
.join("ucrt"));
296 let ucrt_lib
= ucrt
.join("lib").join(&ucrt_version
);
297 tool
.libs
.push(ucrt_lib
.join("ucrt").join(sub
));
299 if let Some((sdk
, version
)) = get_sdk10_dir() {
300 tool
.path
.push(sdk
.join("bin").join(sub
));
301 let sdk_lib
= sdk
.join("lib").join(&version
);
302 tool
.libs
.push(sdk_lib
.join("um").join(sub
));
303 let sdk_include
= sdk
.join("include").join(&version
);
304 tool
.include
.push(sdk_include
.join("um"));
305 tool
.include
.push(sdk_include
.join("winrt"));
306 tool
.include
.push(sdk_include
.join("shared"));
307 } else if let Some(sdk
) = get_sdk81_dir() {
308 tool
.path
.push(sdk
.join("bin").join(sub
));
309 let sdk_lib
= sdk
.join("lib").join("winv6.3");
310 tool
.libs
.push(sdk_lib
.join("um").join(sub
));
311 let sdk_include
= sdk
.join("include");
312 tool
.include
.push(sdk_include
.join("um"));
313 tool
.include
.push(sdk_include
.join("winrt"));
314 tool
.include
.push(sdk_include
.join("shared"));
320 // For MSVC 12 we need to find the Windows 8.1 SDK.
321 pub fn find_msvc_12(tool
: &str, target
: &str) -> Option
<Tool
> {
322 let vcdir
= otry
!(get_vc_dir("12.0"));
323 let mut tool
= otry
!(get_tool(tool
, &vcdir
, target
));
324 let sub
= otry
!(lib_subdir(target
));
325 let sdk81
= otry
!(get_sdk81_dir());
326 tool
.path
.push(sdk81
.join("bin").join(sub
));
327 let sdk_lib
= sdk81
.join("lib").join("winv6.3");
328 tool
.libs
.push(sdk_lib
.join("um").join(sub
));
329 let sdk_include
= sdk81
.join("include");
330 tool
.include
.push(sdk_include
.join("shared"));
331 tool
.include
.push(sdk_include
.join("um"));
332 tool
.include
.push(sdk_include
.join("winrt"));
333 Some(tool
.into_tool())
336 // For MSVC 11 we need to find the Windows 8 SDK.
337 pub fn find_msvc_11(tool
: &str, target
: &str) -> Option
<Tool
> {
338 let vcdir
= otry
!(get_vc_dir("11.0"));
339 let mut tool
= otry
!(get_tool(tool
, &vcdir
, target
));
340 let sub
= otry
!(lib_subdir(target
));
341 let sdk8
= otry
!(get_sdk8_dir());
342 tool
.path
.push(sdk8
.join("bin").join(sub
));
343 let sdk_lib
= sdk8
.join("lib").join("win8");
344 tool
.libs
.push(sdk_lib
.join("um").join(sub
));
345 let sdk_include
= sdk8
.join("include");
346 tool
.include
.push(sdk_include
.join("shared"));
347 tool
.include
.push(sdk_include
.join("um"));
348 tool
.include
.push(sdk_include
.join("winrt"));
349 Some(tool
.into_tool())
352 fn add_env(tool
: &mut Tool
, env
: &str, paths
: Vec
<PathBuf
>) {
353 let prev
= env
::var_os(env
).unwrap_or(OsString
::new());
354 let prev
= env
::split_paths(&prev
);
355 let new
= paths
.into_iter().chain(prev
);
356 tool
.env
.push((env
.to_string().into(), env
::join_paths(new
).unwrap()));
359 // Given a possible MSVC installation directory, we look for the linker and
360 // then add the MSVC library path.
361 fn get_tool(tool
: &str, path
: &Path
, target
: &str) -> Option
<MsvcTool
> {
364 .map(|(sub
, host
)| (path
.join("bin").join(sub
).join(tool
), path
.join("bin").join(host
)))
365 .filter(|&(ref path
, _
)| path
.is_file())
366 .map(|(path
, host
)| {
367 let mut tool
= MsvcTool
::new(path
);
368 tool
.path
.push(host
);
371 .filter_map(|mut tool
| {
372 let sub
= otry
!(vc_lib_subdir(target
));
373 tool
.libs
.push(path
.join("lib").join(sub
));
374 tool
.include
.push(path
.join("include"));
375 let atlmfc_path
= path
.join("atlmfc");
376 if atlmfc_path
.exists() {
377 tool
.libs
.push(atlmfc_path
.join("lib").join(sub
));
378 tool
.include
.push(atlmfc_path
.join("include"));
385 // To find MSVC we look in a specific registry key for the version we are
387 fn get_vc_dir(ver
: &str) -> Option
<PathBuf
> {
388 let key
= r
"SOFTWARE\Microsoft\VisualStudio\SxS\VC7";
389 let key
= otry
!(LOCAL_MACHINE
.open(key
.as_ref()).ok());
390 let path
= otry
!(key
.query_str(ver
).ok());
394 // To find the Universal CRT we look in a specific registry key for where
395 // all the Universal CRTs are located and then sort them asciibetically to
396 // find the newest version. While this sort of sorting isn't ideal, it is
397 // what vcvars does so that's good enough for us.
399 // Returns a pair of (root, version) for the ucrt dir if found
400 fn get_ucrt_dir() -> Option
<(PathBuf
, String
)> {
401 let key
= r
"SOFTWARE\Microsoft\Windows Kits\Installed Roots";
402 let key
= otry
!(LOCAL_MACHINE
.open(key
.as_ref()).ok());
403 let root
= otry
!(key
.query_str("KitsRoot10").ok());
404 let readdir
= otry
!(Path
::new(&root
).join("lib").read_dir().ok());
405 let max_libdir
= otry
!(readdir
.filter_map(|dir
| dir
.ok())
406 .map(|dir
| dir
.path())
410 .and_then(|c
| c
.as_os_str().to_str())
411 .map(|c
| c
.starts_with("10.") && dir
.join("ucrt").is_dir())
415 let version
= max_libdir
.components().last().unwrap();
416 let version
= version
.as_os_str().to_str().unwrap().to_string();
417 Some((root
.into(), version
))
420 // Vcvars finds the correct version of the Windows 10 SDK by looking
421 // for the include `um\Windows.h` because sometimes a given version will
422 // only have UCRT bits without the rest of the SDK. Since we only care about
423 // libraries and not includes, we instead look for `um\x64\kernel32.lib`.
424 // Since the 32-bit and 64-bit libraries are always installed together we
425 // only need to bother checking x64, making this code a tiny bit simpler.
426 // Like we do for the Universal CRT, we sort the possibilities
427 // asciibetically to find the newest one as that is what vcvars does.
428 fn get_sdk10_dir() -> Option
<(PathBuf
, String
)> {
429 let key
= r
"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0";
430 let key
= otry
!(LOCAL_MACHINE
.open(key
.as_ref()).ok());
431 let root
= otry
!(key
.query_str("InstallationFolder").ok());
432 let readdir
= otry
!(Path
::new(&root
).join("lib").read_dir().ok());
433 let mut dirs
= readdir
.filter_map(|dir
| dir
.ok())
434 .map(|dir
| dir
.path())
435 .collect
::<Vec
<_
>>();
437 let dir
= otry
!(dirs
.into_iter()
439 .filter(|dir
| dir
.join("um").join("x64").join("kernel32.lib").is_file())
441 let version
= dir
.components().last().unwrap();
442 let version
= version
.as_os_str().to_str().unwrap().to_string();
443 Some((root
.into(), version
))
446 // Interestingly there are several subdirectories, `win7` `win8` and
447 // `winv6.3`. Vcvars seems to only care about `winv6.3` though, so the same
448 // applies to us. Note that if we were targetting kernel mode drivers
449 // instead of user mode applications, we would care.
450 fn get_sdk81_dir() -> Option
<PathBuf
> {
451 let key
= r
"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1";
452 let key
= otry
!(LOCAL_MACHINE
.open(key
.as_ref()).ok());
453 let root
= otry
!(key
.query_str("InstallationFolder").ok());
457 fn get_sdk8_dir() -> Option
<PathBuf
> {
458 let key
= r
"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.0";
459 let key
= otry
!(LOCAL_MACHINE
.open(key
.as_ref()).ok());
460 let root
= otry
!(key
.query_str("InstallationFolder").ok());
464 const PROCESSOR_ARCHITECTURE_INTEL
: u16 = 0;
465 const PROCESSOR_ARCHITECTURE_AMD64
: u16 = 9;
466 const X86
: u16 = PROCESSOR_ARCHITECTURE_INTEL
;
467 const X86_64
: u16 = PROCESSOR_ARCHITECTURE_AMD64
;
469 // When choosing the tool to use, we have to choose the one which matches
470 // the target architecture. Otherwise we end up in situations where someone
471 // on 32-bit Windows is trying to cross compile to 64-bit and it tries to
472 // invoke the native 64-bit compiler which won't work.
474 // For the return value of this function, the first member of the tuple is
475 // the folder of the tool we will be invoking, while the second member is
476 // the folder of the host toolchain for that tool which is essential when
477 // using a cross linker. We return a Vec since on x64 there are often two
478 // linkers that can target the architecture we desire. The 64-bit host
479 // linker is preferred, and hence first, due to 64-bit allowing it more
480 // address space to work with and potentially being faster.
481 fn bin_subdir(target
: &str) -> Vec
<(&'
static str, &'
static str)> {
482 let arch
= target
.split('
-'
).next().unwrap();
483 match (arch
, host_arch()) {
484 ("i586", X86
) | ("i686", X86
) => vec
![("", "")],
485 ("i586", X86_64
) | ("i686", X86_64
) => vec
![("amd64_x86", "amd64"), ("", "")],
486 ("x86_64", X86
) => vec
![("x86_amd64", "")],
487 ("x86_64", X86_64
) => vec
![("amd64", "amd64"), ("x86_amd64", "")],
488 ("arm", X86
) => vec
![("x86_arm", "")],
489 ("arm", X86_64
) => vec
![("amd64_arm", "amd64"), ("x86_arm", "")],
494 fn lib_subdir(target
: &str) -> Option
<&'
static str> {
495 let arch
= target
.split('
-'
).next().unwrap();
497 "i586" | "i686" => Some("x86"),
498 "x86_64" => Some("x64"),
499 "arm" => Some("arm"),
504 // MSVC's x86 libraries are not in a subfolder
505 fn vc_lib_subdir(target
: &str) -> Option
<&'
static str> {
506 let arch
= target
.split('
-'
).next().unwrap();
508 "i586" | "i686" => Some(""),
509 "x86_64" => Some("amd64"),
510 "arm" => Some("arm"),
516 fn host_arch() -> u16 {
519 type LPVOID
= *mut u8;
520 type DWORD_PTR
= usize;
524 wProcessorArchitecture
: WORD
,
527 _lpMinimumApplicationAddress
: LPVOID
,
528 _lpMaximumApplicationAddress
: LPVOID
,
529 _dwActiveProcessorMask
: DWORD_PTR
,
530 _dwNumberOfProcessors
: DWORD
,
531 _dwProcessorType
: DWORD
,
532 _dwAllocationGranularity
: DWORD
,
533 _wProcessorLevel
: WORD
,
534 _wProcessorRevision
: WORD
,
538 fn GetNativeSystemInfo(lpSystemInfo
: *mut SYSTEM_INFO
);
542 let mut info
= mem
::zeroed();
543 GetNativeSystemInfo(&mut info
);
544 info
.wProcessorArchitecture
548 // Given a registry key, look at all the sub keys and find the one which has
549 // the maximal numeric value.
551 // Returns the name of the maximal key as well as the opened maximal key.
552 fn max_version(key
: &RegistryKey
) -> Option
<(OsString
, RegistryKey
)> {
553 let mut max_vers
= 0;
554 let mut max_key
= None
;
555 for subkey
in key
.iter().filter_map(|k
| k
.ok()) {
556 let val
= subkey
.to_str()
557 .and_then(|s
| s
.trim_left_matches("v").replace(".", "").parse().ok());
558 let val
= match val
{
563 if let Ok(k
) = key
.open(&subkey
) {
565 max_key
= Some((subkey
, k
));
572 pub fn has_msbuild_version(version
: &str) -> bool
{
575 find_msbuild_vs15("x86_64-pc-windows-msvc").is_some() ||
576 find_msbuild_vs15("i686-pc-windows-msvc").is_some()
580 &OsString
::from(format
!("SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions\\{}",
587 // see http://stackoverflow.com/questions/328017/path-to-msbuild
588 pub fn find_msbuild(target
: &str) -> Option
<Tool
> {
589 // VS 15 (2017) changed how to locate msbuild
590 if let Some(r
) = find_msbuild_vs15(target
) {
593 find_old_msbuild(target
)
597 fn find_msbuild_vs15(target
: &str) -> Option
<Tool
> {
598 // Seems like this could also go through SetupConfiguration,
599 // or that find_msvc_15 could just use this registry key
600 // instead of the COM interface.
601 let key
= r
"SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7";
602 LOCAL_MACHINE
.open(key
.as_ref())
605 key
.query_str("15.0").ok()
608 let path
= PathBuf
::from(path
).join(r
"MSBuild\15.0\Bin\MSBuild.exe");
609 let mut tool
= Tool
::new(path
);
610 if target
.contains("x86_64") {
611 tool
.env
.push(("Platform".into(), "X64".into()));
617 fn find_old_msbuild(target
: &str) -> Option
<Tool
> {
618 let key
= r
"SOFTWARE\Microsoft\MSBuild\ToolsVersions";
619 LOCAL_MACHINE
.open(key
.as_ref())
622 max_version(&key
).and_then(|(_vers
, key
)| key
.query_str("MSBuildToolsPath").ok())
625 let mut path
= PathBuf
::from(path
);
626 path
.push("MSBuild.exe");
627 let mut tool
= Tool
::new(path
);
628 if target
.contains("x86_64") {
629 tool
.env
.push(("Platform".into(), "X64".into()));