@@ -95,6 +95,7 @@ pub struct Config {
9595pub 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