diff options
Diffstat (limited to 'gcc')
-rw-r--r-- | gcc/.travis.yml | 5 | ||||
-rw-r--r-- | gcc/Cargo.toml | 4 | ||||
-rw-r--r-- | gcc/src/com.rs | 125 | ||||
-rw-r--r-- | gcc/src/lib.rs | 108 | ||||
-rw-r--r-- | gcc/src/setup_config.rs | 257 | ||||
-rw-r--r-- | gcc/src/winapi.rs | 214 | ||||
-rw-r--r-- | gcc/src/windows_registry.rs | 267 | ||||
-rw-r--r-- | gcc/tests/test.rs | 25 |
8 files changed, 939 insertions, 66 deletions
diff --git a/gcc/.travis.yml b/gcc/.travis.yml index 10d3d13..ff57da5 100644 --- a/gcc/.travis.yml +++ b/gcc/.travis.yml @@ -14,8 +14,7 @@ sudo: false install: - if [ "$TRAVIS_OS_NAME" = "linux" ]; then OS=unknown-linux-gnu; else OS=apple-darwin; fi - export TARGET=$ARCH-$OS - - curl https://static.rust-lang.org/rustup.sh | - sh -s -- --add-target=$TARGET --disable-sudo -y --prefix=`rustc --print sysroot` + - if [ -z "$NO_ADD" ]; then rustup target add $TARGET; fi before_script: - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH script: @@ -35,7 +34,7 @@ env: secure: "CBtqrudgE0PS8x3kTr44jKbC2D4nfnmdYVecooNm0qnER4B4TSvZpZSQoCgKK6k4BYQuOSyFTOwYx6M79w39ZMOgyCP9ytB+tyMWL0/+ZuUQL04yVg4M5vd3oJMkOaXbvG56ncgPyFrseY+FPDg+mXAzvJk/nily37YXjkQj2D0=" matrix: - - ARCH=x86_64 + - ARCH=x86_64 NO_ADD=1 - ARCH=i686 notifications: email: diff --git a/gcc/Cargo.toml b/gcc/Cargo.toml index b9ebcd6..45acf07 100644 --- a/gcc/Cargo.toml +++ b/gcc/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "gcc" -version = "0.3.45" +version = "0.3.48" authors = ["Alex Crichton <alex@alexcrichton.com>"] license = "MIT/Apache-2.0" repository = "https://github.com/alexcrichton/gcc-rs" @@ -18,7 +18,7 @@ travis-ci = { repository = "alexcrichton/gcc-rs" } appveyor = { repository = "alexcrichton/gcc-rs" } [dependencies] -rayon = { version = "0.6", optional = true } +rayon = { version = "0.7", optional = true } [features] parallel = ["rayon"] diff --git a/gcc/src/com.rs b/gcc/src/com.rs new file mode 100644 index 0000000..bd8cce7 --- /dev/null +++ b/gcc/src/com.rs @@ -0,0 +1,125 @@ +// Copyright © 2017 winapi-rs developers +// 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. +// All files in the project carrying such notice may not be copied, modified, or distributed +// except according to those terms. + +#![allow(unused)] + +use std::ffi::{OsStr, OsString}; +use std::mem::forget; +use std::ops::Deref; +use std::os::windows::ffi::{OsStrExt, OsStringExt}; +use std::ptr::null_mut; +use std::slice::from_raw_parts; +use winapi::Interface; +use winapi::BSTR; +use winapi::CoInitializeEx; +use winapi::COINIT_MULTITHREADED; +use winapi::{SysFreeString, SysStringLen}; +use winapi::IUnknown; +use winapi::{S_OK, S_FALSE, HRESULT}; + +pub fn initialize() -> Result<(), HRESULT> { + let err = unsafe { CoInitializeEx(null_mut(), COINIT_MULTITHREADED) }; + if err != S_OK && err != S_FALSE { + // S_FALSE just means COM is already initialized + return Err(err); + } + Ok(()) +} + +pub struct ComPtr<T>(*mut T) where T: Interface; +impl<T> ComPtr<T> where T: Interface { + /// Creates a `ComPtr` to wrap a raw pointer. + /// It takes ownership over the pointer which means it does __not__ call `AddRef`. + /// `T` __must__ be a COM interface that inherits from `IUnknown`. + pub unsafe fn from_raw(ptr: *mut T) -> ComPtr<T> { + assert!(!ptr.is_null()); + ComPtr(ptr) + } + /// Casts up the inheritance chain + pub fn up<U>(self) -> ComPtr<U> where T: Deref<Target=U>, U: Interface { + ComPtr(self.into_raw() as *mut U) + } + /// Extracts the raw pointer. + /// You are now responsible for releasing it yourself. + pub fn into_raw(self) -> *mut T { + let p = self.0; + forget(self); + p + } + /// For internal use only. + fn as_unknown(&self) -> &IUnknown { + unsafe { &*(self.0 as *mut IUnknown) } + } + /// Performs QueryInterface fun. + pub fn cast<U>(&self) -> Result<ComPtr<U>, i32> where U: Interface { + let mut obj = null_mut(); + let err = unsafe { self.as_unknown().QueryInterface(&U::uuidof(), &mut obj) }; + if err < 0 { return Err(err); } + Ok(unsafe { ComPtr::from_raw(obj as *mut U) }) + } +} +impl<T> Deref for ComPtr<T> where T: Interface { + type Target = T; + fn deref(&self) -> &T { + unsafe { &*self.0 } + } +} +impl<T> Clone for ComPtr<T> where T: Interface { + fn clone(&self) -> Self { + unsafe { + self.as_unknown().AddRef(); + ComPtr::from_raw(self.0) + } + } +} +impl<T> Drop for ComPtr<T> where T: Interface { + fn drop(&mut self) { + unsafe { self.as_unknown().Release(); } + } +} +pub struct BStr(BSTR); +impl BStr { + pub unsafe fn from_raw(s: BSTR) -> BStr { + BStr(s) + } + pub fn to_osstring(&self) -> OsString { + let len = unsafe { SysStringLen(self.0) }; + let slice = unsafe { from_raw_parts(self.0, len as usize) }; + OsStringExt::from_wide(slice) + } +} +impl Drop for BStr { + fn drop(&mut self) { + unsafe { SysFreeString(self.0) }; + } +} + +pub trait ToWide { + fn to_wide(&self) -> Vec<u16>; + fn to_wide_null(&self) -> Vec<u16>; +} +impl<T> ToWide for T where T: AsRef<OsStr> { + fn to_wide(&self) -> Vec<u16> { + self.as_ref().encode_wide().collect() + } + fn to_wide_null(&self) -> Vec<u16> { + self.as_ref().encode_wide().chain(Some(0)).collect() + } +} +pub trait FromWide where Self: Sized { + fn from_wide(wide: &[u16]) -> Self; + fn from_wide_null(wide: &[u16]) -> Self { + let len = wide.iter().take_while(|&&c| c != 0).count(); + Self::from_wide(&wide[..len]) + } +} +impl FromWide for OsString { + fn from_wide(wide: &[u16]) -> OsString { + OsStringExt::from_wide(wide) + } +} + diff --git a/gcc/src/lib.rs b/gcc/src/lib.rs index 85d7d22..7d19e13 100644 --- a/gcc/src/lib.rs +++ b/gcc/src/lib.rs @@ -57,8 +57,18 @@ use std::process::{Command, Stdio, Child}; use std::io::{self, BufReader, BufRead, Read, Write}; use std::thread::{self, JoinHandle}; +// These modules are all glue to support reading the MSVC version from +// the registry and from COM interfaces #[cfg(windows)] mod registry; +#[cfg(windows)] +#[macro_use] +mod winapi; +#[cfg(windows)] +mod com; +#[cfg(windows)] +mod setup_config; + pub mod windows_registry; /// Extra configuration to pass to gcc. @@ -81,6 +91,7 @@ pub struct Config { archiver: Option<PathBuf>, cargo_metadata: bool, pic: Option<bool>, + static_crt: Option<bool>, } /// Configuration used to represent an invocation of a C compiler. @@ -187,6 +198,7 @@ impl Config { archiver: None, cargo_metadata: true, pic: None, + static_crt: None, } } @@ -363,6 +375,14 @@ impl Config { self } + /// Configures whether the /MT flag or the /MD flag will be passed to msvc build tools. + /// + /// This option defaults to `false`, and affect only msvc targets. + pub fn static_crt(&mut self, static_crt: bool) -> &mut Config { + self.static_crt = Some(static_crt); + self + } + #[doc(hidden)] pub fn __set_env<A, B>(&mut self, a: A, b: B) -> &mut Config @@ -434,12 +454,13 @@ impl Config { let mut cfg = rayon::Configuration::new(); if let Ok(amt) = env::var("NUM_JOBS") { if let Ok(amt) = amt.parse() { - cfg = cfg.set_num_threads(amt); + cfg = cfg.num_threads(amt); } } drop(rayon::initialize(cfg)); - objs.par_iter().weight_max().for_each(|&(ref src, ref dst)| self.compile_object(src, dst)); + objs.par_iter().with_max_len(1) + .for_each(|&(ref src, ref dst)| self.compile_object(src, dst)); } #[cfg(not(feature = "parallel"))] @@ -533,13 +554,22 @@ impl Config { match cmd.family { ToolFamily::Msvc => { cmd.args.push("/nologo".into()); - let features = env::var("CARGO_CFG_TARGET_FEATURE") + + let crt_flag = match self.static_crt { + Some(true) => "/MT", + Some(false) => "/MD", + None => { + let features = env::var("CARGO_CFG_TARGET_FEATURE") .unwrap_or(String::new()); - if features.contains("crt-static") { - cmd.args.push("/MT".into()); - } else { - cmd.args.push("/MD".into()); - } + if features.contains("crt-static") { + "/MT" + } else { + "/MD" + } + }, + }; + cmd.args.push(crt_flag.into()); + match &opt_level[..] { "z" | "s" => cmd.args.push("/Os".into()), "1" => cmd.args.push("/O1".into()), @@ -593,7 +623,7 @@ impl Config { } // armv7 targets get to use armv7 instructions - if target.starts_with("armv7-unknown-linux-") { + if target.starts_with("armv7-") && target.contains("-linux-") { cmd.args.push("-march=armv7-a".into()); } @@ -602,6 +632,7 @@ impl Config { if target.starts_with("armv7-linux-androideabi") { cmd.args.push("-march=armv7-a".into()); cmd.args.push("-mfpu=vfpv3-d16".into()); + cmd.args.push("-mfloat-abi=softfp".into()); } // For us arm == armv6 by default @@ -610,6 +641,15 @@ impl Config { cmd.args.push("-marm".into()); } + // We can guarantee some settings for FRC + if target.starts_with("arm-frc-") { + cmd.args.push("-march=armv7-a".into()); + cmd.args.push("-mcpu=cortex-a9".into()); + cmd.args.push("-mfpu=vfpv3".into()); + cmd.args.push("-mfloat-abi=softfp".into()); + cmd.args.push("-marm".into()); + } + // Turn codegen down on i586 to avoid some instructions. if target.starts_with("i586-unknown-linux-") { cmd.args.push("-march=pentium".into()); @@ -683,7 +723,7 @@ impl Config { for &(ref key, ref value) in self.definitions.iter() { let lead = if let ToolFamily::Msvc = cmd.family {"/"} else {"-"}; - if let &Some(ref value) = value { + if let Some(ref value) = *value { cmd.args.push(format!("{}D{}={}", lead, key, value).into()); } else { cmd.args.push(format!("{}D{}", lead, key).into()); @@ -704,7 +744,7 @@ impl Config { cmd.arg("/I").arg(directory); } for &(ref key, ref value) in self.definitions.iter() { - if let &Some(ref value) = value { + if let Some(ref value) = *value { cmd.arg(&format!("/D{}={}", key, value)); } else { cmd.arg(&format!("/D{}", key)); @@ -730,7 +770,7 @@ impl Config { if target.contains("msvc") { let mut cmd = match self.archiver { Some(ref s) => self.cmd(s), - None => windows_registry::find(&target, "lib.exe").unwrap_or(self.cmd("lib.exe")), + None => windows_registry::find(&target, "lib.exe").unwrap_or_else(|| self.cmd("lib.exe")), }; let mut out = OsString::from("/OUT:"); out.push(dst); @@ -750,7 +790,6 @@ impl Config { // if hard-link fails, just copy (ignoring the number of bytes written) fs::copy(&dst, &lib_dst).map(|_| ()) }) - .ok() .expect("Copying from {:?} to {:?} failed.");; } else { let ar = self.get_ar(); @@ -816,7 +855,7 @@ impl Config { for &(ref a, ref b) in self.env.iter() { cmd.env(a, b); } - return cmd; + cmd } fn get_base_compiler(&self) -> Tool { @@ -846,15 +885,26 @@ impl Config { for arg in args { t.args.push(arg.into()); } - return t; + t }) .or_else(|| { if target.contains("emscripten") { - if self.cpp { - Some(Tool::new(PathBuf::from("em++"))) + //Windows uses bat file so we have to be a bit more specific + let tool = if self.cpp { + if cfg!(windows) { + "em++.bat" + } else { + "em++" + } } else { - Some(Tool::new(PathBuf::from("emcc"))) - } + if cfg!(windows) { + "emcc.bat" + } else { + "emcc" + } + }; + + Some(Tool::new(PathBuf::from(tool))) } else { None } @@ -876,6 +926,7 @@ impl Config { let prefix = cross_compile.or(match &target[..] { "aarch64-unknown-linux-gnu" => Some("aarch64-linux-gnu"), "arm-unknown-linux-gnueabi" => Some("arm-linux-gnueabi"), + "arm-frc-linux-gnueabi" => Some("arm-frc-linux-gnueabi"), "arm-unknown-linux-gnueabihf" => Some("arm-linux-gnueabihf"), "arm-unknown-linux-musleabi" => Some("arm-linux-musleabi"), "arm-unknown-linux-musleabihf" => Some("arm-linux-musleabihf"), @@ -948,7 +999,7 @@ impl Config { self.get_var(name).ok().map(|tool| { let whitelist = ["ccache", "distcc"]; for t in whitelist.iter() { - if tool.starts_with(t) && tool[t.len()..].starts_with(" ") { + if tool.starts_with(t) && tool[t.len()..].starts_with(' ') { return (t.to_string(), vec![tool[t.len()..].trim_left().to_string()]); } } @@ -981,7 +1032,14 @@ impl Config { if self.get_target().contains("android") { PathBuf::from(format!("{}-ar", self.get_target().replace("armv7", "arm"))) } else if self.get_target().contains("emscripten") { - PathBuf::from("emar") + //Windows use bat files so we have to be a bit more specific + let tool = if cfg!(windows) { + "emar.bat" + } else { + "emar" + }; + + PathBuf::from(tool) } else { PathBuf::from("ar") } @@ -1034,7 +1092,7 @@ impl Tool { let family = if let Some(fname) = path.file_name().and_then(|p| p.to_str()) { if fname.contains("clang") { ToolFamily::Clang - } else if fname.contains("cl") { + } else if fname.contains("cl") && !fname.contains("uclibc") { ToolFamily::Msvc } else { ToolFamily::Gnu @@ -1091,7 +1149,7 @@ fn run(cmd: &mut Command, program: &str) { let (mut child, print) = spawn(cmd, program); let status = child.wait().expect("failed to wait on child process"); print.join().unwrap(); - println!("{:?}", status); + println!("{}", status); if !status.success() { fail(&format!("command did not execute successfully, got: {}", status)); } @@ -1104,11 +1162,11 @@ fn run_output(cmd: &mut Command, program: &str) -> Vec<u8> { child.stdout.take().unwrap().read_to_end(&mut stdout).unwrap(); let status = child.wait().expect("failed to wait on child process"); print.join().unwrap(); - println!("{:?}", status); + println!("{}", status); if !status.success() { fail(&format!("command did not execute successfully, got: {}", status)); } - return stdout + stdout } fn spawn(cmd: &mut Command, program: &str) -> (Child, JoinHandle<()>) { diff --git a/gcc/src/setup_config.rs b/gcc/src/setup_config.rs new file mode 100644 index 0000000..175b7f1 --- /dev/null +++ b/gcc/src/setup_config.rs @@ -0,0 +1,257 @@ +// Copyright © 2017 winapi-rs developers +// 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. +// All files in the project carrying such notice may not be copied, modified, or distributed +// except according to those terms. + +#![allow(bad_style)] +#![allow(unused)] + +use std::ffi::OsString; +use std::ptr::null_mut; +use winapi::Interface; +use winapi::{LPFILETIME, ULONG}; +use winapi::S_FALSE; +use winapi::BSTR; +use winapi::LPCOLESTR; +use winapi::{CLSCTX_ALL, CoCreateInstance}; +use winapi::LPSAFEARRAY; +use winapi::{IUnknown, IUnknownVtbl}; +use winapi::{HRESULT, LCID, LPCWSTR, PULONGLONG}; + +use com::{BStr, ComPtr}; + +// Bindings to the Setup.Configuration stuff +pub type InstanceState = u32; + +pub const eNone: InstanceState = 0; +pub const eLocal: InstanceState = 1; +pub const eRegistered: InstanceState = 2; +pub const eNoRebootRequired: InstanceState = 4; +pub const eComplete: InstanceState = -1i32 as u32; + +RIDL!{#[uuid(0xb41463c3, 0x8866, 0x43b5, 0xbc, 0x33, 0x2b, 0x06, 0x76, 0xf7, 0xf4, 0x2e)] +interface ISetupInstance(ISetupInstanceVtbl): IUnknown(IUnknownVtbl) { + fn GetInstanceId( + pbstrInstanceId: *mut BSTR, + ) -> HRESULT, + fn GetInstallDate( + pInstallDate: LPFILETIME, + ) -> HRESULT, + fn GetInstallationName( + pbstrInstallationName: *mut BSTR, + ) -> HRESULT, + fn GetInstallationPath( + pbstrInstallationPath: *mut BSTR, + ) -> HRESULT, + fn GetInstallationVersion( + pbstrInstallationVersion: *mut BSTR, + ) -> HRESULT, + fn GetDisplayName( + lcid: LCID, + pbstrDisplayName: *mut BSTR, + ) -> HRESULT, + fn GetDescription( + lcid: LCID, + pbstrDescription: *mut BSTR, + ) -> HRESULT, + fn ResolvePath( + pwszRelativePath: LPCOLESTR, + pbstrAbsolutePath: *mut BSTR, + ) -> HRESULT, +}} + +RIDL!{#[uuid(0x89143c9a, 0x05af, 0x49b0, 0xb7, 0x17, 0x72, 0xe2, 0x18, 0xa2, 0x18, 0x5c)] +interface ISetupInstance2(ISetupInstance2Vtbl): ISetupInstance(ISetupInstanceVtbl) { + fn GetState( + pState: *mut InstanceState, + ) -> HRESULT, + fn GetPackages( + ppsaPackages: *mut LPSAFEARRAY, + ) -> HRESULT, + fn GetProduct( + ppPackage: *mut *mut ISetupPackageReference, + ) -> HRESULT, + fn GetProductPath( + pbstrProductPath: *mut BSTR, + ) -> HRESULT, +}} + +RIDL!{#[uuid(0x6380bcff, 0x41d3, 0x4b2e, 0x8b, 0x2e, 0xbf, 0x8a, 0x68, 0x10, 0xc8, 0x48)] +interface IEnumSetupInstances(IEnumSetupInstancesVtbl): IUnknown(IUnknownVtbl) { + fn Next( + celt: ULONG, + rgelt: *mut *mut ISetupInstance, + pceltFetched: *mut ULONG, + ) -> HRESULT, + fn Skip( + celt: ULONG, + ) -> HRESULT, + fn Reset() -> HRESULT, + fn Clone( + ppenum: *mut *mut IEnumSetupInstances, + ) -> HRESULT, +}} + +RIDL!{#[uuid(0x42843719, 0xdb4c, 0x46c2, 0x8e, 0x7c, 0x64, 0xf1, 0x81, 0x6e, 0xfd, 0x5b)] +interface ISetupConfiguration(ISetupConfigurationVtbl): IUnknown(IUnknownVtbl) { + fn EnumInstances( + ppEnumInstances: *mut *mut IEnumSetupInstances, + ) -> HRESULT, + fn GetInstanceForCurrentProcess( + ppInstance: *mut *mut ISetupInstance, + ) -> HRESULT, + fn GetInstanceForPath( + wzPath: LPCWSTR, + ppInstance: *mut *mut ISetupInstance, + ) -> HRESULT, +}} + +RIDL!{#[uuid(0x26aab78c, 0x4a60, 0x49d6, 0xaf, 0x3b, 0x3c, 0x35, 0xbc, 0x93, 0x36, 0x5d)] +interface ISetupConfiguration2(ISetupConfiguration2Vtbl): + ISetupConfiguration(ISetupConfigurationVtbl) { + fn EnumAllInstances( + ppEnumInstances: *mut *mut IEnumSetupInstances, + ) -> HRESULT, +}} + +RIDL!{#[uuid(0xda8d8a16, 0xb2b6, 0x4487, 0xa2, 0xf1, 0x59, 0x4c, 0xcc, 0xcd, 0x6b, 0xf5)] +interface ISetupPackageReference(ISetupPackageReferenceVtbl): IUnknown(IUnknownVtbl) { + fn GetId( + pbstrId: *mut BSTR, + ) -> HRESULT, + fn GetVersion( + pbstrVersion: *mut BSTR, + ) -> HRESULT, + fn GetChip( + pbstrChip: *mut BSTR, + ) -> HRESULT, + fn GetLanguage( + pbstrLanguage: *mut BSTR, + ) -> HRESULT, + fn GetBranch( + pbstrBranch: *mut BSTR, + ) -> HRESULT, + fn GetType( + pbstrType: *mut BSTR, + ) -> HRESULT, + fn GetUniqueId( + pbstrUniqueId: *mut BSTR, + ) -> HRESULT, +}} + +RIDL!{#[uuid(0x42b21b78, 0x6192, 0x463e, 0x87, 0xbf, 0xd5, 0x77, 0x83, 0x8f, 0x1d, 0x5c)] +interface ISetupHelper(ISetupHelperVtbl): IUnknown(IUnknownVtbl) { + fn ParseVersion( + pwszVersion: LPCOLESTR, + pullVersion: PULONGLONG, + ) -> HRESULT, + fn ParseVersionRange( + pwszVersionRange: LPCOLESTR, + pullMinVersion: PULONGLONG, + pullMaxVersion: PULONGLONG, + ) -> HRESULT, +}} + +DEFINE_GUID!{CLSID_SetupConfiguration, + 0x177f0c4a, 0x1cd3, 0x4de7, 0xa3, 0x2c, 0x71, 0xdb, 0xbb, 0x9f, 0xa3, 0x6d} + +// Safe wrapper around the COM interfaces +pub struct SetupConfiguration(ComPtr<ISetupConfiguration>); + +impl SetupConfiguration { + pub fn new() -> Result<SetupConfiguration, i32> { + let mut obj = null_mut(); + let err = unsafe { CoCreateInstance( + &CLSID_SetupConfiguration, null_mut(), CLSCTX_ALL, + &ISetupConfiguration::uuidof(), &mut obj, + ) }; + if err < 0 { return Err(err); } + let obj = unsafe { ComPtr::from_raw(obj as *mut ISetupConfiguration) }; + Ok(SetupConfiguration(obj)) + } + pub fn get_instance_for_current_process(&self) -> Result<SetupInstance, i32> { + let mut obj = null_mut(); + let err = unsafe { self.0.GetInstanceForCurrentProcess(&mut obj) }; + if err < 0 { return Err(err); } + Ok(unsafe { SetupInstance::from_raw(obj) }) + } + pub fn enum_instances(&self) -> Result<EnumSetupInstances, i32> { + let mut obj = null_mut(); + let err = unsafe { self.0.EnumInstances(&mut obj) }; + if err < 0 { return Err(err); } + Ok(unsafe { EnumSetupInstances::from_raw(obj) }) + } + pub fn enum_all_instances(&self) -> Result<EnumSetupInstances, i32> { + let mut obj = null_mut(); + let this = try!(self.0.cast::<ISetupConfiguration2>()); + let err = unsafe { this.EnumAllInstances(&mut obj) }; + if err < 0 { return Err(err); } + Ok(unsafe { EnumSetupInstances::from_raw(obj) }) + } +} + +pub struct SetupInstance(ComPtr<ISetupInstance>); + +impl SetupInstance { + pub unsafe fn from_raw(obj: *mut ISetupInstance) -> SetupInstance { + SetupInstance(ComPtr::from_raw(obj)) + } + pub fn instance_id(&self) -> Result<OsString, i32> { + let mut s = null_mut(); + let err = unsafe { self.0.GetInstanceId(&mut s) }; + let bstr = unsafe { BStr::from_raw(s) }; + if err < 0 { return Err(err); } + Ok(bstr.to_osstring()) + } + pub fn installation_name(&self) -> Result<OsString, i32> { + let mut s = null_mut(); + let err = unsafe { self.0.GetInstallationName(&mut s) }; + let bstr = unsafe { BStr::from_raw(s) }; + if err < 0 { return Err(err); } + Ok(bstr.to_osstring()) + } + pub fn installation_path(&self) -> Result<OsString, i32> { + let mut s = null_mut(); + let err = unsafe { self.0.GetInstallationPath(&mut s) }; + let bstr = unsafe { BStr::from_raw(s) }; + if err < 0 { return Err(err); } + Ok(bstr.to_osstring()) + } + pub fn installation_version(&self) -> Result<OsString, i32> { + let mut s = null_mut(); + let err = unsafe { self.0.GetInstallationVersion(&mut s) }; + let bstr = unsafe { BStr::from_raw(s) }; + if err < 0 { return Err(err); } + Ok(bstr.to_osstring()) + } + pub fn product_path(&self) -> Result<OsString, i32> { + let mut s = null_mut(); + let this = try!(self.0.cast::<ISetupInstance2>()); + let err = unsafe { this.GetProductPath(&mut s) }; + let bstr = unsafe { BStr::from_raw(s) }; + if err < 0 { return Err(err); } + Ok(bstr.to_osstring()) + } +} + +pub struct EnumSetupInstances(ComPtr<IEnumSetupInstances>); + +impl EnumSetupInstances { + pub unsafe fn from_raw(obj: *mut IEnumSetupInstances) -> EnumSetupInstances { + EnumSetupInstances(ComPtr::from_raw(obj)) + } +} + +impl Iterator for EnumSetupInstances { + type Item = Result<SetupInstance, i32>; + fn next(&mut self) -> Option<Result<SetupInstance, i32>> { + let mut obj = null_mut(); + let err = unsafe { self.0.Next(1, &mut obj, null_mut()) }; + if err < 0 { return Some(Err(err)); } + if err == S_FALSE { return None; } + Some(Ok(unsafe { SetupInstance::from_raw(obj) })) + } +} + diff --git a/gcc/src/winapi.rs b/gcc/src/winapi.rs new file mode 100644 index 0000000..010d165 --- /dev/null +++ b/gcc/src/winapi.rs @@ -0,0 +1,214 @@ +// Copyright © 2015-2017 winapi-rs developers +// 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. +// All files in the project carrying such notice may not be copied, modified, or distributed +// except according to those terms. + +#![allow(bad_style)] + +use std::os::raw; + +pub type wchar_t = u16; + +pub type UINT = raw::c_uint; +pub type LPUNKNOWN = *mut IUnknown; +pub type REFIID = *const IID; +pub type IID = GUID; +pub type REFCLSID = *const IID; +pub type PVOID = *mut raw::c_void; +pub type USHORT = raw::c_ushort; +pub type ULONG = raw::c_ulong; +pub type LONG = raw::c_long; +pub type DWORD = u32; +pub type LPVOID = *mut raw::c_void; +pub type HRESULT = raw::c_long; +pub type LPFILETIME = *mut FILETIME; +pub type BSTR = *mut OLECHAR; +pub type OLECHAR = WCHAR; +pub type WCHAR = wchar_t; +pub type LPCOLESTR = *const OLECHAR; +pub type LCID = DWORD; +pub type LPCWSTR = *const WCHAR; +pub type PULONGLONG = *mut ULONGLONG; +pub type ULONGLONG = u64; + +pub const S_OK: HRESULT = 0; +pub const S_FALSE: HRESULT = 1; +pub const COINIT_MULTITHREADED: u32 = 0x0; + +pub type CLSCTX = u32; + +pub const CLSCTX_INPROC_SERVER: CLSCTX = 0x1; +pub const CLSCTX_INPROC_HANDLER: CLSCTX = 0x2; +pub const CLSCTX_LOCAL_SERVER: CLSCTX = 0x4; +pub const CLSCTX_REMOTE_SERVER: CLSCTX = 0x10; + +pub const CLSCTX_ALL: CLSCTX = CLSCTX_INPROC_SERVER | + CLSCTX_INPROC_HANDLER | CLSCTX_LOCAL_SERVER | CLSCTX_REMOTE_SERVER; + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct GUID { + pub Data1: raw::c_ulong, + pub Data2: raw::c_ushort, + pub Data3: raw::c_ushort, + pub Data4: [raw::c_uchar; 8], +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct FILETIME { + pub dwLowDateTime: DWORD, + pub dwHighDateTime: DWORD, +} + +pub trait Interface { + fn uuidof() -> GUID; +} + +#[link(name = "Ole32")] +#[link(name = "OleAut32")] +extern { } + +extern "system" { + pub fn CoInitializeEx(pvReserved: LPVOID, dwCoInit: DWORD) -> HRESULT; + pub fn CoCreateInstance(rclsid: REFCLSID, pUnkOuter: LPUNKNOWN, + dwClsContext: DWORD, riid: REFIID, + ppv: *mut LPVOID) -> HRESULT; + pub fn SysFreeString(bstrString: BSTR); + pub fn SysStringLen(pbstr: BSTR) -> UINT; +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct SAFEARRAYBOUND { + pub cElements: ULONG, + pub lLbound: LONG, +} + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct SAFEARRAY { + pub cDims: USHORT, + pub fFeatures: USHORT, + pub cbElements: ULONG, + pub cLocks: ULONG, + pub pvData: PVOID, + pub rgsabound: [SAFEARRAYBOUND; 1], +} + +pub type LPSAFEARRAY = *mut SAFEARRAY; + +macro_rules! DEFINE_GUID { + ( + $name:ident, $l:expr, $w1:expr, $w2:expr, + $b1:expr, $b2:expr, $b3:expr, $b4:expr, $b5:expr, $b6:expr, $b7:expr, $b8:expr + ) => { + pub const $name: $crate::winapi::GUID = $crate::winapi::GUID { + Data1: $l, + Data2: $w1, + Data3: $w2, + Data4: [$b1, $b2, $b3, $b4, $b5, $b6, $b7, $b8], + }; + } +} + +macro_rules! RIDL { + (#[uuid($($uuid:expr),+)] + interface $interface:ident ($vtbl:ident) {$( + fn $method:ident($($p:ident : $t:ty,)*) -> $rtr:ty, + )+}) => ( + #[repr(C)] + pub struct $vtbl { + $(pub $method: unsafe extern "system" fn( + This: *mut $interface, + $($p: $t),* + ) -> $rtr,)+ + } + #[repr(C)] + pub struct $interface { + pub lpVtbl: *const $vtbl, + } + RIDL!{@impl $interface {$(fn $method($($p: $t,)*) -> $rtr,)+}} + RIDL!{@uuid $interface $($uuid),+} + ); + (#[uuid($($uuid:expr),+)] + interface $interface:ident ($vtbl:ident) : $pinterface:ident ($pvtbl:ident) { + }) => ( + #[repr(C)] + pub struct $vtbl { + pub parent: $pvtbl, + } + #[repr(C)] + pub struct $interface { + pub lpVtbl: *const $vtbl, + } + RIDL!{@deref $interface $pinterface} + RIDL!{@uuid $interface $($uuid),+} + ); + (#[uuid($($uuid:expr),+)] + interface $interface:ident ($vtbl:ident) : $pinterface:ident ($pvtbl:ident) {$( + fn $method:ident($($p:ident : $t:ty,)*) -> $rtr:ty, + )+}) => ( + #[repr(C)] + pub struct $vtbl { + pub parent: $pvtbl, + $(pub $method: unsafe extern "system" fn( + This: *mut $interface, + $($p: $t,)* + ) -> $rtr,)+ + } + #[repr(C)] + pub struct $interface { + pub lpVtbl: *const $vtbl, + } + RIDL!{@impl $interface {$(fn $method($($p: $t,)*) -> $rtr,)+}} + RIDL!{@deref $interface $pinterface} + RIDL!{@uuid $interface $($uuid),+} + ); + (@deref $interface:ident $pinterface:ident) => ( + impl ::std::ops::Deref for $interface { + type Target = $pinterface; + #[inline] + fn deref(&self) -> &$pinterface { + unsafe { &*(self as *const $interface as *const $pinterface) } + } + } + ); + (@impl $interface:ident {$( + fn $method:ident($($p:ident : $t:ty,)*) -> $rtr:ty, + )+}) => ( + impl $interface { + $(#[inline] pub unsafe fn $method(&self, $($p: $t,)*) -> $rtr { + ((*self.lpVtbl).$method)(self as *const _ as *mut _, $($p,)*) + })+ + } + ); + (@uuid $interface:ident + $l:expr, $w1:expr, $w2:expr, + $b1:expr, $b2:expr, $b3:expr, $b4:expr, $b5:expr, $b6:expr, $b7:expr, $b8:expr + ) => ( + impl $crate::winapi::Interface for $interface { + #[inline] + fn uuidof() -> $crate::winapi::GUID { + $crate::winapi::GUID { + Data1: $l, + Data2: $w1, + Data3: $w2, + Data4: [$b1, $b2, $b3, $b4, $b5, $b6, $b7, $b8], + } + } + } + ); +} + +RIDL!{#[uuid(0x00000000, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46)] +interface IUnknown(IUnknownVtbl) { + fn QueryInterface( + riid: REFIID, + ppvObject: *mut *mut raw::c_void, + ) -> HRESULT, + fn AddRef() -> ULONG, + fn Release() -> ULONG, +}} diff --git a/gcc/src/windows_registry.rs b/gcc/src/windows_registry.rs index 08b1df5..d05fc1d 100644 --- a/gcc/src/windows_registry.rs +++ b/gcc/src/windows_registry.rs @@ -15,6 +15,7 @@ use std::process::Command; use Tool; +#[cfg(windows)] macro_rules! otry { ($expr:expr) => (match $expr { Some(val) => val, @@ -50,10 +51,119 @@ pub fn find_tool(_target: &str, _tool: &str) -> Option<Tool> { #[cfg(windows)] pub fn find_tool(target: &str, tool: &str) -> Option<Tool> { use std::env; + + // 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 impl_::find_msbuild(target); + } + + // 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())); + } + + // 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. + + return impl_::find_msvc_15(tool, target) + .or_else(|| impl_::find_msvc_14(tool, target)) + .or_else(|| impl_::find_msvc_12(tool, target)) + .or_else(|| impl_::find_msvc_11(tool, target)); +} + +/// A version of Visual Studio +pub enum VsVers { + /// Visual Studio 12 (2013) + Vs12, + /// Visual Studio 14 (2015) + Vs14, + /// Visual Studio 15 (2017) + Vs15, + + /// Hidden variant that should not be matched on. Callers that want to + /// handle an enumeration of `VsVers` instances should always have a default + /// case meaning that it's a VS version they don't understand. + #[doc(hidden)] + __Nonexhaustive_do_not_match_this_or_your_code_will_break, +} + +/// Find the most recent installed version of Visual Studio +/// +/// This is used by the cmake crate to figure out the correct +/// generator. +#[cfg(not(windows))] +pub fn find_vs_version() -> Result<VsVers, String> { + Err(format!("not windows")) +} + +/// Documented above +#[cfg(windows)] +pub fn find_vs_version() -> Result<VsVers, String> { + use std::env; + + match env::var("VisualStudioVersion") { + Ok(version) => { + match &version[..] { + "15.0" => Ok(VsVers::Vs15), + "14.0" => Ok(VsVers::Vs14), + "12.0" => Ok(VsVers::Vs12), + vers => Err(format!("\n\n\ + unsupported or unknown VisualStudio version: {}\n\ + if another version is installed consider running \ + the appropriate vcvars script before building this \ + crate\n\ + ", vers)), + } + } + _ => { + // Check for the presense of a specific registry key + // that indicates visual studio is installed. + if impl_::has_msbuild_version("15.0") { + Ok(VsVers::Vs15) + } else if impl_::has_msbuild_version("14.0") { + Ok(VsVers::Vs14) + } else if impl_::has_msbuild_version("12.0") { + Ok(VsVers::Vs12) + } else { + Err(format!("\n\n\ + couldn't determine visual studio generator\n\ + if VisualStudio is installed, however, consider \ + running the appropriate vcvars script before building \ + this crate\n\ + ")) + } + } + } +} + +#[cfg(windows)] +mod impl_ { + use std::env; use std::ffi::OsString; use std::mem; use std::path::{Path, PathBuf}; + use std::fs::File; + use std::io::Read; use registry::{RegistryKey, LOCAL_MACHINE}; + use com; + use setup_config::{SetupConfiguration, SetupInstance}; + + use Tool; struct MsvcTool { tool: PathBuf, @@ -82,47 +192,91 @@ pub fn find_tool(target: &str, tool: &str) -> Option<Tool> { } } - // This logic is all tailored for MSVC, if we're not that then bail out - // early. - if !target.contains("msvc") { - return None; - } + // In MSVC 15 (2017) MS once again changed the scheme for locating + // the tooling. Now we must go through some COM interfaces, which + // is super fun for Rust. + pub fn find_msvc_15(tool: &str, target: &str) -> Option<Tool> { + otry!(com::initialize().ok()); + + let config = otry!(SetupConfiguration::new().ok()); + let iter = otry!(config.enum_all_instances().ok()); + for instance in iter { + let instance = otry!(instance.ok()); + let tool = tool_from_vs15_instance(tool, target, &instance); + if tool.is_some() { + return tool; + } + } - // 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); + 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())); + fn tool_from_vs15_instance(tool: &str, target: &str, + instance: &SetupInstance) -> Option<Tool> { + let (bin_path, lib_path, include_path) = otry!(vs15_vc_paths(target, instance)); + let tool_path = bin_path.join(tool); + if !tool_path.exists() { return None }; + + let mut tool = MsvcTool::new(tool_path); + tool.path.push(bin_path.clone()); + tool.libs.push(lib_path); + tool.include.push(include_path); + + if let Some((atl_lib_path, atl_include_path)) = atl_paths(target, &bin_path) { + tool.libs.push(atl_lib_path); + tool.include.push(atl_include_path); + } + + otry!(add_sdks(&mut tool, target)); + + Some(tool.into_tool()) } - // 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. + fn vs15_vc_paths(target: &str, instance: &SetupInstance) -> Option<(PathBuf, PathBuf, PathBuf)> { + let instance_path: PathBuf = otry!(instance.installation_path().ok()).into(); + let version_path = instance_path.join(r"VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt"); + let mut version_file = otry!(File::open(version_path).ok()); + let mut version = String::new(); + otry!(version_file.read_to_string(&mut version).ok()); + let version = version.trim(); + let host = match host_arch() { + X86 => "X86", + X86_64 => "X64", + _ => return None, + }; + let target = otry!(lib_subdir(target)); + let path = instance_path.join(r"VC\Tools\MSVC").join(version); + let bin_path = path.join("bin").join(&format!("Host{}", host)).join(&target); + let lib_path = path.join("lib").join(&target); + let include_path = path.join("include"); + Some((bin_path, lib_path, include_path)) + } - return find_msvc_latest(tool, target, "15.0") - .or_else(|| find_msvc_latest(tool, target, "14.0")) - .or_else(|| find_msvc_12(tool, target)) - .or_else(|| find_msvc_11(tool, target)); + fn atl_paths(target: &str, path: &Path) -> Option<(PathBuf, PathBuf)> { + let atl_path = path.join("atlfmc"); + let sub = otry!(lib_subdir(target)); + if atl_path.exists() { + Some((atl_path.join("lib").join(sub), atl_path.join("include"))) + } else { + None + } + } - // For MSVC 14 or newer we need to find the Universal CRT as well as either + // For MSVC 14 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)); + pub fn find_msvc_14(tool: &str, target: &str) -> Option<Tool> { + let vcdir = otry!(get_vc_dir("14.0")); let mut tool = otry!(get_tool(tool, &vcdir, target)); + otry!(add_sdks(&mut tool, target)); + Some(tool.into_tool()) + } + + fn add_sdks(tool: &mut MsvcTool, target: &str) -> Option<()> { let sub = otry!(lib_subdir(target)); let (ucrt, ucrt_version) = otry!(get_ucrt_dir()); + tool.path.push(ucrt.join("bin").join(&ucrt_version).join(sub)); + let ucrt_include = ucrt.join("include").join(&ucrt_version); tool.include.push(ucrt_include.join("ucrt")); @@ -145,14 +299,13 @@ pub fn find_tool(target: &str, tool: &str) -> Option<Tool> { tool.include.push(sdk_include.join("um")); tool.include.push(sdk_include.join("winrt")); tool.include.push(sdk_include.join("shared")); - } else { - return None; } - Some(tool.into_tool()) + + Some(()) } // For MSVC 12 we need to find the Windows 8.1 SDK. - fn find_msvc_12(tool: &str, target: &str) -> Option<Tool> { + pub 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)); @@ -168,7 +321,7 @@ pub fn find_tool(target: &str, tool: &str) -> Option<Tool> { } // For MSVC 11 we need to find the Windows 8 SDK. - fn find_msvc_11(tool: &str, target: &str) -> Option<Tool> { + pub 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)); @@ -403,8 +556,52 @@ pub fn find_tool(target: &str, tool: &str) -> Option<Tool> { max_key } + pub fn has_msbuild_version(version: &str) -> bool { + match version { + "15.0" => { + find_msbuild_vs15("x86_64-pc-windows-msvc").is_some() || + find_msbuild_vs15("i686-pc-windows-msvc").is_some() + } + "12.0" | "14.0" => { + LOCAL_MACHINE.open( + &OsString::from(format!("SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions\\{}", + version))).is_ok() + } + _ => false + } + } + // see http://stackoverflow.com/questions/328017/path-to-msbuild - fn find_msbuild(target: &str) -> Option<Tool> { + pub fn find_msbuild(target: &str) -> Option<Tool> { + // VS 15 (2017) changed how to locate msbuild + if let Some(r) = find_msbuild_vs15(target) { + return Some(r); + } else { + find_old_msbuild(target) + } + } + + fn find_msbuild_vs15(target: &str) -> Option<Tool> { + // Seems like this could also go through SetupConfiguration, + // or that find_msvc_15 could just use this registry key + // instead of the COM interface. + let key = r"SOFTWARE\WOW6432Node\Microsoft\VisualStudio\SxS\VS7"; + LOCAL_MACHINE.open(key.as_ref()) + .ok() + .and_then(|key| { + key.query_str("15.0").ok() + }) + .map(|path| { + let path = PathBuf::from(path).join(r"MSBuild\15.0\Bin\MSBuild.exe"); + let mut tool = Tool::new(path); + if target.contains("x86_64") { + tool.env.push(("Platform".into(), "X64".into())); + } + tool + }) + } + + fn find_old_msbuild(target: &str) -> Option<Tool> { let key = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions"; LOCAL_MACHINE.open(key.as_ref()) .ok() diff --git a/gcc/tests/test.rs b/gcc/tests/test.rs index 1c51e09..8fda3ed 100644 --- a/gcc/tests/test.rs +++ b/gcc/tests/test.rs @@ -180,7 +180,8 @@ fn msvc_smoke() { .must_have("/O2") .must_have("foo.c") .must_not_have("/Z7") - .must_have("/c"); + .must_have("/c") + .must_have("/MD"); test.cmd(1).must_have(test.td.path().join("foo.o")); } @@ -227,3 +228,25 @@ fn msvc_define() { test.cmd(0).must_have("/DFOO=bar").must_have("/DBAR"); } + +#[test] +fn msvc_static_crt() { + let test = Test::msvc(); + test.gcc() + .static_crt(true) + .file("foo.c") + .compile("libfoo.a"); + + test.cmd(0).must_have("/MT"); +} + +#[test] +fn msvc_no_static_crt() { + let test = Test::msvc(); + test.gcc() + .static_crt(false) + .file("foo.c") + .compile("libfoo.a"); + + test.cmd(0).must_have("/MD"); +} |