@@ -17,7 +17,14 @@ export async function wrap(...args: std.Args<wrap.Arg>): Promise<tg.File> {
17
17
18
18
tg . assert ( arg . executable !== undefined , "No executable was provided." ) ;
19
19
20
- const executable = await manifestExecutableFromArg ( arg . executable ) ;
20
+ // Check if the executable is already a wrapper and get its manifest
21
+ const existingManifest = await wrap . existingManifestFromExecutableArg (
22
+ arg . executable ,
23
+ ) ;
24
+
25
+ const executable =
26
+ existingManifest ?. executable ??
27
+ ( await manifestExecutableFromArg ( arg . executable ) ) ;
21
28
22
29
const detectedBuild = await std . triple . host ( ) ;
23
30
const host = arg . host ?? ( await std . triple . host ( ) ) ;
@@ -31,15 +38,17 @@ export async function wrap(...args: std.Args<wrap.Arg>): Promise<tg.File> {
31
38
)
32
39
: await bootstrap . sdk . env ( host ) ;
33
40
34
- // Construct the interpreter.
41
+ // Construct the interpreter. When an explicit interpreter is provided,
42
+ // we should prioritize it over any interpreter that might be derived from the executable.
35
43
const manifestInterpreter = await manifestInterpreterFromWrapArgObject ( {
36
44
buildToolchain,
37
45
interpreter : arg . interpreter ,
38
- executable : arg . executable ,
46
+ executable : arg . interpreter ? undefined : arg . executable ,
39
47
libraryPaths : arg . libraryPaths ,
40
48
libraryPathStrategy : arg . libraryPathStrategy ,
41
49
} ) ;
42
50
51
+ // Use existing manifest values as defaults if we're wrapping a wrapper
43
52
const manifestEnv = await wrap . manifestEnvFromEnvObject (
44
53
arg . env as std . env . EnvObject ,
45
54
) ;
@@ -50,8 +59,16 @@ export async function wrap(...args: std.Args<wrap.Arg>): Promise<tg.File> {
50
59
const manifest : wrap . Manifest = {
51
60
interpreter : manifestInterpreter ,
52
61
executable,
53
- env : manifestEnv ,
54
- args : manifestArgs ,
62
+ env :
63
+ existingManifest ?. env &&
64
+ manifestEnv &&
65
+ Object . keys ( manifestEnv ) . length === 0
66
+ ? existingManifest . env
67
+ : manifestEnv ,
68
+ args :
69
+ manifestArgs . length === 0 && existingManifest ?. args
70
+ ? existingManifest . args
71
+ : manifestArgs ,
55
72
} ;
56
73
57
74
// Get the wrapper executable.
@@ -252,9 +269,14 @@ export namespace wrap {
252
269
}
253
270
254
271
envs . push ( await wrap . envObjectFromManifestEnv ( existingManifest . env ) ) ;
255
- interpreter = await wrap . interpreterFromManifestInterpreter (
256
- existingManifest . interpreter ,
257
- ) ;
272
+
273
+ // Only use the existing interpreter if no explicit interpreter was provided
274
+ if ( interpreter === undefined ) {
275
+ interpreter = await wrap . interpreterFromManifestInterpreter (
276
+ existingManifest . interpreter ,
277
+ ) ;
278
+ }
279
+
258
280
executable = await wrap . executableFromManifestExecutable (
259
281
existingManifest . executable ,
260
282
) ;
@@ -874,7 +896,7 @@ type ManifestInterpreterArg = {
874
896
| tg . Template
875
897
| wrap . Interpreter
876
898
| undefined ;
877
- executable : string | tg . Template | tg . File | tg . Symlink ;
899
+ executable ? : string | tg . Template | tg . File | tg . Symlink | undefined ;
878
900
libraryPaths ?: Array < tg . Directory | tg . Symlink | tg . Template > | undefined ;
879
901
libraryPathStrategy ?: wrap . LibraryPathStrategy | undefined ;
880
902
} ;
@@ -885,20 +907,24 @@ const manifestInterpreterFromWrapArgObject = async (
885
907
) : Promise < wrap . Manifest . Interpreter | undefined > => {
886
908
let interpreter = arg . interpreter
887
909
? await interpreterFromArg ( arg . interpreter , arg . buildToolchain )
888
- : await interpreterFromExecutableArg ( arg . executable , arg . buildToolchain ) ;
910
+ : arg . executable
911
+ ? await interpreterFromExecutableArg ( arg . executable , arg . buildToolchain )
912
+ : undefined ;
889
913
if ( interpreter === undefined ) {
890
914
return undefined ;
891
915
}
892
916
893
917
// If this is not a "normal" interpreter run the library path optimization, including any additional paths from the user.
894
918
if ( interpreter . kind !== "normal" ) {
895
919
const { executable, libraryPaths, libraryPathStrategy } = arg ;
896
- interpreter = await optimizeLibraryPaths ( {
897
- executable,
898
- interpreter,
899
- libraryPaths,
900
- libraryPathStrategy,
901
- } ) ;
920
+ if ( executable ) {
921
+ interpreter = await optimizeLibraryPaths ( {
922
+ executable,
923
+ interpreter,
924
+ libraryPaths,
925
+ libraryPathStrategy,
926
+ } ) ;
927
+ }
902
928
}
903
929
904
930
return manifestInterpreterFromWrapInterpreter ( interpreter ) ;
@@ -2173,6 +2199,7 @@ export const test = async () => {
2173
2199
testDylibPath ( ) ,
2174
2200
testContentExecutable ( ) ,
2175
2201
testContentExecutableVariadic ( ) ,
2202
+ testInterpreterSwappingNormal ( ) ,
2176
2203
] ) ;
2177
2204
return true ;
2178
2205
} ;
@@ -2394,3 +2421,70 @@ export const testDylibPath = async () => {
2394
2421
console . log ( "libraryPathWrapper" , libraryPathWrapper . id ) ;
2395
2422
return libraryPathWrapper ;
2396
2423
} ;
2424
+
2425
+ export const testInterpreterSwappingNormal = async ( ) => {
2426
+ const buildToolchain = await bootstrap . sdk ( await std . triple . host ( ) ) ;
2427
+
2428
+ // Create a simple bash interpreter wrapper for testing
2429
+ const bashExecutable = await std . utils . bash
2430
+ . build ( { bootstrap : true , env : buildToolchain } )
2431
+ . then ( ( artifact ) => artifact . get ( "bin/bash" ) )
2432
+ . then ( tg . File . expect ) ;
2433
+
2434
+ const firstInterpreter = await wrap ( bashExecutable , {
2435
+ buildToolchain,
2436
+ args : [ "-c" , "echo 'first interpreter'" ] ,
2437
+ } ) ;
2438
+
2439
+ const secondInterpreter = await wrap ( bashExecutable , {
2440
+ buildToolchain,
2441
+ args : [ "-c" , "echo 'second interpreter'" ] ,
2442
+ } ) ;
2443
+
2444
+ const script = "echo hi" ;
2445
+
2446
+ // First, create a wrapper with the first interpreter
2447
+ const firstWrapper = await wrap ( script , {
2448
+ buildToolchain,
2449
+ interpreter : firstInterpreter ,
2450
+ } ) ;
2451
+ await firstWrapper . store ( ) ;
2452
+
2453
+ // Read the manifest to verify the first interpreter
2454
+ const firstManifest = await wrap . Manifest . read ( firstWrapper ) ;
2455
+ tg . assert ( firstManifest ) ;
2456
+ tg . assert ( firstManifest . interpreter ) ;
2457
+ tg . assert ( firstManifest . interpreter . kind === "normal" ) ;
2458
+
2459
+ // Now wrap the wrapper again with a different interpreter
2460
+ const secondWrapper = await wrap ( firstWrapper , {
2461
+ buildToolchain,
2462
+ interpreter : secondInterpreter ,
2463
+ } ) ;
2464
+ await secondWrapper . store ( ) ;
2465
+
2466
+ // Read the manifest to verify the interpreter was swapped
2467
+ const secondManifest = await wrap . Manifest . read ( secondWrapper ) ;
2468
+ tg . assert ( secondManifest ) ;
2469
+ tg . assert ( secondManifest . interpreter ) ;
2470
+ tg . assert ( secondManifest . interpreter . kind === "normal" ) ;
2471
+
2472
+ // The interpreters should be different
2473
+ const firstInterpreterTemplate = firstManifest . interpreter . path ;
2474
+ const secondInterpreterTemplate = secondManifest . interpreter . path ;
2475
+
2476
+ tg . assert (
2477
+ JSON . stringify ( firstInterpreterTemplate ) !==
2478
+ JSON . stringify ( secondInterpreterTemplate ) ,
2479
+ "Expected interpreter to be swapped to the new value" ,
2480
+ ) ;
2481
+
2482
+ // The executable should still be the original executable, not the first wrapper
2483
+ tg . assert (
2484
+ JSON . stringify ( secondManifest . executable ) ===
2485
+ JSON . stringify ( firstManifest . executable ) ,
2486
+ "Expected executable to remain the same as the original" ,
2487
+ ) ;
2488
+
2489
+ return secondWrapper ;
2490
+ } ;
0 commit comments