Skip to content

Commit d5e10b7

Browse files
authored
Merge pull request #134 from Be-ing/library_file_paths
2 parents 28b8442 + a9d0132 commit d5e10b7

File tree

1 file changed

+196
-59
lines changed

1 file changed

+196
-59
lines changed

src/lib.rs

+196-59
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@
3838
//! Find the system library named `foo`, with minimum version 1.2.3:
3939
//!
4040
//! ```no_run
41-
//! extern crate pkg_config;
42-
//!
4341
//! fn main() {
4442
//! pkg_config::Config::new().atleast_version("1.2.3").probe("foo").unwrap();
4543
//! }
@@ -49,8 +47,6 @@
4947
//! recommended):
5048
//!
5149
//! ```no_run
52-
//! extern crate pkg_config;
53-
//!
5450
//! fn main() {
5551
//! pkg_config::probe_library("foo").unwrap();
5652
//! }
@@ -59,8 +55,6 @@
5955
//! Configure how library `foo` is linked to.
6056
//!
6157
//! ```no_run
62-
//! extern crate pkg_config;
63-
//!
6458
//! fn main() {
6559
//! pkg_config::Config::new().atleast_version("1.2.3").statik(true).probe("foo").unwrap();
6660
//! }
@@ -93,14 +87,26 @@ pub struct Config {
9387

9488
#[derive(Clone, Debug)]
9589
pub struct Library {
90+
/// Libraries specified by -l
9691
pub libs: Vec<String>,
92+
/// Library search paths specified by -L
9793
pub link_paths: Vec<PathBuf>,
94+
/// Library file paths specified without -l
95+
pub link_files: Vec<PathBuf>,
96+
/// Darwin frameworks specified by -framework
9897
pub frameworks: Vec<String>,
98+
/// Darwin framework search paths specified by -F
9999
pub framework_paths: Vec<PathBuf>,
100+
/// C/C++ header include paths specified by -I
100101
pub include_paths: Vec<PathBuf>,
102+
/// Linker options specified by -Wl
101103
pub ld_args: Vec<Vec<String>>,
104+
/// C/C++ definitions specified by -D
102105
pub defines: HashMap<String, Option<String>>,
106+
/// Version specified by .pc file's Version field
103107
pub version: String,
108+
/// Ensure that this struct can only be created via its private `[Library::new]` constructor.
109+
/// Users of this crate can only access the struct via `[Config::probe]`.
104110
_priv: (),
105111
}
106112

@@ -558,6 +564,7 @@ impl Library {
558564
Library {
559565
libs: Vec::new(),
560566
link_paths: Vec::new(),
567+
link_files: Vec::new(),
561568
include_paths: Vec::new(),
562569
ld_args: Vec::new(),
563570
frameworks: Vec::new(),
@@ -568,9 +575,67 @@ impl Library {
568575
}
569576
}
570577

578+
/// Extract the &str to pass to cargo:rustc-link-lib from a filename (just the file name, not including directories)
579+
/// using target-specific logic.
580+
fn extract_lib_from_filename<'a>(target: &str, filename: &'a str) -> Option<&'a str> {
581+
fn test_suffixes<'b>(filename: &'b str, suffixes: &[&str]) -> Option<&'b str> {
582+
for suffix in suffixes {
583+
if filename.ends_with(suffix) {
584+
return Some(&filename[..filename.len() - suffix.len()]);
585+
}
586+
}
587+
None
588+
}
589+
590+
let prefix = "lib";
591+
if target.contains("msvc") {
592+
// According to link.exe documentation:
593+
// https://learn.microsoft.com/en-us/cpp/build/reference/link-input-files?view=msvc-170
594+
//
595+
// LINK doesn't use file extensions to make assumptions about the contents of a file.
596+
// Instead, LINK examines each input file to determine what kind of file it is.
597+
//
598+
// However, rustc appends `.lib` to the string it receives from the -l command line argument,
599+
// which it receives from Cargo via cargo:rustc-link-lib:
600+
// https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L828
601+
// https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L843
602+
// So the only file extension that works for MSVC targets is `.lib`
603+
return test_suffixes(filename, &[".lib"]);
604+
} else if target.contains("windows") && target.contains("gnu") {
605+
// GNU targets for Windows, including gnullvm, use `LinkerFlavor::Gcc` internally in rustc,
606+
// which tells rustc to use the GNU linker. rustc does not prepend/append to the string it
607+
// receives via the -l command line argument before passing it to the linker:
608+
// https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L446
609+
// https://github.com/rust-lang/rust/blob/657f246812ab2684e3c3954b1c77f98fd59e0b21/compiler/rustc_codegen_ssa/src/back/linker.rs#L457
610+
// GNU ld can work with more types of files than just the .lib files that MSVC's link.exe needs.
611+
// GNU ld will prepend the `lib` prefix to the filename if necessary, so it is okay to remove
612+
// the `lib` prefix from the filename. The `.a` suffix *requires* the `lib` prefix.
613+
// https://sourceware.org/binutils/docs-2.39/ld.html#index-direct-linking-to-a-dll
614+
if filename.starts_with(prefix) {
615+
let filename = &filename[prefix.len()..];
616+
return test_suffixes(filename, &[".dll.a", ".dll", ".lib", ".a"]);
617+
} else {
618+
return test_suffixes(filename, &[".dll.a", ".dll", ".lib"]);
619+
}
620+
} else if target.contains("apple") {
621+
if filename.starts_with(prefix) {
622+
let filename = &filename[prefix.len()..];
623+
return test_suffixes(filename, &[".a", ".so", ".dylib"]);
624+
}
625+
return None;
626+
} else {
627+
if filename.starts_with(prefix) {
628+
let filename = &filename[prefix.len()..];
629+
return test_suffixes(filename, &[".a", ".so"]);
630+
}
631+
return None;
632+
}
633+
}
634+
571635
fn parse_libs_cflags(&mut self, name: &str, output: &[u8], config: &Config) {
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,36 @@ 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), Some(file_name), Ok(target)) =
746+
(path.parent(), path.file_name(), &target)
747+
{
748+
match Self::extract_lib_from_filename(
749+
target,
750+
&file_name.to_string_lossy(),
751+
) {
752+
Some(lib_basename) => {
753+
let link_search =
754+
format!("rustc-link-search={}", dir.display());
755+
config.print_metadata(&link_search);
756+
757+
let link_lib = format!("rustc-link-lib={}", lib_basename);
758+
config.print_metadata(&link_lib);
759+
self.link_files.push(PathBuf::from(path));
760+
}
761+
None => {
762+
println!("cargo:warning=File path {} found in pkg-config file for {}, but could not extract library base name to pass to linker command line", path.display(), name);
763+
}
764+
}
765+
}
766+
}
767+
}
674768
}
675769
}
676770

@@ -776,60 +870,103 @@ fn split_flags(output: &[u8]) -> Vec<String> {
776870
words
777871
}
778872

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",
873+
#[cfg(test)]
874+
mod tests {
875+
use super::*;
876+
877+
#[test]
878+
#[cfg(target_os = "macos")]
879+
fn system_library_mac_test() {
880+
use std::path::Path;
881+
882+
let system_roots = vec![PathBuf::from("/Library"), PathBuf::from("/System")];
883+
884+
assert!(!is_static_available(
885+
"PluginManager",
810886
&system_roots,
811-
&[PathBuf::from("/usr/local/lib")]
887+
&[PathBuf::from("/Library/Frameworks")]
812888
));
889+
assert!(!is_static_available(
890+
"python2.7",
891+
&system_roots,
892+
&[PathBuf::from(
893+
"/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/config"
894+
)]
895+
));
896+
assert!(!is_static_available(
897+
"ffi_convenience",
898+
&system_roots,
899+
&[PathBuf::from(
900+
"/Library/Ruby/Gems/2.0.0/gems/ffi-1.9.10/ext/ffi_c/libffi-x86_64/.libs"
901+
)]
902+
));
903+
904+
// Homebrew is in /usr/local, and it's not a part of the OS
905+
if Path::new("/usr/local/lib/libpng16.a").exists() {
906+
assert!(is_static_available(
907+
"png16",
908+
&system_roots,
909+
&[PathBuf::from("/usr/local/lib")]
910+
));
911+
912+
let libpng = Config::new()
913+
.range_version("1".."99")
914+
.probe("libpng16")
915+
.unwrap();
916+
assert!(libpng.version.find('\n').is_none());
917+
}
918+
}
813919

814-
let libpng = Config::new()
815-
.range_version("1".."99")
816-
.probe("libpng16")
817-
.unwrap();
818-
assert!(libpng.version.find('\n').is_none());
920+
#[test]
921+
#[cfg(target_os = "linux")]
922+
fn system_library_linux_test() {
923+
assert!(!is_static_available(
924+
"util",
925+
&[PathBuf::from("/usr")],
926+
&[PathBuf::from("/usr/lib/x86_64-linux-gnu")]
927+
));
928+
assert!(!is_static_available(
929+
"dialog",
930+
&[PathBuf::from("/usr")],
931+
&[PathBuf::from("/usr/lib")]
932+
));
819933
}
820-
}
821934

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-
));
935+
fn test_library_filename(target: &str, filename: &str) {
936+
assert_eq!(
937+
Library::extract_lib_from_filename(target, filename),
938+
Some("foo")
939+
);
940+
}
941+
942+
#[test]
943+
fn link_filename_linux() {
944+
let target = "x86_64-unknown-linux-gnu";
945+
test_library_filename(target, "libfoo.a");
946+
test_library_filename(target, "libfoo.so");
947+
}
948+
949+
#[test]
950+
fn link_filename_apple() {
951+
let target = "x86_64-apple-darwin";
952+
test_library_filename(target, "libfoo.a");
953+
test_library_filename(target, "libfoo.so");
954+
test_library_filename(target, "libfoo.dylib");
955+
}
956+
957+
#[test]
958+
fn link_filename_msvc() {
959+
let target = "x86_64-pc-windows-msvc";
960+
// static and dynamic libraries have the same .lib suffix
961+
test_library_filename(target, "foo.lib");
962+
}
963+
964+
#[test]
965+
fn link_filename_mingw() {
966+
let target = "x86_64-pc-windows-gnu";
967+
test_library_filename(target, "foo.lib");
968+
test_library_filename(target, "libfoo.a");
969+
test_library_filename(target, "foo.dll");
970+
test_library_filename(target, "foo.dll.a");
971+
}
835972
}

0 commit comments

Comments
 (0)