Browse Source

Merge pull request #85 from alexcrichton/update-msvc-logic

Update MSVC logic for most recent VS
add-rc-path
Alex Crichton 9 years ago
committed by GitHub
parent
commit
6769da7ca2
  1. 2
      gcc-test/build.rs
  2. 560
      src/windows_registry.rs

2
gcc-test/build.rs

@ -46,6 +46,7 @@ fn main() {
if target.contains("msvc") {
let out = out.join("tmp");
fs::create_dir(&out).unwrap();
println!("nmake 1");
let status = gcc::windows_registry::find(&target, "nmake.exe").unwrap()
.arg("/fsrc/NMakefile")
.env("OUT_DIR", &out)
@ -60,6 +61,7 @@ fn main() {
env::remove_var("VCINSTALLDIR");
env::remove_var("INCLUDE");
env::remove_var("LIB");
println!("nmake 2");
let status = gcc::windows_registry::find(&target, "nmake.exe").unwrap()
.arg("/fsrc/NMakefile")
.env("OUT_DIR", &out)

560
src/windows_registry.rs

@ -15,6 +15,13 @@ use std::process::Command;
use Tool;
macro_rules! otry {
($expr:expr) => (match $expr {
Some(val) => val,
None => return None,
})
}
/// Attempts to find a tool within an MSVC installation using the Windows
/// registry as a point to search from.
///
@ -44,184 +51,323 @@ pub fn find_tool(_target: &str, _tool: &str) -> Option<Tool> {
pub fn find_tool(target: &str, tool: &str) -> Option<Tool> {
use std::env;
use std::ffi::OsString;
use std::io;
use std::fs;
use std::mem;
use std::path::{Path, PathBuf};
use registry::{RegistryKey, LOCAL_MACHINE};
if !target.contains("msvc") { return None }
struct MsvcTool {
tool: PathBuf,
libs: Vec<PathBuf>,
path: Vec<PathBuf>,
include: Vec<PathBuf>,
}
impl MsvcTool {
fn new(tool: PathBuf) -> MsvcTool {
MsvcTool {
tool: tool,
libs: Vec::new(),
path: Vec::new(),
include: Vec::new(),
}
}
fn into_tool(self) -> Tool {
let MsvcTool { tool, libs, path, include } = self;
let mut tool = Tool::new(tool.into());
add_env(&mut tool, "LIB", libs);
add_env(&mut tool, "PATH", path);
add_env(&mut tool, "INCLUDE", include);
return tool
}
}
// This logic is all tailored for MSVC, if we're not that then bail out
// early.
if !target.contains("msvc") {
return None
}
// Looks like msbuild isn't located in the same location as other tools like
// cl.exe and lib.exe. To handle this we probe for it manually with
// dedicated registry keys.
if tool.contains("msbuild") {
return find_msbuild(target)
}
// When finding binaries the 32-bit version is at the top level but the
// versions to cross to other architectures are stored in sub-folders.
// Unknown architectures also just bail out early to return the standard
// `link.exe` command.
let extra = if target.starts_with("i686") {
""
} else if target.starts_with("x86_64") {
"amd64"
} else if target.starts_with("arm") {
"arm"
} else {
return None
};
// If VCINSTALLDIR is set, then someone's probably already run vcvars and we
// should just find whatever that indicates.
if env::var_os("VCINSTALLDIR").is_some() {
return env::var_os("PATH").and_then(|path| {
env::split_paths(&path).map(|p| p.join(tool)).find(|p| p.exists())
}).map(|path| {
Tool::new(path.into())
})
}
let vs_install_dir = get_vs_install_dir();
let mut path_to_add = None;
// Ok, if we're here, now comes the fun part of the probing. Default shells
// or shells like MSYS aren't really configured to execute `cl.exe` and the
// various compiler tools shipped as part of Visual Studio. Here we try to
// first find the relevant tool, then we also have to be sure to fill in
// environment variables like `LIB`, `INCLUDE`, and `PATH` to ensure that
// the tool is actually usable.
// First up, we need to find the `link.exe` binary itself, and there's a few
// locations that we can look. First up is the standard VCINSTALLDIR
// environment variable which is normally set by the vcvarsall.bat file. If
// an environment is set up manually by whomever's driving the compiler then
// we shouldn't muck with that decision and should instead respect that.
//
// Finally we read the Windows registry to discover the VS install root.
// From here we probe just to make sure that it exists.
let mut cmd = env::var_os("VCINSTALLDIR").and_then(|dir| {
let mut p = PathBuf::from(dir);
p.push("bin");
p.push(extra);
let tool = p.join(tool);
if fs::metadata(&tool).is_ok() {
path_to_add = Some(p);
Some(tool)
} else {
None
}
return find_msvc_latest(tool, target, "15.0").or_else(|| {
find_msvc_latest(tool, target, "14.0")
}).or_else(|| {
env::var_os("PATH").and_then(|path| {
env::split_paths(&path).map(|p| p.join(tool)).find(|path| {
fs::metadata(path).is_ok()
})
})
find_msvc_12(tool, target)
}).or_else(|| {
vs_install_dir.as_ref().and_then(|p| {
let mut p = p.join("VC/bin");
p.push(extra);
let tool = p.join(tool);
if fs::metadata(&tool).is_ok() {
path_to_add = Some(p);
Some(tool)
} else {
None
}
})
}).map(|tool| {
Tool::new(tool.into())
}).unwrap_or_else(|| {
Tool::new(tool.into())
find_msvc_11(tool, target)
});
let mut paths = Vec::new();
if let Some(path) = path_to_add {
paths.push(path);
if let Some(root) = get_windows_sdk_bin_path(target) {
paths.push(root);
// For MSVC 14 or newer we need to find the Universal CRT as well as either
// the Windows 10 SDK or Windows 8.1 SDK.
fn find_msvc_latest(tool: &str, target: &str, ver: &str) -> Option<Tool> {
let vcdir = otry!(get_vc_dir(ver));
let mut tool = otry!(get_tool(tool, &vcdir, target));
let sub = otry!(lib_subdir(target));
let (ucrt, ucrt_version) = otry!(get_ucrt_dir());
let ucrt_include = ucrt.join("Include").join(&ucrt_version);
tool.include.push(ucrt_include.join("ucrt"));
tool.include.push(ucrt_include.join("um"));
tool.include.push(ucrt_include.join("winrt"));
tool.include.push(ucrt_include.join("shared"));
let ucrt_lib = ucrt.join("Lib").join(&ucrt_version);
tool.libs.push(ucrt_lib.join("ucrt").join(sub));
if let Some(dir) = get_sdk10_dir() {
tool.libs.push(dir.join("um").join(sub));
tool.path.push(dir.join("bin").join(sub));
tool.include.push(dir.join("include/shared"));
tool.include.push(dir.join("include/um"));
tool.include.push(dir.join("include/winrt"));
} else if let Some(dir) = get_sdk81_dir() {
tool.libs.push(dir.join("um").join(sub));
tool.path.push(dir.join("bin").join(sub));
tool.include.push(dir.join("include/shared"));
tool.include.push(dir.join("include/um"));
tool.include.push(dir.join("include/winrt"));
} else {
return None
}
Some(tool.into_tool())
}
// For MSVC 12 we need to find the Windows 8.1 SDK.
fn find_msvc_12(tool: &str, target: &str) -> Option<Tool> {
let vcdir = otry!(get_vc_dir("12.0"));
let mut tool = otry!(get_tool(tool, &vcdir, target));
let sub = otry!(lib_subdir(target));
let sdk81 = otry!(get_sdk81_dir());
tool.libs.push(sdk81.join("um").join(sub));
tool.path.push(sdk81.join("bin").join(sub));
tool.include.push(sdk81.join("include/shared"));
tool.include.push(sdk81.join("include/um"));
tool.include.push(sdk81.join("include/winrt"));
Some(tool.into_tool())
}
// For MSVC 11 we need to find the Windows 8 SDK.
fn find_msvc_11(tool: &str, target: &str) -> Option<Tool> {
let vcdir = otry!(get_vc_dir("11.0"));
let mut tool = otry!(get_tool(tool, &vcdir, target));
let sub = otry!(lib_subdir(target));
let sdk8 = otry!(get_sdk8_dir());
tool.libs.push(sdk8.join("um").join(sub));
tool.path.push(sdk8.join("bin").join(sub));
tool.include.push(sdk8.join("include/shared"));
tool.include.push(sdk8.join("include/um"));
tool.include.push(sdk8.join("include/winrt"));
Some(tool.into_tool())
}
fn add_env(tool: &mut Tool, env: &str, paths: Vec<PathBuf>) {
let prev = env::var_os(env).unwrap_or(OsString::new());
let prev = env::split_paths(&prev);
let new = paths.into_iter().chain(prev);
tool.env.push((env.to_string().into(), env::join_paths(new).unwrap()));
}
// Given a possible MSVC installation directory, we look for the linker and
// then add the MSVC library path.
fn get_tool(tool: &str, path: &Path, target: &str) -> Option<MsvcTool> {
bin_subdir(target).into_iter().map(|(sub, host)| {
(path.join("bin").join(sub).join(tool),
path.join("bin").join(host))
}).filter(|&(ref path, _)| {
path.is_file()
}).map(|(path, host)| {
let mut tool = MsvcTool::new(path);
tool.path.push(host);
tool
}).filter_map(|mut tool| {
let sub = otry!(vc_lib_subdir(target));
tool.libs.push(path.join("lib").join(sub));
tool.include.push(path.join("include"));
Some(tool)
}).next()
}
if let Some(path) = env::var_os("PATH") {
paths.extend(env::split_paths(&path));
// To find MSVC we look in a specific registry key for the version we are
// trying to find.
fn get_vc_dir(ver: &str) -> Option<PathBuf> {
let key = r"SOFTWARE\Microsoft\VisualStudio\SxS\VC7";
let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok());
let path = otry!(key.query_str(ver).ok());
Some(path.into())
}
cmd.env.push(("PATH".into(), env::join_paths(&paths).unwrap().into()));
// The MSVC compiler uses the INCLUDE environment variable as the default
// lookup path for headers. This environment variable is normally set up
// by the VS shells, so we only want to start adding our own pieces if it's
// not set.
// To find the Universal CRT we look in a specific registry key for where
// all the Universal CRTs are located and then sort them asciibetically to
// find the newest version. While this sort of sorting isn't ideal, it is
// what vcvars does so that's good enough for us.
//
// If we're adding our own pieces, then we need to add two primary
// directories to the default search path for the linker. The first is in
// the VS install direcotry and the next is the Windows SDK directory.
if env::var_os("INCLUDE").is_none() {
let mut includes = Vec::new();
if let Some(ref vs_install_dir) = vs_install_dir {
includes.push(vs_install_dir.join("VC/include"));
if let Some((ucrt_root, vers)) = ucrt_install_dir(vs_install_dir) {
let include = ucrt_root.join("Include").join(vers);
includes.push(include.join("ucrt"));
includes.push(include.join("um"));
includes.push(include.join("winrt"));
includes.push(include.join("shared"));
}
}
if let Some((path, major)) = get_windows_sdk_path() {
if major >= 8 {
includes.push(path.join("include/shared"));
includes.push(path.join("include/um"));
includes.push(path.join("include/winrt"));
} else {
includes.push(path.join("include"));
}
} else if let Some(ref vs_install_dir) = vs_install_dir {
includes.push(vs_install_dir.clone());
// Returns a pair of (root, version) for the ucrt dir if found
fn get_ucrt_dir() -> Option<(PathBuf, String)> {
let key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots";
let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok());
let root = otry!(key.query_str("KitsRoot10").ok());
let readdir = otry!(Path::new(&root).join("lib").read_dir().ok());
let max_libdir = otry!(readdir.filter_map(|dir| {
dir.ok()
}).map(|dir| {
dir.path()
}).filter(|dir| {
dir.components().last().and_then(|c| {
c.as_os_str().to_str()
}).map(|c| {
c.starts_with("10.") && dir.join("ucrt").is_dir()
}).unwrap_or(false)
}).max());
let version = max_libdir.components().last().unwrap();
let version = version.as_os_str().to_str().unwrap().to_string();
Some((root.into(), version))
}
// Vcvars finds the correct version of the Windows 10 SDK by looking
// for the include `um\Windows.h` because sometimes a given version will
// only have UCRT bits without the rest of the SDK. Since we only care about
// libraries and not includes, we instead look for `um\x64\kernel32.lib`.
// Since the 32-bit and 64-bit libraries are always installed together we
// only need to bother checking x64, making this code a tiny bit simpler.
// Like we do for the Universal CRT, we sort the possibilities
// asciibetically to find the newest one as that is what vcvars does.
fn get_sdk10_dir() -> Option<PathBuf> {
let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v10.0";
let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok());
let root = otry!(key.query_str("InstallationFolder").ok());
let readdir = otry!(Path::new(&root).join("lib").read_dir().ok());
let mut dirs = readdir.filter_map(|dir| dir.ok())
.map(|dir| dir.path())
.collect::<Vec<_>>();
dirs.sort();
dirs.into_iter().rev().filter(|dir| {
dir.join("um").join("x64").join("kernel32.lib").is_file()
}).next()
}
// Interestingly there are several subdirectories, `win7` `win8` and
// `winv6.3`. Vcvars seems to only care about `winv6.3` though, so the same
// applies to us. Note that if we were targetting kernel mode drivers
// instead of user mode applications, we would care.
fn get_sdk81_dir() -> Option<PathBuf> {
let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.1";
let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok());
let root = otry!(key.query_str("InstallationFolder").ok());
Some(Path::new(&root).join("lib").join("winv6.3"))
}
fn get_sdk8_dir() -> Option<PathBuf> {
let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.0";
let key = otry!(LOCAL_MACHINE.open(key.as_ref()).ok());
let root = otry!(key.query_str("InstallationFolder").ok());
Some(Path::new(&root).join("lib").join("win8"))
}
const PROCESSOR_ARCHITECTURE_INTEL: u16 = 0;
const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9;
const X86: u16 = PROCESSOR_ARCHITECTURE_INTEL;
const X86_64: u16 = PROCESSOR_ARCHITECTURE_AMD64;
// When choosing the tool to use, we have to choose the one which matches
// the target architecture. Otherwise we end up in situations where someone
// on 32-bit Windows is trying to cross compile to 64-bit and it tries to
// invoke the native 64-bit compiler which won't work.
//
// For the return value of this function, the first member of the tuple is
// the folder of the tool we will be invoking, while the second member is
// the folder of the host toolchain for that tool which is essential when
// using a cross linker. We return a Vec since on x64 there are often two
// linkers that can target the architecture we desire. The 64-bit host
// linker is preferred, and hence first, due to 64-bit allowing it more
// address space to work with and potentially being faster.
fn bin_subdir(target: &str) -> Vec<(&'static str, &'static str)> {
let arch = target.split('-').next().unwrap();
match (arch, host_arch()) {
("i686", X86) => vec![("", "")],
("i686", X86_64) => vec![("amd64_x86", "amd64"), ("", "")],
("x86_64", X86) => vec![("x86_amd64", "")],
("x86_64", X86_64) => vec![("amd64", "amd64"), ("x86_amd64", "")],
("arm", X86) => vec![("x86_arm", "")],
("arm", X86_64) => vec![("amd64_arm", "amd64"), ("x86_arm", "")],
_ => vec![],
}
cmd.env.push(("INCLUDE".into(),
env::join_paths(&includes).unwrap().into()));
}
// Similarly with INCLUDE above, let's set LIB if it's not defined.
if env::var_os("LIB").is_none() {
let mut libs = Vec::new();
if let Some(ref vs_install_dir) = vs_install_dir {
libs.push(vs_install_dir.join("VC/lib").join(extra));
if let Some((ucrt_root, vers)) = ucrt_install_dir(vs_install_dir) {
if let Some(arch) = windows_sdk_v8_subdir(target) {
let lib = ucrt_root.join("Lib").join(vers);
libs.push(lib.join("ucrt").join(arch));
libs.push(lib.join("um").join(arch));
}
}
fn lib_subdir(target: &str) -> Option<&'static str> {
let arch = target.split('-').next().unwrap();
match arch {
"i686" => Some("x86"),
"x86_64" => Some("x64"),
"arm" => Some("arm"),
_ => None,
}
if let Some(path) = get_windows_sdk_lib_path(target) {
libs.push(path);
}
// MSVC's x86 libraries are not in a subfolder
fn vc_lib_subdir(target: &str) -> Option<&'static str> {
let arch = target.split('-').next().unwrap();
match arch {
"i686" => Some(""),
"x86_64" => Some("amd64"),
"arm" => Some("arm"),
_ => None,
}
cmd.env.push(("LIB".into(), env::join_paths(&libs).unwrap().into()));
}
return Some(cmd);
#[allow(bad_style)]
pub fn host_arch() -> u16 {
type DWORD = u32;
type WORD = u16;
type LPVOID = *mut u8;
type DWORD_PTR = usize;
// When looking for the Visual Studio installation directory we look in a
// number of locations in varying degrees of precedence:
//
// 1. The Visual Studio registry keys
// 2. The Visual Studio Express registry keys
// 3. A number of somewhat standard environment variables
//
// If we find a hit from any of these keys then we strip off the IDE/Tools
// folders which are typically found at the end.
//
// As a final note, when we take a look at the registry keys they're
// typically found underneath the version of what's installed, but we don't
// quite know what's installed. As a result we probe all sub-keys of the two
// keys we're looking at to find out the maximum version of what's installed
// and we use that root directory.
fn get_vs_install_dir() -> Option<PathBuf> {
LOCAL_MACHINE.open(r"SOFTWARE\Microsoft\VisualStudio".as_ref()).or_else(|_| {
LOCAL_MACHINE.open(r"SOFTWARE\Microsoft\VCExpress".as_ref())
}).ok().and_then(|key| {
max_version(&key).and_then(|(_vers, key)| {
key.query_str("InstallDir").ok()
})
}).or_else(|| {
env::var_os("VS120COMNTOOLS")
}).or_else(|| {
env::var_os("VS100COMNTOOLS")
}).or_else(|| {
env::var_os("VS90COMNTOOLS")
}).or_else(|| {
env::var_os("VS80COMNTOOLS")
}).map(PathBuf::from).and_then(|mut dir| {
if dir.ends_with("Common7/IDE") || dir.ends_with("Common7/Tools") {
dir.pop();
dir.pop();
Some(dir)
} else {
None
}
})
#[repr(C)]
struct SYSTEM_INFO {
wProcessorArchitecture: WORD,
_wReserved: WORD,
_dwPageSize: DWORD,
_lpMinimumApplicationAddress: LPVOID,
_lpMaximumApplicationAddress: LPVOID,
_dwActiveProcessorMask: DWORD_PTR,
_dwNumberOfProcessors: DWORD,
_dwProcessorType: DWORD,
_dwAllocationGranularity: DWORD,
_wProcessorLevel: WORD,
_wProcessorRevision: WORD,
}
extern "system" {
fn GetNativeSystemInfo(lpSystemInfo: *mut SYSTEM_INFO);
}
unsafe {
let mut info = mem::zeroed();
GetNativeSystemInfo(&mut info);
info.wProcessorArchitecture
}
}
// Given a registry key, look at all the sub keys and find the one which has
@ -249,118 +395,6 @@ pub fn find_tool(target: &str, tool: &str) -> Option<Tool> {
return max_key
}
fn get_windows_sdk_path() -> Option<(PathBuf, usize)> {
let key = r"SOFTWARE\Microsoft\Microsoft SDKs\Windows";
let key = LOCAL_MACHINE.open(key.as_ref());
let (n, k) = match key.ok().as_ref().and_then(max_version) {
Some(p) => p,
None => return None,
};
let mut parts = n.to_str().unwrap().trim_left_matches("v").splitn(2, ".");
let major = parts.next().unwrap().parse::<usize>().unwrap();
let _minor = parts.next().unwrap().parse::<usize>().unwrap();
k.query_str("InstallationFolder").ok().map(|p| {
(PathBuf::from(p), major)
})
}
fn get_windows_sdk_lib_path(target: &str) -> Option<PathBuf> {
let (mut root, major) = match get_windows_sdk_path() {
Some(pair) => pair,
None => return None,
};
root.push("Lib");
if major <= 7 {
// In Windows SDK 7.x, x86 libraries are directly in the Lib
// folder, x64 libraries are inside, and it's not necessary to
// link agains the SDK 7.x when targeting ARM or other
// architectures.
if target.starts_with("i686") {
Some(root)
} else if target.starts_with("x86_64") {
Some(root.join("x64"))
} else {
None
}
} else {
// Windows SDK 8.x installes libraries in a folder whose names
// depend on the version of the OS you're targeting. By default
// choose the newest, which usually corresponds to the version of
// the OS you've installed the SDK on.
let extra = match windows_sdk_v8_subdir(target) {
Some(extra) => extra,
None => return None,
};
["winv6.3", "win8", "win7"].iter().map(|p| root.join(p)).find(|part| {
fs::metadata(part).is_ok()
}).map(|path| {
path.join("um").join(extra)
})
}
}
fn get_windows_sdk_bin_path(target: &str) -> Option<PathBuf> {
let (mut root, major) = match get_windows_sdk_path() {
Some(pair) => pair,
None => return None,
};
root.push("bin");
if major <= 7 {
None // untested path, not sure if this dir exists
} else {
root.push(match windows_sdk_v8_subdir(target) {
Some(extra) => extra,
None => return None,
});
if fs::metadata(&root).is_ok() {Some(root)} else {None}
}
}
fn windows_sdk_v8_subdir(target: &str) -> Option<&'static str> {
if target.starts_with("i686") {
Some("x86")
} else if target.starts_with("x86_64") {
Some("x64")
} else if target.starts_with("arm") {
Some("arm")
} else {
None
}
}
fn ucrt_install_dir(vs_install_dir: &Path) -> Option<(PathBuf, String)> {
let is_vs_14 = vs_install_dir.iter().filter_map(|p| p.to_str()).any(|s| {
s == "Microsoft Visual Studio 14.0"
});
if !is_vs_14 {
return None
}
let key = r"SOFTWARE\Microsoft\Windows Kits\Installed Roots";
let sdk_dir = LOCAL_MACHINE.open(key.as_ref()).and_then(|p| {
p.query_str("KitsRoot10")
}).map(PathBuf::from);
let sdk_dir = match sdk_dir {
Ok(p) => p,
Err(..) => return None,
};
(move || -> io::Result<_> {
let mut max = None;
let mut max_s = None;
for entry in try!(fs::read_dir(&sdk_dir.join("Lib"))) {
let entry = try!(entry);
if let Ok(s) = entry.file_name().into_string() {
if let Ok(u) = s.replace(".", "").parse::<usize>() {
if Some(u) > max {
max = Some(u);
max_s = Some(s);
}
}
}
}
Ok(max_s.map(|m| (sdk_dir, m)))
})().ok().and_then(|x| x)
}
// see http://stackoverflow.com/questions/328017/path-to-msbuild
fn find_msbuild(target: &str) -> Option<Tool> {
let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions";

Loading…
Cancel
Save