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").and_then(|path| {
102 env::split_paths(&path).map(|p| p.join(tool)).find(|p| p.exists())
104 Tool::new(path.into())
108 // Ok, if we're here, now comes the fun part of the probing. Default shells
109 // or shells like MSYS aren't really configured to execute `cl.exe` and the
110 // various compiler tools shipped as part of Visual Studio. Here we try to
111 // first find the relevant tool, then we also have to be sure to fill in
112 // environment variables like `LIB`, `INCLUDE`, and `PATH` to ensure that
113 // the tool is actually usable.
115 return find_msvc_latest(tool, target, "15.0").or_else(|| {
116 find_msvc_latest(tool, target, "14.0")
118 find_msvc_12(tool, target)
120 find_msvc_11(tool, target)
123 // For MSVC 14 or newer we need to find the Universal CRT as well as either
124 // the Windows 10 SDK or Windows 8.1 SDK.
125 fn find_msvc_latest(tool: &str, target: &str, ver: &str) -> Option<Tool> {
126 let vcdir = otry!(get_vc_dir(ver));
127 let mut tool = otry!(get_tool(tool, &vcdir, target));
128 let sub = otry!(lib_subdir(target));
129 let (ucrt, ucrt_version) = otry!(get_ucrt_dir());
131 let ucrt_include = ucrt.join("include").join(&ucrt_version);
132 tool.include.push(ucrt_include.join("ucrt"));
134 let ucrt_lib = ucrt.join("lib").join(&ucrt_version);
135 tool.libs.push(ucrt_lib.join("ucrt").join(sub));
137 if let Some((sdk, version)) = get_sdk10_dir() {
138 tool.path.push(sdk.join("bin").join(sub));
139 let sdk_lib = sdk.join("lib").join(&version);
140 tool.libs.push(sdk_lib.join("um").join(sub));
141 let sdk_include = sdk.join("include").join(&version);
142 tool.include.push(sdk_include.join("um"));
143 tool.include.push(sdk_include.join("winrt"));
144 tool.include.push(sdk_include.join("shared"));
145 } else if let Some(sdk) = get_sdk81_dir() {
146 tool.path.push(sdk.join("bin").join(sub));
147 let sdk_lib = sdk.join("lib").join("winv6.3");
148 tool.libs.push(sdk_lib.join("um").join(sub));
149 let sdk_include = sdk.join("include");
150 tool.include.push(sdk_include.join("um"));
151 tool.include.push(sdk_include.join("winrt"));
152 tool.include.push(sdk_include.join("shared"));
156 Some(tool.into_tool())
159 // For MSVC 12 we need to find the Windows 8.1 SDK.
160 fn find_msvc_12(tool: &str, target: &str) -> Option<Tool> {
161 let vcdir = otry!(get_vc_dir("12.0"));
162 let mut tool = otry!(get_tool(tool, &vcdir, target));
163 let sub = otry!(lib_subdir(target));
164 let sdk81 = otry!(get_sdk81_dir());
165 tool.path.push(sdk81.join("bin").join(sub));
166 let sdk_lib = sdk81.join("lib").join("winv6.3");
167 tool.libs.push(sdk_lib.join("um").join(sub));
168 let sdk_include = sdk81.join("include");
169 tool.include.push(sdk_include.join("shared"));
170 tool.include.push(sdk_include.join("um"));
171 tool.include.push(sdk_include.join("winrt"));
172 Some(tool.into_tool())
175 // For MSVC 11 we need to find the Windows 8 SDK.
176 fn find_msvc_11(tool: &str, target: &str) -> Option<Tool> {
177 let vcdir = otry!(get_vc_dir("11.0"));
178 let mut tool = otry!(get_tool(tool, &vcdir, target));
179 let sub = otry!(lib_subdir(target));
180 let sdk8 = otry!(get_sdk8_dir());
181 tool.path.push(sdk8.join("bin").join(sub));
182 let sdk_lib = sdk8.join("lib").join("win8");
183 tool.libs.push(sdk_lib.join("um").join(sub));
184 let sdk_include = sdk8.join("include");
185 tool.include.push(sdk_include.join("shared"));
186 tool.include.push(sdk_include.join("um"));
187 tool.include.push(sdk_include.join("winrt"));
188 Some(tool.into_tool())
191 fn add_env(tool: &mut Tool, env: &str, paths: Vec<PathBuf>) {
192 let prev = env::var_os(env).unwrap_or(OsString::new());
193 let prev = env::split_paths(&prev);
194 let new = paths.into_iter().chain(prev);
195 tool.env.push((env.to_string().into(), env::join_paths(new).unwrap()));
198 // Given a possible MSVC installation directory, we look for the linker and
199 // then add the MSVC library path.
200 fn get_tool(tool: &str, path: &Path, target: &str) -> Option<MsvcTool> {
201 bin_subdir(target).into_iter().map(|(sub, host)| {
202 (path.join("bin").join(sub).join(tool),
203 path.join("bin").join(host))
204 }).filter(|&(ref path, _)| {
206 }).map(|(path, host)| {
207 let mut tool = MsvcTool::new(path);
208 tool.path.push(host);
210 }).filter_map(|mut tool| {
211 let sub = otry!(vc_lib_subdir(target));
212 tool.libs.push(path.join("lib").join(sub));
213 tool.include.push(path.join("include"));
214 let atlmfc_path = path.join("atlmfc");
215 if atlmfc_path.exists() {
216 tool.libs.push(atlmfc_path.join("lib").join(sub));
217 tool.include.push(atlmfc_path.join("include"));
223 // To find MSVC we look in a specific registry key for the version we are
225 fn get_vc_dir(ver: &str) -> Option<PathBuf> {
226 let key = r"SOFTWARE\Microsoft\VisualStudio\SxS\VC7";
227 let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok());
228 let path = otry!(key.query_str(ver).ok());
232 // To find the Universal CRT we look in a specific registry key for where
233 // all the Universal CRTs are located and then sort them asciibetically to
234 // find the newest version. While this sort of sorting isn't ideal, it is
235 // what vcvars does so that's good enough for us.
237 // Returns a pair of (root, version) for the ucrt dir if found
238 fn get_ucrt_dir() -> Option<(PathBuf, String)> {
239 let key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots";
240 let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok());
241 let root = otry!(key.query_str("KitsRoot10").ok());
242 let readdir = otry!(Path::new(&root).join("lib").read_dir().ok());
243 let max_libdir = otry!(readdir.filter_map(|dir| {
248 dir.components().last().and_then(|c| {
249 c.as_os_str().to_str()
251 c.starts_with("10.") && dir.join("ucrt").is_dir()
254 let version = max_libdir.components().last().unwrap();
255 let version = version.as_os_str().to_str().unwrap().to_string();
256 Some((root.into(), version))
259 // Vcvars finds the correct version of the Windows 10 SDK by looking
260 // for the include `um\Windows.h` because sometimes a given version will
261 // only have UCRT bits without the rest of the SDK. Since we only care about
262 // libraries and not includes, we instead look for `um\x64\kernel32.lib`.
263 // Since the 32-bit and 64-bit libraries are always installed together we
264 // only need to bother checking x64, making this code a tiny bit simpler.
265 // Like we do for the Universal CRT, we sort the possibilities
266 // asciibetically to find the newest one as that is what vcvars does.
267 fn get_sdk10_dir() -> Option<(PathBuf, String)> {
268 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0";
269 let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok());
270 let root = otry!(key.query_str("InstallationFolder").ok());
271 let readdir = otry!(Path::new(&root).join("lib").read_dir().ok());
272 let mut dirs = readdir.filter_map(|dir| dir.ok())
273 .map(|dir| dir.path())
274 .collect::<Vec<_>>();
276 let dir = otry!(dirs.into_iter().rev().filter(|dir| {
277 dir.join("um").join("x64").join("kernel32.lib").is_file()
279 let version = dir.components().last().unwrap();
280 let version = version.as_os_str().to_str().unwrap().to_string();
281 Some((root.into(), version))
284 // Interestingly there are several subdirectories, `win7` `win8` and
285 // `winv6.3`. Vcvars seems to only care about `winv6.3` though, so the same
286 // applies to us. Note that if we were targetting kernel mode drivers
287 // instead of user mode applications, we would care.
288 fn get_sdk81_dir() -> Option<PathBuf> {
289 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1";
290 let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok());
291 let root = otry!(key.query_str("InstallationFolder").ok());
295 fn get_sdk8_dir() -> Option<PathBuf> {
296 let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.0";
297 let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok());
298 let root = otry!(key.query_str("InstallationFolder").ok());
302 const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0;
303 const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9;
304 const X86: u16 = PROCESSOR_ARCHITECTURE_INTEL;
305 const X86_64: u16 = PROCESSOR_ARCHITECTURE_AMD64;
307 // When choosing the tool to use, we have to choose the one which matches
308 // the target architecture. Otherwise we end up in situations where someone
309 // on 32-bit Windows is trying to cross compile to 64-bit and it tries to
310 // invoke the native 64-bit compiler which won't work.
312 // For the return value of this function, the first member of the tuple is
313 // the folder of the tool we will be invoking, while the second member is
314 // the folder of the host toolchain for that tool which is essential when
315 // using a cross linker. We return a Vec since on x64 there are often two
316 // linkers that can target the architecture we desire. The 64-bit host
317 // linker is preferred, and hence first, due to 64-bit allowing it more
318 // address space to work with and potentially being faster.
319 fn bin_subdir(target: &str) -> Vec<(&'static str, &'static str)> {
320 let arch = target.split('-').next().unwrap();
321 match (arch, host_arch()) {
323 ("i686", X86) => vec![("", "")],
325 ("i686", X86_64) => vec![("amd64_x86", "amd64"), ("", "")],
326 ("x86_64", X86) => vec![("x86_amd64", "")],
327 ("x86_64", X86_64) => vec![("amd64", "amd64"), ("x86_amd64", "")],
328 ("arm", X86) => vec![("x86_arm", "")],
329 ("arm", X86_64) => vec![("amd64_arm", "amd64"), ("x86_arm", "")],
334 fn lib_subdir(target: &str) -> Option<&'static str> {
335 let arch = target.split('-').next().unwrap();
337 "i586" | "i686" => Some("x86"),
338 "x86_64" => Some("x64"),
339 "arm" => Some("arm"),
344 // MSVC's x86 libraries are not in a subfolder
345 fn vc_lib_subdir(target: &str) -> Option<&'static str> {
346 let arch = target.split('-').next().unwrap();
348 "i586" | "i686" => Some(""),
349 "x86_64" => Some("amd64"),
350 "arm" => Some("arm"),
356 fn host_arch() -> u16 {
359 type LPVOID = *mut u8;
360 type DWORD_PTR = usize;
364 wProcessorArchitecture: WORD,
367 _lpMinimumApplicationAddress: LPVOID,
368 _lpMaximumApplicationAddress: LPVOID,
369 _dwActiveProcessorMask: DWORD_PTR,
370 _dwNumberOfProcessors: DWORD,
371 _dwProcessorType: DWORD,
372 _dwAllocationGranularity: DWORD,
373 _wProcessorLevel: WORD,
374 _wProcessorRevision: WORD,
378 fn GetNativeSystemInfo(lpSystemInfo: *mut SYSTEM_INFO);
382 let mut info = mem::zeroed();
383 GetNativeSystemInfo(&mut info);
384 info.wProcessorArchitecture
388 // Given a registry key, look at all the sub keys and find the one which has
389 // the maximal numeric value.
391 // Returns the name of the maximal key as well as the opened maximal key.
392 fn max_version(key: &RegistryKey) -> Option<(OsString, RegistryKey)> {
393 let mut max_vers = 0;
394 let mut max_key = None;
395 for subkey in key.iter().filter_map(|k| k.ok()) {
396 let val = subkey.to_str().and_then(|s| {
397 s.trim_left_matches("v").replace(".", "").parse().ok()
399 let val = match val {
404 if let Ok(k) = key.open(&subkey) {
406 max_key = Some((subkey, k));
413 // see http://stackoverflow.com/questions/328017/path-to-msbuild
414 fn find_msbuild(target: &str) -> Option<Tool> {
415 let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions";
416 LOCAL_MACHINE.open(key.as_ref()).ok().and_then(|key| {
417 max_version(&key).and_then(|(_vers, key)| {
418 key.query_str("MSBuildToolsPath").ok()
421 let mut path = PathBuf::from(path);
422 path.push("MSBuild.exe");
423 let mut tool = Tool::new(path);
424 if target.contains("x86_64") {
425 tool.env.push(("Platform".into(), "X64".into()));