Skip to content

Commit eb9a46e

Browse files
Be-ingahayzen-kdab
authored andcommitted
qt-build-utils: vendor code from pkg-config
instead of using my branch hacking some private pkg-config functions to be public. Publication to crates.io requires only using crates on crates.io, not Git URLs.
1 parent 83f753c commit eb9a46e

File tree

3 files changed

+226
-29
lines changed

3 files changed

+226
-29
lines changed

crates/qt-build-utils/Cargo.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,4 @@ repository = "https://github.com/KDAB/cxx-qt/"
1414

1515
[dependencies]
1616
versions = "4.1.0"
17-
pkg-config = { git = "https://github.com/Be-ing/pkg-config-rs.git", branch = "library_pub" }
18-
regex = "1.6.0"
19-
lazy_static = "1.0"
2017
thiserror = "1.0"

crates/qt-build-utils/src/lib.rs

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
//! [cxx_build](https://docs.rs/cxx-build/latest/cxx_build/), or
1212
//! [cpp_build](https://docs.rs/cpp_build/latest/cpp_build/).
1313
14+
mod parse_cflags;
15+
1416
use std::{
1517
env,
1618
path::{Path, PathBuf},
@@ -228,22 +230,19 @@ impl QtBuild {
228230

229231
/// Tell Cargo to link each Qt module.
230232
pub fn cargo_link_libraries(&self) {
231-
lazy_static::lazy_static! {
232-
static ref QMAKE_PRL_LIBS: regex::Regex = regex::RegexBuilder::new(r"^QMAKE_PRL_LIBS = (.*)$").multi_line(true).build().unwrap();
233-
}
234233
let lib_path = self.qmake_query("QT_INSTALL_LIBS");
235234
println!("cargo:rustc-link-search={}", lib_path);
236235

237-
// The needed information is in qmake's .prl files, so using pkgconfig is not necessary.
238-
// There is no guarantee that pkgconfig is installed if Qt is installed, particularly on
239-
// Windows. However, the pkg_config crate provides a useful function for parsing the
240-
// information from the .prl files into linking instructions for Cargo.
241-
let pkg_config = pkg_config::Config::new();
242-
243-
#[cfg(windows)]
244-
let prefix = "";
245-
#[cfg(not(windows))]
246-
let prefix = "lib";
236+
let prefix = match env::var("TARGET") {
237+
Ok(target) => {
238+
if target.contains("msvc") {
239+
""
240+
} else {
241+
"lib"
242+
}
243+
}
244+
Err(_) => "lib",
245+
};
247246

248247
for qt_module in &self.qt_modules {
249248
println!("cargo:rustc-link-lib=Qt{}{}", self.version.major, qt_module);
@@ -253,19 +252,15 @@ impl QtBuild {
253252
);
254253
match std::fs::read_to_string(&prl_path) {
255254
Ok(prl) => {
256-
if let Some(captures) = QMAKE_PRL_LIBS.captures(&prl) {
257-
let link_args = captures
258-
.get(1)
259-
.unwrap()
260-
.as_str()
261-
.replace(r"$$[QT_INSTALL_LIBS]", &lib_path)
262-
.replace(r"$$[QT_INSTALL_PREFIX]", &lib_path);
263-
let mut lib = pkg_config::Library::new();
264-
lib.parse_libs_cflags(
265-
&format!("Qt{}{}", self.version.major, qt_module),
266-
link_args.as_bytes(),
267-
&pkg_config,
268-
);
255+
for line in prl.lines() {
256+
if let Some(line) = line.strip_prefix("QMAKE_PRL_LIBS = ") {
257+
parse_cflags::parse_libs_cflags(
258+
&format!("Qt{}{}", self.version.major, qt_module),
259+
line.replace(r"$$[QT_INSTALL_LIBS]", &lib_path)
260+
.replace(r"$$[QT_INSTALL_PREFIX]", &lib_path)
261+
.as_bytes(),
262+
);
263+
}
269264
}
270265
}
271266
Err(e) => {
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
// SPDX-FileCopyrightText: 2022 Klarälvdalens Datakonsult AB, a KDAB Group company <[email protected]>
2+
// SPDX-FileContributor: Be Wilson <[email protected]>
3+
// SPDX-FileContributor: pkg-config crate contributors
4+
//
5+
// SPDX-License-Identifier: MIT OR Apache-2.0
6+
7+
//! This module contains code from the [pkg-config crate](https://github.com/rust-lang/pkg-config-rs).
8+
//! It has been decoupled from the pkg-config crate because qt-build-utils reads Qt's .prl files instead, which
9+
//! does not require a pkg-config executable to be available.
10+
11+
use std::env;
12+
13+
/// Extract the &str to pass to cargo:rustc-link-lib from a filename (just the file name, not including directories)
14+
/// using target-specific logic.
15+
fn extract_lib_from_filename<'a>(target: &str, filename: &'a str) -> Option<&'a str> {
16+
fn test_suffixes<'b>(filename: &'b str, suffixes: &[&str]) -> Option<&'b str> {
17+
for suffix in suffixes {
18+
if let Some(lib_basename) = filename.strip_suffix(suffix) {
19+
return Some(lib_basename);
20+
}
21+
}
22+
None
23+
}
24+
25+
let prefix = "lib";
26+
#[allow(clippy::collapsible_else_if)]
27+
if target.contains("msvc") {
28+
// According to link.exe documentation:
29+
// https://learn.microsoft.com/en-us/cpp/build/reference/link-input-files?view=msvc-170
30+
//
31+
// LINK doesn't use file extensions to make assumptions about the contents of a file.
32+
// Instead, LINK examines each input file to determine what kind of file it is.
33+
//
34+
// However, rustc appends `.lib` to the string it receives from the -l command line argument,
35+
// which it receives from Cargo via cargo:rustc-link-lib:
36+
// https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L828
37+
// https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L843
38+
// So the only file extension that works for MSVC targets is `.lib`
39+
test_suffixes(filename, &[".lib"])
40+
} else if target.contains("windows") && target.contains("gnu") {
41+
// GNU targets for Windows, including gnullvm, use `LinkerFlavor::Gcc` internally in rustc,
42+
// which tells rustc to use the GNU linker. rustc does not prepend/append to the string it
43+
// receives via the -l command line argument before passing it to the linker:
44+
// https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L446
45+
// https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L457
46+
// GNU ld can work with more types of files than just the .lib files that MSVC's link.exe needs.
47+
// GNU ld will prepend the `lib` prefix to the filename if necessary, so it is okay to remove
48+
// the `lib` prefix from the filename. The `.a` suffix *requires* the `lib` prefix.
49+
// https://sourceware.org/binutils/docs-2.39/ld.html#index-direct-linking-to-a-dll
50+
if let Some(filename) = filename.strip_prefix(prefix) {
51+
test_suffixes(filename, &[".dll.a", ".dll", ".lib", ".a"])
52+
} else {
53+
test_suffixes(filename, &[".dll.a", ".dll", ".lib"])
54+
}
55+
} else if target.contains("apple") {
56+
if let Some(filename) = filename.strip_prefix(prefix) {
57+
test_suffixes(filename, &[".a", ".so", ".dylib"])
58+
} else {
59+
None
60+
}
61+
} else {
62+
if let Some(filename) = filename.strip_prefix(prefix) {
63+
test_suffixes(filename, &[".a", ".so"])
64+
} else {
65+
None
66+
}
67+
}
68+
}
69+
70+
/// Split link_args produced by pkg-config --cflags and / or --libs into separate flags.
71+
///
72+
/// Backslash in link_args is used to preserve literal meaning of following byte. Different words are
73+
/// separated by unescaped space. Other whitespace characters generally should not occur unescaped
74+
/// at all, apart from the newline at the end of link_args. For compatibility with what others
75+
/// consumers of pkg-config link_args would do in this scenario, they are used here for splitting as
76+
/// well.
77+
fn split_flags(link_args: &[u8]) -> Vec<String> {
78+
let mut word = Vec::new();
79+
let mut words = Vec::new();
80+
let mut escaped = false;
81+
82+
for &b in link_args {
83+
match b {
84+
_ if escaped => {
85+
escaped = false;
86+
word.push(b);
87+
}
88+
b'\\' => escaped = true,
89+
b'\t' | b'\n' | b'\r' | b' ' => {
90+
if !word.is_empty() {
91+
words.push(String::from_utf8(word).unwrap());
92+
word = Vec::new();
93+
}
94+
}
95+
_ => word.push(b),
96+
}
97+
}
98+
99+
if !word.is_empty() {
100+
words.push(String::from_utf8(word).unwrap());
101+
}
102+
103+
words
104+
}
105+
106+
pub(crate) fn parse_libs_cflags(name: &str, link_args: &[u8]) {
107+
let mut is_msvc = false;
108+
let target = env::var("TARGET");
109+
if let Ok(target) = &target {
110+
if target.contains("msvc") {
111+
is_msvc = true;
112+
}
113+
}
114+
115+
let words = split_flags(link_args);
116+
117+
// Handle single-character arguments like `-I/usr/include`
118+
let parts = words
119+
.iter()
120+
.filter(|l| l.len() > 2)
121+
.map(|arg| (&arg[0..2], &arg[2..]));
122+
for (flag, val) in parts {
123+
match flag {
124+
"-L" => {
125+
println!("cargo:rustc-link-search=native={}", val);
126+
}
127+
"-F" => {
128+
println!("cargo:rustc-link-search=framework={}", val);
129+
}
130+
"-I" => (),
131+
"-l" => {
132+
// These are provided by the CRT with MSVC
133+
if is_msvc && ["m", "c", "pthread"].contains(&val) {
134+
continue;
135+
}
136+
137+
println!("cargo:rustc-link-lib={}", val);
138+
}
139+
"-D" => (),
140+
_ => {}
141+
}
142+
}
143+
144+
// Handle multi-character arguments with space-separated value like `-framework foo`
145+
let mut iter = words.iter().flat_map(|arg| {
146+
if let Some(arg) = arg.strip_prefix("-Wl,") {
147+
arg.split(',').collect()
148+
} else {
149+
vec![arg.as_ref()]
150+
}
151+
});
152+
while let Some(part) = iter.next() {
153+
match part {
154+
"-framework" => {
155+
if let Some(lib) = iter.next() {
156+
println!("cargo:rustc-link-lib=framework={}", lib);
157+
}
158+
}
159+
"-isystem" | "-iquote" | "-idirafter" => {}
160+
_ => {
161+
let path = std::path::Path::new(part);
162+
if path.is_file() {
163+
// Cargo doesn't have a means to directly specify a file path to link,
164+
// so split up the path into the parent directory and library name.
165+
// TODO: pass file path directly when link-arg library type is stabilized
166+
// https://github.com/rust-lang/rust/issues/99427
167+
if let (Some(dir), Some(file_name), Ok(target)) =
168+
(path.parent(), path.file_name(), &target)
169+
{
170+
match extract_lib_from_filename(target, &file_name.to_string_lossy()) {
171+
Some(lib_basename) => {
172+
println!("cargo:rustc-link-search={}", dir.display());
173+
println!("cargo:rustc-link-lib={}", lib_basename);
174+
}
175+
None => {
176+
println!("cargo:warning=File path {} found in .prl file for {}, but could not extract library base name to pass to linker command line", path.display(), name);
177+
}
178+
}
179+
}
180+
}
181+
}
182+
}
183+
}
184+
185+
let linker_options = words.iter().filter(|arg| arg.starts_with("-Wl,"));
186+
for option in linker_options {
187+
let mut pop = false;
188+
let mut ld_option = vec![];
189+
for subopt in option[4..].split(',') {
190+
if pop {
191+
pop = false;
192+
continue;
193+
}
194+
195+
if subopt == "-framework" {
196+
pop = true;
197+
continue;
198+
}
199+
200+
ld_option.push(subopt);
201+
}
202+
203+
println!("cargo:rustc-link-arg=-Wl,{}", ld_option.join(","));
204+
}
205+
}

0 commit comments

Comments
 (0)