mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
188 lines
6.8 KiB
TypeScript
188 lines
6.8 KiB
TypeScript
import { isAbsolute } from "node:path";
|
|
|
|
import { defaultKunaCacheDirectory } from "../config/kunaCacheDirectory.js";
|
|
import { homeDirectoryFromEnvironment } from "../config/homeDirectory.js";
|
|
import { providerStartupTimeoutSchema } from "../config/providerStartupTimeout.js";
|
|
import { err, ok, type Result } from "../domain/result.js";
|
|
import {
|
|
KUNA_COMMAND_REMEDIATION,
|
|
KUNA_UNSUPPORTED_HOST_REMEDIATION,
|
|
} from "./KunaRelease.js";
|
|
|
|
/** Kuna caps its own decode lanes and worker pool at 32. */
|
|
const MAX_KUNA_JOBS = 32;
|
|
|
|
/**
|
|
* A 1.4 MB sqlite3 shell exported serially in 66-108 s on Apple M-series and
|
|
* Linux hosts; thirty minutes admits images an order of magnitude larger
|
|
* without an unbounded run. Larger images need a raised deadline or more jobs.
|
|
*/
|
|
const DEFAULT_PROJECT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
|
|
/** One function decompiles in well under a second; sqlite3VdbeExec (42 KB) took 15-25 s. */
|
|
const DEFAULT_FUNCTION_TIMEOUT_MS = 120_000;
|
|
|
|
/**
|
|
* A serial (`--jobs 1`) export is Kuna's reference answer: it decompiles
|
|
* callees before callers, which recovers prototypes that parallel workers
|
|
* cannot. Measured on coreutils sort, 40-60% of functions differ otherwise.
|
|
*/
|
|
const DEFAULT_JOBS = 1;
|
|
|
|
/** Measured sqlite3 exports occupy ~25 MiB; four GiB retains many projects. */
|
|
const DEFAULT_CACHE_MAX_BYTES = 4 * 1024 ** 3;
|
|
|
|
/** One project needs its .c, .h, .asm, inventory and manifest; refuse smaller caps. */
|
|
const MIN_CACHE_MAX_BYTES = 1024 ** 2;
|
|
|
|
/** Caller-selected Kuna installation and REA-owned project cache policy. */
|
|
export interface KunaConfiguration {
|
|
/** Caller-selected executable path, preserved exactly as configured. */
|
|
readonly command: string;
|
|
/** REA-owned cache root; Kuna's own spec cache lives beneath it too. */
|
|
readonly cacheRoot: string;
|
|
readonly cacheMaxBytes: number;
|
|
readonly projectTimeoutMs: number;
|
|
/** Deadline for one single-function `kuna decompile` run. */
|
|
readonly functionTimeoutMs: number;
|
|
readonly jobs: number;
|
|
}
|
|
|
|
/** One configured deadline and the setting that owns it. */
|
|
export interface KunaDeadline {
|
|
readonly setting: "REA_KUNA_TIMEOUT_MS" | "REA_KUNA_PROJECT_TIMEOUT_MS";
|
|
readonly ms: number;
|
|
}
|
|
|
|
/** Single-function decompiles and the standalone inventory. */
|
|
export const kunaFunctionDeadline = (
|
|
configuration: KunaConfiguration,
|
|
): KunaDeadline => ({
|
|
setting: "REA_KUNA_TIMEOUT_MS",
|
|
ms: configuration.functionTimeoutMs,
|
|
});
|
|
|
|
/** The project export and whole-program string, graph and xref scans. */
|
|
export const kunaProjectDeadline = (
|
|
configuration: KunaConfiguration,
|
|
): KunaDeadline => ({
|
|
setting: "REA_KUNA_PROJECT_TIMEOUT_MS",
|
|
ms: configuration.projectTimeoutMs,
|
|
});
|
|
|
|
/** Why the configured environment cannot select a Kuna installation. */
|
|
export interface KunaConfigurationFailure {
|
|
readonly code: "not_configured" | "open_options_invalid" | "unsupported_host";
|
|
readonly reason: string;
|
|
readonly setting: string | null;
|
|
readonly remediation: string;
|
|
}
|
|
|
|
const failure = (
|
|
code: KunaConfigurationFailure["code"],
|
|
reason: string,
|
|
setting: string | null = null,
|
|
): Result<never, KunaConfigurationFailure> =>
|
|
err({
|
|
code,
|
|
reason,
|
|
setting,
|
|
remediation:
|
|
code === "unsupported_host"
|
|
? KUNA_UNSUPPORTED_HOST_REMEDIATION
|
|
: setting === null || setting === "REA_KUNA_COMMAND"
|
|
? KUNA_COMMAND_REMEDIATION
|
|
: `Correct ${setting} to meet the reported constraint, or unset it to use its default. Restart REA after changing the environment.`,
|
|
});
|
|
|
|
const positiveInteger = (value: string): number | undefined =>
|
|
/^[1-9]\d*$/u.test(value) && Number.isSafeInteger(Number(value))
|
|
? Number(value)
|
|
: undefined;
|
|
|
|
/** Parse the Kuna settings without touching the filesystem or the executable. */
|
|
export const readKunaConfiguration = (
|
|
environment: Readonly<NodeJS.ProcessEnv>,
|
|
platform: NodeJS.Platform = process.platform,
|
|
): Result<KunaConfiguration, KunaConfigurationFailure> => {
|
|
const command = environment.REA_KUNA_COMMAND;
|
|
if (command === undefined || command.length === 0)
|
|
return failure("not_configured", KUNA_COMMAND_REMEDIATION);
|
|
if (!isAbsolute(command))
|
|
return failure(
|
|
"open_options_invalid",
|
|
`REA_KUNA_COMMAND must be an absolute path; received ${command}. ${KUNA_COMMAND_REMEDIATION}`,
|
|
"REA_KUNA_COMMAND",
|
|
);
|
|
if (platform !== "darwin" && platform !== "linux")
|
|
return failure("unsupported_host", KUNA_UNSUPPORTED_HOST_REMEDIATION);
|
|
|
|
const cacheRoot = environment.REA_KUNA_CACHE_DIR;
|
|
if (cacheRoot !== undefined && !isAbsolute(cacheRoot))
|
|
return failure(
|
|
"open_options_invalid",
|
|
`REA_KUNA_CACHE_DIR must be an absolute directory; received ${cacheRoot}.`,
|
|
"REA_KUNA_CACHE_DIR",
|
|
);
|
|
|
|
const rawMaxBytes = environment.REA_KUNA_CACHE_MAX_BYTES;
|
|
const cacheMaxBytes =
|
|
rawMaxBytes === undefined
|
|
? DEFAULT_CACHE_MAX_BYTES
|
|
: positiveInteger(rawMaxBytes);
|
|
if (cacheMaxBytes === undefined || cacheMaxBytes < MIN_CACHE_MAX_BYTES)
|
|
return failure(
|
|
"open_options_invalid",
|
|
`REA_KUNA_CACHE_MAX_BYTES must be a decimal byte count of at least ${String(MIN_CACHE_MAX_BYTES)}; received ${String(rawMaxBytes)}.`,
|
|
"REA_KUNA_CACHE_MAX_BYTES",
|
|
);
|
|
|
|
const rawTimeout = environment.REA_KUNA_PROJECT_TIMEOUT_MS;
|
|
const timeout =
|
|
rawTimeout === undefined
|
|
? undefined
|
|
: providerStartupTimeoutSchema.safeParse(rawTimeout);
|
|
if (timeout !== undefined && !timeout.success)
|
|
return failure(
|
|
"open_options_invalid",
|
|
`REA_KUNA_PROJECT_TIMEOUT_MS must be a decimal millisecond count from 1 to 2147483647; received ${String(rawTimeout)}.`,
|
|
"REA_KUNA_PROJECT_TIMEOUT_MS",
|
|
);
|
|
|
|
const rawFunctionTimeout = environment.REA_KUNA_TIMEOUT_MS;
|
|
const functionTimeout =
|
|
rawFunctionTimeout === undefined
|
|
? undefined
|
|
: providerStartupTimeoutSchema.safeParse(rawFunctionTimeout);
|
|
if (functionTimeout !== undefined && !functionTimeout.success)
|
|
return failure(
|
|
"open_options_invalid",
|
|
`REA_KUNA_TIMEOUT_MS must be a decimal millisecond count from 1 to 2147483647; received ${String(rawFunctionTimeout)}.`,
|
|
"REA_KUNA_TIMEOUT_MS",
|
|
);
|
|
|
|
const rawJobs = environment.REA_KUNA_JOBS;
|
|
const jobs = rawJobs === undefined ? DEFAULT_JOBS : positiveInteger(rawJobs);
|
|
if (jobs === undefined || jobs > MAX_KUNA_JOBS)
|
|
return failure(
|
|
"open_options_invalid",
|
|
`REA_KUNA_JOBS must be an integer from 1 to ${String(MAX_KUNA_JOBS)}; received ${String(rawJobs)}.`,
|
|
"REA_KUNA_JOBS",
|
|
);
|
|
|
|
return ok({
|
|
command,
|
|
cacheRoot:
|
|
cacheRoot ??
|
|
defaultKunaCacheDirectory(
|
|
homeDirectoryFromEnvironment(environment, platform),
|
|
environment,
|
|
platform,
|
|
),
|
|
cacheMaxBytes,
|
|
projectTimeoutMs: timeout?.data ?? DEFAULT_PROJECT_TIMEOUT_MS,
|
|
functionTimeoutMs: functionTimeout?.data ?? DEFAULT_FUNCTION_TIMEOUT_MS,
|
|
jobs,
|
|
});
|
|
};
|