refactor(artifacts): retain admitted PyInstaller cookie facts

This commit is contained in:
morluto committed 2026-10-27 08:02:51 +08:00
1 parent 9222526fe2
commit 8c8a2752c0
11 files changed
+194 -31

No files matched your search

+12 -7
View File
@@ -13,14 +13,17 @@ import {
locateNodeSea,
type NodeSeaLocation,
} from "./nodeSea/NodeSeaLocation.js";
import { containsPyInstallerArchive } from "./pyinstaller/CArchiveLayout.js";
import {
findPyInstallerCookie,
type PyInstallerCookie,
} from "./pyinstaller/CArchiveLayout.js";
/** Completed observations only; null means examined absence, omission means unexamined. */
export interface ArtifactEmbeddedFormatProbeFacts {
readonly sourceState: RegularFileState;
readonly godotPck?: GodotPckLocation | null;
readonly nodeSea?: NodeSeaLocation | null;
readonly pyInstaller?: boolean;
readonly pyInstaller?: PyInstallerCookie | null;
}
/** Share format observations only across admissions of the same unchanged regular file. */
@@ -72,17 +75,19 @@ export class ArtifactEmbeddedFormatProbes {
return location;
}
async pyInstaller(): Promise<boolean> {
async pyInstaller(): Promise<PyInstallerCookie | undefined> {
abortIfNeeded(this.signal);
if (this.#facts.pyInstaller !== undefined) return this.#facts.pyInstaller;
const found = await containsPyInstallerArchive(
if (this.#facts.pyInstaller !== undefined)
return this.#facts.pyInstaller ?? undefined;
const found = await findPyInstallerCookie(
this.source.handle,
this.source.initial.size,
this.signal,
);
abortIfNeeded(this.signal);
this.#facts = { ...this.#facts, pyInstaller: found };
return found;
const cookie = found?.ok === true ? found.value : undefined;
this.#facts = { ...this.#facts, pyInstaller: cookie ?? null };
return cookie;
}
completedFacts(): ArtifactEmbeddedFormatProbeFacts {
+16 -3
View File
@@ -1,3 +1,4 @@
import type { PyInstallerAdmission } from "./pyinstaller/PyInstallerAdmission.js";
import type { Readable } from "node:stream";
import { finished } from "node:stream/promises";
import { setImmediate } from "node:timers/promises";
@@ -110,6 +111,7 @@ interface LoadedArchive {
export class PyInstallerArtifactReader implements ArtifactReader {
readonly format = "pyinstaller" as const;
readonly #source: ArtifactFileSource;
#admission: PyInstallerAdmission | undefined;
#closed = false;
readonly #scope = new ArtifactResourceScope();
readonly #cleanup = new ArtifactResourceScope();
@@ -124,8 +126,16 @@ export class PyInstallerArtifactReader implements ArtifactReader {
readonly #members = new Map<string, PyInstallerMember>();
readonly #openedPyzSizes = new Map<string, number>();
constructor(path: string, source?: StableRegularFileDescriptor) {
this.#source = new ArtifactFileSource(path, "PyInstaller", source);
constructor(
path: string,
source?: StableRegularFileDescriptor | PyInstallerAdmission,
) {
if (source !== undefined && "kind" in source) {
this.#admission = source;
this.#source = new ArtifactFileSource(path, "PyInstaller", source.source);
} else {
this.#source = new ArtifactFileSource(path, "PyInstaller", source);
}
this.#cleanup.retain({
kind: "lease",
resource: "PyInstaller private PYZ images",
@@ -287,6 +297,7 @@ export class PyInstallerArtifactReader implements ArtifactReader {
await this.#cleanup.close();
this.#closed = true;
this.#loaded = undefined;
this.#admission = undefined;
this.#members.clear();
this.#openedPyzSizes.clear();
}
@@ -313,7 +324,8 @@ export class PyInstallerArtifactReader implements ArtifactReader {
const { handle, initial } = await this.#source.readSelectedPath();
const size = initial.size;
const pathDialect = await carrierPathDialect(handle, size, signal);
const cookie = await this.#cookie(handle, size, signal);
const cookie =
this.#admission?.cookie ?? (await this.#cookie(handle, size, signal));
this.#budget = artifactExpansionBudgetBytes(cookie.packageLength);
const toc = await this.#toc(handle, cookie, signal);
for (const entry of toc) {
@@ -339,6 +351,7 @@ export class PyInstallerArtifactReader implements ArtifactReader {
if (member.read?.kind === "carchive")
this.#charge(member.read.header?.length ?? 0);
await this.#source.readSelectedPath();
this.#admission = undefined;
this.#metadata = cookieMetadata(cookie, toc, encrypted);
let opaqueImages = 0;
for (const image of images.values())
+18 -2
View File
@@ -1,3 +1,4 @@
import type { PyInstallerAdmission } from "./pyinstaller/PyInstallerAdmission.js";
import type { NodeSeaAdmission } from "./nodeSea/NodeSeaAdmission.js";
import type { AsarHeaderAdmission } from "../config/asarHeaderAdmission.js";
import { AsarArtifactReader } from "./AsarArtifactReader.js";
@@ -33,6 +34,7 @@ interface ArtifactReaderConstruction {
readonly admission?:
| StableRegularFileDescriptor
| NodeSeaAdmission
| PyInstallerAdmission
| undefined;
readonly asarHeaderAdmission?: AsarHeaderAdmission | undefined;
/** Caller-selected root spelling; only ASAR companion lookup consumes this. */
@@ -75,9 +77,23 @@ export function createArtifactReader(
: { selectedPath: options.selectedPath }),
});
case "pyinstaller":
return new PyInstallerArtifactReader(path, rootSource);
return new PyInstallerArtifactReader(
path,
admission !== undefined &&
"kind" in admission &&
admission.kind === "pyinstaller"
? admission
: rootSource,
);
case "node-sea":
return new NodeSeaArtifactReader(path, admission);
return new NodeSeaArtifactReader(
path,
admission !== undefined &&
"kind" in admission &&
admission.kind === "pyinstaller"
? rootSource
: admission,
);
case "godot-pck":
return new GodotPckArtifactReader(path, rootSource);
case "mach-o-universal":
@@ -375,7 +375,7 @@ it.each([false, true] as const)(
vi.mock("../pyinstaller/CArchiveLayout.js", async (importOriginal) => ({
...(await importOriginal<object>()),
containsPyInstallerArchive: () =>
findPyInstallerCookie: () =>
Promise.reject(new ArtifactReaderFailure("format", "detection failed")),
}));
@@ -603,7 +603,7 @@ const requireExtractableFormat = async (
(await probes.godotPck()) !== undefined ||
((format === "elf" || format === "pe" || format === "mach-o") &&
((await probes.nodeSea()) !== undefined ||
(await probes.pyInstaller())));
(await probes.pyInstaller()) !== undefined));
if (pack && input.expectedRootSha256 !== undefined) {
const digest = await hashStableRootArtifactHandle(
path,
+9 -2
View File
@@ -1,3 +1,4 @@
import type { PyInstallerAdmission } from "../pyinstaller/PyInstallerAdmission.js";
import { assertArtifactSelectedSource } from "../ArtifactSelectedSource.js";
import { artifactNameForClassification } from "../../domain/artifactNameSelection.js";
import type { NodeSeaAdmission } from "../nodeSea/NodeSeaAdmission.js";
@@ -51,6 +52,7 @@ type RootInventoryClassification = RootClassification & {
readonly readerAdmission:
| StableRegularFileDescriptor
| NodeSeaAdmission
| PyInstallerAdmission
| undefined;
};
@@ -202,7 +204,11 @@ const classifyRootFormat = async (
signal?: AbortSignal,
asarHeaderAdmission?: AsarHeaderAdmission,
selectedPath?: string,
): Promise<ArtifactOccurrence["artifact_format"] | NodeSeaAdmission> => {
): Promise<
| ArtifactOccurrence["artifact_format"]
| NodeSeaAdmission
| PyInstallerAdmission
> => {
const { handle } = source;
const probe = createArtifactClassificationProbe(asarHeaderAdmission);
const chunk = Buffer.alloc(64 * 1024);
@@ -292,7 +298,8 @@ const classifyRootFormat = async (
}
const packaged = await embeddedProbes.pyInstaller();
abortIfNeeded(signal);
if (packaged) return "pyinstaller";
if (packaged !== undefined)
return { kind: "pyinstaller", source, cookie: packaged };
}
return format;
};
+5 -1
View File
@@ -1,3 +1,4 @@
import type { PyInstallerAdmission } from "../pyinstaller/PyInstallerAdmission.js";
import type { NodeSeaAdmission } from "../nodeSea/NodeSeaAdmission.js";
import type { AsarHeaderAdmission } from "../../config/asarHeaderAdmission.js";
import type {
@@ -19,7 +20,10 @@ export const createReader = (
path: string,
format: ArtifactOccurrence["artifact_format"],
environment: Readonly<NodeJS.ProcessEnv>,
readerAdmission?: StableRegularFileDescriptor | NodeSeaAdmission,
readerAdmission?:
| StableRegularFileDescriptor
| NodeSeaAdmission
| PyInstallerAdmission,
asarHeaderAdmission?: AsarHeaderAdmission,
selectedPath?: string,
): ArtifactReader | undefined => {
@@ -83,16 +83,6 @@ export const isCArchiveDeclaration = (entry: CArchiveEntry): boolean =>
export const isCArchivePythonModule = (entry: CArchiveEntry): boolean =>
entry.role === "module" || entry.role === "package";
/** True when a native executable has a structurally valid CArchive cookie. */
export const containsPyInstallerArchive = async (
handle: FileHandle,
fileSize: number,
signal?: AbortSignal,
): Promise<boolean> => {
const found = await findPyInstallerCookie(handle, fileSize, signal);
return found?.ok === true;
};
/** Locate a structurally valid cookie. Invalid magic copies are skipped. */
export const findPyInstallerCookie = async (
handle: FileHandle,
@@ -0,0 +1,118 @@
import { appendFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it, vi } from "vitest";
import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirectory.js";
import { withOwnedRegularFile } from "../../filesystem/OwnedRegularFile.js";
import { ArtifactEmbeddedFormatProbes } from "../ArtifactEmbeddedFormatProbes.js";
import { ArtifactResourceScope } from "../ArtifactResourceScope.js";
import { inventoryArtifact } from "../inventory/ArtifactInventory.js";
import { PyInstallerArtifactReader } from "../PyInstallerArtifactReader.js";
import { cArchiveExecutable } from "./CArchive.fixture.js";
import * as layout from "./CArchiveLayout.js";
const fixture = () =>
cArchiveExecutable({
layout: "2.1",
pythonVersion: 314,
pythonLibrary: "libpython3.14.so",
members: [
{
name: "payload",
typeCode: "x",
payload: Buffer.from("fixture"),
compress: false,
},
],
});
it("inventories members using the cookie discovered by root classification", async () => {
const root = await createTestTempDirectory(
"rea-pyinstaller-cookie-inventory-",
);
const path = join(root, "app");
await writeFile(path, fixture());
const locate = vi.spyOn(layout, "findPyInstallerCookie");
const resourceScope = new ArtifactResourceScope();
try {
const result = await inventoryArtifact(path, {
environment: {},
resourceScope,
});
expect(result.manifest.root_format).toBe("pyinstaller");
expect(
result.occurrences.some(
(entry) =>
entry.logical_path === "payload" && entry.hash_status === "verified",
),
).toBe(true);
expect(locate).toHaveBeenCalledTimes(1);
} finally {
await resourceScope.close();
locate.mockRestore();
}
});
it("rejects changed source before consuming an admitted cookie and leaves the borrowed descriptor open", async () => {
const root = await createTestTempDirectory("rea-pyinstaller-cookie-change-");
const path = join(root, "app");
await writeFile(path, fixture());
await withOwnedRegularFile(path, {}, async (source) => {
const cookie = await new ArtifactEmbeddedFormatProbes(
path,
source,
).pyInstaller();
if (cookie === undefined) throw new Error("Fixture cookie was not found");
const reader = new PyInstallerArtifactReader(path, {
kind: "pyinstaller",
source,
cookie,
});
await appendFile(path, "changed");
try {
await expect(
(async () => {
for await (const entry of reader.entries())
throw new Error(`Changed source exposed member ${entry.path}`);
})(),
).rejects.toMatchObject({ reason: "integrity" });
} finally {
await reader.close();
}
await expect(source.handle.stat()).resolves.toMatchObject({
size: fixture().length + 7,
});
});
});
it("retains completed cookie facts across unchanged admissions and honors cached cancellation", async () => {
const root = await createTestTempDirectory("rea-pyinstaller-cookie-cache-");
const path = join(root, "app");
await writeFile(path, fixture());
const locate = vi.spyOn(layout, "findPyInstallerCookie");
try {
await withOwnedRegularFile(path, {}, async (source) => {
const controller = new AbortController();
const probes = new ArtifactEmbeddedFormatProbes(
path,
source,
controller.signal,
);
const cookie = await probes.pyInstaller();
expect(cookie).toMatchObject({ layout: "2.1", pythonVersion: 314 });
const resumed = new ArtifactEmbeddedFormatProbes(
path,
source,
controller.signal,
probes.completedFacts(),
);
await expect(resumed.pyInstaller()).resolves.toBe(cookie);
controller.abort(new Error("cancel cached cookie"));
await expect(resumed.pyInstaller()).rejects.toMatchObject({
reason: "cancelled",
});
expect(locate).toHaveBeenCalledTimes(1);
});
} finally {
locate.mockRestore();
}
});
@@ -0,0 +1,9 @@
import type { StableRegularFileDescriptor } from "../../filesystem/RegularFile.js";
import type { PyInstallerCookie } from "./CArchiveLayout.js";
/** Validated cookie facts borrowed with the descriptor that produced them. */
export interface PyInstallerAdmission {
readonly kind: "pyinstaller";
readonly source: StableRegularFileDescriptor;
readonly cookie: PyInstallerCookie;
}
@@ -266,10 +266,7 @@ it.each([
await writeFile(source, bytes());
const pck = vi.spyOn(pckLocation, "locateGodotPck");
const sea = vi.spyOn(seaLocation, "locateNodeSea");
const pyInstaller = vi.spyOn(
pyInstallerLocation,
"containsPyInstallerArchive",
);
const pyInstaller = vi.spyOn(pyInstallerLocation, "findPyInstallerCookie");
const hashRoot = rootHash.hashStableRootArtifactHandle;
let probesBeforeHash: number[] | undefined;
const hash = vi
@@ -310,6 +307,10 @@ it.each([
expect(extracted.manifest.root_format).toBe(format);
expect(extracted.artifacts.length).toBeGreaterThan(0);
expect(probesBeforeHash).toEqual(probes);
// Preflight's cookie also supplies inventory. Extraction opens its
// own reader and establishes that reader's cookie independently.
if (format === "pyinstaller")
expect(pyInstaller).toHaveBeenCalledTimes(2);
},
);
} finally {