Skip to content

Commit

Permalink
Fix cross-compiling ios targets with cmake 3.14 as rust-lang#88 states (
Browse files Browse the repository at this point in the history
rust-lang#93)

    * Rename target variable to target_triple
    * Remove a bit of code duplication
    * Add GenericTarget with several override points
    * Add support for cross-compiling Apple targets with cmake 3.14

Co-authored-by: simlay <[email protected]>
Co-authored-by: Kyle Fleming <[email protected]>
  • Loading branch information
3 people committed Feb 12, 2021
1 parent 03acfcf commit ffcd3a7
Showing 1 changed file with 244 additions and 21 deletions.
265 changes: 244 additions & 21 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ impl Config {
/// This will run both the build system generator command as well as the
/// command to build the library.
pub fn build(&mut self) -> PathBuf {
let target = match self.target.clone() {
let target_triple = match self.target.clone() {
Some(t) => t,
None => {
let mut t = getenv_unwrap("TARGET");
Expand All @@ -397,8 +397,9 @@ impl Config {
t
}
};
let target = get_target(&target_triple);
let host = self.host.clone().unwrap_or_else(|| getenv_unwrap("HOST"));
let msvc = target.contains("msvc");
let msvc = target_triple.contains("msvc");
let ndk = self.uses_android_ndk();
let mut c_cfg = cc::Build::new();
c_cfg
Expand All @@ -409,7 +410,7 @@ impl Config {
.host(&host)
.no_default_flags(ndk);
if !ndk {
c_cfg.target(&target);
c_cfg.target(&target_triple);
}
let mut cxx_cfg = cc::Build::new();
cxx_cfg
Expand All @@ -421,7 +422,7 @@ impl Config {
.host(&host)
.no_default_flags(ndk);
if !ndk {
cxx_cfg.target(&target);
cxx_cfg.target(&target_triple);
}
if let Some(static_crt) = self.static_crt {
c_cfg.static_crt(static_crt);
Expand Down Expand Up @@ -470,7 +471,7 @@ impl Config {
if let Some(ref generator) = self.generator {
is_ninja = generator.to_string_lossy().contains("Ninja");
}
if target.contains("windows-gnu") {
if target_triple.contains("windows-gnu") {
if host.contains("windows") {
// On MinGW we need to coerce cmake to not generate a visual
// studio build system but instead use makefiles that MinGW can
Expand Down Expand Up @@ -529,22 +530,22 @@ impl Config {
if self.generator.is_none() {
conf_cmd
.arg("-G")
.arg(self.visual_studio_generator(&target));
.arg(self.visual_studio_generator(&target_triple));
using_nmake_generator = false;
} else {
using_nmake_generator = self.generator.as_ref().unwrap() == "NMake Makefiles";
}
if !is_ninja && !using_nmake_generator {
if target.contains("x86_64") {
if target_triple.contains("x86_64") {
conf_cmd.arg("-Thost=x64");
conf_cmd.arg("-Ax64");
} else if target.contains("thumbv7a") {
} else if target_triple.contains("thumbv7a") {
conf_cmd.arg("-Thost=x64");
conf_cmd.arg("-Aarm");
} else if target.contains("aarch64") {
} else if target_triple.contains("aarch64") {
conf_cmd.arg("-Thost=x64");
conf_cmd.arg("-AARM64");
} else if target.contains("i686") {
} else if target_triple.contains("i686") {
use cc::windows_registry::{find_vs_version, VsVers};
match find_vs_version() {
Ok(VsVers::Vs16) => {
Expand All @@ -557,18 +558,21 @@ impl Config {
_ => {}
};
} else {
panic!("unsupported msvc target: {}", target);
panic!("unsupported msvc target: {}", target_triple);
}
}
} else if target.contains("redox") {
} else if target_triple.contains("redox") {
if !self.defined("CMAKE_SYSTEM_NAME") {
conf_cmd.arg("-DCMAKE_SYSTEM_NAME=Generic");
}
} else if target.contains("solaris") {
} else if target_triple.contains("solaris") {
if !self.defined("CMAKE_SYSTEM_NAME") {
conf_cmd.arg("-DCMAKE_SYSTEM_NAME=SunOS");
}
}

target.add_cmake_defines(&mut conf_cmd, self);

if let Some(ref generator) = self.generator {
conf_cmd.arg("-G").arg(generator);
}
Expand Down Expand Up @@ -607,17 +611,27 @@ impl Config {
let mut set_compiler = |kind: &str, compiler: &cc::Tool, extra: &OsString| {
let mut add_compiler_flags = |flag_var_name: &str| {
if !self.defined(flag_var_name) {
let mut flagsflag = OsString::from("-D");
flagsflag.push(flag_var_name);
flagsflag.push("=");
flagsflag.push(extra);
let mut compiler_flags = OsString::new();
for arg in compiler.args() {
if skip_arg(arg) {
continue;
}
flagsflag.push(" ");
flagsflag.push(arg);
compiler_flags.push(" ");
compiler_flags.push(arg);
}
target.filter_compiler_args(&mut compiler_flags);

// We want to filter compiler args from cc-rs, but not user-supplied ones,
// so we add user-supplied ones after we filter.
if !compiler_flags.is_empty() {
compiler_flags.push(" ");
}
compiler_flags.push(extra);

let mut flagsflag = OsString::from("-D");
flagsflag.push(flag_var_name);
flagsflag.push("=");
flagsflag.push(compiler_flags);
conf_cmd.arg(flagsflag);
}
};
Expand Down Expand Up @@ -696,6 +710,9 @@ impl Config {
}

for &(ref k, ref v) in c_compiler.env().iter().chain(&self.env) {
if target.should_exclude_env_var(k, v) {
continue;
}
conf_cmd.env(k, v);
}

Expand Down Expand Up @@ -751,10 +768,13 @@ impl Config {
}

// And build!
let target = self.cmake_target.clone().unwrap_or("install".to_string());
let cmake_target = self.cmake_target.clone().unwrap_or("install".to_string());
let mut build_cmd = Command::new(&executable);
build_cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
for &(ref k, ref v) in c_compiler.env().iter().chain(&self.env) {
if target.should_exclude_env_var(k, v) {
continue;
}
build_cmd.env(k, v);
}

Expand All @@ -765,7 +785,7 @@ impl Config {
build_cmd.arg("--build").arg(".");

if !self.no_build_target {
build_cmd.arg("--target").arg(target);
build_cmd.arg("--target").arg(cmake_target);
}

build_cmd
Expand Down Expand Up @@ -866,6 +886,209 @@ impl Config {
}
}

trait Target {
fn add_cmake_defines(&self, _cmd: &mut Command, _config: &Config) {}

fn should_exclude_env_var(&self, _key: &OsStr, _value: &OsStr) -> bool {
false
}

fn filter_compiler_args(&self, _flags: &mut OsString) {}
}

fn get_target(target_triple: &str) -> Box<dyn Target> {
let target: Option<Box<dyn Target>>;
target = AppleTarget::new(target_triple)
.map(|apple_target| Box::new(apple_target) as Box<dyn Target>);
target.unwrap_or_else(|| Box::new(GenericTarget))
}

struct GenericTarget;

impl Target for GenericTarget {}

struct AppleTarget {
rust_target: String,
rust_target_arch: String,
rust_target_platform: String,
}

impl AppleTarget {
fn new(target_triple: &str) -> Option<AppleTarget> {
let parts: Vec<&str> = target_triple.split('-').collect();
if parts.len() < 3 {
return None;
}

let rust_target_arch = parts[0];
let rust_target_vendor = parts[1];
let rust_target_platform = parts[2];

if rust_target_vendor != "apple" {
return None;
}
if rust_target_platform != "ios" && !rust_target_platform.starts_with("darwin") {
eprintln!(
"Warning: unknown Apple platform ({}) for target: {}",
rust_target_platform, target_triple
);
return None;
}

Some(AppleTarget {
rust_target: target_triple.to_owned(),
rust_target_arch: rust_target_arch.to_owned(),
rust_target_platform: rust_target_platform.to_owned(),
})
}

fn is_ios_target(&self) -> bool {
self.rust_target_platform == "ios"
}

fn is_osx_target(&self) -> bool {
self.rust_target_platform.starts_with("darwin")
}

fn cmake_target_arch(&self) -> Option<String> {
match self.rust_target_arch.as_str() {
"aarch64" => Some("arm64".to_owned()),
"armv7" => Some("armv7".to_owned()),
"armv7s" => Some("armv7s".to_owned()),
"i386" => Some("i386".to_owned()),
"x86_64" => Some("x86_64".to_owned()),
_ => {
eprintln!(
"Warning: unknown architecture for target: {}",
self.rust_target_arch
);
None
}
}
}

fn sdk_name(&self) -> Option<String> {
if self.is_ios_target() {
match self.rust_target_arch.as_str() {
"aarch64" | "armv7" | "armv7s" => Some("iphoneos".to_owned()),
"i386" | "x86_64" => Some("iphonesimulator".to_owned()),
_ => {
eprintln!(
"Warning: unknown architecture for Apple target: {}",
self.rust_target_arch
);
None
}
}
} else if self.is_osx_target() {
Some("macosx".to_owned())
} else {
eprintln!(
"Warning: could not determine sdk name for Apple target: {}",
self.rust_target
);
None
}
}

fn deployment_target(&self) -> Option<String> {
if self.is_ios_target() {
println!("cargo:rerun-if-env-changed=IPHONEOS_DEPLOYMENT_TARGET");
Some(std::env::var("IPHONEOS_DEPLOYMENT_TARGET").unwrap_or_else(|_| "7.0".into()))
} else if self.is_osx_target() {
println!("cargo:rerun-if-env-changed=MACOSX_DEPLOYMENT_TARGET");
Some(std::env::var("MACOSX_DEPLOYMENT_TARGET").unwrap_or_else(|_| "".into()))
} else {
eprintln!(
"Warning: could not determine deployment target for Apple target: {}",
self.rust_target
);
None
}
}
}

impl Target for AppleTarget {
fn add_cmake_defines(&self, cmd: &mut Command, config: &Config) {
// These 3 CMAKE_OSX_* variables apply to all Apple platforms

if !config.defined("CMAKE_OSX_ARCHITECTURES") {
if let Some(cmake_target_arch) = self.cmake_target_arch() {
cmd.arg(format!("-DCMAKE_OSX_ARCHITECTURES={}", cmake_target_arch));
}
}

if !config.defined("CMAKE_OSX_SYSROOT") {
if let Some(sdk_name) = self.sdk_name() {
cmd.arg(format!("-DCMAKE_OSX_SYSROOT={}", sdk_name));
}
}

if !config.defined("CMAKE_OSX_DEPLOYMENT_TARGET") {
if let Some(deployment_target) = self.deployment_target() {
cmd.arg(format!(
"-DCMAKE_OSX_DEPLOYMENT_TARGET={}",
deployment_target
));
}
}

// CMAKE_SYSTEM_NAME is used to tell cmake we're cross-compiling
if self.is_ios_target() && !config.defined("CMAKE_SYSTEM_NAME") {
cmd.arg("-DCMAKE_SYSTEM_NAME=iOS");
}
}

fn should_exclude_env_var(&self, key: &OsStr, _value: &OsStr) -> bool {
key.to_str().map_or(false, |key| {
// These cause issues with llvm if an env var for a different Apple platform
// is present. Since cmake handles communicating these values to llvm, and
// we use cmake defines to tell cmake what the value is, the env vars themselves
// are filtered out.
key.ends_with("DEPLOYMENT_TARGET") || key.starts_with("SDK")
})
}

fn filter_compiler_args(&self, flags: &mut OsString) {
if let Some(flags_str) = flags.to_str() {
let mut flags_string = flags_str.to_owned();
flags_string.push(' ');
// These are set by cmake
// The initial version of this logic used the Regex crate and lazy_static.
// Architecture regex: "-arch [^ ]+ "
// Deployment target regex: "-m[\\w-]+-version-min=[\\d.]+ "
// sysroot regex: "-isysroot [^ ]+ "
// The following forloop emulates that set of regular expressions.
for i in flags.to_string_lossy().split(" -") {
if i.starts_with("isysroot")
|| i.starts_with("arch")
|| (i.starts_with("m") && i.contains("-version-min="))
{
flags_string = flags_string.replace(&format!(" -{}", i), "");
}
}

if flags_string.ends_with(' ') {
flags_string.pop();
}

flags.clear();
flags.push(OsString::from(flags_string));
}
}
}

#[test]
fn test_filter_compiler_args_ios() {
let target = AppleTarget::new("aarch64-apple-ios").unwrap();
let mut input_flags = OsString::from(" -fPIC -m64 -m64 -mios-simulator-version-min=7.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.2.sdk -fembed-bitcode -arch aarch64-apple-ios");
target.filter_compiler_args(&mut input_flags);
assert_eq!(
input_flags,
OsString::from(" -fPIC -m64 -m64 -fembed-bitcode")
);
}

enum CMakeAction<'a> {
Configure {
conf_cmd: &'a mut Command,
Expand Down

0 comments on commit ffcd3a7

Please sign in to comment.