@@ -265,24 +265,32 @@ static ELF_ALLOWED_LIBRARIES_BY_TRIPLE: Lazy<HashMap<&'static str, Vec<&'static
265265 . collect ( )
266266 } ) ;
267267
268- static ELF_ALLOWED_LIBRARIES_BY_MODULE : Lazy < HashMap < & ' static str , Vec < & ' static str > > > =
269- Lazy :: new ( || {
270- [
271- (
272- // libcrypt is provided by the system, but only on older distros.
273- "_crypt" ,
274- vec ! [ "libcrypt.so.1" ] ,
275- ) ,
276- (
277- // libtcl and libtk are shipped in our distribution.
278- "_tkinter" ,
279- vec ! [ "libtcl8.6.so" , "libtk8.6.so" ] ,
280- ) ,
281- ]
282- . iter ( )
283- . cloned ( )
284- . collect ( )
285- } ) ;
268+ #[ derive( Copy , Clone , PartialEq ) ]
269+ enum DepSource {
270+ SystemRequired ,
271+ SystemOptional ,
272+ Vendored ,
273+ }
274+ use DepSource :: * ;
275+
276+ static ELF_ALLOWED_LIBRARIES_BY_MODULE : Lazy <
277+ HashMap < & ' static str , Vec < ( & ' static str , DepSource ) > > ,
278+ > = Lazy :: new ( || {
279+ [
280+ (
281+ // libcrypt is provided by the system, but only on older distros.
282+ "_crypt" ,
283+ vec ! [ ( "libcrypt.so.1" , SystemOptional ) ] ,
284+ ) ,
285+ (
286+ "_tkinter" ,
287+ vec ! [ ( "libtcl8.6.so" , Vendored ) , ( "libtk8.6.so" , Vendored ) ] ,
288+ ) ,
289+ ]
290+ . iter ( )
291+ . cloned ( )
292+ . collect ( )
293+ } ) ;
286294
287295static DARWIN_ALLOWED_DYLIBS : Lazy < Vec < MachOAllowedDylib > > = Lazy :: new ( || {
288296 [
@@ -1022,7 +1030,7 @@ fn validate_elf<Elf: FileHeader<Endian = Endianness>>(
10221030 if let Some ( filename) = path. file_name ( ) {
10231031 if let Some ( ( module, _) ) = filename. to_string_lossy ( ) . split_once ( ".cpython-" ) {
10241032 if let Some ( extra) = ELF_ALLOWED_LIBRARIES_BY_MODULE . get ( module) {
1025- allowed_libraries. extend ( extra. iter ( ) . map ( |x| x. to_string ( ) ) ) ;
1033+ allowed_libraries. extend ( extra. iter ( ) . map ( |x| x. 0 . to_string ( ) ) ) ;
10261034 }
10271035 }
10281036 }
@@ -2194,11 +2202,6 @@ fn verify_distribution_behavior(dist_path: &Path) -> Result<Vec<String>> {
21942202 // https://github.com/pyinstaller/pyinstaller/issues/9204#issuecomment-3171050891
21952203 if cfg ! ( target_os = "linux" ) {
21962204 for ( name, variants) in python_json. build_info . extensions . iter ( ) {
2197- if name == "_crypt" {
2198- // Our test environment may lack libcrypt (and _crypt is
2199- // split out specifically because of this problem).
2200- continue ;
2201- }
22022205 for ext in variants {
22032206 let Some ( shared_lib) = & ext. shared_lib else {
22042207 continue ;
@@ -2208,10 +2211,52 @@ fn verify_distribution_behavior(dist_path: &Path) -> Result<Vec<String>> {
22082211 . unchecked ( )
22092212 . stdout_capture ( )
22102213 . run ( )
2211- . context ( format ! ( "Failed to run `ldd {}`" , shared_lib ) ) ?;
2214+ . context ( format ! ( "Failed to run `ldd {shared_lib }`" ) ) ?;
22122215 let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
2213- if !output. status . success ( ) || stdout. contains ( "not found" ) {
2214- errors. push ( format ! ( "Error from `ldd {}`:\n {}" , shared_lib, stdout) ) ;
2216+ // Format of ldd output, for both glibc and musl:
2217+ // - Everything starts with a tab.
2218+ // - Most things are "libxyz.so.1 => /usr/lib/libxyz.so.1 (0xabcde000)".
2219+ // - The ELF interpreter is displayed as just "/lib/ld.so (0xabcde000)".
2220+ // - glibc, but not musl, shows the vDSO as "linux-vdso.so.1 (0xfffff000)".
2221+ // - If a library is listed in DT_NEEDED with an absolute path, or (currently only
2222+ // supported on glibc) with an $ORIGIN-relative path, it displays as just
2223+ // "/path/to/libxyz.so (0xabcde000)".
2224+ // - On glibc, if a library cannot be found ldd returns zero and shows "=> not
2225+ // found" as the resolution (even if it wouldn't use the => form if found).
2226+ // - On musl, if a library cannot be found, ldd returns nonzero and shows "Error
2227+ // loading shared library ...:" on stderr.
2228+ if !output. status . success ( ) {
2229+ // TODO: If we ever have any optional dependencies besides libcrypt (which is
2230+ // glibc-only), we will need to capture musl ldd's stderr and parse it.
2231+ errors. push ( format ! (
2232+ "`ldd {shared_lib}` exited with {}:\n {stdout}" ,
2233+ output. status
2234+ ) ) ;
2235+ } else {
2236+ let mut ldd_errors = vec ! [ ] ;
2237+ let deps = ELF_ALLOWED_LIBRARIES_BY_MODULE . get ( & name[ ..] ) ;
2238+ let temp_dir_lossy = temp_dir. path ( ) . to_string_lossy ( ) . into_owned ( ) ;
2239+ for line in stdout. lines ( ) {
2240+ let Some ( ( needed, resolution) ) = line. trim ( ) . split_once ( " => " ) else {
2241+ continue ;
2242+ } ;
2243+ let dep_source = deps
2244+ . and_then ( |deps| deps. iter ( ) . find ( |dep| dep. 0 == needed) . map ( |dep| dep. 1 ) )
2245+ . unwrap_or ( SystemRequired ) ;
2246+ if resolution. starts_with ( "not found" ) && dep_source != SystemOptional {
2247+ ldd_errors. push ( format ! ( "{needed} was expected to be found" ) ) ;
2248+ } else if !resolution. contains ( & temp_dir_lossy) && dep_source == Vendored {
2249+ ldd_errors. push ( format ! (
2250+ "{needed} should not come from the OS (missing rpath/$ORIGIN?)"
2251+ ) ) ;
2252+ }
2253+ }
2254+ if !ldd_errors. is_empty ( ) {
2255+ errors. push ( format ! (
2256+ "In `ldd {shared_lib}`:\n - {}\n {stdout}" ,
2257+ ldd_errors. join( "\n - " )
2258+ ) ) ;
2259+ }
22152260 }
22162261 }
22172262 }
0 commit comments