]> git.lizzy.rs Git - rust.git/blob - src/librustc_trans/back/msvc/mod.rs
Auto merge of #42264 - GuillaumeGomez:new-error-codes, r=Susurrus
[rust.git] / src / librustc_trans / back / msvc / mod.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 //! MSVC-specific logic for linkers and such.
12 //!
13 //! This module contains a cross-platform interface but has a blank unix
14 //! implementation. The Windows implementation builds on top of Windows native
15 //! libraries (reading registry keys), so it otherwise wouldn't link on unix.
16 //!
17 //! Note that we don't have much special logic for finding the system linker on
18 //! any other platforms, so it may seem a little odd to single out MSVC to have
19 //! a good deal of code just to find the linker. Unlike Unix systems, however,
20 //! the MSVC linker is not in the system PATH by default. It also additionally
21 //! needs a few environment variables or command line flags to be able to link
22 //! against system libraries.
23 //!
24 //! In order to have a nice smooth experience on Windows, the logic in this file
25 //! is here to find the MSVC linker and set it up in the default configuration
26 //! one would need to set up anyway. This means that the Rust compiler can be
27 //! run not only in the developer shells of MSVC but also the standard cmd.exe
28 //! shell or MSYS shells.
29 //!
30 //! As a high-level note, all logic in this module for looking up various
31 //! paths/files is based on Microsoft's logic in their vcvars bat files, but
32 //! comments can also be found below leading through the various code paths.
33
34 // A simple macro to make this option mess easier to read
35 #[cfg(windows)]
36 macro_rules! otry {
37     ($expr:expr) => (match $expr {
38         Some(val) => val,
39         None => return None,
40     })
41 }
42
43 #[cfg(windows)]
44 mod registry;
45 #[cfg(windows)]
46 mod arch;
47
48 #[cfg(windows)]
49 mod platform {
50     use std::env;
51     use std::ffi::OsString;
52     use std::fs;
53     use std::path::{Path, PathBuf};
54     use std::process::Command;
55     use rustc::session::Session;
56     use super::arch::{host_arch, Arch};
57     use super::registry::LOCAL_MACHINE;
58
59     // First we need to figure out whether the environment is already correctly
60     // configured by vcvars. We do this by looking at the environment variable
61     // `VCINSTALLDIR` which is always set by vcvars, and unlikely to be set
62     // otherwise. If it is defined, then we find `link.exe` in `PATH and trust
63     // that everything else is configured correctly.
64     //
65     // If `VCINSTALLDIR` wasn't defined (or we couldn't find the linker where
66     // it claimed it should be), then we resort to finding everything
67     // ourselves. First we find where the latest version of MSVC is installed
68     // and what version it is. Then based on the version we find the
69     // appropriate SDKs.
70     //
71     // If despite our best efforts we are still unable to find MSVC then we
72     // just blindly call `link.exe` and hope for the best.
73     //
74     // This code only supports VC 11 through 15. For versions older than that
75     // the user will need to manually execute the appropriate vcvars bat file
76     // and it should hopefully work.
77     //
78     // The second member of the tuple we return is the directory for the host
79     // linker toolchain, which is necessary when using the cross linkers.
80     pub fn link_exe_cmd(sess: &Session) -> (Command, Option<PathBuf>) {
81         let arch = &sess.target.target.arch;
82         env::var_os("VCINSTALLDIR").and_then(|_| {
83             debug!("Detected that vcvars was already run.");
84             let path = otry!(env::var_os("PATH"));
85             // Mingw has its own link which is not the link we want so we
86             // look for `cl.exe` too as a precaution.
87             env::split_paths(&path).find(|path| {
88                 path.join("cl.exe").is_file()
89                     && path.join("link.exe").is_file()
90             }).map(|path| {
91                 (Command::new(path.join("link.exe")), None)
92             })
93         }).or_else(|| {
94             None.or_else(|| {
95                 find_msvc_latest(arch, "15.0")
96             }).or_else(|| {
97                 find_msvc_latest(arch, "14.0")
98             }).or_else(|| {
99                 find_msvc_12(arch)
100             }).or_else(|| {
101                 find_msvc_11(arch)
102             }).map(|(cmd, path)| (cmd, Some(path)))
103         }).unwrap_or_else(|| {
104             debug!("Failed to locate linker.");
105             (Command::new("link.exe"), None)
106         })
107     }
108
109     // For MSVC 14 or newer we need to find the Universal CRT as well as either
110     // the Windows 10 SDK or Windows 8.1 SDK.
111     fn find_msvc_latest(arch: &str, ver: &str) -> Option<(Command, PathBuf)> {
112         let vcdir = otry!(get_vc_dir(ver));
113         let (mut cmd, host) = otry!(get_linker(&vcdir, arch));
114         let sub = otry!(lib_subdir(arch));
115         let ucrt = otry!(get_ucrt_dir());
116         debug!("Found Universal CRT {:?}", ucrt);
117         add_lib(&mut cmd, &ucrt.join("ucrt").join(sub));
118         if let Some(dir) = get_sdk10_dir() {
119             debug!("Found Win10 SDK {:?}", dir);
120             add_lib(&mut cmd, &dir.join("um").join(sub));
121         } else if let Some(dir) = get_sdk81_dir() {
122             debug!("Found Win8.1 SDK {:?}", dir);
123             add_lib(&mut cmd, &dir.join("um").join(sub));
124         } else {
125             return None
126         }
127         Some((cmd, host))
128     }
129
130     // For MSVC 12 we need to find the Windows 8.1 SDK.
131     fn find_msvc_12(arch: &str) -> Option<(Command, PathBuf)> {
132         let vcdir = otry!(get_vc_dir("12.0"));
133         let (mut cmd, host) = otry!(get_linker(&vcdir, arch));
134         let sub = otry!(lib_subdir(arch));
135         let sdk81 = otry!(get_sdk81_dir());
136         debug!("Found Win8.1 SDK {:?}", sdk81);
137         add_lib(&mut cmd, &sdk81.join("um").join(sub));
138         Some((cmd, host))
139     }
140
141     // For MSVC 11 we need to find the Windows 8 SDK.
142     fn find_msvc_11(arch: &str) -> Option<(Command, PathBuf)> {
143         let vcdir = otry!(get_vc_dir("11.0"));
144         let (mut cmd, host) = otry!(get_linker(&vcdir, arch));
145         let sub = otry!(lib_subdir(arch));
146         let sdk8 = otry!(get_sdk8_dir());
147         debug!("Found Win8 SDK {:?}", sdk8);
148         add_lib(&mut cmd, &sdk8.join("um").join(sub));
149         Some((cmd, host))
150     }
151
152     // A convenience function to append library paths.
153     fn add_lib(cmd: &mut Command, lib: &Path) {
154         let mut arg: OsString = "/LIBPATH:".into();
155         arg.push(lib);
156         cmd.arg(arg);
157     }
158
159     // Given a possible MSVC installation directory, we look for the linker and
160     // then add the MSVC library path.
161     fn get_linker(path: &Path, arch: &str) -> Option<(Command, PathBuf)> {
162         debug!("Looking for linker in {:?}", path);
163         bin_subdir(arch).into_iter().map(|(sub, host)| {
164             (path.join("bin").join(sub).join("link.exe"),
165              path.join("bin").join(host))
166         }).filter(|&(ref path, _)| {
167             path.is_file()
168         }).map(|(path, host)| {
169             (Command::new(path), host)
170         }).filter_map(|(mut cmd, host)| {
171             let sub = otry!(vc_lib_subdir(arch));
172             add_lib(&mut cmd, &path.join("lib").join(sub));
173             Some((cmd, host))
174         }).next()
175     }
176
177     // To find MSVC we look in a specific registry key for the version we are
178     // trying to find.
179     fn get_vc_dir(ver: &str) -> Option<PathBuf> {
180         let key = otry!(LOCAL_MACHINE
181             .open(r"SOFTWARE\Microsoft\VisualStudio\SxS\VC7".as_ref()).ok());
182         let path = otry!(key.query_str(ver).ok());
183         Some(path.into())
184     }
185
186     // To find the Universal CRT we look in a specific registry key for where
187     // all the Universal CRTs are located and then sort them asciibetically to
188     // find the newest version. While this sort of sorting isn't ideal,  it is
189     // what vcvars does so that's good enough for us.
190     fn get_ucrt_dir() -> Option<PathBuf> {
191         let key = otry!(LOCAL_MACHINE
192             .open(r"SOFTWARE\Microsoft\Windows Kits\Installed Roots".as_ref()).ok());
193         let root = otry!(key.query_str("KitsRoot10").ok());
194         let readdir = otry!(fs::read_dir(Path::new(&root).join("lib")).ok());
195         readdir.filter_map(|dir| {
196             dir.ok()
197         }).map(|dir| {
198             dir.path()
199         }).filter(|dir| {
200             dir.components().last().and_then(|c| {
201                 c.as_os_str().to_str()
202             }).map(|c| {
203                 c.starts_with("10.") && dir.join("ucrt").is_dir()
204             }).unwrap_or(false)
205         }).max()
206     }
207
208     // Vcvars finds the correct version of the Windows 10 SDK by looking
209     // for the include `um\Windows.h` because sometimes a given version will
210     // only have UCRT bits without the rest of the SDK. Since we only care about
211     // libraries and not includes, we instead look for `um\x64\kernel32.lib`.
212     // Since the 32-bit and 64-bit libraries are always installed together we
213     // only need to bother checking x64, making this code a tiny bit simpler.
214     // Like we do for the Universal CRT, we sort the possibilities
215     // asciibetically to find the newest one as that is what vcvars does.
216     fn get_sdk10_dir() -> Option<PathBuf> {
217         let key = otry!(LOCAL_MACHINE
218             .open(r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0".as_ref()).ok());
219         let root = otry!(key.query_str("InstallationFolder").ok());
220         let readdir = otry!(fs::read_dir(Path::new(&root).join("lib")).ok());
221         let mut dirs: Vec<_> = readdir.filter_map(|dir| dir.ok())
222             .map(|dir| dir.path()).collect();
223         dirs.sort();
224         dirs.into_iter().rev().filter(|dir| {
225             dir.join("um").join("x64").join("kernel32.lib").is_file()
226         }).next()
227     }
228
229     // Interestingly there are several subdirectories, `win7` `win8` and
230     // `winv6.3`. Vcvars seems to only care about `winv6.3` though, so the same
231     // applies to us. Note that if we were targetting kernel mode drivers
232     // instead of user mode applications, we would care.
233     fn get_sdk81_dir() -> Option<PathBuf> {
234         let key = otry!(LOCAL_MACHINE
235             .open(r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1".as_ref()).ok());
236         let root = otry!(key.query_str("InstallationFolder").ok());
237         Some(Path::new(&root).join("lib").join("winv6.3"))
238     }
239
240     fn get_sdk8_dir() -> Option<PathBuf> {
241         let key = otry!(LOCAL_MACHINE
242             .open(r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.0".as_ref()).ok());
243         let root = otry!(key.query_str("InstallationFolder").ok());
244         Some(Path::new(&root).join("lib").join("win8"))
245     }
246
247     // When choosing the linker toolchain to use, we have to choose the one
248     // which matches the host architecture. Otherwise we end up in situations
249     // where someone on 32-bit Windows is trying to cross compile to 64-bit and
250     // it tries to invoke the native 64-bit linker which won't work.
251     //
252     // For the return value of this function, the first member of the tuple is
253     // the folder of the linker we will be invoking, while the second member
254     // is the folder of the host toolchain for that linker which is essential
255     // when using a cross linker. We return a Vec since on x64 there are often
256     // two linkers that can target the architecture we desire. The 64-bit host
257     // linker is preferred, and hence first, due to 64-bit allowing it more
258     // address space to work with and potentially being faster.
259     //
260     // FIXME - Figure out what happens when the host architecture is arm.
261     fn bin_subdir(arch: &str) -> Vec<(&'static str, &'static str)> {
262         match (arch, host_arch()) {
263             ("x86", Some(Arch::X86)) => vec![("", "")],
264             ("x86", Some(Arch::Amd64)) => vec![("amd64_x86", "amd64"), ("", "")],
265             ("x86_64", Some(Arch::X86)) => vec![("x86_amd64", "")],
266             ("x86_64", Some(Arch::Amd64)) => vec![("amd64", "amd64"), ("x86_amd64", "")],
267             ("arm", Some(Arch::X86)) => vec![("x86_arm", "")],
268             ("arm", Some(Arch::Amd64)) => vec![("amd64_arm", "amd64"), ("x86_arm", "")],
269             _ => vec![],
270         }
271     }
272
273     fn lib_subdir(arch: &str) -> Option<&'static str> {
274         match arch {
275             "x86" => Some("x86"),
276             "x86_64" => Some("x64"),
277             "arm" => Some("arm"),
278             _ => None,
279         }
280     }
281
282     // MSVC's x86 libraries are not in a subfolder
283     fn vc_lib_subdir(arch: &str) -> Option<&'static str> {
284         match arch {
285             "x86" => Some(""),
286             "x86_64" => Some("amd64"),
287             "arm" => Some("arm"),
288             _ => None,
289         }
290     }
291 }
292
293 // If we're not on Windows, then there's no registry to search through and MSVC
294 // wouldn't be able to run, so we just call `link.exe` and hope for the best.
295 #[cfg(not(windows))]
296 mod platform {
297     use std::path::PathBuf;
298     use std::process::Command;
299     use rustc::session::Session;
300     pub fn link_exe_cmd(_sess: &Session) -> (Command, Option<PathBuf>) {
301         (Command::new("link.exe"), None)
302     }
303 }
304
305 pub use self::platform::*;