Skip to content

Commit af28836

Browse files
committed
Indicate static linker flag for static libraries
1 parent 3629df7 commit af28836

File tree

7 files changed

+348
-210
lines changed

7 files changed

+348
-210
lines changed

README.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,9 @@ The following variables must be set when building without `pkg_config`, `cmake`
4545
on any platform, the specified values will override those automatically discovered.
4646

4747
* `OPENCV_LINK_LIBS`
48-
Comma separated list of library names to link to. `.lib`, `.so` or `.dylib` extension is optional. If you
49-
specify the ".framework" extension then build script will link a macOS framework instead of plain shared
50-
library.
51-
E.g. "opencv_world411".
48+
Comma separated list of library names to link to. `.lib`, `.so` or `.dylib` extension is optional. For every
49+
library you can specify optional "dylib=", "static=" or "framework=" prefix to indicate the specific type.
50+
E.g. "opencv_world411", "framework=OpenCL".
5251

5352
If this list starts with '+' (plus sign) then the specified items will be appended to whatever the system
5453
probe returned. E.g. a value of "+dc1394" will do a system discovery of the OpenCV library and its linked
@@ -76,9 +75,9 @@ The following variables are rarely used, but you might need them under some circ
7675
extension in the package directory. Cmake will look for that file with `.cmake` extension. And vcpkg will use
7776
that name to try to find package in `packages` directory under `VCPKG_ROOT`. You can also use separate
7877
environment variables to set different package names for different package systems:
79-
* `OPENCV_PKGCONFIG_NAME`
80-
* `OPENCV_CMAKE_NAME`
81-
* `OPENCV_VCPKG_NAME`
78+
* `OPENCV_PKGCONFIG_NAME`
79+
* `OPENCV_CMAKE_NAME`
80+
* `OPENCV_VCPKG_NAME`
8281

8382
* `OPENCV_CMAKE_BIN`
8483
Path to cmake binary (used in OpenCV discovery process using cmake). If not set then just "cmake" will be
@@ -87,13 +86,13 @@ The following variables are rarely used, but you might need them under some circ
8786
* `OPENCV_DISABLE_PROBES`
8887
Comma separated list of OpenCV package auto-discovery systems to exclude from running. Might be useful if
8988
one of the higher priority systems is producing incorrect results. Can contain the following values:
90-
* environment - reads data only from the `OPENCV_LINK_LIBS`, `OPENCV_LINK_PATHS` and `OPENCV_INCLUDE_PATHS`
91-
environment variables
92-
* pkg_config
93-
* cmake
94-
* vcpkg_cmake - like vcpkg, but only uses vcpkg for path discovery, the actual OpenCV probe is done using
95-
cmake (cmake related environment variables are applicable with this probe)
96-
* vcpkg
89+
* environment - reads data only from the `OPENCV_LINK_LIBS`, `OPENCV_LINK_PATHS` and `OPENCV_INCLUDE_PATHS`
90+
environment variables
91+
* pkg_config
92+
* cmake
93+
* vcpkg_cmake - like vcpkg, but only uses vcpkg for path discovery, the actual OpenCV probe is done using
94+
cmake (cmake related environment variables are applicable with this probe)
95+
* vcpkg
9796

9897
* `OPENCV_MSVC_CRT`
9998
Allows selecting the CRT library when building with MSVC for Windows. Allowed values are `"static"` for `/MT`
@@ -129,7 +128,8 @@ The following variables affect the building the of the `opencv` crate, but belon
129128
in `PATH` might be interpreted as the entry separator. Summary [here](https://stackoverflow.com/a/6546427).
130129

131130
* `OPENCV_CLANG_ARGS`
132-
Allow custom arguments for generating and parsing code with clang, see the [documentation for clang arguments](https://docs.rs/clang/latest/clang/struct.Parser.html#method.arguments).
131+
Allow custom arguments for generating and parsing code with clang, see
132+
the [documentation for clang arguments](https://docs.rs/clang/latest/clang/struct.Parser.html#method.arguments).
133133

134134
* clang crate environment variables
135135
See crate's [README](https://github.com/KyleMayes/clang-sys/blob/master/README.md#environment-variables)

build.rs

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ mod docs;
2222
#[path = "build/generator.rs"]
2323
mod generator;
2424
#[path = "build/library.rs"]
25-
mod library;
25+
pub mod library;
2626

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

@@ -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() {

build/cmake_probe.rs

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

1399
pub struct ProbeResult {
14100
pub version: Option<Version>,
15101
pub include_paths: Vec<PathBuf>,
16-
pub link_paths: Vec<PathBuf>,
17-
pub link_libs: Vec<String>,
102+
pub link_paths: Vec<LinkSearch>,
103+
pub link_libs: Vec<LinkLib>,
18104
}
19105

20106
pub struct CmakeProbe<'r> {
@@ -117,12 +203,16 @@ impl<'r> CmakeProbe<'r> {
117203

118204
pub(crate) fn extract_from_cmdline(
119205
cmdline: &str,
206+
skip_cmd: bool,
120207
include_paths: &mut Vec<PathBuf>,
121-
link_paths: &mut Vec<PathBuf>,
122-
link_libs: &mut Vec<String>,
208+
link_paths: &mut Vec<LinkSearch>,
209+
link_libs: &mut Vec<LinkLib>,
123210
) {
124211
eprintln!("=== Extracting build arguments from: {cmdline}");
125212
let mut args = Shlex::new(cmdline.trim());
213+
if skip_cmd {
214+
args.next();
215+
}
126216
while let Some(arg) = args.next() {
127217
let arg = arg.trim();
128218
if let Some(path) = arg.strip_prefix("-I") {
@@ -131,14 +221,14 @@ impl<'r> CmakeProbe<'r> {
131221
include_paths.push(path);
132222
}
133223
} else if let Some(path) = arg.strip_prefix("-L").or_else(|| arg.strip_prefix("-Wl,-rpath,")) {
134-
let path = PathBuf::from(path.trim_start());
224+
let path = LinkSearch(Linkage::Default, PathBuf::from(path.trim_start()));
135225
if !link_paths.contains(&path) {
136226
link_paths.push(path);
137227
}
138228
} else if let Some(lib) = arg.strip_prefix("-l") {
139229
// unresolved cmake dependency specification like Qt5::Core
140-
if !lib.contains("::") {
141-
link_libs.push(lib.trim_start().to_string());
230+
if !lib.contains("::") && lib != "gflags_shared" {
231+
link_libs.push(LinkLib(Linkage::Default, lib.trim_start().to_string()));
142232
}
143233
} else if let Some(framework) = arg.strip_prefix("-framework") {
144234
let framework = framework.trim_start();
@@ -147,45 +237,45 @@ impl<'r> CmakeProbe<'r> {
147237
} else {
148238
framework.to_string()
149239
};
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));
240+
link_libs.push(LinkLib(Linkage::Framework, framework));
241+
} else if let Some(output_file) = arg.strip_prefix("-o") {
242+
if output_file.trim().is_empty() {
243+
args.next().expect("No output file after -o");
159244
}
160245
} else if !arg.starts_with('-') {
161246
let path = Path::new(arg);
162-
if let Some(file) = path.file_name().and_then(super::cleanup_lib_filename) {
247+
if let Some(cleaned_lib_filename) = path.file_name().and_then(LinkLib::cleanup_lib_filename) {
248+
let linkage = Linkage::from_path(path);
163249
if let Some(parent) = path.parent().map(|p| p.to_owned()) {
164-
if !link_paths.contains(&parent) {
165-
link_paths.push(parent);
250+
let search_path = LinkSearch(linkage, parent);
251+
if !link_paths.contains(&search_path) {
252+
link_paths.push(search_path);
166253
}
167254
} else {
168255
panic!("{}", arg.to_string());
169256
}
170-
link_libs.push(file.to_str().expect("Non-UTF8 filename").to_string());
257+
link_libs.push(LinkLib(
258+
linkage,
259+
cleaned_lib_filename.to_str().expect("Non-UTF8 filename").to_string(),
260+
));
171261
}
172262
} else {
173263
eprintln!("=== Unexpected cmake compiler argument found: {arg}");
174264
}
175265
}
176266
}
177267

178-
fn extract_from_makefile(&self, link_paths: &mut Vec<PathBuf>, link_libs: &mut Vec<String>) -> Result<()> {
268+
fn extract_from_makefile(&self, link_paths: &mut Vec<LinkSearch>, link_libs: &mut Vec<LinkLib>) -> Result<()> {
179269
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);
270+
Self::extract_from_cmdline(&link_cmdline, true, &mut vec![], link_paths, link_libs);
181271
Ok(())
182272
}
183273

184274
fn extract_from_ninja(
185275
&self,
186276
include_paths: &mut Vec<PathBuf>,
187-
link_paths: &mut Vec<PathBuf>,
188-
link_libs: &mut Vec<String>,
277+
link_paths: &mut Vec<LinkSearch>,
278+
link_libs: &mut Vec<LinkLib>,
189279
) -> Result<()> {
190280
let mut link_cmdline = BufReader::new(File::open(self.build_dir.join("build.ninja"))?);
191281
let mut line = String::with_capacity(2048);
@@ -208,9 +298,9 @@ impl<'r> CmakeProbe<'r> {
208298
State::Reading => {
209299
let trimmed_line = line.trim_start();
210300
if let Some(paths) = trimmed_line.strip_prefix("LINK_PATH = ") {
211-
Self::extract_from_cmdline(paths, include_paths, link_paths, link_libs);
301+
Self::extract_from_cmdline(paths, false, include_paths, link_paths, link_libs);
212302
} else if let Some(libs) = trimmed_line.strip_prefix("LINK_LIBRARIES = ") {
213-
Self::extract_from_cmdline(libs, include_paths, link_paths, link_libs);
303+
Self::extract_from_cmdline(libs, false, include_paths, link_paths, link_libs);
214304
}
215305
}
216306
}
@@ -292,7 +382,7 @@ impl<'r> CmakeProbe<'r> {
292382
if output.status.success() {
293383
let stdout = String::from_utf8(output.stdout)?;
294384
eprintln!("=== cmake include arguments: {stdout:#?}");
295-
Self::extract_from_cmdline(&stdout, &mut include_paths, &mut link_paths, &mut link_libs);
385+
Self::extract_from_cmdline(&stdout, false, &mut include_paths, &mut link_paths, &mut link_libs);
296386
Ok(())
297387
} else {
298388
Err(
@@ -314,7 +404,7 @@ impl<'r> CmakeProbe<'r> {
314404
if output.status.success() {
315405
let stdout = String::from_utf8(output.stdout)?;
316406
eprintln!("=== cmake link arguments: {stdout:#?}");
317-
Self::extract_from_cmdline(&stdout, &mut include_paths, &mut link_paths, &mut link_libs);
407+
Self::extract_from_cmdline(&stdout, false, &mut include_paths, &mut link_paths, &mut link_libs);
318408
Ok(())
319409
} else {
320410
Err(

0 commit comments

Comments
 (0)