Skip to content

Commit

Permalink
Indicate static linker flag for static libraries
Browse files Browse the repository at this point in the history
  • Loading branch information
twistedfall committed Jul 21, 2024
1 parent 3278d78 commit 23f3aee
Show file tree
Hide file tree
Showing 6 changed files with 329 additions and 204 deletions.
53 changes: 8 additions & 45 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ mod docs;
#[path = "build/generator.rs"]
mod generator;
#[path = "build/library.rs"]
mod library;
pub mod library;

type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;

Expand Down Expand Up @@ -94,43 +94,6 @@ fn files_with_extension<'e>(dir: &Path, extension: impl AsRef<OsStr> + 'e) -> Re
})
}

/// Returns Some(new_file_name) if some parts of the filename were removed, None otherwise
fn cleanup_lib_filename(filename: &OsStr) -> Option<&OsStr> {
let mut new_filename = filename;
// used to check for the file extension (with dots stripped) and for the part of the filename
const LIB_EXTS: [&str; 7] = [".so.", ".a.", ".dll.", ".lib.", ".dylib.", ".framework.", ".tbd."];
let filename_path = Path::new(new_filename);
// strip lib extension from the filename
if let (Some(stem), Some(extension)) = (filename_path.file_stem(), filename_path.extension().and_then(OsStr::to_str)) {
if LIB_EXTS.iter().any(|e| e.trim_matches('.').eq_ignore_ascii_case(extension)) {
new_filename = stem;
}
}
if let Some(mut file) = new_filename.to_str() {
let orig_len = file.len();

// strip "lib" prefix from the filename unless targeting MSVC
if !*TARGET_ENV_MSVC {
file = file.strip_prefix("lib").unwrap_or(file);
}

// strip lib extension + suffix (e.g. .so.4.6.0) from the filename
LIB_EXTS.iter().for_each(|&inner_ext| {
if let Some(inner_ext_idx) = file.find(inner_ext) {
file = &file[..inner_ext_idx];
}
});
if orig_len != file.len() {
new_filename = OsStr::new(file);
}
}
if new_filename.len() != filename.len() {
Some(new_filename)
} else {
None
}
}

fn get_module_header_dir(header_dir: &Path) -> Option<PathBuf> {
let mut out = header_dir.join("opencv2.framework/Headers");
if out.exists() {
Expand Down Expand Up @@ -269,18 +232,18 @@ fn build_compiler(opencv: &Library) -> cc::Build {

fn setup_rerun() -> Result<()> {
for &v in AFFECTING_ENV_VARS.iter() {
println!("cargo:rerun-if-env-changed={v}");
println!("cargo:rerun-if-env-changed={v}"); // replace with cargo:: syntax when MSRV is 1.77
}

let include_exts = &[OsStr::new("cpp"), OsStr::new("hpp")];
let files_with_include_exts =
files_with_predicate(&SRC_CPP_DIR, |p| p.extension().map_or(false, |e| include_exts.contains(&e)))?;
for path in files_with_include_exts {
if let Some(path) = path.to_str() {
println!("cargo:rerun-if-changed={path}");
println!("cargo:rerun-if-changed={path}"); // replace with cargo:: syntax when MSRV is 1.77
}
}
println!("cargo:rerun-if-changed=Cargo.toml");
println!("cargo:rerun-if-changed=Cargo.toml"); // replace with cargo:: syntax when MSRV is 1.77
Ok(())
}

Expand All @@ -289,7 +252,7 @@ fn build_wrapper(opencv: &Library) {
eprintln!("=== Compiler information: {:#?}", cc.get_compiler());
let modules = MODULES.get().expect("MODULES not initialized");
for module in modules.iter() {
println!("cargo:rustc-cfg=ocvrs_has_module_{module}");
println!("cargo:rustc-cfg=ocvrs_has_module_{module}"); // replace with cargo:: syntax when MSRV is 1.77
cc.file(OUT_DIR.join(format!("{module}.cpp")));
let manual_cpp = SRC_CPP_DIR.join(format!("manual-{module}.cpp"));
if manual_cpp.exists() {
Expand Down Expand Up @@ -337,11 +300,11 @@ fn main() -> Result<()> {
let opencv = Library::probe()?;
eprintln!("=== OpenCV library configuration: {opencv:#?}");
if OPENCV_BRANCH_4.matches(&opencv.version) {
println!("cargo:rustc-cfg=ocvrs_opencv_branch_4");
println!("cargo:rustc-cfg=ocvrs_opencv_branch_4"); // replace with cargo:: syntax when MSRV is 1.77
} else if OPENCV_BRANCH_34.matches(&opencv.version) {
println!("cargo:rustc-cfg=ocvrs_opencv_branch_34");
println!("cargo:rustc-cfg=ocvrs_opencv_branch_34"); // replace with cargo:: syntax when MSRV is 1.77
} else if OPENCV_BRANCH_32.matches(&opencv.version) {
println!("cargo:rustc-cfg=ocvrs_opencv_branch_32");
println!("cargo:rustc-cfg=ocvrs_opencv_branch_32"); // replace with cargo:: syntax when MSRV is 1.77
} else {
panic!(
"Unsupported OpenCV version: {}, must be from 3.2, 3.4 or 4.x branch",
Expand Down
142 changes: 112 additions & 30 deletions build/cmake_probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,95 @@ use std::process::{Command, Output};
use semver::Version;
use shlex::Shlex;

use super::Result;
use super::library::Linkage;
use super::{Result, TARGET_ENV_MSVC};

#[derive(Debug, PartialEq, Eq)]
pub struct LinkLib(pub Linkage, pub String);

impl LinkLib {
#[inline]
pub fn emit_cargo_rustc_link(&self) -> String {
format!("cargo:rustc-link-lib={}{}", self.0.as_cargo_rustc_link_spec(), self.1) // replace with cargo:: syntax when MSRV is 1.77
}

/// Returns Some(new_file_name) if some parts of the filename were removed, None otherwise
pub fn cleanup_lib_filename(filename: &OsStr) -> Option<&OsStr> {
let mut new_filename = filename;
// used to check for the file extension (with dots stripped) and for the part of the filename
const LIB_EXTS: [&str; 7] = [".so.", ".a.", ".dll.", ".lib.", ".dylib.", ".framework.", ".tbd."];
let filename_path = Path::new(new_filename);
// strip lib extension from the filename
if let (Some(stem), Some(extension)) = (filename_path.file_stem(), filename_path.extension().and_then(OsStr::to_str)) {
if LIB_EXTS.iter().any(|e| e.trim_matches('.').eq_ignore_ascii_case(extension)) {
new_filename = stem;
}
}
if let Some(mut file) = new_filename.to_str() {
let orig_len = file.len();

// strip "lib" prefix from the filename unless targeting MSVC
if !*TARGET_ENV_MSVC {
file = file.strip_prefix("lib").unwrap_or(file);
}

// strip lib extension + suffix (e.g. .so.4.6.0) from the filename
LIB_EXTS.iter().for_each(|&inner_ext| {
if let Some(inner_ext_idx) = file.find(inner_ext) {
file = &file[..inner_ext_idx];
}
});
if orig_len != file.len() {
new_filename = OsStr::new(file);
}
}
if new_filename.len() != filename.len() {
Some(new_filename)
} else {
None
}
}
}

impl From<&str> for LinkLib {
fn from(value: &str) -> Self {
let (linkage, value) = Linkage::from_prefixed_str(value);
let path = Path::new(value);
let value = path
.file_name()
.and_then(Self::cleanup_lib_filename)
.and_then(OsStr::to_str)
.unwrap_or(value);
Self(linkage, value.to_string())
}
}

#[derive(Debug, PartialEq, Eq)]
pub struct LinkSearch(pub Linkage, pub PathBuf);

impl LinkSearch {
#[inline]
pub fn emit_cargo_rustc_link_search(&self) -> String {
format!(
"cargo:rustc-link-search={}{}",
self.0.as_cargo_rustc_link_search_spec(),
self.1.to_str().expect("Can't convert link search path to UTF-8 string")
) // replace with cargo:: syntax when MSRV is 1.77
}
}

impl From<&str> for LinkSearch {
fn from(value: &str) -> Self {
let (linkage, value) = Linkage::from_prefixed_str(value);
Self(linkage, value.into())
}
}

pub struct ProbeResult {
pub version: Option<Version>,
pub include_paths: Vec<PathBuf>,
pub link_paths: Vec<PathBuf>,
pub link_libs: Vec<String>,
pub link_paths: Vec<LinkSearch>,
pub link_libs: Vec<LinkLib>,
}

pub struct CmakeProbe<'r> {
Expand Down Expand Up @@ -117,12 +199,16 @@ impl<'r> CmakeProbe<'r> {

pub(crate) fn extract_from_cmdline(
cmdline: &str,
skip_cmd: bool,
include_paths: &mut Vec<PathBuf>,
link_paths: &mut Vec<PathBuf>,
link_libs: &mut Vec<String>,
link_paths: &mut Vec<LinkSearch>,
link_libs: &mut Vec<LinkLib>,
) {
eprintln!("=== Extracting build arguments from: {cmdline}");
let mut args = Shlex::new(cmdline.trim());
if skip_cmd {
args.next();
}
while let Some(arg) = args.next() {
let arg = arg.trim();
if let Some(path) = arg.strip_prefix("-I") {
Expand All @@ -131,14 +217,14 @@ impl<'r> CmakeProbe<'r> {
include_paths.push(path);
}
} else if let Some(path) = arg.strip_prefix("-L").or_else(|| arg.strip_prefix("-Wl,-rpath,")) {
let path = PathBuf::from(path.trim_start());
let path = LinkSearch(Linkage::Default, PathBuf::from(path.trim_start()));
if !link_paths.contains(&path) {
link_paths.push(path);
}
} else if let Some(lib) = arg.strip_prefix("-l") {
// unresolved cmake dependency specification like Qt5::Core
if !lib.contains("::") {
link_libs.push(lib.trim_start().to_string());
if !lib.contains("::") && lib != "gflags_shared" {
link_libs.push(LinkLib(Linkage::Default, lib.trim_start().to_string()));
}
} else if let Some(framework) = arg.strip_prefix("-framework") {
let framework = framework.trim_start();
Expand All @@ -147,45 +233,41 @@ impl<'r> CmakeProbe<'r> {
} else {
framework.to_string()
};
let framework_path = Path::new(&framework);
let has_extension = framework_path
.extension()
.and_then(OsStr::to_str)
.map_or(false, |ext| ext.eq_ignore_ascii_case("framework"));
if has_extension {
link_libs.push(framework);
} else {
link_libs.push(format!("{}.framework", framework));
}
link_libs.push(LinkLib(Linkage::Framework, framework));
} else if !arg.starts_with('-') {
let path = Path::new(arg);
if let Some(file) = path.file_name().and_then(super::cleanup_lib_filename) {
if let Some(cleaned_lib_filename) = path.file_name().and_then(LinkLib::cleanup_lib_filename) {
let linkage = Linkage::from_path(path);
if let Some(parent) = path.parent().map(|p| p.to_owned()) {
if !link_paths.contains(&parent) {
link_paths.push(parent);
let search_path = LinkSearch(linkage, parent);
if !link_paths.contains(&search_path) {
link_paths.push(search_path);
}
} else {
panic!("{}", arg.to_string());
}
link_libs.push(file.to_str().expect("Non-UTF8 filename").to_string());
link_libs.push(LinkLib(
linkage,
cleaned_lib_filename.to_str().expect("Non-UTF8 filename").to_string(),
));
}
} else {
eprintln!("=== Unexpected cmake compiler argument found: {arg}");
}
}
}

fn extract_from_makefile(&self, link_paths: &mut Vec<PathBuf>, link_libs: &mut Vec<String>) -> Result<()> {
fn extract_from_makefile(&self, link_paths: &mut Vec<LinkSearch>, link_libs: &mut Vec<LinkLib>) -> Result<()> {
let link_cmdline = fs::read_to_string(self.build_dir.join("CMakeFiles/ocvrs_probe.dir/link.txt"))?;
Self::extract_from_cmdline(&link_cmdline, &mut vec![], link_paths, link_libs);
Self::extract_from_cmdline(&link_cmdline, true, &mut vec![], link_paths, link_libs);
Ok(())
}

fn extract_from_ninja(
&self,
include_paths: &mut Vec<PathBuf>,
link_paths: &mut Vec<PathBuf>,
link_libs: &mut Vec<String>,
link_paths: &mut Vec<LinkSearch>,
link_libs: &mut Vec<LinkLib>,
) -> Result<()> {
let mut link_cmdline = BufReader::new(File::open(self.build_dir.join("build.ninja"))?);
let mut line = String::with_capacity(2048);
Expand All @@ -208,9 +290,9 @@ impl<'r> CmakeProbe<'r> {
State::Reading => {
let trimmed_line = line.trim_start();
if let Some(paths) = trimmed_line.strip_prefix("LINK_PATH = ") {
Self::extract_from_cmdline(paths, include_paths, link_paths, link_libs);
Self::extract_from_cmdline(paths, false, include_paths, link_paths, link_libs);
} else if let Some(libs) = trimmed_line.strip_prefix("LINK_LIBRARIES = ") {
Self::extract_from_cmdline(libs, include_paths, link_paths, link_libs);
Self::extract_from_cmdline(libs, false, include_paths, link_paths, link_libs);
}
}
}
Expand Down Expand Up @@ -292,7 +374,7 @@ impl<'r> CmakeProbe<'r> {
if output.status.success() {
let stdout = String::from_utf8(output.stdout)?;
eprintln!("=== cmake include arguments: {stdout:#?}");
Self::extract_from_cmdline(&stdout, &mut include_paths, &mut link_paths, &mut link_libs);
Self::extract_from_cmdline(&stdout, false, &mut include_paths, &mut link_paths, &mut link_libs);
Ok(())
} else {
Err(
Expand All @@ -314,7 +396,7 @@ impl<'r> CmakeProbe<'r> {
if output.status.success() {
let stdout = String::from_utf8(output.stdout)?;
eprintln!("=== cmake link arguments: {stdout:#?}");
Self::extract_from_cmdline(&stdout, &mut include_paths, &mut link_paths, &mut link_libs);
Self::extract_from_cmdline(&stdout, false, &mut include_paths, &mut link_paths, &mut link_libs);
Ok(())
} else {
Err(
Expand Down
4 changes: 2 additions & 2 deletions build/docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ pub fn handle_running_in_docsrs() -> GenerateFullBindings {
if env::var_os("DOCS_RS").is_some() {
let docs_dir = MANIFEST_DIR.join("docs");
// fake setup for docs.rs
println!(r#"cargo:rustc-cfg=ocvrs_opencv_branch_4"#);
println!("cargo:rustc-cfg=ocvrs_opencv_branch_4"); // replace with cargo:: syntax when MSRV is 1.77
transfer_bindings_from_docs(&docs_dir, &OUT_DIR);
for path in files_with_extension(&docs_dir, "rs").expect("Can't read hub dir") {
if let Some(module) = path.file_stem().and_then(OsStr::to_str) {
println!("cargo:rustc-cfg=ocvrs_has_module_{module}");
println!("cargo:rustc-cfg=ocvrs_has_module_{module}"); // replace with cargo:: syntax when MSRV is 1.77
}
}
GenerateFullBindings::Stop
Expand Down
Loading

0 comments on commit 23f3aee

Please sign in to comment.