]> git.lizzy.rs Git - rust.git/blob - src/vendor/gcc/src/windows_registry.rs
Rollup merge of #39604 - est31:i128_tests, r=alexcrichton
[rust.git] / src / vendor / gcc / src / windows_registry.rs
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 Tool;
17
18 macro_rules! otry {
19     ($expr:expr) => (match $expr {
20         Some(val) => val,
21         None => return None,
22     })
23 }
24
25 /// Attempts to find a tool within an MSVC installation using the Windows
26 /// registry as a point to search from.
27 ///
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`).
31 ///
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.
35 ///
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())
39 }
40
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.
44 #[cfg(not(windows))]
45 pub fn find_tool(_target: &str, _tool: &str) -> Option<Tool> {
46     None
47 }
48
49 /// Documented above.
50 #[cfg(windows)]
51 pub fn find_tool(target: &str, tool: &str) -> Option<Tool> {
52     use std::env;
53     use std::ffi::OsString;
54     use std::mem;
55     use std::path::{Path, PathBuf};
56     use registry::{RegistryKey, LOCAL_MACHINE};
57
58     struct MsvcTool {
59         tool: PathBuf,
60         libs: Vec<PathBuf>,
61         path: Vec<PathBuf>,
62         include: Vec<PathBuf>,
63     }
64
65     impl MsvcTool {
66         fn new(tool: PathBuf) -> MsvcTool {
67             MsvcTool {
68                 tool: tool,
69                 libs: Vec::new(),
70                 path: Vec::new(),
71                 include: Vec::new(),
72             }
73         }
74
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);
81             tool
82         }
83     }
84
85     // This logic is all tailored for MSVC, if we're not that then bail out
86     // early.
87     if !target.contains("msvc") {
88         return None;
89     }
90
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);
96     }
97
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()));
104     }
105
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.
112
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));
117
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());
125
126         let ucrt_include = ucrt.join("include").join(&ucrt_version);
127         tool.include.push(ucrt_include.join("ucrt"));
128
129         let ucrt_lib = ucrt.join("lib").join(&ucrt_version);
130         tool.libs.push(ucrt_lib.join("ucrt").join(sub));
131
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"));
148         } else {
149             return None;
150         }
151         Some(tool.into_tool())
152     }
153
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())
168     }
169
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())
184     }
185
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()));
191     }
192
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> {
196         bin_subdir(target)
197             .into_iter()
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);
203                 tool
204             })
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"));
213                 }
214                 Some(tool)
215             })
216             .next()
217     }
218
219     // To find MSVC we look in a specific registry key for the version we are
220     // trying to find.
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());
225         Some(path.into())
226     }
227
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.
232     //
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())
241             .filter(|dir| {
242                 dir.components()
243                     .last()
244                     .and_then(|c| c.as_os_str().to_str())
245                     .map(|c| c.starts_with("10.") && dir.join("ucrt").is_dir())
246                     .unwrap_or(false)
247             })
248             .max());
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))
252     }
253
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<_>>();
270         dirs.sort();
271         let dir = otry!(dirs.into_iter()
272             .rev()
273             .filter(|dir| dir.join("um").join("x64").join("kernel32.lib").is_file())
274             .next());
275         let version = dir.components().last().unwrap();
276         let version = version.as_os_str().to_str().unwrap().to_string();
277         Some((root.into(), version))
278     }
279
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());
288         Some(root.into())
289     }
290
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());
295         Some(root.into())
296     }
297
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;
302
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.
307     //
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", "")],
324             _ => vec![],
325         }
326     }
327
328     fn lib_subdir(target: &str) -> Option<&'static str> {
329         let arch = target.split('-').next().unwrap();
330         match arch {
331             "i586" | "i686" => Some("x86"),
332             "x86_64" => Some("x64"),
333             "arm" => Some("arm"),
334             _ => None,
335         }
336     }
337
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();
341         match arch {
342             "i586" | "i686" => Some(""),
343             "x86_64" => Some("amd64"),
344             "arm" => Some("arm"),
345             _ => None,
346         }
347     }
348
349     #[allow(bad_style)]
350     fn host_arch() -> u16 {
351         type DWORD = u32;
352         type WORD = u16;
353         type LPVOID = *mut u8;
354         type DWORD_PTR = usize;
355
356         #[repr(C)]
357         struct SYSTEM_INFO {
358             wProcessorArchitecture: WORD,
359             _wReserved: WORD,
360             _dwPageSize: DWORD,
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,
369         }
370
371         extern "system" {
372             fn GetNativeSystemInfo(lpSystemInfo: *mut SYSTEM_INFO);
373         }
374
375         unsafe {
376             let mut info = mem::zeroed();
377             GetNativeSystemInfo(&mut info);
378             info.wProcessorArchitecture
379         }
380     }
381
382     // Given a registry key, look at all the sub keys and find the one which has
383     // the maximal numeric value.
384     //
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 {
393                 Some(s) => s,
394                 None => continue,
395             };
396             if val > max_vers {
397                 if let Ok(k) = key.open(&subkey) {
398                     max_vers = val;
399                     max_key = Some((subkey, k));
400                 }
401             }
402         }
403         max_key
404     }
405
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())
410             .ok()
411             .and_then(|key| {
412                 max_version(&key).and_then(|(_vers, key)| key.query_str("MSBuildToolsPath").ok())
413             })
414             .map(|path| {
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()));
420                 }
421                 tool
422             })
423     }
424 }