@@ -265,24 +265,32 @@ static ELF_ALLOWED_LIBRARIES_BY_TRIPLE: Lazy<HashMap<&'static str, Vec<&'static
265
265
. collect ( )
266
266
} ) ;
267
267
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
+ } ) ;
286
294
287
295
static DARWIN_ALLOWED_DYLIBS : Lazy < Vec < MachOAllowedDylib > > = Lazy :: new ( || {
288
296
[
@@ -1022,7 +1030,7 @@ fn validate_elf<Elf: FileHeader<Endian = Endianness>>(
1022
1030
if let Some ( filename) = path. file_name ( ) {
1023
1031
if let Some ( ( module, _) ) = filename. to_string_lossy ( ) . split_once ( ".cpython-" ) {
1024
1032
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 ( ) ) ) ;
1026
1034
}
1027
1035
}
1028
1036
}
@@ -2194,11 +2202,6 @@ fn verify_distribution_behavior(dist_path: &Path) -> Result<Vec<String>> {
2194
2202
// https://github.com/pyinstaller/pyinstaller/issues/9204#issuecomment-3171050891
2195
2203
if cfg ! ( target_os = "linux" ) {
2196
2204
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
- }
2202
2205
for ext in variants {
2203
2206
let Some ( shared_lib) = & ext. shared_lib else {
2204
2207
continue ;
@@ -2208,10 +2211,52 @@ fn verify_distribution_behavior(dist_path: &Path) -> Result<Vec<String>> {
2208
2211
. unchecked ( )
2209
2212
. stdout_capture ( )
2210
2213
. run ( )
2211
- . context ( format ! ( "Failed to run `ldd {}`" , shared_lib ) ) ?;
2214
+ . context ( format ! ( "Failed to run `ldd {shared_lib }`" ) ) ?;
2212
2215
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
+ }
2215
2260
}
2216
2261
}
2217
2262
}
0 commit comments