diff options
author | partouf <partouf@gmail.com> | 2024-04-27 21:24:04 +0200 |
---|---|---|
committer | partouf <partouf@gmail.com> | 2024-04-27 21:24:04 +0200 |
commit | 6ce79d25954196e1a38ce7aec6fe508cbd8e02bf (patch) | |
tree | 4d2f6179db304482b4c0d4592a30f1dfba8d6ba2 | |
parent | 8d3ba37fec3b074cd5657766e5d67258e53c57d2 (diff) | |
download | compiler-explorer-gh-11529.tar.gz compiler-explorer-gh-11529.zip |
sketch for localexecution classgh-11529
-rw-r--r-- | lib/execution-env.ts | 101 | ||||
-rw-r--r-- | lib/handlers/api.ts | 44 |
2 files changed, 145 insertions, 0 deletions
diff --git a/lib/execution-env.ts b/lib/execution-env.ts new file mode 100644 index 000000000..31f81d895 --- /dev/null +++ b/lib/execution-env.ts @@ -0,0 +1,101 @@ +import os from 'os'; +import path from 'path'; + +import fs from 'fs-extra'; +import temp from 'temp'; + +import {ExecutionOptions, ExecutionParams} from '../types/compilation/compilation.interfaces.js'; +import {UnprocessedExecResult} from '../types/execution/execution.interfaces.js'; + +import {unwrap} from './assert.js'; +import * as exec from './exec.js'; +import {logger} from './logger.js'; +import {Packager} from './packager.js'; +import * as utils from './utils.js'; + +export interface IExecutionEnvironment { + downloadExecutablePackage(hash: string): Promise<void>; + execute(params: ExecutionParams): Promise<UnprocessedExecResult>; +} + +export class LocalExecutionEnvironment implements IExecutionEnvironment { + protected packager: Packager; + protected executableCache: any; + protected dirPath: string; + protected buildResult: any; + + constructor() { + this.packager = new Packager(); + this.dirPath = 'not initialized'; + } + + protected async executableGet(key: string, destinationFolder: string) { + const result = await this.executableCache.get(key); + if (!result.hit) return null; + const filepath = destinationFolder + '/' + key; + await fs.writeFile(filepath, unwrap(result.data)); + return filepath; + } + + protected async loadPackageWithExecutable(hash: string, dirPath: string) { + const compilationResultFilename = 'compilation-result.json'; + try { + const startTime = process.hrtime.bigint(); + const outputFilename = await this.executableGet(hash, dirPath); + if (outputFilename) { + logger.debug(`Using cached package ${outputFilename}`); + await this.packager.unpack(outputFilename, dirPath); + const buildResultsBuf = await fs.readFile(path.join(dirPath, compilationResultFilename)); + const buildResults = JSON.parse(buildResultsBuf.toString('utf8')); + const endTime = process.hrtime.bigint(); + + // todo: get inputFilename and executableFilename from somewhere else? + + // let inputFilename = path.join(dirPath, this.compileFilename); + // if (buildResults.inputFilename) { + // inputFilename = path.join(dirPath, path.basename(buildResults.inputFilename)); + // } + + return Object.assign({}, buildResults, { + code: 0, + // inputFilename: inputFilename, + dirPath: dirPath, + // executableFilename: this.getExecutableFilename(dirPath, this.outputFilebase), + packageDownloadAndUnzipTime: ((endTime - startTime) / BigInt(1000000)).toString(), + }); + } + logger.debug('Tried to get executable from cache, but got a cache miss'); + } catch (err) { + logger.error('Tried to get executable from cache, but got an error:', {err}); + } + return false; + } + + async downloadExecutablePackage(hash: string): Promise<void> { + this.dirPath = await temp.mkdir({prefix: utils.ce_temp_prefix, dir: os.tmpdir()}); + + this.buildResult = await this.loadPackageWithExecutable(hash, this.dirPath); + } + + private getDefaultExecOptions(): ExecutionOptions & {env: Record<string, string>} { + // const env = this.env.getEnv(this.compiler.needsMulti); + const env: Record<string, string> = {}; + if (!env.PATH) env.PATH = ''; + // env.PATH = [...this.getExtraPaths(), env.PATH].filter(Boolean).join(path.delimiter); + + return { + // timeoutMs: this.env.ceProps('compileTimeoutMs', 7500), + // maxErrorOutput: this.env.ceProps('max-error-output', 5000), + env, + // wrapper: this.compilerWrapper, + }; + } + + async execute(params: ExecutionParams): Promise<UnprocessedExecResult> { + return await exec.execute( + this.buildResult.executableFilename, + typeof params.args === 'string' ? utils.splitArguments(params.args) : params.args || [], + this.getDefaultExecOptions(), + ); + } +} diff --git a/lib/handlers/api.ts b/lib/handlers/api.ts index c64f8c269..e48e01304 100644 --- a/lib/handlers/api.ts +++ b/lib/handlers/api.ts @@ -31,6 +31,7 @@ import {CompilerInfo} from '../../types/compiler.interfaces.js'; import {Language, LanguageKey} from '../../types/languages.interfaces.js'; import {assert, unwrap} from '../assert.js'; import {ClientStateNormalizer} from '../clientstate-normalizer.js'; +import {IExecutionEnvironment, LocalExecutionEnvironment} from '../execution-env.js'; import {logger} from '../logger.js'; import {ClientOptionsHandler} from '../options-handler.js'; import {PropertyGetter} from '../properties.interfaces.js'; @@ -107,6 +108,8 @@ export class ApiHandler { .post(compileHandler.handleCmake.bind(compileHandler)) .all(methodNotAllowed); + this.handle.route('/localexecution/:hash').post(this.handleLocalExecution.bind(this)).all(methodNotAllowed); + this.handle .route('/popularArguments/:compiler') .post(compileHandler.handlePopularArguments.bind(compileHandler)) @@ -277,6 +280,47 @@ export class ApiHandler { } } + handleLocalExecution(req: express.Request, res: express.Response, next: express.NextFunction) { + if (!req.params.hash) { + next({statusCode: 404, message: 'No hash supplied'}); + return; + } + + if (!req.body.ExecutionParams) { + next({statusCode: 404, message: 'No ExecutionParams'}); + return; + } + + /* + +export type ExecutionOptions = { + timeoutMs?: number; + maxErrorOutput?: number; + env?: Record<string, string>; + wrapper?: any; + maxOutput?: number; + ldPath?: string[]; + appHome?: string; + customCwd?: string; + createAndUseTempDir?: boolean; + // Stdin + input?: any; + killChild?: () => void; +}; + +export type ExecutionParams = { + args?: string[] | string; + stdin?: string; + runtimeTools?: ConfiguredRuntimeTools; +}; + +*/ + + const env: IExecutionEnvironment = new LocalExecutionEnvironment(); + env.downloadExecutablePackage(req.params.hash); + env.execute(req.body.ExecutionParams); + } + handleAllLibraries(req: express.Request, res: express.Response, next: express.NextFunction) { if (this.options) { res.send(this.options.options.libs); |