Files
makelore/electron/utils/python-runtime.ts

133 lines
4.0 KiB
TypeScript

import { existsSync } from 'node:fs';
import { dirname, isAbsolute, join, resolve } from 'node:path';
export type PythonRuntimeSource = 'configured' | 'bundled' | 'system';
export type PythonRuntime = {
executable: string;
binDir: string;
source: PythonRuntimeSource;
};
export type UvRuntime = {
executable: string;
binDir: string;
source: 'bundled' | 'system';
};
export type PythonRuntimeOptions = {
isPackaged: boolean;
resourcesPath: string;
appPath: string;
platform: NodeJS.Platform;
arch: string;
configuredPath?: string;
};
export type UvRuntimeOptions = Omit<PythonRuntimeOptions, 'configuredPath'>;
function configuredRuntime(configuredPath: string | undefined, appPath: string): PythonRuntime | null {
const trimmed = configuredPath?.trim();
if (!trimmed) return null;
const executable = isAbsolute(trimmed) ? trimmed : resolve(appPath, trimmed);
if (!existsSync(executable)) return null;
return { executable, binDir: dirname(executable), source: 'configured' };
}
function bundledExecutable(resourcesPath: string, platform: NodeJS.Platform): string {
return platform === 'win32'
? join(resourcesPath, 'python', 'python.exe')
: join(resourcesPath, 'python', 'bin', 'python3');
}
export function resolvePythonRuntime(options: PythonRuntimeOptions): PythonRuntime {
const configured = options.isPackaged
? null
: configuredRuntime(options.configuredPath, options.appPath);
if (configured) return configured;
const executable = bundledExecutable(options.resourcesPath, options.platform);
if (existsSync(executable)) {
return { executable, binDir: dirname(executable), source: 'bundled' };
}
if (options.isPackaged) {
throw new Error(`Bundled Python runtime is missing: ${executable}`);
}
return {
executable: options.platform === 'win32' ? 'python' : 'python3',
binDir: '',
source: 'system',
};
}
function uvExecutableName(platform: NodeJS.Platform): string {
return platform === 'win32' ? 'uv.exe' : 'uv';
}
export function resolveUvRuntime(options: UvRuntimeOptions): UvRuntime {
const executable = options.isPackaged
? join(options.resourcesPath, 'bin', uvExecutableName(options.platform))
: join(
options.appPath,
'resources',
'bin',
`${options.platform}-${options.arch}`,
uvExecutableName(options.platform),
);
if (existsSync(executable)) {
return { executable, binDir: dirname(executable), source: 'bundled' };
}
if (options.isPackaged) {
throw new Error(`Bundled uv runtime is missing: ${executable}`);
}
return { executable: 'uv', binDir: '', source: 'system' };
}
function prependRuntimeBin(
environment: Record<string, string>,
binDir: string,
platform: NodeJS.Platform,
): Record<string, string> {
if (!binDir) return environment;
const result = { ...environment };
const existingKey = Object.keys(result).find((key) => key.toLowerCase() === 'path');
const pathKey = platform === 'win32' && existingKey ? existingKey : 'PATH';
const existingPath = result[pathKey];
const separator = platform === 'win32' ? ';' : ':';
result[pathKey] = existingPath ? `${binDir}${separator}${existingPath}` : binDir;
return result;
}
export function prependPythonToPath(
environment: Record<string, string>,
runtime: PythonRuntime,
platform: NodeJS.Platform = process.platform,
): Record<string, string> {
return prependRuntimeBin(
{ ...environment, NIANCODE_PYTHON_PATH: runtime.executable },
runtime.binDir,
platform,
);
}
export function prependManagedRuntimesToPath(
environment: Record<string, string>,
pythonRuntime?: PythonRuntime,
uvRuntime?: UvRuntime,
platform: NodeJS.Platform = process.platform,
): Record<string, string> {
const withPython = pythonRuntime
? prependPythonToPath(environment, pythonRuntime, platform)
: { ...environment };
if (!uvRuntime) return withPython;
return prependRuntimeBin(
{ ...withPython, NIANCODE_UV_PATH: uvRuntime.executable },
uvRuntime.binDir,
platform,
);
}