use std::{
collections::HashSet,
io,
};
use crate::{Ruby, RubyExecError};
use RubyLinkError::*;
fn link_static(lib: &str) {
println!("cargo:rustc-link-lib=static={}", lib);
}
fn link_dynamic(lib: &str) {
println!("cargo:rustc-link-lib=dylib={}", lib);
}
fn link_framework(lib: &str) {
println!("cargo:rustc-link-lib=framework={}", lib);
}
fn lib_name(lib_flag: &str) -> &str {
&lib_flag[2..]
}
fn lib_name_msvc(lib_flag: &str) -> &str {
&lib_flag[..(lib_flag.len() - 4)]
}
#[cfg(target_os = "linux")]
fn os_helper(ruby: &Ruby, static_lib: bool) -> Result<(), RubyLinkError> {
use std::env;
use std::os::unix::fs::symlink;
use std::path::{Path, PathBuf};
if static_lib {
return Ok(());
}
let mut link_path = match env::var_os("OUT_DIR") {
Some(out_dir) => {
let mut out_dir = PathBuf::from(out_dir);
for _ in 0..3 {
if !out_dir.pop() {
let mesg = "Could not find 'deps' directory";
let kind = io::ErrorKind::NotFound;
return Err(io::Error::new(kind, mesg).into());
}
}
out_dir.push("deps");
out_dir
},
None => return Err(RubyLinkError::MissingEnvVar("OUT_DIR")),
};
let version = ruby.version();
let so_name = format!("libruby.so.{}.{}", version.major, version.minor);
let so_path = ruby.lib_dir().join(&so_name);
fn is_symlink(path: &Path) -> io::Result<bool> {
match std::fs::symlink_metadata(path) {
Ok(metadata) => Ok(metadata.file_type().is_symlink()),
Err(error) => match error.kind() {
io::ErrorKind::NotFound => Ok(false),
_ => Err(error),
},
}
}
link_path.push(&so_name);
if !is_symlink(&link_path)? {
symlink(&so_path, link_path)?;
}
Ok(())
}
#[cfg(not(target_os = "linux"))]
fn os_helper(_ruby: &Ruby, _static_lib: bool) -> Result<(), RubyLinkError> {
Ok(())
}
pub(crate) fn link(ruby: &Ruby, static_lib: bool) -> Result<(), RubyLinkError> {
os_helper(ruby, static_lib)?;
println!("cargo:rustc-link-search=native={}", ruby.lib_dir().display());
let target = ruby.get_config("target")?;
let target_msvc = target.contains("msvc") || target.contains("mswin");
let lib_name = if target_msvc { lib_name_msvc } else { lib_name };
let key = if static_lib {
"LIBRUBYARG_STATIC"
} else {
"LIBRUBYARG_SHARED"
};
let args = ruby.get_config(key)?;
if args.trim().is_empty() {
return Err(RubyLinkError::MissingLibs { static_lib });
}
let libs = ruby.libs()?;
let main_libs = ruby.main_libs()?;
let so_libs = ruby.so_libs()?;
let mut dy_libs = HashSet::new();
for libs in [&libs, &main_libs, &so_libs].iter() {
if *libs != "nil" {
dy_libs.extend(libs.split_ascii_whitespace().map(lib_name));
}
}
let ruby_lib = ruby.lib_name(static_lib)?;
if static_lib {
link_static(&ruby_lib);
} else {
link_dynamic(&ruby_lib);
}
let seen_lib = |lib: &str| {
lib == ruby_lib || dy_libs.contains(lib)
};
for lib in &dy_libs {
link_dynamic(lib);
}
if target_msvc {
return Ok(());
}
let mut args_iter = args.split_ascii_whitespace();
while let Some(arg) = args_iter.next() {
if arg.len() < 2 {
return Err(UnknownFlags(args));
}
let (opt, val) = arg.split_at(2);
match opt {
"-l" => if !seen_lib(val) {
link_dynamic(val);
},
"-L" => {
println!("cargo:rustc-link-search=native={}", val);
},
"-F" => {
println!("cargo:rustc-link-search=framework={}", val);
},
"-W" => {
continue;
},
_ => if arg == "-framework" {
let framework = match args_iter.next() {
Some(arg) => arg,
None => return Err(MissingFramework(args)),
};
link_framework(framework);
} else {
return Err(UnknownFlags(args));
}
}
}
Ok(())
}
#[derive(Debug)]
pub enum RubyLinkError {
Exec(RubyExecError),
UnknownFlags(String),
MissingFramework(String),
MissingLibs {
static_lib: bool
},
MissingEnvVar(&'static str),
Io(io::Error),
}
impl From<RubyExecError> for RubyLinkError {
#[inline]
fn from(error: RubyExecError) -> Self {
RubyLinkError::Exec(error)
}
}
impl From<io::Error> for RubyLinkError {
#[inline]
fn from(error: io::Error) -> Self {
RubyLinkError::Io(error)
}
}