@@ -528,6 +528,7 @@ pub fn perform_linking_checks(
528528 let system_libs = find_system_libs ( output) ?;
529529
530530 let prefix_info = PrefixInfo :: from_prefix ( output. prefix ( ) ) ?;
531+ let staging_lib_map = output. staging_library_name_map . as_ref ( ) ;
531532
532533 let host_dso_packages = host_run_export_dso_packages ( output, & prefix_info. package_to_nature ) ;
533534 tracing:: trace!( "Host run_export DSO packages: {host_dso_packages:#?}" , ) ;
@@ -644,6 +645,26 @@ pub fn perform_linking_checks(
644645 ) ;
645646 }
646647
648+ // Fallback: if the library couldn't be resolved on disk (e.g. from
649+ // a staging cache whose host deps are not installed), try to match
650+ // it by filename against the cached library name map.
651+ if let Some ( lib_map) = staging_lib_map
652+ && let Some ( providing_package) = lib_map. find_package ( lib)
653+ && run_dependency_names. contains ( & providing_package)
654+ {
655+ tracing:: debug!(
656+ "Library {lib:?} matched to '{}' via staging library name map" ,
657+ providing_package. as_normalized( )
658+ ) ;
659+ link_info. linked_packages . push ( LinkedPackage {
660+ name : lib. to_path_buf ( ) ,
661+ link_origin : LinkOrigin :: ForeignPackage (
662+ providing_package. as_normalized ( ) . to_string ( ) ,
663+ ) ,
664+ } ) ;
665+ continue ;
666+ }
667+
647668 // Check if the library is one of the system libraries (i.e. comes from sysroot).
648669 if system_libs. allow . is_match ( lib) && !system_libs. deny . is_match ( lib) {
649670 link_info. linked_packages . push ( LinkedPackage {
@@ -730,7 +751,21 @@ pub fn perform_linking_checks(
730751
731752#[ cfg( test) ]
732753mod tests {
754+ use std:: collections:: BTreeMap ;
755+ use std:: sync:: { Arc , Mutex } ;
756+
757+ use rattler_conda_types:: { MatchSpec , package:: CondaArchiveType } ;
758+ use rattler_solve:: { ChannelPriority , SolveStrategy } ;
759+
733760 use super :: * ;
761+ use crate :: render:: resolved_dependencies:: {
762+ DependencyInfo , FinalizedDependencies , FinalizedRunDependencies , SourceDependency ,
763+ } ;
764+ use crate :: system_tools:: SystemTools ;
765+ use crate :: types:: {
766+ BuildConfiguration , BuildSummary , Directories , PackagingSettings ,
767+ PlatformWithVirtualPackages ,
768+ } ;
734769 use fs_err;
735770
736771 #[ test]
@@ -1088,4 +1123,136 @@ mod tests {
10881123 assert ! ( result. is_err( ) ) ;
10891124 assert ! ( result. unwrap_err( ) . to_string( ) . contains( "Failed to parse" ) ) ;
10901125 }
1126+
1127+ /// Creates a minimal `Output` for testing `perform_linking_checks`.
1128+ ///
1129+ /// The recipe is parsed from YAML. The remaining fields (`BuildConfiguration`,
1130+ /// `FinalizedDependencies`, etc.) are not part of the recipe format and must
1131+ /// be constructed manually.
1132+ fn create_test_output (
1133+ target_platform : Platform ,
1134+ host_prefix : PathBuf ,
1135+ build_prefix : PathBuf ,
1136+ run_deps : Vec < & str > ,
1137+ recipe_yaml : & str ,
1138+ ) -> crate :: metadata:: Output {
1139+ let recipe: rattler_build_recipe:: Stage1Recipe =
1140+ serde_yaml:: from_str ( recipe_yaml) . expect ( "failed to parse recipe YAML" ) ;
1141+
1142+ let pvp = PlatformWithVirtualPackages {
1143+ platform : target_platform,
1144+ virtual_packages : vec ! [ ] ,
1145+ } ;
1146+
1147+ let depends = run_deps
1148+ . into_iter ( )
1149+ . map ( |name| {
1150+ DependencyInfo :: Source ( SourceDependency {
1151+ spec : MatchSpec :: from_str ( name, rattler_conda_types:: ParseStrictness :: Lenient )
1152+ . unwrap ( ) ,
1153+ } )
1154+ } )
1155+ . collect ( ) ;
1156+
1157+ crate :: metadata:: Output {
1158+ recipe,
1159+ build_configuration : BuildConfiguration {
1160+ target_platform,
1161+ host_platform : pvp. clone ( ) ,
1162+ build_platform : pvp,
1163+ variant : BTreeMap :: new ( ) ,
1164+ hash : rattler_build_recipe:: stage1:: HashInfo {
1165+ hash : "test" . into ( ) ,
1166+ prefix : String :: new ( ) ,
1167+ } ,
1168+ directories : Directories {
1169+ host_prefix,
1170+ build_prefix,
1171+ ..Default :: default ( )
1172+ } ,
1173+ channels : vec ! [ ] ,
1174+ channel_priority : ChannelPriority :: Strict ,
1175+ solve_strategy : SolveStrategy :: Highest ,
1176+ timestamp : chrono:: Utc :: now ( ) ,
1177+ subpackages : BTreeMap :: new ( ) ,
1178+ packaging_settings : PackagingSettings {
1179+ archive_type : CondaArchiveType :: Conda ,
1180+ compression_level : 1 ,
1181+ } ,
1182+ store_recipe : false ,
1183+ force_colors : false ,
1184+ sandbox_config : None ,
1185+ exclude_newer : None ,
1186+ } ,
1187+ finalized_dependencies : Some ( FinalizedDependencies {
1188+ build : None ,
1189+ host : None ,
1190+ run : FinalizedRunDependencies {
1191+ depends,
1192+ constraints : vec ! [ ] ,
1193+ run_exports : Default :: default ( ) ,
1194+ } ,
1195+ } ) ,
1196+ finalized_sources : None ,
1197+ finalized_cache_dependencies : None ,
1198+ finalized_cache_sources : None ,
1199+ staging_library_name_map : None ,
1200+ build_summary : Arc :: new ( Mutex :: new ( BuildSummary :: default ( ) ) ) ,
1201+ system_tools : SystemTools :: new ( "test" , "0.0.0" ) ,
1202+ extra_meta : None ,
1203+ }
1204+ }
1205+
1206+ /// Simulates a staging output scenario: the binary (zlink) links against
1207+ /// libz.so.1, zlib IS in the run dependencies (via inherited run_exports),
1208+ /// but zlib is NOT installed in the host prefix (no conda-meta, no library
1209+ /// files). With the staging library name map providing the fallback
1210+ /// mapping, the overlinking check should pass.
1211+ #[ test]
1212+ fn test_staging_overlinking ( ) {
1213+ let test_data = Path :: new ( env ! ( "CARGO_MANIFEST_DIR" ) ) . join ( "../../test-data/binary_files" ) ;
1214+
1215+ let tmp = tempfile:: tempdir ( ) . unwrap ( ) ;
1216+ let tmp_prefix = tmp. path ( ) . join ( "tmp_prefix" ) ;
1217+ let host_prefix = tmp. path ( ) . join ( "host_prefix" ) ;
1218+ let build_prefix = tmp. path ( ) . join ( "build_prefix" ) ;
1219+ fs_err:: create_dir_all ( & tmp_prefix) . unwrap ( ) ;
1220+ fs_err:: create_dir_all ( & host_prefix) . unwrap ( ) ;
1221+ fs_err:: create_dir_all ( & build_prefix) . unwrap ( ) ;
1222+
1223+ let binary_dest = tmp_prefix. join ( "zlink" ) ;
1224+ fs_err:: copy ( test_data. join ( "zlink" ) , & binary_dest) . unwrap ( ) ;
1225+
1226+ let mut output = create_test_output (
1227+ Platform :: Linux64 ,
1228+ host_prefix,
1229+ build_prefix,
1230+ vec ! [ "zlib" ] ,
1231+ r#"
1232+ package:
1233+ name: test-pkg
1234+ version: "1.0.0"
1235+ build:
1236+ dynamic_linking:
1237+ overlinking_behavior: error
1238+ missing_dso_allowlist:
1239+ - "libc*"
1240+ "# ,
1241+ ) ;
1242+ output. staging_library_name_map =
1243+ Some ( crate :: post_process:: package_nature:: LibraryNameMap {
1244+ library_to_package : [ ( "libz.so.1" . to_string ( ) , "zlib" . to_string ( ) ) ]
1245+ . into_iter ( )
1246+ . collect ( ) ,
1247+ } ) ;
1248+
1249+ let new_files: HashSet < PathBuf > = [ binary_dest] . into_iter ( ) . collect ( ) ;
1250+
1251+ let result = perform_linking_checks ( & output, & new_files, & tmp_prefix) ;
1252+ assert ! (
1253+ result. is_ok( ) ,
1254+ "Expected overlinking check to pass since zlib is a run dependency \
1255+ with a staging library name map, but got: {result:?}"
1256+ ) ;
1257+ }
10911258}
0 commit comments