@@ -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
}
@@ -2186,6 +2194,85 @@ fn verify_distribution_behavior(dist_path: &Path) -> Result<Vec<String>> {
2186
2194
errors. push ( "errors running interpreter tests" . to_string ( ) ) ;
2187
2195
}
2188
2196
2197
+ // Explicitly test ldd directly on the extension modules, which PyInstaller
2198
+ // relies on. This is not strictly needed for a working distribution (e.g.
2199
+ // you can set an rpath on just python+libpython), so we test here for
2200
+ // compatibility with tools that run ldd.
2201
+ // that fails this check (e.g. by setting an rpath on just python+libpython).
2202
+ // https://github.com/pyinstaller/pyinstaller/issues/9204#issuecomment-3171050891
2203
+ // TODO(geofft): musl doesn't do lazy binding for the argument to
2204
+ // ldd, so we will get complaints about missing Py_* symbols. Need
2205
+ // to handle this somehow, skip testing for now.
2206
+ if cfg ! ( target_os = "linux" ) && !python_json. target_triple . contains ( "-musl" ) {
2207
+ // musl's ldd is packaged in the "musl-tools" Debian package.
2208
+ let ldd = if python_json. target_triple . contains ( "-musl" ) && cfg ! ( not( target_env = "musl" ) ) {
2209
+ "musl-ldd"
2210
+ } else {
2211
+ "ldd"
2212
+ } ;
2213
+ for ( name, variants) in python_json. build_info . extensions . iter ( ) {
2214
+ for ext in variants {
2215
+ let Some ( shared_lib) = & ext. shared_lib else {
2216
+ continue ;
2217
+ } ;
2218
+ let shared_lib_path = temp_dir. path ( ) . join ( "python" ) . join ( shared_lib) ;
2219
+ let output = duct:: cmd ( ldd, [ shared_lib_path] )
2220
+ . unchecked ( )
2221
+ . stdout_capture ( )
2222
+ . run ( )
2223
+ . context ( format ! ( "Failed to run `{ldd} {shared_lib}`" ) ) ?;
2224
+ let stdout = String :: from_utf8_lossy ( & output. stdout ) ;
2225
+ // Format of ldd output, for both glibc and musl:
2226
+ // - Everything starts with a tab.
2227
+ // - Most things are "libxyz.so.1 => /usr/lib/libxyz.so.1 (0xabcde000)".
2228
+ // - The ELF interpreter is displayed as just "/lib/ld.so (0xabcde000)".
2229
+ // - glibc, but not musl, shows the vDSO as "linux-vdso.so.1 (0xfffff000)".
2230
+ // - If a library is listed in DT_NEEDED with an absolute path, or (currently only
2231
+ // supported on glibc) with an $ORIGIN-relative path, it displays as just
2232
+ // "/path/to/libxyz.so (0xabcde000)".
2233
+ // - On glibc, if a library cannot be found ldd returns zero and shows "=> not
2234
+ // found" as the resolution (even if it wouldn't use the => form if found).
2235
+ // - On musl, if a library cannot be found, ldd returns nonzero and shows "Error
2236
+ // loading shared library ...:" on stderr.
2237
+ if !output. status . success ( ) {
2238
+ // TODO: If we ever have any optional dependencies besides libcrypt (which is
2239
+ // glibc-only), we will need to capture musl ldd's stderr and parse it.
2240
+ errors. push ( format ! (
2241
+ "`{ldd} {shared_lib}` exited with {}:\n {stdout}" ,
2242
+ output. status
2243
+ ) ) ;
2244
+ } else {
2245
+ let mut ldd_errors = vec ! [ ] ;
2246
+ let deps = ELF_ALLOWED_LIBRARIES_BY_MODULE . get ( & name[ ..] ) ;
2247
+ let temp_dir_lossy = temp_dir. path ( ) . to_string_lossy ( ) . into_owned ( ) ;
2248
+ for line in stdout. lines ( ) {
2249
+ let Some ( ( needed, resolution) ) = line. trim ( ) . split_once ( " => " ) else {
2250
+ continue ;
2251
+ } ;
2252
+ let dep_source = deps
2253
+ . and_then ( |deps| {
2254
+ deps. iter ( ) . find ( |dep| dep. 0 == needed) . map ( |dep| dep. 1 )
2255
+ } )
2256
+ . unwrap_or ( SystemRequired ) ;
2257
+ if resolution. starts_with ( "not found" ) && dep_source != SystemOptional {
2258
+ ldd_errors. push ( format ! ( "{needed} was expected to be found" ) ) ;
2259
+ } else if !resolution. contains ( & temp_dir_lossy) && dep_source == Vendored {
2260
+ ldd_errors. push ( format ! (
2261
+ "{needed} should not come from the OS (missing rpath/$ORIGIN?)"
2262
+ ) ) ;
2263
+ }
2264
+ }
2265
+ if !ldd_errors. is_empty ( ) {
2266
+ errors. push ( format ! (
2267
+ "In `{ldd} {shared_lib}`:\n - {}\n {stdout}" ,
2268
+ ldd_errors. join( "\n - " )
2269
+ ) ) ;
2270
+ }
2271
+ }
2272
+ }
2273
+ }
2274
+ }
2275
+
2189
2276
Ok ( errors)
2190
2277
}
2191
2278
0 commit comments