Browse Source

Start probing for MSVC tools

Helps compiling for MSVC "just work" in as many situations as possible. Most of
this logic is just copied from the compiler.
add-rc-path
Alex Crichton 9 years ago
parent
commit
60aaeb79f8
  1. 13
      Cargo.toml
  2. 6
      gcc-test/build.rs
  3. 3
      gcc-test/src/lib.rs
  4. 3
      gcc-test/src/windows.c
  5. 8
      gcc-test/tests/all.rs
  6. 71
      src/lib.rs
  7. 131
      src/registry.rs
  8. 297
      src/windows_registry.rs

13
Cargo.toml

@ -12,3 +12,16 @@ C compiler to compile native C code into a static archive to be linked into Rust
code.
"""
keywords = ["build-dependencies"]
[target.i686-pc-windows-msvc.dependencies]
winapi = "0.2.1"
advapi32-sys = "0.1.2"
[target.x86_64-pc-windows-msvc.dependencies]
winapi = "0.2.1"
advapi32-sys = "0.1.2"
[target.i686-pc-windows-gnu.dependencies]
winapi = "0.2.1"
advapi32-sys = "0.1.2"
[target.x86_64-pc-windows-gnu.dependencies]
winapi = "0.2.1"
advapi32-sys = "0.1.2"

6
gcc-test/build.rs

@ -25,4 +25,10 @@ fn main() {
.file("src/baz.cpp")
.cpp(true)
.compile("libbaz.a");
if target.contains("windows") {
gcc::Config::new()
.file("src/windows.c")
.compile("libwindows.a");
}
}

3
gcc-test/src/lib.rs

@ -7,4 +7,7 @@ extern {
pub fn asm() -> i32;
pub fn baz() -> i32;
#[cfg(windows)]
pub fn windows();
}

3
gcc-test/src/windows.c

@ -0,0 +1,3 @@
#include <windows.h>
void windows() {}

8
gcc-test/tests/all.rs

@ -30,3 +30,11 @@ fn baz_here() {
assert_eq!(baz(), 8);
}
}
#[test]
#[cfg(windows)]
fn windows_here() {
unsafe {
windows();
}
}

71
src/lib.rs

@ -52,6 +52,10 @@ use std::io;
use std::path::{PathBuf, Path};
use std::process::{Command, Stdio};
#[cfg(windows)]
mod registry;
pub mod windows_registry;
/// Extra configuration to pass to gcc.
pub struct Config {
include_directories: Vec<PathBuf>,
@ -246,7 +250,7 @@ impl Config {
fn compile_object(&self, file: &Path, dst: &Path) {
let is_asm = file.extension().and_then(|s| s.to_str()) == Some("asm");
let msvc = self.target.contains("msvc");
let mut cmd = if msvc && is_asm {
let (mut cmd, name) = if msvc && is_asm {
self.msvc_macro_assembler()
} else {
self.compile_cmd()
@ -269,40 +273,52 @@ impl Config {
}
cmd.arg(file);
run(&mut cmd, &self.compiler());
run(&mut cmd, &name);
}
fn compiler(&self) -> String {
fn compiler(&self) -> (Command, String) {
let (env, msvc, gnu, default) = if self.cpp {
("CXX", "cl", "g++", "c++")
} else {
("CC", "cl", "gcc", "cc")
};
get_var(env).unwrap_or(if self.target.contains("windows") {
if self.target.contains("msvc") {
msvc.to_string()
get_var(env).ok().map(|env| {
let fname = Path::new(&env).file_name().unwrap().to_string_lossy()
.into_owned();
(Command::new(env), fname)
}).or_else(|| {
windows_registry::find(&self.target, "cl.exe").map(|cmd| {
(cmd, "cl.exe".to_string())
})
}).unwrap_or_else(|| {
let compiler = if self.target.contains("windows") {
if self.target.contains("msvc") {
msvc.to_string()
} else {
gnu.to_string()
}
} else if self.target.contains("android") {
format!("{}-{}", self.target, gnu)
} else {
gnu.to_string()
}
} else if self.target.contains("android") {
format!("{}-{}", self.target, gnu)
} else {
default.to_string()
default.to_string()
};
(Command::new(compiler.clone()), compiler)
})
}
fn compile_cmd(&self) -> Command {
fn compile_cmd(&self) -> (Command, String) {
let opt_level = getenv_unwrap("OPT_LEVEL");
let profile = getenv_unwrap("PROFILE");
let msvc = self.target.contains("msvc");
println!("{} {}", profile, opt_level);
let mut cmd = Command::new(self.compiler());
let (mut cmd, name) = self.compiler();
if msvc {
cmd.arg("/MD"); // link against msvcrt.dll for now
cmd.arg(format!("/O{}", opt_level));
if opt_level != "0" {
cmd.arg("/O2");
}
} else {
cmd.arg(format!("-O{}", opt_level));
cmd.arg("-c");
@ -355,15 +371,14 @@ impl Config {
cmd.arg(&format!("{}D{}", lead, key));
}
}
return cmd
(cmd, name)
}
fn msvc_macro_assembler(&self) -> Command {
let mut cmd = if self.target.contains("x86_64") {
Command::new("ml64.exe")
} else {
Command::new("ml.exe")
};
fn msvc_macro_assembler(&self) -> (Command, String) {
let tool = if self.target.contains("x86_64") {"ml64.exe"} else {"ml.exe"};
let mut cmd = windows_registry::find(&self.target, tool).unwrap_or_else(|| {
Command::new(tool)
});
for directory in self.include_directories.iter() {
cmd.arg("/I").arg(directory);
}
@ -374,16 +389,18 @@ impl Config {
cmd.arg(&format!("/D{}", key));
}
}
return cmd
(cmd, tool.to_string())
}
fn assemble(&self, lib_name: &str, dst: &Path, objects: &[PathBuf]) {
if self.target.contains("msvc") {
let cmd = windows_registry::find(&self.target, "lib.exe");
let mut cmd = cmd.unwrap_or(Command::new("lib.exe"));
let mut out = OsString::from("/OUT:");
out.push(dst);
run(Command::new("lib.exe").arg(out).arg("/nologo")
.args(objects)
.args(&self.objects), "lib.exe");
run(cmd.arg(out).arg("/nologo")
.args(objects)
.args(&self.objects), "lib.exe");
// The Rust compiler will look for libfoo.a and foo.lib, but the
// MSVC linker will also be passed foo.lib, so be sure that both

131
src/registry.rs

@ -0,0 +1,131 @@
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
extern crate winapi;
extern crate advapi32;
use std::io;
use std::ffi::{OsString, OsStr};
use std::os::windows::prelude::*;
use std::ops::RangeFrom;
use self::winapi::*;
use self::advapi32::*;
pub struct RegistryKey(Repr);
struct OwnedKey(HKEY);
enum Repr {
Const(HKEY),
Owned(OwnedKey),
}
pub struct Iter<'a> {
idx: RangeFrom<DWORD>,
key: &'a RegistryKey,
}
unsafe impl Sync for Repr {}
unsafe impl Send for Repr {}
pub static LOCAL_MACHINE: RegistryKey =
RegistryKey(Repr::Const(HKEY_LOCAL_MACHINE));
impl RegistryKey {
fn raw(&self) -> HKEY {
match self.0 {
Repr::Const(val) => val,
Repr::Owned(ref val) => val.0,
}
}
pub fn open(&self, key: &OsStr) -> io::Result<RegistryKey> {
let key = key.encode_wide().chain(Some(0)).collect::<Vec<_>>();
let mut ret = 0 as *mut _;
let err = unsafe {
RegOpenKeyExW(self.raw(), key.as_ptr(), 0,
KEY_READ | KEY_WOW64_32KEY, &mut ret)
};
if err == ERROR_SUCCESS as LONG {
Ok(RegistryKey(Repr::Owned(OwnedKey(ret))))
} else {
Err(io::Error::from_raw_os_error(err as i32))
}
}
pub fn iter(&self) -> Iter {
Iter { idx: 0.., key: self }
}
pub fn query_str(&self, name: &str) -> io::Result<OsString> {
let name: &OsStr = name.as_ref();
let name = name.encode_wide().chain(Some(0)).collect::<Vec<_>>();
let mut len = 0;
let mut kind = 0;
unsafe {
let err = RegQueryValueExW(self.raw(), name.as_ptr(), 0 as *mut _,
&mut kind, 0 as *mut _, &mut len);
if err != ERROR_SUCCESS as LONG {
return Err(io::Error::from_raw_os_error(err as i32))
}
if kind != REG_SZ {
return Err(io::Error::new(io::ErrorKind::Other,
"registry key wasn't a string"))
}
// The length here is the length in bytes, but we're using wide
// characters so we need to be sure to halve it for the capacity
// passed in.
let mut v = Vec::with_capacity(len as usize / 2);
let err = RegQueryValueExW(self.raw(), name.as_ptr(), 0 as *mut _,
0 as *mut _, v.as_mut_ptr() as *mut _,
&mut len);
if err != ERROR_SUCCESS as LONG {
return Err(io::Error::from_raw_os_error(err as i32))
}
v.set_len(len as usize / 2);
// Some registry keys may have a terminating nul character, but
// we're not interested in that, so chop it off if it's there.
if v[v.len() - 1] == 0 {
v.pop();
}
Ok(OsString::from_wide(&v))
}
}
}
impl Drop for OwnedKey {
fn drop(&mut self) {
unsafe { RegCloseKey(self.0); }
}
}
impl<'a> Iterator for Iter<'a> {
type Item = io::Result<OsString>;
fn next(&mut self) -> Option<io::Result<OsString>> {
self.idx.next().and_then(|i| unsafe {
let mut v = Vec::with_capacity(256);
let mut len = v.capacity() as DWORD;
let ret = RegEnumKeyExW(self.key.raw(), i, v.as_mut_ptr(), &mut len,
0 as *mut _, 0 as *mut _, 0 as *mut _,
0 as *mut _);
if ret == ERROR_NO_MORE_ITEMS as LONG {
None
} else if ret != ERROR_SUCCESS as LONG {
Some(Err(io::Error::from_raw_os_error(ret as i32)))
} else {
v.set_len(len as usize);
Some(Ok(OsString::from_wide(&v)))
}
})
}
}

297
src/windows_registry.rs

@ -0,0 +1,297 @@
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::process::Command;
/// Attempts to find a tool within an MSVC installation using the Windows
/// registry as a point to search from.
///
/// The `target` argument is the target that the tool should work for (e.g.
/// compile or link for) and the `tool` argument is the tool to find (e.g.
/// `cl.exe` or `link.exe`).
///
/// This function will return `None` if the tool could not be found, or it will
/// return `Some(cmd)` which represents a command that's ready to execute the
/// tool with the appropriate environment variables set.
///
/// Note that this function always returns `None` for non-MSVC targets.
#[cfg(not(windows))]
pub fn find(_target: &str, _tool: &str) -> Option<Command> {
None
}
#[cfg(windows)]
pub fn find(target: &str, tool: &str) -> Option<Command> {
use std::env;
use std::ffi::OsString;
use std::fs;
use std::path::PathBuf;
use registry::{RegistryKey, LOCAL_MACHINE};
if !target.contains("msvc") { return None }
// 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
};
let vs_install_dir = get_vs_install_dir();
let mut path_to_add = None;
// 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
}
}).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()
})
})
}).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| {
Command::new(tool)
}).unwrap_or_else(|| {
Command::new(tool)
});
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);
}
}
if let Some(path) = env::var_os("PATH") {
paths.extend(env::split_paths(&path));
}
cmd.env("PATH", env::join_paths(&paths).unwrap());
// 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.
//
// 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((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());
}
cmd.env("INCLUDE", env::join_paths(&includes).unwrap());
}
// 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(path) = get_windows_sdk_lib_path(target) {
libs.push(path);
}
cmd.env("LIB", env::join_paths(&libs).unwrap());
}
return Some(cmd);
// 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
}
})
}
// Given a registry key, look at all the sub keys and find the one which has
// the maximal numeric value.
//
// Returns the name of the maximal key as well as the opened maximal key.
fn max_version(key: &RegistryKey) -> Option<(OsString, RegistryKey)> {
let mut max_vers = 0;
let mut max_key = None;
for subkey in key.iter().filter_map(|k| k.ok()) {
let val = subkey.to_str().and_then(|s| {
s.trim_left_matches("v").replace(".", "").parse().ok()
});
let val = match val {
Some(s) => s,
None => continue,
};
if val > max_vers {
if let Ok(k) = key.open(&subkey) {
max_vers = val;
max_key = Some((subkey, k));
}
}
}
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
}
}
}
Loading…
Cancel
Save