Skip to content

Commit e3478ac

Browse files
committed
Implement pkg-config flag deduplication
Should reduce a lot of the noise in Cflags and Libraries. See #455
1 parent 96a0b56 commit e3478ac

3 files changed

Lines changed: 196 additions & 24 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ glob = "0.3"
4545
itertools = "0.14"
4646
implib = "0.3.5"
4747
object = { version = "0.36.4", default-features = false, features = ["std", "read_core", "pe"] }
48+
pkg-config = "0.3.32"
4849

4950
[features]
5051
default = []

src/build.rs

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -235,14 +235,14 @@ struct FingerPrint {
235235
root_output: PathBuf,
236236
build_targets: BuildTargets,
237237
install_paths: InstallPaths,
238-
static_libs: String,
238+
static_libs: Vec<String>,
239239
hasher: DefaultHasher,
240240
}
241241

242242
#[derive(serde::Serialize, serde::Deserialize)]
243243
struct Cache {
244244
hash: String,
245-
static_libs: String,
245+
static_libs: Vec<String>,
246246
}
247247

248248
impl FingerPrint {
@@ -262,7 +262,7 @@ impl FingerPrint {
262262
root_output: root_output.to_owned(),
263263
build_targets: build_targets.clone(),
264264
install_paths: install_paths.clone(),
265-
static_libs: String::new(),
265+
static_libs: vec![],
266266
hasher,
267267
}
268268
}
@@ -1071,8 +1071,8 @@ impl LibraryTypes {
10711071
}
10721072
}
10731073

1074-
fn static_libraries(link_line: &str, rustc_target: &target::Target) -> String {
1075-
link_line
1074+
fn static_libraries(link_line: &str, rustc_target: &target::Target) -> Vec<String> {
1075+
let libs = link_line
10761076
.trim()
10771077
.split(' ')
10781078
.filter(|s| {
@@ -1081,16 +1081,43 @@ fn static_libraries(link_line: &str, rustc_target: &target::Target) -> String {
10811081
}
10821082
!s.is_empty()
10831083
})
1084-
.unique()
10851084
.map(|lib| {
10861085
if rustc_target.env == "msvc" && lib.ends_with(".lib") {
10871086
return format!("-l{}", lib.trim_end_matches(".lib"));
10881087
}
10891088
lib.trim().to_string()
10901089
})
10911090
.filter(|s| !s.is_empty())
1092-
.collect::<Vec<_>>()
1093-
.join(" ")
1091+
.collect::<Vec<_>>();
1092+
1093+
let mut final_libs: Vec<String> = vec![];
1094+
1095+
let mut iter = libs.iter();
1096+
1097+
// See pkg_config::Library::parse_libs_cflags
1098+
// Reconstitute improperly split lines
1099+
while let Some(part) = iter.next() {
1100+
match part.as_str() {
1101+
"-framework" => {
1102+
if let Some(lib) = iter.next() {
1103+
final_libs.push(format!("-framework {}", lib));
1104+
}
1105+
}
1106+
"-isystem" | "-iquote" | "-idirafter" => {
1107+
if let Some(inc) = iter.next() {
1108+
final_libs.push(format!("{} {}", part, inc));
1109+
}
1110+
}
1111+
"-undefined" | "--undefined" => {
1112+
if let Some(symbol) = iter.next() {
1113+
final_libs.push(format!("-Wl,{},{}", part, symbol));
1114+
}
1115+
}
1116+
_ => final_libs.push(part.to_string()),
1117+
}
1118+
}
1119+
1120+
final_libs.into_iter().unique().collect()
10941121
}
10951122

10961123
pub fn cbuild(
@@ -1191,20 +1218,27 @@ pub fn cbuild(
11911218
if new_build {
11921219
let name = &cpkg.capi_config.library.name;
11931220
let (pkg_config_static_libs, static_libs) = if library_types.only_cdylib() {
1194-
(String::new(), String::new())
1221+
(vec![String::new()], vec![String::new()])
11951222
} else if let Some(libs) = exec.link_line.lock().unwrap().get(&cpkg.finger_print.id) {
1196-
(static_libraries(libs, &rustc_target), libs.to_string())
1223+
(
1224+
static_libraries(libs, &rustc_target),
1225+
vec![libs.to_string()],
1226+
)
11971227
} else {
1198-
(String::new(), String::new())
1228+
(vec![String::new()], vec![String::new()])
11991229
};
12001230
let capi_config = &cpkg.capi_config;
12011231
let build_targets = &cpkg.build_targets;
12021232

12031233
let mut pc = PkgConfig::from_workspace(name, &cpkg.install_paths, args, capi_config);
12041234
if library_types.only_staticlib() {
1205-
pc.add_lib(&pkg_config_static_libs);
1235+
for lib in &pkg_config_static_libs {
1236+
pc.add_lib(lib);
1237+
}
1238+
}
1239+
for lib in pkg_config_static_libs {
1240+
pc.add_lib_private(&lib);
12061241
}
1207-
pc.add_lib_private(&pkg_config_static_libs);
12081242

12091243
build_pc_files(ws, &capi_config.pkg_config.filename, &root_output, &pc)?;
12101244

@@ -1324,7 +1358,7 @@ pub fn ctest(
13241358

13251359
// We push the static_libs as CFLAGS as well to avoid mangling the options on msvc
13261360
cflags.push(" ");
1327-
cflags.push(&pkg.finger_print.static_libs);
1361+
cflags.push(pkg.finger_print.static_libs.join(" "));
13281362
}
13291363

13301364
std::env::set_var("INLINE_C_RS_CFLAGS", cflags);
@@ -1408,21 +1442,24 @@ mod tests {
14081442
let target_msvc = target::Target::new(Some("x86_64-pc-windows-msvc"), false).unwrap();
14091443
let target_mingw = target::Target::new(Some("x86_64-pc-windows-gnu"), false).unwrap();
14101444

1411-
assert_eq!(static_libraries(libs_osx, &target_osx), "-lSystem -lc -lm");
14121445
assert_eq!(
1413-
static_libraries(libs_linux, &target_linux),
1446+
static_libraries(libs_osx, &target_osx).join(" "),
1447+
"-lSystem -lc -lm"
1448+
);
1449+
assert_eq!(
1450+
static_libraries(libs_linux, &target_linux).join(" "),
14141451
"-lgcc_s -lutil -lrt -lpthread -lm -ldl -lc"
14151452
);
14161453
assert_eq!(
1417-
static_libraries(libs_hurd, &target_hurd),
1454+
static_libraries(libs_hurd, &target_hurd).join(" "),
14181455
"-lgcc_s -lutil -lrt -lpthread -lm -ldl -lc"
14191456
);
14201457
assert_eq!(
1421-
static_libraries(libs_msvc, &target_msvc),
1458+
static_libraries(libs_msvc, &target_msvc).join(" "),
14221459
"-lkernel32 -ladvapi32 -lntdll -luserenv -lws2_32 -lmsvcrt"
14231460
);
14241461
assert_eq!(
1425-
static_libraries(libs_mingw, &target_mingw),
1462+
static_libraries(libs_mingw, &target_mingw).join(" "),
14261463
"-lkernel32 -ladvapi32 -lntdll -luserenv -lws2_32"
14271464
);
14281465
}

src/pkg_config_gen.rs

Lines changed: 139 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22

33
use crate::build::CApiConfig;
44
use crate::install::InstallPaths;
5-
use std::path::{Component, Path, PathBuf};
5+
use log::warn;
6+
use pkg_config;
7+
use std::{
8+
collections::HashSet,
9+
path::{Component, Path, PathBuf},
10+
};
611

712
fn canonicalize<P: AsRef<Path>>(path: P) -> String {
813
let mut stack = Vec::with_capacity(16);
@@ -57,6 +62,12 @@ fn canonicalize<P: AsRef<Path>>(path: P) -> String {
5762
}
5863
}
5964

65+
#[derive(Debug, Clone)]
66+
struct PkgConfigDedupInformation {
67+
requires: Vec<pkg_config::Library>,
68+
requires_private: Vec<pkg_config::Library>,
69+
}
70+
6071
#[derive(Debug, Clone)]
6172
pub struct PkgConfig {
6273
prefix: PathBuf,
@@ -77,6 +88,8 @@ pub struct PkgConfig {
7788
cflags: Vec<String>,
7889

7990
conflicts: Vec<String>,
91+
92+
dedup: PkgConfigDedupInformation,
8093
}
8194

8295
impl PkgConfig {
@@ -99,19 +112,61 @@ impl PkgConfig {
99112
Some(reqs) => reqs.split(',').map(|s| s.trim().to_string()).collect(),
100113
_ => Vec::new(),
101114
};
115+
116+
let requires_libs = {
117+
let cfg = {
118+
let mut c = pkg_config::Config::new();
119+
// This is not sinkholed by cargo-c
120+
c.env_metadata(false);
121+
c.cargo_metadata(false);
122+
123+
c
124+
};
125+
126+
// TODO: log which probe fails
127+
requires
128+
.iter()
129+
.flat_map(|req| {
130+
let c = cfg.probe(req);
131+
if c.is_err() {
132+
warn!("WARNING: library not found: {}", c.as_ref().err().unwrap())
133+
}
134+
c
135+
})
136+
.collect::<Vec<_>>()
137+
};
138+
102139
let requires_private = match &capi_config.pkg_config.requires_private {
103140
Some(reqs) => reqs.split(',').map(|s| s.trim().to_string()).collect(),
104141
_ => Vec::new(),
105142
};
106143

144+
let requires_private_libs = {
145+
let cfg = {
146+
let mut c = pkg_config::Config::new();
147+
c.statik(true);
148+
// This is not sinkholed by cargo-c
149+
c.env_metadata(false);
150+
c.cargo_metadata(false);
151+
152+
c
153+
};
154+
155+
// TODO: log which probe fails
156+
requires_private
157+
.iter()
158+
.flat_map(|req| cfg.probe(req))
159+
.collect::<Vec<_>>()
160+
};
161+
107162
let mut libdir = PathBuf::new();
108163
libdir.push("${libdir}");
109164
if let Some(subdir) = &capi_config.library.install_subdir {
110165
libdir.push(subdir);
111166
}
112167

113168
let libs = vec![
114-
format!("-L{}", libdir.display()),
169+
format!("-L{}", canonicalize(libdir.display().to_string())),
115170
format!("-l{}", capi_config.library.name),
116171
];
117172

@@ -146,6 +201,11 @@ impl PkgConfig {
146201
cflags: vec![cflags],
147202

148203
conflicts: Vec::new(),
204+
205+
dedup: PkgConfigDedupInformation {
206+
requires: requires_libs,
207+
requires_private: requires_private_libs,
208+
},
149209
}
150210
}
151211

@@ -236,7 +296,81 @@ impl PkgConfig {
236296
self.render_help(String::with_capacity(1024)).unwrap()
237297
}
238298

299+
fn get_libs_cflags(arg: &[pkg_config::Library]) -> (HashSet<String>, HashSet<String>) {
300+
let mut libs: HashSet<String> = HashSet::new();
301+
let mut cflags: HashSet<String> = HashSet::new();
302+
303+
for lib in arg.iter() {
304+
for lib in lib.include_paths.iter() {
305+
libs.insert(format!("-I{}", lib.to_string_lossy()));
306+
}
307+
for lib in lib.link_files.iter() {
308+
libs.insert(lib.to_string_lossy().to_string());
309+
}
310+
for lib in lib.libs.iter() {
311+
libs.insert(format!("-l{}", lib));
312+
}
313+
for lib in lib.link_paths.iter() {
314+
libs.insert(format!("-L{}", lib.to_string_lossy()));
315+
}
316+
for lib in lib.frameworks.iter() {
317+
libs.insert(format!("-framework {}", lib));
318+
}
319+
for lib in lib.framework_paths.iter() {
320+
libs.insert(format!("-F{}", lib.to_string_lossy()));
321+
}
322+
for lib in lib.defines.iter() {
323+
let v = match lib.1 {
324+
Some(v) => format!("-D{}={}", lib.0, v),
325+
None => format!("D{}", lib.0),
326+
};
327+
libs.insert(v);
328+
}
329+
for lib in lib.ld_args.iter() {
330+
cflags.insert(format!("-Wl,{}", lib.join(",")));
331+
}
332+
}
333+
334+
(libs, cflags)
335+
}
336+
337+
fn dedup_flags(known_flags: &HashSet<String>, flags: &[String]) -> String {
338+
flags
339+
.iter()
340+
.filter(|&lib| {
341+
!known_flags.contains(lib) || lib.starts_with("-Wl,") || lib.starts_with("/LINK")
342+
})
343+
.map(|lib| lib.as_str())
344+
.collect::<Vec<_>>()
345+
.join(" ")
346+
}
347+
239348
fn render_help<W: core::fmt::Write>(&self, mut w: W) -> Result<W, core::fmt::Error> {
349+
// Dedup
350+
// What libs are already known here?
351+
let (dedup_cflags, dedup_libs, dedup_libs_private) = {
352+
let (known_libs, known_cflags) = PkgConfig::get_libs_cflags(&self.dedup.requires);
353+
354+
let cflags = PkgConfig::dedup_flags(&known_cflags, &self.cflags);
355+
let libs = PkgConfig::dedup_flags(&known_libs, &self.libs);
356+
357+
// FIXME: There's no Cflags.private?
358+
let (mut known_libs_private, _) =
359+
PkgConfig::get_libs_cflags(&self.dedup.requires_private);
360+
// Need to be deduplicated against libs too!
361+
for i in &self.libs {
362+
known_libs_private.insert(i.clone());
363+
}
364+
365+
let libs_private = PkgConfig::dedup_flags(&known_libs_private, &self.libs_private);
366+
367+
println!(
368+
"{:?} => {:?} => {:?}",
369+
known_libs_private, self.libs_private, libs_private
370+
);
371+
(cflags, libs, libs_private)
372+
};
373+
240374
writeln!(w, "prefix={}", canonicalize(&self.prefix))?;
241375
writeln!(w, "exec_prefix={}", canonicalize(&self.exec_prefix))?;
242376
writeln!(w, "libdir={}", canonicalize(&self.libdir))?;
@@ -247,11 +381,11 @@ impl PkgConfig {
247381
writeln!(w, "Name: {}", self.name)?;
248382
writeln!(w, "Description: {}", self.description.replace('\n', " "))?; // avoid endlines
249383
writeln!(w, "Version: {}", self.version)?;
250-
writeln!(w, "Libs: {}", self.libs.join(" "))?;
251-
writeln!(w, "Cflags: {}", self.cflags.join(" "))?;
384+
writeln!(w, "Libs: {}", dedup_libs)?;
385+
writeln!(w, "Cflags: {}", dedup_cflags)?;
252386

253387
if !self.libs_private.is_empty() {
254-
writeln!(w, "Libs.private: {}", self.libs_private.join(" "))?;
388+
writeln!(w, "Libs.private: {}", dedup_libs_private)?;
255389
}
256390

257391
if !self.requires.is_empty() {

0 commit comments

Comments
 (0)