Skip to content

Commit 18189eb

Browse files
committed
handle file paths to libraries
Typically pkgconfig files specify cflags for linking with -L and -l, however, pkgconfig files can also specify paths to library files. For example, building Qt5 statically on macOS generates the following pkgconfig file. Notice the absolute path to libqtpcre.a in Libs.private: prefix=/Users/be/qt5-installed exec_prefix=${prefix} libdir=${prefix}/lib includedir=${prefix}/include host_bins=${prefix}/bin qt_config=debug_and_release release debug build_all c++11 c++14 c++17 c++1z concurrent dbus no-pkg-config reduce_exports release_tools static stl Name: Qt5 Core Description: Qt Core module Version: 5.15.5 Libs: -L${libdir} -lQt5Core Libs.private: -framework DiskArbitration -framework IOKit -lm -framework AppKit -framework Security -framework ApplicationServices -framework CoreServices -framework CoreFoundation -framework Foundation -lz /Users/be/sw/qt-everywhere-src-5.15.5/qtbase/lib/libqtpcre2.a Cflags: -DQT_CORE_LIB -I${includedir}/QtCore -I${includedir} Building Qt5 statically on macOS with vcpkg generates this pkgconfig file which has a handful of file paths for libraries: prefix=${pcfiledir}/../.. exec_prefix=${prefix} libdir=${prefix}/lib includedir=${prefix}/include/qt5 host_bins=${prefix}/tools/qt5/bin qt_config=release c++11 c++14 c++17 c++1z concurrent dbus no-pkg-config reduce_exports static stl properties animation textcodec big_codecs codecs itemmodel proxymodel concatenatetablesproxymodel textdate datestring doubleconversion filesystemiterator filesystemwatcher gestures identityproxymodel library mimetype process statemachine regularexpression settings sharedmemory sortfilterproxymodel stringlistmodel systemsemaphore temporaryfile translation transposeproxymodel xmlstream xmlstreamreader xmlstreamwriter Name: Qt5 Core Description: Qt Core module Version: 5.15.3 Libs: -L"${libdir}" -lQt5Core -L"${prefix}/lib" -L"${prefix}/lib/manual-link" -framework DiskArbitration -framework IOKit -lm -framework AppKit -framework Security -framework ApplicationServices -framework CoreServices -framework CoreFoundation -framework Foundation ${prefix}/lib/libz.a -ldouble-conversion ${prefix}/lib/libicui18n.a ${prefix}/lib/libicutu.a ${prefix}/lib/libicuuc.a ${prefix}/lib/libicuio.a ${prefix}/lib/libicudata.a ${prefix}/lib/libpcre2-16.a -lzstd ${prefix}/lib/libbz2.a ${prefix}/lib/libpng16.a ${prefix}/lib/libicui18n.a ${prefix}/lib/libicutu.a ${prefix}/lib/libicuuc.a ${prefix}/lib/libicuio.a ${prefix}/lib/libicudata.a ${prefix}/lib/libzstd.a Cflags: -DQT_CORE_LIB -I"${includedir}/QtCore" -I"${includedir}"
1 parent 28b8442 commit 18189eb

File tree

1 file changed

+179
-54
lines changed

1 file changed

+179
-54
lines changed

src/lib.rs

Lines changed: 179 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ pub struct Config {
9595
pub struct Library {
9696
pub libs: Vec<String>,
9797
pub link_paths: Vec<PathBuf>,
98+
pub link_files: Vec<PathBuf>,
9899
pub frameworks: Vec<String>,
99100
pub framework_paths: Vec<PathBuf>,
100101
pub include_paths: Vec<PathBuf>,
@@ -558,6 +559,7 @@ impl Library {
558559
Library {
559560
libs: Vec::new(),
560561
link_paths: Vec::new(),
562+
link_files: Vec::new(),
561563
include_paths: Vec::new(),
562564
ld_args: Vec::new(),
563565
frameworks: Vec::new(),
@@ -568,9 +570,72 @@ impl Library {
568570
}
569571
}
570572

571-
fn parse_libs_cflags(&mut self, name: &str, output: &[u8], config: &Config) {
573+
/// Extract the &str to pass to cargo:rustc-link-lib from a filename (just the file name, not including directories)
574+
/// using target-specific logic.
575+
fn extract_lib_from_filename<'a>(target: &str, filename: &'a str) -> Option<&'a str> {
576+
fn test_suffixes<'b>(filename: &'b str, suffixes: &[&str]) -> Option<&'b str> {
577+
for suffix in suffixes {
578+
if filename.ends_with(suffix) {
579+
return Some(&filename[..filename.len()-suffix.len()]);
580+
}
581+
}
582+
None
583+
}
584+
585+
let prefix = "lib";
586+
if target.contains("msvc") {
587+
// According to link.exe documentation:
588+
// https://learn.microsoft.com/en-us/cpp/build/reference/link-input-files?view=msvc-170
589+
//
590+
// LINK doesn't use file extensions to make assumptions about the contents of a file.
591+
// Instead, LINK examines each input file to determine what kind of file it is.
592+
//
593+
// However, rustc appends `.lib` to the string it receives from the -l command line argument,
594+
// which it receives from Cargo via cargo:rustc-link-lib:
595+
// https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L828
596+
// https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L843
597+
// So the only file extension that works for MSVC targets is `.lib`
598+
return test_suffixes(filename, &[".lib"]);
599+
} else if target.contains("windows") && target.contains("gnu") {
600+
// GNU targets for Windows, including gnullvm, use `LinkerFlavor::Gcc` internally in rustc,
601+
// which tells rustc to use the GNU linker. rustc does not prepend/append to the string it
602+
// receives via the -l command line argument before passing it to the linker:
603+
// https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L446
604+
// https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L457
605+
// GNU ld can work with more types of files than just the .lib files that MSVC's link.exe needs.
606+
// GNU ld will prepend the `lib` prefix to the filename if necessary, so it is okay to remove
607+
// the `lib` prefix from the filename. The `.a` suffix *requires* the `lib` prefix.
608+
// https://sourceware.org/binutils/docs-2.39/ld.html#index-direct-linking-to-a-dll
609+
if filename.starts_with(prefix) {
610+
let filename = &filename[prefix.len()..];
611+
return test_suffixes(filename, &[".dll.a", ".dll", ".lib", ".a"]);
612+
} else {
613+
return test_suffixes(filename, &[".dll.a", ".dll", ".lib"]);
614+
}
615+
} else if target.contains("apple") {
616+
if filename.starts_with(prefix) {
617+
let filename = &filename[prefix.len()..];
618+
return test_suffixes(filename, &[".a", ".dylib"]);
619+
}
620+
return None;
621+
} else {
622+
if filename.starts_with(prefix) {
623+
let filename = &filename[prefix.len()..];
624+
return test_suffixes(filename, &[".a", ".so"]);
625+
}
626+
return None;
627+
}
628+
}
629+
630+
fn parse_libs_cflags(
631+
&mut self,
632+
name: &str,
633+
output: &[u8],
634+
config: &Config,
635+
) {
572636
let mut is_msvc = false;
573-
if let Ok(target) = env::var("TARGET") {
637+
let target = env::var("TARGET");
638+
if let Ok(target) = &target {
574639
if target.contains("msvc") {
575640
is_msvc = true;
576641
}
@@ -670,7 +735,28 @@ impl Library {
670735
self.include_paths.push(PathBuf::from(inc));
671736
}
672737
}
673-
_ => (),
738+
_ => {
739+
let path = std::path::Path::new(part);
740+
if path.is_file() {
741+
// Cargo doesn't have a means to directly specify a file path to link,
742+
// so split up the path into the parent directory and library name.
743+
// TODO: pass file path directly when link-arg library type is stabilized
744+
// https://github.com/rust-lang/rust/issues/99427
745+
if let Some(dir) = path.parent() {
746+
let link_search = format!("rustc-link-search={}", dir.display());
747+
config.print_metadata(&link_search);
748+
}
749+
if let (Some(file_name), Ok(target)) = (path.file_name(), &target) {
750+
if let Some(lib_basename) =
751+
Self::extract_lib_from_filename(target, &file_name.to_string_lossy())
752+
{
753+
let link_lib = format!("rustc-link-lib={}", lib_basename);
754+
config.print_metadata(&link_lib);
755+
self.link_files.push(PathBuf::from(file_name));
756+
}
757+
}
758+
}
759+
}
674760
}
675761
}
676762

@@ -776,60 +862,99 @@ fn split_flags(output: &[u8]) -> Vec<String> {
776862
words
777863
}
778864

779-
#[test]
780-
#[cfg(target_os = "macos")]
781-
fn system_library_mac_test() {
782-
use std::path::Path;
783-
784-
let system_roots = vec![PathBuf::from("/Library"), PathBuf::from("/System")];
785-
786-
assert!(!is_static_available(
787-
"PluginManager",
788-
&system_roots,
789-
&[PathBuf::from("/Library/Frameworks")]
790-
));
791-
assert!(!is_static_available(
792-
"python2.7",
793-
&system_roots,
794-
&[PathBuf::from(
795-
"/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/config"
796-
)]
797-
));
798-
assert!(!is_static_available(
799-
"ffi_convenience",
800-
&system_roots,
801-
&[PathBuf::from(
802-
"/Library/Ruby/Gems/2.0.0/gems/ffi-1.9.10/ext/ffi_c/libffi-x86_64/.libs"
803-
)]
804-
));
805-
806-
// Homebrew is in /usr/local, and it's not a part of the OS
807-
if Path::new("/usr/local/lib/libpng16.a").exists() {
808-
assert!(is_static_available(
809-
"png16",
865+
#[cfg(test)]
866+
mod tests {
867+
use super::*;
868+
869+
#[test]
870+
#[cfg(target_os = "macos")]
871+
fn system_library_mac_test() {
872+
use std::path::Path;
873+
874+
let system_roots = vec![PathBuf::from("/Library"), PathBuf::from("/System")];
875+
876+
assert!(!is_static_available(
877+
"PluginManager",
810878
&system_roots,
811-
&[PathBuf::from("/usr/local/lib")]
879+
&[PathBuf::from("/Library/Frameworks")]
880+
));
881+
assert!(!is_static_available(
882+
"python2.7",
883+
&system_roots,
884+
&[PathBuf::from(
885+
"/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/config"
886+
)]
887+
));
888+
assert!(!is_static_available(
889+
"ffi_convenience",
890+
&system_roots,
891+
&[PathBuf::from(
892+
"/Library/Ruby/Gems/2.0.0/gems/ffi-1.9.10/ext/ffi_c/libffi-x86_64/.libs"
893+
)]
812894
));
813895

814-
let libpng = Config::new()
815-
.range_version("1".."99")
816-
.probe("libpng16")
817-
.unwrap();
818-
assert!(libpng.version.find('\n').is_none());
896+
// Homebrew is in /usr/local, and it's not a part of the OS
897+
if Path::new("/usr/local/lib/libpng16.a").exists() {
898+
assert!(is_static_available(
899+
"png16",
900+
&system_roots,
901+
&[PathBuf::from("/usr/local/lib")]
902+
));
903+
904+
let libpng = Config::new()
905+
.range_version("1".."99")
906+
.probe("libpng16")
907+
.unwrap();
908+
assert!(libpng.version.find('\n').is_none());
909+
}
819910
}
820-
}
821911

822-
#[test]
823-
#[cfg(target_os = "linux")]
824-
fn system_library_linux_test() {
825-
assert!(!is_static_available(
826-
"util",
827-
&[PathBuf::from("/usr")],
828-
&[PathBuf::from("/usr/lib/x86_64-linux-gnu")]
829-
));
830-
assert!(!is_static_available(
831-
"dialog",
832-
&[PathBuf::from("/usr")],
833-
&[PathBuf::from("/usr/lib")]
834-
));
912+
#[test]
913+
#[cfg(target_os = "linux")]
914+
fn system_library_linux_test() {
915+
assert!(!is_static_available(
916+
"util",
917+
&[PathBuf::from("/usr")],
918+
&[PathBuf::from("/usr/lib/x86_64-linux-gnu")]
919+
));
920+
assert!(!is_static_available(
921+
"dialog",
922+
&[PathBuf::from("/usr")],
923+
&[PathBuf::from("/usr/lib")]
924+
));
925+
}
926+
927+
#[test]
928+
fn link_filename_linux() {
929+
let target = "x86_64-unknown-linux-gnu";
930+
let result = Some("foo");
931+
assert_eq!(Library::extract_lib_from_filename(target, "libfoo.a"), result);
932+
assert_eq!(Library::extract_lib_from_filename(target, "libfoo.so"), result);
933+
}
934+
935+
#[test]
936+
fn link_filename_apple() {
937+
let target = "x86_64-apple-darwin";
938+
let result = Some("foo");
939+
assert_eq!(Library::extract_lib_from_filename(target, "libfoo.a"), result);
940+
assert_eq!(Library::extract_lib_from_filename(target, "libfoo.dylib"), result);
941+
}
942+
943+
#[test]
944+
fn link_filename_msvc() {
945+
let target = "x86_64-pc-windows-msvc";
946+
let result = Some("foo");
947+
// static and dynamic libraries have the same .lib suffix
948+
assert_eq!(Library::extract_lib_from_filename(target, "foo.lib"), result);
949+
}
950+
951+
#[test]
952+
fn link_filename_mingw() {
953+
let target = "x86_64-pc-windows-gnu";
954+
let result = Some("foo");
955+
assert_eq!(Library::extract_lib_from_filename(target, "foo.lib"), result);
956+
assert_eq!(Library::extract_lib_from_filename(target, "libfoo.a"), result);
957+
assert_eq!(Library::extract_lib_from_filename(target, "foo.dll"), result);
958+
assert_eq!(Library::extract_lib_from_filename(target, "foo.dll.a"), result);
959+
}
835960
}

0 commit comments

Comments
 (0)