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;
19 ($expr:expr) => (match $expr {
25 /// Attempts to find a tool within an MSVC installation using the Windows
26 /// registry as a point to search from.
28 /// The `target` argument is the target that the tool should work for (e.g.
29 /// compile or link for) and the `tool` argument is the tool to find (e.g.
30 /// `cl.exe` or `link.exe`).
32 /// This function will return `None` if the tool could not be found, or it will
33 /// return `Some(cmd)` which represents a command that's ready to execute the
34 /// tool with the appropriate environment variables set.
36 /// Note that this function always returns `None` for non-MSVC targets.
37 pub fn find(target: &str, tool: &str) -> Option<Command> {
38 find_tool(target, tool).map(|c| c.to_command())
41 /// Similar to the `find` function above, this function will attempt the same
42 /// operation (finding a MSVC tool in a local install) but instead returns a
43 /// `Tool` which may be introspected.
45 pub fn find_tool(_target: &str, _tool: &str) -> Option<Tool> {
51 pub fn find_tool(target: &str, tool: &str) -> Option<Tool> {
53 use std::ffi::OsString;
55 use std::path::{Path, PathBuf};
56 use registry::{RegistryKey, LOCAL_MACHINE};
62 include: Vec<PathBuf>,
66 fn new(tool: PathBuf) -> MsvcTool {
75 fn into_tool(self) -> Tool {
76 let MsvcTool { tool, libs, path, include } = self;
77 let mut tool = Tool::new(tool.into());
78 add_env(&mut tool, "LIB", libs);
79 add_env(&mut tool, "PATH", path);
80 add_env(&mut tool, "INCLUDE", include);
85 // This logic is all tailored for MSVC, if we're not that then bail out
87 if !target.contains("msvc") {
91 // Looks like msbuild isn't located in the same location as other tools like
92 // cl.exe and lib.exe. To handle this we probe for it manually with
93 // dedicated registry keys.
94 if tool.contains("msbuild") {
95 return find_msbuild(target);
98 // If VCINSTALLDIR is set, then someone's probably already run vcvars and we
99 // should just find whatever that indicates.
100 if env::var_os("VCINSTALLDIR").is_some() {
101 return env::var_os("PATH")
102 .and_then(|path| env::split_paths(&path).map(|p| p.join(tool)).find(|p| p.exists()))
103 .map(|path| Tool::new(path.into()));
106 // Ok, if we're here, now comes the fun part of the probing. Default shells
107 // or shells like MSYS aren't really configured to execute `cl.exe` and the
108 // various compiler tools shipped as part of Visual Studio. Here we try to
109 // first find the relevant tool, then we also have to be sure to fill in
110 // environment variables like `LIB`, `INCLUDE`, and `PATH` to ensure that
111 // the tool is actually usable.
113 return find_msvc_latest(tool, target, "15.0")
114 .or_else(|| find_msvc_latest(tool, target, "14.0"))
115 .or_else(|| find_msvc_12(tool, target))
116 .or_else(|| find_msvc_11(tool, target));
118 // For MSVC 14 or newer we need to find the Universal CRT as well as either
119 // the Windows 10 SDK or Windows 8.1 SDK.
120 fn find_msvc_latest(tool: &str, target: &str, ver: &str) -> Option<Tool> {
121 let vcdir = otry!(get_vc_dir(ver));
122 let mut tool = otry!(get_tool(tool, &vcdir, target));
123 let sub = otry!(lib_subdir(target));
124 let (ucrt, ucrt_version) = otry!(get_ucrt_dir());
126 let ucrt_include = ucrt.join("include").join(&ucrt_version);
127 tool.include.push(ucrt_include.join("ucrt"));
129 let ucrt_lib = ucrt.join("lib").join(&ucrt_version);
130 tool.libs.push(ucrt_lib.join("ucrt").join(sub));
132 if let Some((sdk, version)) = get_sdk10_dir() {
133 tool.path.push(sdk.join("bin").join(sub));
134 let sdk_lib = sdk.join("lib").join(&version);
135 tool.libs.push(sdk_lib.join("um").join(sub));
136 let sdk_include = sdk.join("include").join(&version);
137 tool.include.push(sdk_include.join("um"));
138 tool.include.push(sdk_include.join("winrt"));
139 tool.include.push(sdk_include.join("shared"));
140 } else if let Some(sdk) = get_sdk81_dir() {
141 tool.path.push(sdk.join("bin").join(sub));
142 let sdk_lib = sdk.join("lib").join("winv6.3");
143 tool.libs.push(sdk_lib.join("um").join(sub));
144 let sdk_include = sdk.join("include");
145 tool.include.push(sdk_include.join("um"));
146 tool.include.push(sdk_include.join("winrt"));
147 tool.include.push(sdk_include.join("shared"));
151 Some(tool.into_tool())
154 // For MSVC 12 we need to find the Windows 8.1 SDK.
155 fn find_msvc_12(tool: &str, target: &str) -> Option<Tool> {
156 let vcdir = otry!(get_vc_dir("12.0"));
157 let mut tool = otry!(get_tool(tool, &vcdir, target));
158 let sub = otry!(lib_subdir(target));
159 let sdk81 = otry!(get_sdk81_dir());
160 tool.path.push(sdk81.join("bin").join(sub));
161 let sdk_lib = sdk81.join("lib").join("winv6.3");
162 tool.libs.push(sdk_lib.join("um").join(sub));
163 let sdk_include = sdk81.join("include");
164 tool.include.push(sdk_include.join("shared"));
165 tool.include.push(sdk_include.join("um"));
166 tool.include.push(sdk_include.join("winrt"));
167 Some(tool.into_tool())
170 // For MSVC 11 we need to find the Windows 8 SDK.
171 fn find_msvc_11(tool: &str, target: &str) -> Option<Tool> {
172 let vcdir = otry!(get_vc_dir("11.0"));
173 let mut tool = otry!(get_tool(tool, &vcdir, target));
174 let sub = otry!(lib_subdir(target));
175 let sdk8 = otry!(get_sdk8_dir());
176 tool.path.push(sdk8.join("bin").join(sub));
177 let sdk_lib = sdk8.join("lib").join("win8");
178 tool.libs.push(sdk_lib.join("um").join(sub));
179 let sdk_include = sdk8.join("include");
180 tool.include.push(sdk_include.join("shared"));
181 tool.include.push(sdk_include.join("um"));
182 tool.include.push(sdk_include.join("winrt"));
183 Some(tool.into_tool())
186 fn add_env(tool: &mut Tool, env: &str, paths: Vec<PathBuf>) {
187 let prev = env::var_os(env).unwrap_or(OsString::new());
188 let prev = env::split_paths(&prev);
189 let new = paths.into_iter().chain(prev);
190 tool.env.push((env.to_string().into(), env::join_paths(new).unwrap()));
193 // Given a possible MSVC installation directory, we look for the linker and
194 // then add the MSVC library path.
195 fn get_tool(tool: &str, path: &Path, target: &str) -> Option<MsvcTool> {
198 .map(|(sub, host)| (path.join("bin").join(sub).join(tool), path.join("bin").join(host)))
199 .filter(|&(ref path, _)| path.is_file())
200 .map(|(path, host)| {
201 let mut tool = MsvcTool::new(path);
202 tool.path.push(host);
205 .filter_map(|mut tool| {
206 let sub = otry!(vc_lib_subdir(target));
207 tool.libs.push(path.join("lib").join(sub));
208 tool.include.push(path.join("include"));
209 let atlmfc_path = path.join("atlmfc");
210 if atlmfc_path.exists() {
211 tool.libs.push(atlmfc_path.join("lib").join(sub));
212 tool.include.push(atlmfc_path.join("include"));
219 // To find MSVC we look in a specific registry key for the version we are
221 fn get_vc_dir(ver: &str) -> Option<PathBuf> {
222 let key = r"SOFTWARE\Microsoft\VisualStudio\SxS\VC7";
223 let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok());
224 let path = otry!(key.query_str(ver).ok());
228 // To find the Universal CRT we look in a specific registry key for where
229 // all the Universal CRTs are located and then sort them asciibetically to
230 // find the newest version. While this sort of sorting isn't ideal, it is
231 // what vcvars does so that's good enough for us.
233 // Returns a pair of (root, version) for the ucrt dir if found
234 fn get_ucrt_dir() -> Option<(PathBuf, String)> {
235 let key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots";
236 let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok());
237 let root = otry!(key.query_str("KitsRoot10").ok());
238 let readdir = otry!(Path::new(&root).join("lib").read_dir().ok());
239 let max_libdir = otry!(readdir.filter_map(|dir| dir.ok())
240 .map(|dir| dir.path())
244 .and_then(|c| c.as_os_str().to_str())
245 .map(|c| c.starts_with("10.") && dir.join("ucrt").is_dir())
249 let version = max_libdir.components().last().unwrap();
250 let version = version.as_os_str().to_str().unwrap().to_string();
251 Some((root.into(), version))
254 // Vcvars finds the correct version of the Windows 10 SDK by looking
255 // for the include `um\Windows.h` because sometimes a given version will
256 // only have UCRT bits without the rest of the SDK. Since we only care about
257 // libraries and not includes, we instead look for `um\x64\kernel32.lib`.
258 // Since the 32-bit and 64-bit libraries are always installed together we
259 // only need to bother checking x64, making this code a tiny bit simpler.
260 // Like we do for the Universal CRT, we sort the possibilities
261 // asciibetically to find the newest one as that is what vcvars does.
262 fn get_sdk10_dir() -> Option<(PathBuf, String)> {
263 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0";
264 let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok());
265 let root = otry!(key.query_str("InstallationFolder").ok());
266 let readdir = otry!(Path::new(&root).join("lib").read_dir().ok());
267 let mut dirs = readdir.filter_map(|dir| dir.ok())
268 .map(|dir| dir.path())
269 .collect::<Vec<_>>();
271 let dir = otry!(dirs.into_iter()
273 .filter(|dir| dir.join("um").join("x64").join("kernel32.lib").is_file())
275 let version = dir.components().last().unwrap();
276 let version = version.as_os_str().to_str().unwrap().to_string();
277 Some((root.into(), version))
280 // Interestingly there are several subdirectories, `win7` `win8` and
281 // `winv6.3`. Vcvars seems to only care about `winv6.3` though, so the same
282 // applies to us. Note that if we were targetting kernel mode drivers
283 // instead of user mode applications, we would care.
284 fn get_sdk81_dir() -> Option<PathBuf> {
285 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1";
286 let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok());
287 let root = otry!(key.query_str("InstallationFolder").ok());
291 fn get_sdk8_dir() -> Option<PathBuf> {
292 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.0";
293 let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok());
294 let root = otry!(key.query_str("InstallationFolder").ok());
298 const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0;
299 const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9;
300 const X86: u16 = PROCESSOR_ARCHITECTURE_INTEL;
301 const X86_64: u16 = PROCESSOR_ARCHITECTURE_AMD64;
303 // When choosing the tool to use, we have to choose the one which matches
304 // the target architecture. Otherwise we end up in situations where someone
305 // on 32-bit Windows is trying to cross compile to 64-bit and it tries to
306 // invoke the native 64-bit compiler which won't work.
308 // For the return value of this function, the first member of the tuple is
309 // the folder of the tool we will be invoking, while the second member is
310 // the folder of the host toolchain for that tool which is essential when
311 // using a cross linker. We return a Vec since on x64 there are often two
312 // linkers that can target the architecture we desire. The 64-bit host
313 // linker is preferred, and hence first, due to 64-bit allowing it more
314 // address space to work with and potentially being faster.
315 fn bin_subdir(target: &str) -> Vec<(&'static str, &'static str)> {
316 let arch = target.split('-').next().unwrap();
317 match (arch, host_arch()) {
318 ("i586", X86) | ("i686", X86) => vec![("", "")],
319 ("i586", X86_64) | ("i686", X86_64) => vec![("amd64_x86", "amd64"), ("", "")],
320 ("x86_64", X86) => vec![("x86_amd64", "")],
321 ("x86_64", X86_64) => vec![("amd64", "amd64"), ("x86_amd64", "")],
322 ("arm", X86) => vec![("x86_arm", "")],
323 ("arm", X86_64) => vec![("amd64_arm", "amd64"), ("x86_arm", "")],
328 fn lib_subdir(target: &str) -> Option<&'static str> {
329 let arch = target.split('-').next().unwrap();
331 "i586" | "i686" => Some("x86"),
332 "x86_64" => Some("x64"),
333 "arm" => Some("arm"),
338 // MSVC's x86 libraries are not in a subfolder
339 fn vc_lib_subdir(target: &str) -> Option<&'static str> {
340 let arch = target.split('-').next().unwrap();
342 "i586" | "i686" => Some(""),
343 "x86_64" => Some("amd64"),
344 "arm" => Some("arm"),
350 fn host_arch() -> u16 {
353 type LPVOID = *mut u8;
354 type DWORD_PTR = usize;
358 wProcessorArchitecture: WORD,
361 _lpMinimumApplicationAddress: LPVOID,
362 _lpMaximumApplicationAddress: LPVOID,
363 _dwActiveProcessorMask: DWORD_PTR,
364 _dwNumberOfProcessors: DWORD,
365 _dwProcessorType: DWORD,
366 _dwAllocationGranularity: DWORD,
367 _wProcessorLevel: WORD,
368 _wProcessorRevision: WORD,
372 fn GetNativeSystemInfo(lpSystemInfo: *mut SYSTEM_INFO);
376 let mut info = mem::zeroed();
377 GetNativeSystemInfo(&mut info);
378 info.wProcessorArchitecture
382 // Given a registry key, look at all the sub keys and find the one which has
383 // the maximal numeric value.
385 // Returns the name of the maximal key as well as the opened maximal key.
386 fn max_version(key: &RegistryKey) -> Option<(OsString, RegistryKey)> {
387 let mut max_vers = 0;
388 let mut max_key = None;
389 for subkey in key.iter().filter_map(|k| k.ok()) {
390 let val = subkey.to_str()
391 .and_then(|s| s.trim_left_matches("v").replace(".", "").parse().ok());
392 let val = match val {
397 if let Ok(k) = key.open(&subkey) {
399 max_key = Some((subkey, k));
406 // see http://stackoverflow.com/questions/328017/path-to-msbuild
407 fn find_msbuild(target: &str) -> Option<Tool> {
408 let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions";
409 LOCAL_MACHINE.open(key.as_ref())
412 max_version(&key).and_then(|(_vers, key)| key.query_str("MSBuildToolsPath").ok())
415 let mut path = PathBuf::from(path);
416 path.push("MSBuild.exe");
417 let mut tool = Tool::new(path);
418 if target.contains("x86_64") {
419 tool.env.push(("Platform".into(), "X64".into()));