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//! }
4947//! recommended):
5048//!
5149//! ```no_run
52- //! extern crate pkg_config;
53- //!
5450//! fn main() {
5551//! pkg_config::probe_library("foo").unwrap();
5652//! }
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 ) ]
9589pub 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