Skip to content

Commit ef42cce

Browse files
committed
fix(std): correctly override interpreter when rewwrapping
1 parent 4bb7e63 commit ef42cce

File tree

2 files changed

+114
-17
lines changed

2 files changed

+114
-17
lines changed

packages/std/file.tg.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import * as std from "./tangram.ts";
22
import * as bootstrap from "./bootstrap.tg.ts";
3-
import { type ElfExecutableMetadata, elfExecutableMetadata } from "./file/elf.tg.ts";
3+
import {
4+
type ElfExecutableMetadata,
5+
elfExecutableMetadata,
6+
} from "./file/elf.tg.ts";
47
import {
58
type MachOExecutableMetadata,
69
machoExecutableMetadata,

packages/std/wrap.tg.ts

Lines changed: 110 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,14 @@ export async function wrap(...args: std.Args<wrap.Arg>): Promise<tg.File> {
1717

1818
tg.assert(arg.executable !== undefined, "No executable was provided.");
1919

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));
2128

2229
const detectedBuild = await std.triple.host();
2330
const host = arg.host ?? (await std.triple.host());
@@ -31,15 +38,17 @@ export async function wrap(...args: std.Args<wrap.Arg>): Promise<tg.File> {
3138
)
3239
: await bootstrap.sdk.env(host);
3340

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.
3543
const manifestInterpreter = await manifestInterpreterFromWrapArgObject({
3644
buildToolchain,
3745
interpreter: arg.interpreter,
38-
executable: arg.executable,
46+
executable: arg.interpreter ? undefined : arg.executable,
3947
libraryPaths: arg.libraryPaths,
4048
libraryPathStrategy: arg.libraryPathStrategy,
4149
});
4250

51+
// Use existing manifest values as defaults if we're wrapping a wrapper
4352
const manifestEnv = await wrap.manifestEnvFromEnvObject(
4453
arg.env as std.env.EnvObject,
4554
);
@@ -50,8 +59,16 @@ export async function wrap(...args: std.Args<wrap.Arg>): Promise<tg.File> {
5059
const manifest: wrap.Manifest = {
5160
interpreter: manifestInterpreter,
5261
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,
5572
};
5673

5774
// Get the wrapper executable.
@@ -252,9 +269,14 @@ export namespace wrap {
252269
}
253270

254271
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+
258280
executable = await wrap.executableFromManifestExecutable(
259281
existingManifest.executable,
260282
);
@@ -874,7 +896,7 @@ type ManifestInterpreterArg = {
874896
| tg.Template
875897
| wrap.Interpreter
876898
| undefined;
877-
executable: string | tg.Template | tg.File | tg.Symlink;
899+
executable?: string | tg.Template | tg.File | tg.Symlink | undefined;
878900
libraryPaths?: Array<tg.Directory | tg.Symlink | tg.Template> | undefined;
879901
libraryPathStrategy?: wrap.LibraryPathStrategy | undefined;
880902
};
@@ -885,20 +907,24 @@ const manifestInterpreterFromWrapArgObject = async (
885907
): Promise<wrap.Manifest.Interpreter | undefined> => {
886908
let interpreter = arg.interpreter
887909
? 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;
889913
if (interpreter === undefined) {
890914
return undefined;
891915
}
892916

893917
// If this is not a "normal" interpreter run the library path optimization, including any additional paths from the user.
894918
if (interpreter.kind !== "normal") {
895919
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+
}
902928
}
903929

904930
return manifestInterpreterFromWrapInterpreter(interpreter);
@@ -2173,6 +2199,7 @@ export const test = async () => {
21732199
testDylibPath(),
21742200
testContentExecutable(),
21752201
testContentExecutableVariadic(),
2202+
testInterpreterSwappingNormal(),
21762203
]);
21772204
return true;
21782205
};
@@ -2394,3 +2421,70 @@ export const testDylibPath = async () => {
23942421
console.log("libraryPathWrapper", libraryPathWrapper.id);
23952422
return libraryPathWrapper;
23962423
};
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

Comments
 (0)