@@ -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,138 @@ 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+ /// Creates a minimal `Output` for testing `perform_linking_checks`.
1129+ ///
1130+ /// The recipe is parsed from YAML. The remaining fields (`BuildConfiguration`,
1131+ /// `FinalizedDependencies`, etc.) are not part of the recipe format and must
1132+ /// be constructed manually.
1133+ fn create_test_output (
1134+ target_platform : Platform ,
1135+ host_prefix : PathBuf ,
1136+ build_prefix : PathBuf ,
1137+ run_deps : Vec < & str > ,
1138+ recipe_yaml : & str ,
1139+ ) -> crate :: metadata:: Output {
1140+ let recipe: rattler_build_recipe:: Stage1Recipe =
1141+ serde_yaml:: from_str ( recipe_yaml) . expect ( "failed to parse recipe YAML" ) ;
1142+
1143+ let pvp = PlatformWithVirtualPackages {
1144+ platform : target_platform,
1145+ virtual_packages : vec ! [ ] ,
1146+ } ;
1147+
1148+ let depends = run_deps
1149+ . into_iter ( )
1150+ . map ( |name| {
1151+ DependencyInfo :: Source ( SourceDependency {
1152+ spec : MatchSpec :: from_str ( name, rattler_conda_types:: ParseStrictness :: Lenient )
1153+ . unwrap ( ) ,
1154+ } )
1155+ } )
1156+ . collect ( ) ;
1157+
1158+ crate :: metadata:: Output {
1159+ recipe,
1160+ build_configuration : BuildConfiguration {
1161+ target_platform,
1162+ host_platform : pvp. clone ( ) ,
1163+ build_platform : pvp,
1164+ variant : BTreeMap :: new ( ) ,
1165+ hash : rattler_build_recipe:: stage1:: HashInfo {
1166+ hash : "test" . into ( ) ,
1167+ prefix : String :: new ( ) ,
1168+ } ,
1169+ directories : Directories {
1170+ host_prefix,
1171+ build_prefix,
1172+ ..Default :: default ( )
1173+ } ,
1174+ channels : vec ! [ ] ,
1175+ channel_priority : ChannelPriority :: Strict ,
1176+ solve_strategy : SolveStrategy :: Highest ,
1177+ timestamp : chrono:: Utc :: now ( ) ,
1178+ subpackages : BTreeMap :: new ( ) ,
1179+ packaging_settings : PackagingSettings {
1180+ archive_type : CondaArchiveType :: Conda ,
1181+ compression_level : 1 ,
1182+ } ,
1183+ store_recipe : false ,
1184+ force_colors : false ,
1185+ sandbox_config : None ,
1186+ exclude_newer : None ,
1187+ } ,
1188+ finalized_dependencies : Some ( FinalizedDependencies {
1189+ build : None ,
1190+ host : None ,
1191+ run : FinalizedRunDependencies {
1192+ depends,
1193+ constraints : vec ! [ ] ,
1194+ run_exports : Default :: default ( ) ,
1195+ } ,
1196+ } ) ,
1197+ finalized_sources : None ,
1198+ finalized_cache_dependencies : None ,
1199+ finalized_cache_sources : None ,
1200+ staging_library_name_map : None ,
1201+ build_summary : Arc :: new ( Mutex :: new ( BuildSummary :: default ( ) ) ) ,
1202+ system_tools : SystemTools :: new ( "test" , "0.0.0" ) ,
1203+ extra_meta : None ,
1204+ }
1205+ }
1206+
1207+ /// Simulates a staging output scenario: the binary (zlink) links against
1208+ /// libz.so.1, zlib IS in the run dependencies (via inherited run_exports),
1209+ /// but zlib is NOT installed in the host prefix (no conda-meta, no library
1210+ /// files). With the staging library name map providing the fallback
1211+ /// mapping, the overlinking check should pass.
1212+ #[ test]
1213+ fn test_staging_overlinking ( ) {
1214+ let test_data =
1215+ Path :: new ( env ! ( "CARGO_MANIFEST_DIR" ) ) . join ( "../../test-data/binary_files" ) ;
1216+
1217+ let tmp = tempfile:: tempdir ( ) . unwrap ( ) ;
1218+ let tmp_prefix = tmp. path ( ) . join ( "tmp_prefix" ) ;
1219+ let host_prefix = tmp. path ( ) . join ( "host_prefix" ) ;
1220+ let build_prefix = tmp. path ( ) . join ( "build_prefix" ) ;
1221+ fs_err:: create_dir_all ( & tmp_prefix) . unwrap ( ) ;
1222+ fs_err:: create_dir_all ( & host_prefix) . unwrap ( ) ;
1223+ fs_err:: create_dir_all ( & build_prefix) . unwrap ( ) ;
1224+
1225+ let binary_dest = tmp_prefix. join ( "zlink" ) ;
1226+ fs_err:: copy ( test_data. join ( "zlink" ) , & binary_dest) . unwrap ( ) ;
1227+
1228+ let mut output = create_test_output (
1229+ Platform :: Linux64 ,
1230+ host_prefix,
1231+ build_prefix,
1232+ vec ! [ "zlib" ] ,
1233+ r#"
1234+ package:
1235+ name: test-pkg
1236+ version: "1.0.0"
1237+ build:
1238+ dynamic_linking:
1239+ overlinking_behavior: error
1240+ missing_dso_allowlist:
1241+ - "libc*"
1242+ "# ,
1243+ ) ;
1244+ output. staging_library_name_map =
1245+ Some ( crate :: post_process:: package_nature:: LibraryNameMap {
1246+ library_to_package : [ ( "libz.so.1" . to_string ( ) , "zlib" . to_string ( ) ) ]
1247+ . into_iter ( )
1248+ . collect ( ) ,
1249+ } ) ;
1250+
1251+ let new_files: HashSet < PathBuf > = [ binary_dest] . into_iter ( ) . collect ( ) ;
1252+
1253+ let result = perform_linking_checks ( & output, & new_files, & tmp_prefix) ;
1254+ assert ! (
1255+ result. is_ok( ) ,
1256+ "Expected overlinking check to pass since zlib is a run dependency \
1257+ with a staging library name map, but got: {result:?}"
1258+ ) ;
1259+ }
10911260}
0 commit comments