Skip to content

Commit 3748977

Browse files
committed
Indicate static linker flag for static libraries
1 parent 7a2d4af commit 3748977

File tree

4 files changed

+181
-123
lines changed

4 files changed

+181
-123
lines changed

build.rs

Lines changed: 1 addition & 1 deletion
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

build/cmake_probe.rs

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

11+
use super::library::Linkage;
1112
use super::Result;
1213

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+
let link_spec = self.0.as_cargo_rustc_link_spec();
21+
let link_spec = if self.1.starts_with(link_spec) {
22+
""
23+
} else {
24+
link_spec
25+
};
26+
format!("cargo:rustc-link-lib={link_spec}{}", self.1)
27+
}
28+
}
29+
30+
impl From<&str> for LinkLib {
31+
fn from(value: &str) -> Self {
32+
Self(Linkage::Default, value.to_string())
33+
}
34+
}
35+
36+
#[derive(Debug, PartialEq, Eq)]
37+
pub struct LinkSearch(pub Linkage, pub PathBuf);
38+
39+
impl LinkSearch {
40+
#[inline]
41+
pub fn emit_cargo_rustc_link_search(&self) -> String {
42+
format!(
43+
"cargo:rustc-link-search={}{}",
44+
self.0.as_cargo_rustc_link_spec(),
45+
self.1.to_str().expect("Can't convert link search path to UTF-8 string")
46+
)
47+
}
48+
}
49+
50+
impl From<&str> for LinkSearch {
51+
fn from(value: &str) -> Self {
52+
Self(Linkage::Default, value.into())
53+
}
54+
}
55+
1356
pub struct ProbeResult {
1457
pub version: Option<Version>,
1558
pub include_paths: Vec<PathBuf>,
16-
pub link_paths: Vec<PathBuf>,
17-
pub link_libs: Vec<String>,
59+
pub link_paths: Vec<LinkSearch>,
60+
pub link_libs: Vec<LinkLib>,
1861
}
1962

2063
pub struct CmakeProbe<'r> {
@@ -118,8 +161,8 @@ impl<'r> CmakeProbe<'r> {
118161
pub(crate) fn extract_from_cmdline(
119162
cmdline: &str,
120163
include_paths: &mut Vec<PathBuf>,
121-
link_paths: &mut Vec<PathBuf>,
122-
link_libs: &mut Vec<String>,
164+
link_paths: &mut Vec<LinkSearch>,
165+
link_libs: &mut Vec<LinkLib>,
123166
) {
124167
eprintln!("=== Extracting build arguments from: {cmdline}");
125168
let mut args = Shlex::new(cmdline.trim());
@@ -131,14 +174,14 @@ impl<'r> CmakeProbe<'r> {
131174
include_paths.push(path);
132175
}
133176
} else if let Some(path) = arg.strip_prefix("-L").or_else(|| arg.strip_prefix("-Wl,-rpath,")) {
134-
let path = PathBuf::from(path.trim_start());
177+
let path = LinkSearch(Linkage::Default, PathBuf::from(path.trim_start()));
135178
if !link_paths.contains(&path) {
136179
link_paths.push(path);
137180
}
138181
} else if let Some(lib) = arg.strip_prefix("-l") {
139182
// unresolved cmake dependency specification like Qt5::Core
140183
if !lib.contains("::") {
141-
link_libs.push(lib.trim_start().to_string());
184+
link_libs.push(LinkLib(Linkage::Default, lib.trim_start().to_string()));
142185
}
143186
} else if let Some(framework) = arg.strip_prefix("-framework") {
144187
let framework = framework.trim_start();
@@ -152,30 +195,42 @@ impl<'r> CmakeProbe<'r> {
152195
.extension()
153196
.and_then(OsStr::to_str)
154197
.map_or(false, |ext| ext.eq_ignore_ascii_case("framework"));
155-
if has_extension {
156-
link_libs.push(framework);
198+
let name = if has_extension {
199+
framework
157200
} else {
158-
link_libs.push(format!("{}.framework", framework));
159-
}
201+
format!("{}.framework", framework)
202+
};
203+
link_libs.push(LinkLib(Linkage::Framework, name));
160204
} else if !arg.starts_with('-') {
161205
let path = Path::new(arg);
206+
let linkage = if Self::is_library_static(path) {
207+
Linkage::Static
208+
} else {
209+
Linkage::Default
210+
};
162211
if let Some(file) = path.file_name().and_then(super::cleanup_lib_filename) {
163212
if let Some(parent) = path.parent().map(|p| p.to_owned()) {
164-
if !link_paths.contains(&parent) {
165-
link_paths.push(parent);
213+
let search_path = LinkSearch(linkage, parent);
214+
if !link_paths.contains(&search_path) {
215+
link_paths.push(search_path);
166216
}
167217
} else {
168218
panic!("{}", arg.to_string());
169219
}
170-
link_libs.push(file.to_str().expect("Non-UTF8 filename").to_string());
220+
let file = file.to_str().expect("Non-UTF8 filename");
221+
link_libs.push(LinkLib(linkage, file.to_string()));
171222
}
172223
} else {
173224
eprintln!("=== Unexpected cmake compiler argument found: {arg}");
174225
}
175226
}
176227
}
177228

178-
fn extract_from_makefile(&self, link_paths: &mut Vec<PathBuf>, link_libs: &mut Vec<String>) -> Result<()> {
229+
fn is_library_static(path: &Path) -> bool {
230+
path.extension().map_or(false, |ext| ext.eq_ignore_ascii_case("a"))
231+
}
232+
233+
fn extract_from_makefile(&self, link_paths: &mut Vec<LinkSearch>, link_libs: &mut Vec<LinkLib>) -> Result<()> {
179234
let link_cmdline = fs::read_to_string(self.build_dir.join("CMakeFiles/ocvrs_probe.dir/link.txt"))?;
180235
Self::extract_from_cmdline(&link_cmdline, &mut vec![], link_paths, link_libs);
181236
Ok(())
@@ -184,8 +239,8 @@ impl<'r> CmakeProbe<'r> {
184239
fn extract_from_ninja(
185240
&self,
186241
include_paths: &mut Vec<PathBuf>,
187-
link_paths: &mut Vec<PathBuf>,
188-
link_libs: &mut Vec<String>,
242+
link_paths: &mut Vec<LinkSearch>,
243+
link_libs: &mut Vec<LinkLib>,
189244
) -> Result<()> {
190245
let mut link_cmdline = BufReader::new(File::open(self.build_dir.join("build.ninja"))?);
191246
let mut line = String::with_capacity(2048);

build/library.rs

Lines changed: 65 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::{env, fmt, iter};
77
use dunce::canonicalize;
88
use semver::Version;
99

10-
use super::cmake_probe::CmakeProbe;
10+
use super::cmake_probe::{CmakeProbe, LinkLib, LinkSearch};
1111
use super::{cleanup_lib_filename, get_version_from_headers, Result, MANIFEST_DIR, OUT_DIR, TARGET_VENDOR_APPLE};
1212

1313
struct PackageName;
@@ -86,6 +86,25 @@ impl fmt::Display for EnvList<'_> {
8686
}
8787
}
8888

89+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90+
pub enum Linkage {
91+
Default,
92+
Dynamic,
93+
Static,
94+
Framework,
95+
}
96+
97+
impl Linkage {
98+
pub fn as_cargo_rustc_link_spec(self) -> &'static str {
99+
match self {
100+
Self::Default => "",
101+
Self::Dynamic => "dylib=",
102+
Self::Static => "static=",
103+
Self::Framework => "framework=",
104+
}
105+
}
106+
}
107+
89108
#[derive(Debug)]
90109
pub struct Library {
91110
pub include_paths: Vec<PathBuf>,
@@ -94,22 +113,12 @@ pub struct Library {
94113
}
95114

96115
impl Library {
97-
fn process_library_list(libs: impl IntoIterator<Item = impl AsRef<Path>>) -> impl Iterator<Item = String> {
98-
libs.into_iter().filter_map(|x| {
99-
let path = x.as_ref();
100-
let is_framework = path
101-
.extension()
102-
.and_then(OsStr::to_str)
103-
.map_or(false, |e| e.eq_ignore_ascii_case("framework"));
116+
fn process_library_list(libs: impl IntoIterator<Item = LinkLib>) -> impl Iterator<Item = LinkLib> {
117+
libs.into_iter().filter_map(|LinkLib(linkage, path)| {
118+
let path = Path::new(&path);
104119
if let Some(filename) = path.file_name() {
105120
let filename = cleanup_lib_filename(filename).unwrap_or(filename);
106-
filename.to_str().map(|f| {
107-
if is_framework {
108-
format!("framework={f}")
109-
} else {
110-
f.to_owned()
111-
}
112-
})
121+
filename.to_str().map(|f| LinkLib(linkage, f.to_owned()))
113122
} else {
114123
None
115124
}
@@ -120,34 +129,6 @@ impl Library {
120129
include_paths.into_iter().find_map(|x| get_version_from_headers(x.as_ref()))
121130
}
122131

123-
#[inline]
124-
fn emit_link_search(path: &Path, typ: Option<&str>) -> String {
125-
format!(
126-
"cargo:rustc-link-search={}{}",
127-
typ.map_or_else(|| "".to_string(), |t| format!("{t}=")),
128-
path.to_str().expect("Can't convert link search path to UTF-8 string")
129-
)
130-
}
131-
132-
#[inline]
133-
fn emit_link_lib(lib: &str, typ: Option<&str>) -> String {
134-
format!(
135-
"cargo:rustc-link-lib={}{}",
136-
typ.map_or_else(
137-
|| "".to_string(),
138-
|t| {
139-
let prefix = format!("{t}=");
140-
if lib.starts_with(&prefix) {
141-
"".to_string()
142-
} else {
143-
prefix
144-
}
145-
}
146-
),
147-
lib
148-
)
149-
}
150-
151132
fn process_env_var_list<'a, T: From<&'a str>>(env_list: Option<EnvList<'a>>, sys_list: Vec<T>) -> Vec<T> {
152133
if let Some(env_list) = env_list {
153134
let mut paths = if env_list.is_extend() {
@@ -162,25 +143,19 @@ impl Library {
162143
}
163144
}
164145

165-
fn process_link_paths<'a>(
166-
link_paths: Option<EnvList>,
167-
sys_link_paths: Vec<PathBuf>,
168-
typ: Option<&'a str>,
169-
) -> impl Iterator<Item = String> + 'a {
146+
fn process_link_paths<'a>(link_paths: Option<EnvList>, sys_link_paths: Vec<LinkSearch>) -> impl Iterator<Item = String> + 'a {
170147
Self::process_env_var_list(link_paths, sys_link_paths)
171148
.into_iter()
172149
.flat_map(move |path| {
173-
iter::once(Self::emit_link_search(&path, typ))
174-
.chain(TARGET_VENDOR_APPLE.then(|| Self::emit_link_search(&path, Some("framework"))))
150+
iter::once(path.emit_cargo_rustc_link_search()).chain(
151+
(*TARGET_VENDOR_APPLE && path.0 != Linkage::Framework)
152+
.then(|| LinkSearch(Linkage::Framework, path.1).emit_cargo_rustc_link_search()),
153+
)
175154
})
176155
}
177156

178-
fn process_link_libs<'a>(
179-
link_libs: Option<EnvList>,
180-
sys_link_libs: Vec<String>,
181-
typ: Option<&'a str>,
182-
) -> impl Iterator<Item = String> + 'a {
183-
Self::process_library_list(Self::process_env_var_list(link_libs, sys_link_libs)).map(move |l| Self::emit_link_lib(&l, typ))
157+
fn process_link_libs<'a>(link_libs: Option<EnvList>, sys_link_libs: Vec<LinkLib>) -> impl Iterator<Item = String> + 'a {
158+
Self::process_library_list(Self::process_env_var_list(link_libs, sys_link_libs)).map(|l| l.emit_cargo_rustc_link())
184159
}
185160

186161
fn find_vcpkg_tool(vcpkg_root: &Path, tool_name: &str) -> Option<PathBuf> {
@@ -228,8 +203,8 @@ impl Library {
228203

229204
let version = Self::version_from_include_paths(&include_paths).ok_or("could not get versions from header files")?;
230205

231-
cargo_metadata.extend(Self::process_link_paths(Some(link_paths), vec![], None));
232-
cargo_metadata.extend(Self::process_link_libs(Some(link_libs), vec![], None));
206+
cargo_metadata.extend(Self::process_link_paths(Some(link_paths), vec![]));
207+
cargo_metadata.extend(Self::process_link_libs(Some(link_libs), vec![]));
233208

234209
Ok(Self {
235210
include_paths,
@@ -266,14 +241,38 @@ impl Library {
266241
let opencv = opencv.ok_or_else(|| errors.join(", "))?;
267242
let mut cargo_metadata = Vec::with_capacity(64);
268243

269-
cargo_metadata.extend(Self::process_link_paths(link_paths, opencv.link_paths, None));
244+
cargo_metadata.extend(Self::process_link_paths(
245+
link_paths,
246+
opencv
247+
.link_paths
248+
.into_iter()
249+
.map(|p| LinkSearch(Linkage::Default, p))
250+
.collect(),
251+
));
270252
if link_paths.map_or(true, |link_paths| link_paths.is_extend()) {
271-
cargo_metadata.extend(Self::process_link_paths(None, opencv.framework_paths, Some("framework")));
253+
cargo_metadata.extend(Self::process_link_paths(
254+
None,
255+
opencv
256+
.framework_paths
257+
.into_iter()
258+
.map(|p| LinkSearch(Linkage::Framework, p))
259+
.collect(),
260+
));
272261
}
273262

274-
cargo_metadata.extend(Self::process_link_libs(link_libs, opencv.libs, None));
263+
cargo_metadata.extend(Self::process_link_libs(
264+
link_libs,
265+
opencv.libs.into_iter().map(|l| LinkLib(Linkage::Default, l)).collect(),
266+
));
275267
if link_libs.map_or(false, |link_libs| link_libs.is_extend()) {
276-
cargo_metadata.extend(Self::process_link_libs(None, opencv.frameworks, Some("framework")));
268+
cargo_metadata.extend(Self::process_link_libs(
269+
None,
270+
opencv
271+
.frameworks
272+
.into_iter()
273+
.map(|f| LinkLib(Linkage::Framework, f))
274+
.collect(),
275+
));
277276
}
278277

279278
let include_paths = Self::process_env_var_list(include_paths, opencv.include_paths);
@@ -326,8 +325,8 @@ impl Library {
326325
}
327326

328327
let mut cargo_metadata = Vec::with_capacity(probe_result.link_paths.len() + probe_result.link_libs.len());
329-
cargo_metadata.extend(Self::process_link_paths(link_paths, probe_result.link_paths, None));
330-
cargo_metadata.extend(Self::process_link_libs(link_libs, probe_result.link_libs, None));
328+
cargo_metadata.extend(Self::process_link_paths(link_paths, probe_result.link_paths));
329+
cargo_metadata.extend(Self::process_link_libs(link_libs, probe_result.link_libs));
331330

332331
Ok(Self {
333332
include_paths: Self::process_env_var_list(include_paths, probe_result.include_paths),
@@ -367,12 +366,12 @@ impl Library {
367366
if link_paths.as_ref().map_or(false, |lp| !lp.is_extend()) {
368367
cargo_metadata.retain(|p| !p.starts_with("cargo:rustc-link-search="));
369368
}
370-
cargo_metadata.extend(Self::process_link_paths(link_paths, vec![], None));
369+
cargo_metadata.extend(Self::process_link_paths(link_paths, vec![]));
371370

372371
if link_libs.as_ref().map_or(false, |ll| !ll.is_extend()) {
373372
cargo_metadata.retain(|p| !p.starts_with("cargo:rustc-link-lib="));
374373
}
375-
cargo_metadata.extend(Self::process_link_libs(link_libs, vec![], None));
374+
cargo_metadata.extend(Self::process_link_libs(link_libs, vec![]));
376375

377376
Ok(Self {
378377
include_paths,

0 commit comments

Comments
 (0)