38
38
//! Find the system library named `foo`, with minimum version 1.2.3:
39
39
//!
40
40
//! ```no_run
41
- //! extern crate pkg_config;
42
- //!
43
41
//! fn main() {
44
42
//! pkg_config::Config::new().atleast_version("1.2.3").probe("foo").unwrap();
45
43
//! }
49
47
//! recommended):
50
48
//!
51
49
//! ```no_run
52
- //! extern crate pkg_config;
53
- //!
54
50
//! fn main() {
55
51
//! pkg_config::probe_library("foo").unwrap();
56
52
//! }
59
55
//! Configure how library `foo` is linked to.
60
56
//!
61
57
//! ```no_run
62
- //! extern crate pkg_config;
63
- //!
64
58
//! fn main() {
65
59
//! pkg_config::Config::new().atleast_version("1.2.3").statik(true).probe("foo").unwrap();
66
60
//! }
@@ -93,14 +87,26 @@ pub struct Config {
93
87
94
88
#[ derive( Clone , Debug ) ]
95
89
pub struct Library {
90
+ /// Libraries specified by -l
96
91
pub libs : Vec < String > ,
92
+ /// Library search paths specified by -L
97
93
pub link_paths : Vec < PathBuf > ,
94
+ /// Library file paths specified without -l
95
+ pub link_files : Vec < PathBuf > ,
96
+ /// Darwin frameworks specified by -framework
98
97
pub frameworks : Vec < String > ,
98
+ /// Darwin framework search paths specified by -F
99
99
pub framework_paths : Vec < PathBuf > ,
100
+ /// C/C++ header include paths specified by -I
100
101
pub include_paths : Vec < PathBuf > ,
102
+ /// Linker options specified by -Wl
101
103
pub ld_args : Vec < Vec < String > > ,
104
+ /// C/C++ definitions specified by -D
102
105
pub defines : HashMap < String , Option < String > > ,
106
+ /// Version specified by .pc file's Version field
103
107
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]`.
104
110
_priv : ( ) ,
105
111
}
106
112
@@ -558,6 +564,7 @@ impl Library {
558
564
Library {
559
565
libs : Vec :: new ( ) ,
560
566
link_paths : Vec :: new ( ) ,
567
+ link_files : Vec :: new ( ) ,
561
568
include_paths : Vec :: new ( ) ,
562
569
ld_args : Vec :: new ( ) ,
563
570
frameworks : Vec :: new ( ) ,
@@ -568,9 +575,67 @@ impl Library {
568
575
}
569
576
}
570
577
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
+
571
635
fn parse_libs_cflags ( & mut self , name : & str , output : & [ u8 ] , config : & Config ) {
572
636
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 {
574
639
if target. contains ( "msvc" ) {
575
640
is_msvc = true ;
576
641
}
@@ -670,7 +735,36 @@ impl Library {
670
735
self . include_paths . push ( PathBuf :: from ( inc) ) ;
671
736
}
672
737
}
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
+ }
674
768
}
675
769
}
676
770
@@ -776,60 +870,103 @@ fn split_flags(output: &[u8]) -> Vec<String> {
776
870
words
777
871
}
778
872
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" ,
810
886
& system_roots,
811
- & [ PathBuf :: from( "/usr/local/lib " ) ]
887
+ & [ PathBuf :: from( "/Library/Frameworks " ) ]
812
888
) ) ;
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
+ }
813
919
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
+ ) ) ;
819
933
}
820
- }
821
934
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
+ }
835
972
}
0 commit comments