142 lines
4.2 KiB
JavaScript
142 lines
4.2 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { execFile } from "node:child_process";
|
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { promisify } from "node:util";
|
|
|
|
import {
|
|
artifactCliEvidence,
|
|
artifactMcpResult,
|
|
withArtifactMcp,
|
|
} from "../../lib/artifact-e2e.mjs";
|
|
|
|
const exec = promisify(execFile);
|
|
|
|
if (process.platform !== "darwin")
|
|
throw new Error(
|
|
"Legacy simulator verification requires a macOS host with full Xcode.",
|
|
);
|
|
for (const tool of ["clang", "ld", "vtool", "otool"])
|
|
await exec("/usr/bin/xcrun", ["--find", tool], { timeout: 30_000 });
|
|
const { stdout: sdk } = await exec(
|
|
"/usr/bin/xcrun",
|
|
["--sdk", "iphonesimulator", "--show-sdk-path"],
|
|
{ timeout: 30_000 },
|
|
);
|
|
const sdkPath = sdk.trim();
|
|
assert.ok(sdkPath.length > 0, "iPhoneSimulator SDK path is empty");
|
|
|
|
const root = await mkdtemp(join(tmpdir(), "rea-legacy-simulator-"));
|
|
const xcrun = (...args) =>
|
|
exec("/usr/bin/xcrun", args, {
|
|
timeout: 120_000,
|
|
maxBuffer: 16 * 1024 * 1024,
|
|
});
|
|
try {
|
|
// Source-owned fixture, never executed: a legacy deployment target makes
|
|
// the Apple driver emit LC_VERSION_MIN_IPHONEOS (no simulator flag), while
|
|
// the modern dylib carries LC_BUILD_VERSION with an explicit simulator ID.
|
|
await writeFile(join(root, "library.c"), "int fixture(void) { return 7; }\n");
|
|
await writeFile(
|
|
join(root, "main.c"),
|
|
"extern int fixture(void);\nint main(void) { return fixture(); }\n",
|
|
);
|
|
for (const [source, deployment] of [
|
|
["library", "14.0"],
|
|
["main", "9.0"],
|
|
]) {
|
|
await xcrun(
|
|
"clang",
|
|
"-target",
|
|
`x86_64-apple-ios${deployment}-simulator`,
|
|
"-isysroot",
|
|
sdkPath,
|
|
"-c",
|
|
join(root, `${source}.c`),
|
|
"-o",
|
|
join(root, `${source}.o`),
|
|
);
|
|
}
|
|
const common = ["-arch", "x86_64", "-syslibroot", sdkPath, "-lSystem"];
|
|
await xcrun(
|
|
"ld",
|
|
...common,
|
|
"-dylib",
|
|
"-platform_version",
|
|
"ios-simulator",
|
|
"14.0",
|
|
"14.0",
|
|
"-install_name",
|
|
"@rpath/libFixture.dylib",
|
|
join(root, "library.o"),
|
|
"-o",
|
|
join(root, "libFixture.dylib"),
|
|
);
|
|
await xcrun(
|
|
"clang",
|
|
"-target",
|
|
"x86_64-apple-ios9.0-simulator",
|
|
"-isysroot",
|
|
sdkPath,
|
|
"-e",
|
|
"_main",
|
|
"-rpath",
|
|
"@loader_path",
|
|
join(root, "main.o"),
|
|
join(root, "libFixture.dylib"),
|
|
"-o",
|
|
join(root, "Main"),
|
|
);
|
|
|
|
// Apple oracle: the executable really uses the legacy command, the dylib
|
|
// really declares the modern simulator platform.
|
|
const { stdout: legacyBuild } = await xcrun(
|
|
"vtool",
|
|
"-show-build",
|
|
join(root, "Main"),
|
|
);
|
|
assert.match(legacyBuild, /LC_VERSION_MIN_IPHONEOS/u);
|
|
assert.doesNotMatch(legacyBuild, /LC_BUILD_VERSION/u);
|
|
const { stdout: modernBuild } = await xcrun(
|
|
"vtool",
|
|
"-show-build",
|
|
join(root, "libFixture.dylib"),
|
|
);
|
|
assert.match(modernBuild, /IOSSIMULATOR/u);
|
|
|
|
// Real CLI over the fixture: the target is the executable file (plain
|
|
// directories are not binary targets); @loader_path resolves against its
|
|
// directory. The legacy x86 image must classify as iOS simulator
|
|
// (platform 7) and resolve against the modern dylib.
|
|
// The fixture is inspected, never executed or loaded into this process.
|
|
const mainPath = join(root, "Main");
|
|
const trace = (await artifactCliEvidence("trace-dylib-resolution", mainPath))
|
|
.normalized_result;
|
|
const main = trace.images.find(({ path }) => path === "Main");
|
|
assert.ok(main, "trace omitted the Main image");
|
|
assert.equal(main.parse_status, "parsed");
|
|
assert.deepEqual(main.slices[0]?.platforms, [7]);
|
|
const edge = trace.edges.find(
|
|
({ loader, install_name: name }) =>
|
|
loader === "Main" && name === "@rpath/libFixture.dylib",
|
|
);
|
|
assert.ok(edge, "trace omitted the @rpath edge");
|
|
assert.deepEqual(edge.resolution, {
|
|
status: "resolved",
|
|
image: "libFixture.dylib",
|
|
});
|
|
|
|
await withArtifactMcp(mainPath, async (client) => {
|
|
assert.deepEqual(
|
|
await artifactMcpResult(client, "trace_dylib_resolution"),
|
|
trace,
|
|
);
|
|
});
|
|
console.log(
|
|
"Legacy simulator verification passed against Apple clang, ld, vtool, and otool.",
|
|
);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|