Skip to content

Commit fd2b5d6

Browse files
committed
Improve static linking
1 parent 126103d commit fd2b5d6

File tree

6 files changed

+220
-133
lines changed

6 files changed

+220
-133
lines changed

build.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ mod generator;
2828
mod header;
2929
#[path = "build/library.rs"]
3030
pub mod library;
31+
#[path = "build/path_ext.rs"]
32+
mod path_ext;
3133

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

build/cmake_probe.rs

Lines changed: 33 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use std::ffi::OsStr;
21
use std::fs;
32
use std::fs::File;
43
use std::io::{BufRead, BufReader};
@@ -8,91 +7,8 @@ use std::process::{Command, Output};
87
use semver::Version;
98
use shlex::Shlex;
109

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-
)
25-
}
26-
27-
/// Removes some common library filename parts that are not meant to be passed as part of the link name.
28-
///
29-
/// E.g. "libopencv_core.so.4.6.0" becomes "opencv_core".
30-
pub fn cleanup_lib_filename(filename: &OsStr) -> &OsStr {
31-
let mut new_filename = filename;
32-
// used to check for the file extension (with dots stripped) and for the part of the filename
33-
const LIB_EXTS: [&str; 6] = [".so.", ".a.", ".dll.", ".lib.", ".dylib.", ".tbd."];
34-
let filename_path = Path::new(new_filename);
35-
// strip lib extension from the filename
36-
if let (Some(stem), Some(extension)) = (filename_path.file_stem(), filename_path.extension().and_then(OsStr::to_str)) {
37-
if LIB_EXTS.iter().any(|e| e.trim_matches('.').eq_ignore_ascii_case(extension)) {
38-
new_filename = stem;
39-
}
40-
}
41-
if let Some(mut file) = new_filename.to_str() {
42-
let orig_len = file.len();
43-
44-
// strip "lib" prefix from the filename unless targeting MSVC
45-
if !*TARGET_ENV_MSVC {
46-
file = file.strip_prefix("lib").unwrap_or(file);
47-
}
48-
49-
// strip lib extension + suffix (e.g. .so.4.6.0) from the filename
50-
LIB_EXTS.iter().for_each(|&inner_ext| {
51-
if let Some(inner_ext_idx) = file.rfind(inner_ext) {
52-
file = &file[..inner_ext_idx];
53-
}
54-
});
55-
if orig_len != file.len() {
56-
new_filename = OsStr::new(file);
57-
}
58-
}
59-
new_filename
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-
.map(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-
)
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, PathBuf::from(value))
94-
}
95-
}
10+
use super::library::{LinkLib, LinkSearch, Linkage};
11+
use super::Result;
9612

9713
pub struct ProbeResult {
9814
pub version: Option<Version>,
@@ -221,6 +137,9 @@ impl<'r> CmakeProbe<'r> {
221137
}
222138
while let Some(arg) = args.next() {
223139
let arg = arg.trim();
140+
if Self::skip_ignorable_arg(&mut args, arg) {
141+
continue;
142+
}
224143
if let Some(path) = arg.strip_prefix("-I") {
225144
let path = PathBuf::from(path.trim_start());
226145
if !include_paths.contains(&path) {
@@ -244,31 +163,46 @@ impl<'r> CmakeProbe<'r> {
244163
framework.to_string()
245164
};
246165
link_libs.push(LinkLib(Linkage::Framework, framework));
247-
} else if let Some(output_file) = arg.strip_prefix("-o") {
248-
if output_file.trim().is_empty() {
249-
args.next().expect("No output file after -o");
250-
}
251166
} else if !arg.starts_with('-') {
252167
let path = Path::new(arg);
253-
if let Some(cleaned_lib_filename) = path.file_name().map(LinkLib::cleanup_lib_filename) {
254-
let linkage = Linkage::from_path(path);
168+
if let Some(link_lib) = LinkLib::try_from_library_path(path) {
255169
if let Some(parent) = path.parent().map(|p| p.to_owned()) {
256-
let search_path = LinkSearch(linkage, parent);
257-
if !link_paths.contains(&search_path) {
258-
link_paths.push(search_path);
170+
// only care about non-empty parent paths
171+
if parent.components().next().is_some() {
172+
let search_path = LinkSearch(link_lib.0, parent);
173+
if !link_paths.contains(&search_path) {
174+
link_paths.push(search_path);
175+
}
259176
}
260177
}
261-
link_libs.push(LinkLib(
262-
linkage,
263-
cleaned_lib_filename.to_str().expect("Non-UTF8 filename").to_string(),
264-
));
178+
link_libs.push(link_lib);
265179
}
266180
} else {
267181
eprintln!("=== Unexpected cmake compiler argument found: {arg}");
268182
}
269183
}
270184
}
271185

186+
fn skip_ignorable_arg(args: &mut Shlex, arg: &str) -> bool {
187+
if let Some(output_file) = arg.strip_prefix("-o") {
188+
if output_file.trim().is_empty() {
189+
args.next().expect("No output file after -o");
190+
return true;
191+
}
192+
} else if let Some(sysroot) = arg.strip_prefix("-isysroot") {
193+
if sysroot.trim().is_empty() {
194+
args.next().expect("No sysroot path after -isysroot");
195+
return true;
196+
}
197+
} else if let Some(arch) = arg.strip_prefix("-arch") {
198+
if arch.trim().is_empty() {
199+
args.next().expect("No arch name after -arch");
200+
return true;
201+
}
202+
}
203+
false
204+
}
205+
272206
fn extract_from_makefile(&self, link_paths: &mut Vec<LinkSearch>, link_libs: &mut Vec<LinkLib>) -> Result<()> {
273207
let link_cmdline = fs::read_to_string(self.build_dir.join("CMakeFiles/ocvrs_probe.dir/link.txt"))?;
274208
Self::extract_from_cmdline(&link_cmdline, true, &mut vec![], link_paths, link_libs);

build/library.rs

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

10-
use super::cmake_probe::{CmakeProbe, LinkLib, LinkSearch};
10+
use super::cmake_probe::CmakeProbe;
1111
use super::header::IncludePath;
12+
use super::path_ext::{LibraryKind, PathExt};
1213
use super::{header, Result, MANIFEST_DIR, OUT_DIR, TARGET_VENDOR_APPLE};
1314

1415
struct PackageName;
@@ -90,6 +91,17 @@ pub enum Linkage {
9091
}
9192

9293
impl Linkage {
94+
pub fn from_extension(path: &Path) -> Self {
95+
path.library_kind().map_or(Self::Default, Self::from)
96+
}
97+
98+
fn from_prefixed_str(s: &str) -> (Self, &str) {
99+
[Self::Dynamic, Self::Static, Self::Framework]
100+
.iter()
101+
.find_map(|l| s.strip_prefix(l.as_cargo_rustc_link_spec()).map(|s| (*l, s)))
102+
.unwrap_or_else(|| (Linkage::from_extension(Path::new(s)), s))
103+
}
104+
93105
pub fn as_cargo_rustc_link_spec(self) -> &'static str {
94106
match self {
95107
Self::Default => "",
@@ -99,44 +111,64 @@ impl Linkage {
99111
}
100112
}
101113

102-
pub fn as_cargo_rustc_link_spec_no_static(self) -> &'static str {
103-
// fixme: specifying static linkage breaks things in CI
104-
match self {
105-
Self::Default | Self::Dynamic | Self::Static => "",
106-
Self::Framework => "framework=",
107-
}
108-
}
109-
110114
pub fn as_cargo_rustc_link_search_spec(self) -> &'static str {
111115
match self {
112116
Self::Default => "",
113117
Self::Dynamic | Self::Static => "native=",
114118
Self::Framework => "framework=",
115119
}
116120
}
121+
}
117122

118-
pub fn from_path(path: &Path) -> Self {
119-
let ext = path.extension();
120-
if Self::is_static_archive(ext) {
121-
Self::Static
122-
} else {
123-
Self::Default
123+
impl From<LibraryKind> for Linkage {
124+
fn from(value: LibraryKind) -> Self {
125+
match value {
126+
LibraryKind::Framework => Self::Framework,
127+
LibraryKind::Static => Self::Static,
128+
LibraryKind::Other => Self::Default,
124129
}
125130
}
131+
}
126132

127-
pub fn from_prefixed_str(s: &str) -> (Self, &str) {
128-
// for backwards compatibility to allow specifying as "OpenCL.framework" in addition to "framework=OpenCL"
129-
if let Some(name) = s.strip_suffix(".framework") {
130-
return (Self::Framework, name);
131-
}
132-
[Self::Dynamic, Self::Static, Self::Framework]
133-
.iter()
134-
.find_map(|l| s.strip_prefix(l.as_cargo_rustc_link_spec()).map(|s| (*l, s)))
135-
.unwrap_or((Self::Default, s))
133+
#[derive(Debug, PartialEq, Eq)]
134+
pub struct LinkLib(pub Linkage, pub String);
135+
136+
impl LinkLib {
137+
pub fn try_from_library_path(path: &Path) -> Option<Self> {
138+
path.library_kind().and_then(|lib_kind| {
139+
Some(Self(
140+
Linkage::from(lib_kind),
141+
path.cleanedup_lib_filename()?.to_str()?.to_string(),
142+
))
143+
})
144+
}
145+
146+
#[inline]
147+
pub fn emit_cargo_rustc_link(&self) -> String {
148+
format!("cargo::rustc-link-lib={}{}", self.0.as_cargo_rustc_link_spec(), self.1)
149+
}
150+
}
151+
152+
impl From<&str> for LinkLib {
153+
fn from(user_str: &str) -> Self {
154+
let (linkage, lib_name) = Linkage::from_prefixed_str(user_str);
155+
let path = Path::new(lib_name);
156+
let lib_name = path.cleanedup_lib_filename().and_then(OsStr::to_str).unwrap_or(lib_name);
157+
Self(linkage, lib_name.to_string())
136158
}
159+
}
160+
161+
#[derive(Debug, Clone, PartialEq, Eq)]
162+
pub struct LinkSearch(pub Linkage, pub PathBuf);
137163

138-
fn is_static_archive(ext: Option<&OsStr>) -> bool {
139-
ext.is_some_and(|ext| ext.eq_ignore_ascii_case("a"))
164+
impl LinkSearch {
165+
#[inline]
166+
pub fn emit_cargo_rustc_link_search(&self) -> String {
167+
format!(
168+
"cargo::rustc-link-search={}{}",
169+
self.0.as_cargo_rustc_link_search_spec(),
170+
self.1.to_str().expect("Can't convert link search path to UTF-8 string")
171+
)
140172
}
141173
}
142174

@@ -182,17 +214,18 @@ impl Library {
182214
fn process_link_paths<'a>(link_paths: Option<EnvList>, sys_link_paths: Vec<LinkSearch>) -> impl Iterator<Item = String> + 'a {
183215
Self::process_env_var_list(link_paths, sys_link_paths)
184216
.into_iter()
185-
.flat_map(move |path| {
186-
iter::once(path.emit_cargo_rustc_link_search()).chain(
187-
(*TARGET_VENDOR_APPLE && path.0 != Linkage::Framework)
188-
.then(|| LinkSearch(Linkage::Framework, path.1).emit_cargo_rustc_link_search()),
189-
)
217+
.flat_map(move |search| {
218+
iter::once(search.clone())
219+
.chain((*TARGET_VENDOR_APPLE && search.0 != Linkage::Framework).then(|| LinkSearch(Linkage::Framework, search.1)))
190220
})
221+
.inspect(|s| eprintln!("=== Link search path: {s:?}"))
222+
.map(|search| search.emit_cargo_rustc_link_search())
191223
}
192224

193225
fn process_link_libs<'a>(link_libs: Option<EnvList>, sys_link_libs: Vec<LinkLib>) -> impl Iterator<Item = String> + 'a {
194226
Self::process_env_var_list(link_libs, sys_link_libs)
195227
.into_iter()
228+
.inspect(|l| eprintln!("=== Linking to library: {l:?}"))
196229
.map(|l| l.emit_cargo_rustc_link())
197230
}
198231

@@ -653,3 +686,10 @@ impl Probe {
653686
fn probe_list(probes: &[Probe]) -> String {
654687
probes.iter().map(|probe| probe.as_str()).collect::<Vec<_>>().join(", ")
655688
}
689+
690+
impl From<&str> for LinkSearch {
691+
fn from(user_str: &str) -> Self {
692+
let (linkage, lib_name) = Linkage::from_prefixed_str(user_str);
693+
Self(linkage, PathBuf::from(lib_name))
694+
}
695+
}

0 commit comments

Comments
 (0)