Skip to content

Commit cbf03b4

Browse files
committed
Indicate static linker flag for static libraries
1 parent 639e0c8 commit cbf03b4

File tree

6 files changed

+334
-203
lines changed

6 files changed

+334
-203
lines changed

build.rs

Lines changed: 8 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ mod docs;
2323
#[path = "build/generator.rs"]
2424
mod generator;
2525
#[path = "build/library.rs"]
26-
mod library;
26+
pub mod library;
2727

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

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

97-
/// Returns Some(new_file_name) if some parts of the filename were removed, None otherwise
98-
fn cleanup_lib_filename(filename: &OsStr) -> Option<&OsStr> {
99-
let mut new_filename = filename;
100-
// used to check for the file extension (with dots stripped) and for the part of the filename
101-
const LIB_EXTS: [&str; 7] = [".so.", ".a.", ".dll.", ".lib.", ".dylib.", ".framework.", ".tbd."];
102-
let filename_path = Path::new(new_filename);
103-
// strip lib extension from the filename
104-
if let (Some(stem), Some(extension)) = (filename_path.file_stem(), filename_path.extension().and_then(OsStr::to_str)) {
105-
if LIB_EXTS.iter().any(|e| e.trim_matches('.').eq_ignore_ascii_case(extension)) {
106-
new_filename = stem;
107-
}
108-
}
109-
if let Some(mut file) = new_filename.to_str() {
110-
let orig_len = file.len();
111-
112-
// strip "lib" prefix from the filename unless targeting MSVC
113-
if !*TARGET_ENV_MSVC {
114-
file = file.strip_prefix("lib").unwrap_or(file);
115-
}
116-
117-
// strip lib extension + suffix (e.g. .so.4.6.0) from the filename
118-
LIB_EXTS.iter().for_each(|&inner_ext| {
119-
if let Some(inner_ext_idx) = file.find(inner_ext) {
120-
file = &file[..inner_ext_idx];
121-
}
122-
});
123-
if orig_len != file.len() {
124-
new_filename = OsStr::new(file);
125-
}
126-
}
127-
if new_filename.len() != filename.len() {
128-
Some(new_filename)
129-
} else {
130-
None
131-
}
132-
}
133-
13497
fn get_module_header_dir(header_dir: &Path) -> Option<PathBuf> {
13598
let mut out = header_dir.join("opencv2.framework/Headers");
13699
if out.exists() {
@@ -269,18 +232,18 @@ fn build_compiler(opencv: &Library) -> cc::Build {
269232

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

275238
let include_exts = &[OsStr::new("cpp"), OsStr::new("hpp")];
276239
let files_with_include_exts =
277240
files_with_predicate(&SRC_CPP_DIR, |p| p.extension().map_or(false, |e| include_exts.contains(&e)))?;
278241
for path in files_with_include_exts {
279242
if let Some(path) = path.to_str() {
280-
println!("cargo:rerun-if-changed={path}");
243+
println!("cargo:rerun-if-changed={path}"); // replace with cargo:: syntax when MSRV is 1.77
281244
}
282245
}
283-
println!("cargo:rerun-if-changed=Cargo.toml");
246+
println!("cargo:rerun-if-changed=Cargo.toml"); // replace with cargo:: syntax when MSRV is 1.77
284247
Ok(())
285248
}
286249

@@ -289,7 +252,7 @@ fn build_wrapper(opencv: &Library) {
289252
eprintln!("=== Compiler information: {:#?}", cc.get_compiler());
290253
let modules = MODULES.get().expect("MODULES not initialized");
291254
for module in modules.iter() {
292-
println!("cargo:rustc-cfg=ocvrs_has_module_{module}");
255+
println!("cargo:rustc-cfg=ocvrs_has_module_{module}"); // replace with cargo:: syntax when MSRV is 1.77
293256
cc.file(OUT_DIR.join(format!("{module}.cpp")));
294257
let manual_cpp = SRC_CPP_DIR.join(format!("manual-{module}.cpp"));
295258
if manual_cpp.exists() {
@@ -337,11 +300,11 @@ fn main() -> Result<()> {
337300
let opencv = Library::probe()?;
338301
eprintln!("=== OpenCV library configuration: {opencv:#?}");
339302
if OPENCV_BRANCH_4.matches(&opencv.version) {
340-
println!("cargo:rustc-cfg=ocvrs_opencv_branch_4");
303+
println!("cargo:rustc-cfg=ocvrs_opencv_branch_4"); // replace with cargo:: syntax when MSRV is 1.77
341304
} else if OPENCV_BRANCH_34.matches(&opencv.version) {
342-
println!("cargo:rustc-cfg=ocvrs_opencv_branch_34");
305+
println!("cargo:rustc-cfg=ocvrs_opencv_branch_34"); // replace with cargo:: syntax when MSRV is 1.77
343306
} else if OPENCV_BRANCH_32.matches(&opencv.version) {
344-
println!("cargo:rustc-cfg=ocvrs_opencv_branch_32");
307+
println!("cargo:rustc-cfg=ocvrs_opencv_branch_32"); // replace with cargo:: syntax when MSRV is 1.77
345308
} else {
346309
panic!(
347310
"Unsupported OpenCV version: {}, must be from 3.2, 3.4 or 4.x branch",

build/cmake_probe.rs

Lines changed: 117 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,97 @@ use std::process::{Command, Output};
88
use semver::Version;
99
use shlex::Shlex;
1010

11-
use super::Result;
11+
use super::library::Linkage;
12+
use super::{Result, TARGET_ENV_MSVC};
13+
14+
#[derive(Debug, PartialEq, Eq)]
15+
pub struct LinkLib(pub Linkage, pub String);
16+
17+
impl LinkLib {
18+
#[inline]
19+
pub fn emit_cargo_rustc_link(&self) -> String {
20+
// fixme: specifying static linkage breaks things in CI
21+
// format!("cargo:rustc-link-lib={}{}", self.0.as_cargo_rustc_link_spec(), self.1) // replace with cargo:: syntax when MSRV is 1.77
22+
format!("cargo:rustc-link-lib={}", self.1) // replace with cargo:: syntax when MSRV is 1.77
23+
}
24+
25+
/// Returns Some(new_file_name) if some parts of the filename were removed, None otherwise
26+
pub fn cleanup_lib_filename(filename: &OsStr) -> Option<&OsStr> {
27+
let mut new_filename = filename;
28+
// used to check for the file extension (with dots stripped) and for the part of the filename
29+
const LIB_EXTS: [&str; 6] = [".so.", ".a.", ".dll.", ".lib.", ".dylib.", ".tbd."];
30+
let filename_path = Path::new(new_filename);
31+
// strip lib extension from the filename
32+
if let (Some(stem), Some(extension)) = (filename_path.file_stem(), filename_path.extension().and_then(OsStr::to_str)) {
33+
if LIB_EXTS.iter().any(|e| e.trim_matches('.').eq_ignore_ascii_case(extension)) {
34+
new_filename = stem;
35+
}
36+
}
37+
if let Some(mut file) = new_filename.to_str() {
38+
let orig_len = file.len();
39+
40+
// strip "lib" prefix from the filename unless targeting MSVC
41+
if !*TARGET_ENV_MSVC {
42+
file = file.strip_prefix("lib").unwrap_or(file);
43+
}
44+
45+
// strip lib extension + suffix (e.g. .so.4.6.0) from the filename
46+
LIB_EXTS.iter().for_each(|&inner_ext| {
47+
if let Some(inner_ext_idx) = file.find(inner_ext) {
48+
file = &file[..inner_ext_idx];
49+
}
50+
});
51+
if orig_len != file.len() {
52+
new_filename = OsStr::new(file);
53+
}
54+
}
55+
if new_filename.len() != filename.len() {
56+
Some(new_filename)
57+
} else {
58+
None
59+
}
60+
}
61+
}
62+
63+
impl From<&str> for LinkLib {
64+
fn from(value: &str) -> Self {
65+
let (linkage, value) = Linkage::from_prefixed_str(value);
66+
let path = Path::new(value);
67+
let value = path
68+
.file_name()
69+
.and_then(Self::cleanup_lib_filename)
70+
.and_then(OsStr::to_str)
71+
.unwrap_or(value);
72+
Self(linkage, value.to_string())
73+
}
74+
}
75+
76+
#[derive(Debug, PartialEq, Eq)]
77+
pub struct LinkSearch(pub Linkage, pub PathBuf);
78+
79+
impl LinkSearch {
80+
#[inline]
81+
pub fn emit_cargo_rustc_link_search(&self) -> String {
82+
format!(
83+
"cargo:rustc-link-search={}{}",
84+
self.0.as_cargo_rustc_link_search_spec(),
85+
self.1.to_str().expect("Can't convert link search path to UTF-8 string")
86+
) // replace with cargo:: syntax when MSRV is 1.77
87+
}
88+
}
89+
90+
impl From<&str> for LinkSearch {
91+
fn from(value: &str) -> Self {
92+
let (linkage, value) = Linkage::from_prefixed_str(value);
93+
Self(linkage, value.into())
94+
}
95+
}
1296

1397
pub struct ProbeResult {
1498
pub version: Option<Version>,
1599
pub include_paths: Vec<PathBuf>,
16-
pub link_paths: Vec<PathBuf>,
17-
pub link_libs: Vec<String>,
100+
pub link_paths: Vec<LinkSearch>,
101+
pub link_libs: Vec<LinkLib>,
18102
}
19103

20104
pub struct CmakeProbe<'r> {
@@ -117,12 +201,16 @@ impl<'r> CmakeProbe<'r> {
117201

118202
pub(crate) fn extract_from_cmdline(
119203
cmdline: &str,
204+
skip_cmd: bool,
120205
include_paths: &mut Vec<PathBuf>,
121-
link_paths: &mut Vec<PathBuf>,
122-
link_libs: &mut Vec<String>,
206+
link_paths: &mut Vec<LinkSearch>,
207+
link_libs: &mut Vec<LinkLib>,
123208
) {
124209
eprintln!("=== Extracting build arguments from: {cmdline}");
125210
let mut args = Shlex::new(cmdline.trim());
211+
if skip_cmd {
212+
args.next();
213+
}
126214
while let Some(arg) = args.next() {
127215
let arg = arg.trim();
128216
if let Some(path) = arg.strip_prefix("-I") {
@@ -131,14 +219,14 @@ impl<'r> CmakeProbe<'r> {
131219
include_paths.push(path);
132220
}
133221
} else if let Some(path) = arg.strip_prefix("-L").or_else(|| arg.strip_prefix("-Wl,-rpath,")) {
134-
let path = PathBuf::from(path.trim_start());
222+
let path = LinkSearch(Linkage::Default, PathBuf::from(path.trim_start()));
135223
if !link_paths.contains(&path) {
136224
link_paths.push(path);
137225
}
138226
} else if let Some(lib) = arg.strip_prefix("-l") {
139227
// unresolved cmake dependency specification like Qt5::Core
140-
if !lib.contains("::") {
141-
link_libs.push(lib.trim_start().to_string());
228+
if !lib.contains("::") && lib != "gflags_shared" {
229+
link_libs.push(LinkLib(Linkage::Default, lib.trim_start().to_string()));
142230
}
143231
} else if let Some(framework) = arg.strip_prefix("-framework") {
144232
let framework = framework.trim_start();
@@ -147,45 +235,45 @@ impl<'r> CmakeProbe<'r> {
147235
} else {
148236
framework.to_string()
149237
};
150-
let framework_path = Path::new(&framework);
151-
let has_extension = framework_path
152-
.extension()
153-
.and_then(OsStr::to_str)
154-
.map_or(false, |ext| ext.eq_ignore_ascii_case("framework"));
155-
if has_extension {
156-
link_libs.push(framework);
157-
} else {
158-
link_libs.push(format!("{}.framework", framework));
238+
link_libs.push(LinkLib(Linkage::Framework, framework));
239+
} else if let Some(output_file) = arg.strip_prefix("-o") {
240+
if output_file.trim().is_empty() {
241+
args.next().expect("No output file after -o");
159242
}
160243
} else if !arg.starts_with('-') {
161244
let path = Path::new(arg);
162-
if let Some(file) = path.file_name().and_then(super::cleanup_lib_filename) {
245+
if let Some(cleaned_lib_filename) = path.file_name().and_then(LinkLib::cleanup_lib_filename) {
246+
let linkage = Linkage::from_path(path);
163247
if let Some(parent) = path.parent().map(|p| p.to_owned()) {
164-
if !link_paths.contains(&parent) {
165-
link_paths.push(parent);
248+
let search_path = LinkSearch(linkage, parent);
249+
if !link_paths.contains(&search_path) {
250+
link_paths.push(search_path);
166251
}
167252
} else {
168253
panic!("{}", arg.to_string());
169254
}
170-
link_libs.push(file.to_str().expect("Non-UTF8 filename").to_string());
255+
link_libs.push(LinkLib(
256+
linkage,
257+
cleaned_lib_filename.to_str().expect("Non-UTF8 filename").to_string(),
258+
));
171259
}
172260
} else {
173261
eprintln!("=== Unexpected cmake compiler argument found: {arg}");
174262
}
175263
}
176264
}
177265

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

184272
fn extract_from_ninja(
185273
&self,
186274
include_paths: &mut Vec<PathBuf>,
187-
link_paths: &mut Vec<PathBuf>,
188-
link_libs: &mut Vec<String>,
275+
link_paths: &mut Vec<LinkSearch>,
276+
link_libs: &mut Vec<LinkLib>,
189277
) -> Result<()> {
190278
let mut link_cmdline = BufReader::new(File::open(self.build_dir.join("build.ninja"))?);
191279
let mut line = String::with_capacity(2048);
@@ -208,9 +296,9 @@ impl<'r> CmakeProbe<'r> {
208296
State::Reading => {
209297
let trimmed_line = line.trim_start();
210298
if let Some(paths) = trimmed_line.strip_prefix("LINK_PATH = ") {
211-
Self::extract_from_cmdline(paths, include_paths, link_paths, link_libs);
299+
Self::extract_from_cmdline(paths, false, include_paths, link_paths, link_libs);
212300
} else if let Some(libs) = trimmed_line.strip_prefix("LINK_LIBRARIES = ") {
213-
Self::extract_from_cmdline(libs, include_paths, link_paths, link_libs);
301+
Self::extract_from_cmdline(libs, false, include_paths, link_paths, link_libs);
214302
}
215303
}
216304
}
@@ -292,7 +380,7 @@ impl<'r> CmakeProbe<'r> {
292380
if output.status.success() {
293381
let stdout = String::from_utf8(output.stdout)?;
294382
eprintln!("=== cmake include arguments: {stdout:#?}");
295-
Self::extract_from_cmdline(&stdout, &mut include_paths, &mut link_paths, &mut link_libs);
383+
Self::extract_from_cmdline(&stdout, false, &mut include_paths, &mut link_paths, &mut link_libs);
296384
Ok(())
297385
} else {
298386
Err(
@@ -314,7 +402,7 @@ impl<'r> CmakeProbe<'r> {
314402
if output.status.success() {
315403
let stdout = String::from_utf8(output.stdout)?;
316404
eprintln!("=== cmake link arguments: {stdout:#?}");
317-
Self::extract_from_cmdline(&stdout, &mut include_paths, &mut link_paths, &mut link_libs);
405+
Self::extract_from_cmdline(&stdout, false, &mut include_paths, &mut link_paths, &mut link_libs);
318406
Ok(())
319407
} else {
320408
Err(

build/docs.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ pub fn handle_running_in_docsrs() -> GenerateFullBindings {
88
if env::var_os("DOCS_RS").is_some() {
99
let docs_dir = MANIFEST_DIR.join("docs");
1010
// fake setup for docs.rs
11-
println!(r#"cargo:rustc-cfg=ocvrs_opencv_branch_4"#);
11+
println!("cargo:rustc-cfg=ocvrs_opencv_branch_4"); // replace with cargo:: syntax when MSRV is 1.77
1212
transfer_bindings_from_docs(&docs_dir, &OUT_DIR);
1313
for path in files_with_extension(&docs_dir, "rs").expect("Can't read hub dir") {
1414
if let Some(module) = path.file_stem().and_then(OsStr::to_str) {
15-
println!("cargo:rustc-cfg=ocvrs_has_module_{module}");
15+
println!("cargo:rustc-cfg=ocvrs_has_module_{module}"); // replace with cargo:: syntax when MSRV is 1.77
1616
}
1717
}
1818
GenerateFullBindings::Stop

0 commit comments

Comments
 (0)