diff --git a/api/src/api/v2.ts b/api/src/api/v2.ts index 69f6d3d..b8bd8cb 100644 --- a/api/src/api/v2.ts +++ b/api/src/api/v2.ts @@ -59,6 +59,7 @@ const SIGNALS = [ function get_job(body: RequestBody): Promise { let { language, + tool, version, args, stdin, @@ -159,6 +160,7 @@ function get_job(body: RequestBody): Promise { run: run_memory_limit, compile: compile_memory_limit, }, + tool }) ); }); @@ -291,6 +293,22 @@ router.post('/execute', async (req, res) => { } }); +router.post('/tooling',async (req, res) => { + try { + const job = await get_job(req.body); + + await job.prime(); + + const result = await job.analysis(); + + await job.cleanup(); + + return res.status(200).send(result); + } catch (error) { + return res.status(400).json(error) + } +}) + router.get('/runtimes', (req, res) => { const runtimes = _runtimes.map(rt => { return { @@ -298,6 +316,7 @@ router.get('/runtimes', (req, res) => { version: rt.version.raw, aliases: rt.aliases, runtime: rt.runtime, + tooling: rt.tooling }; }); diff --git a/api/src/job.ts b/api/src/job.ts index db5d77c..86b4a1e 100644 --- a/api/src/job.ts +++ b/api/src/job.ts @@ -6,6 +6,7 @@ import { spawn } from 'child_process'; import { join, relative, dirname } from 'node:path'; import config from './config.js'; import * as globals from './globals.js'; +import { type Runtime } from './runtime.js'; import { mkdir, chown, @@ -18,7 +19,7 @@ import { readdirSync, readFileSync } from 'node:fs'; import wait_pid from 'waitpid'; import EventEmitter from 'events'; -import { File, ResponseBody } from './types.js'; +import { File, LimitObject, ResponseBody } from './types.js'; const job_states = { READY: Symbol('Ready to be primed'), @@ -42,35 +43,40 @@ setInterval(() => { export default class Job { uuid: string; logger: Logger; - runtime: any; + runtime: Runtime; + tool?: string; files: File[]; args: string[]; stdin: string; - timeouts: { compile: number; run: number }; - memory_limits: { compile: number; run: number }; + timeouts: LimitObject; + memory_limits: LimitObject; uid: number; gid: number; state: symbol; dir: string; constructor({ runtime, + tool, files, args, stdin, timeouts, memory_limits, }: { - runtime: unknown; + runtime: Runtime; + tool?: string; files: File[]; args: string[]; stdin: string; - timeouts: { compile: number; run: number }; - memory_limits: { compile: number; run: number }; + timeouts: LimitObject; + memory_limits: LimitObject; }) { this.uuid = uuidv4(); this.logger = create(`job/${this.uuid}`, {}); - + + this.tool = tool; + this.runtime = runtime; this.files = files.map((file: File, i: number) => ({ name: file.name || `file${i}.code`, @@ -80,6 +86,7 @@ export default class Job { : 'utf8', })); + this.args = args; this.stdin = stdin; @@ -193,6 +200,7 @@ export default class Job { env: { ...this.runtime.env_vars, PISTON_LANGUAGE: this.runtime.language, + PISTON_TOOLING: this.tool }, stdio: 'pipe', cwd: this.dir, @@ -271,6 +279,45 @@ export default class Job { }); } + async analysis() { + if (this.state !== job_states.PRIMED) { + throw new Error( + 'Job must be in primed state, current state: ' + + this.state.toString() + ); + } + + if (!this.runtime.tooling.includes(this.tool)) { + throw new Error( + `Tool ${this.tool} does not exist on ${this.runtime.toString()}` + ) + } + + this.logger.info(`Tooling job runtime=${this.runtime.toString()}; tooling=${this.tool}`); + + const code_files = + (this.runtime.language === 'file' && this.files) || + this.files.filter((file: File) => file.encoding == 'utf8'); + + + const output = await this.safe_call( + join(this.runtime.pkgdir, 'tooling'), + code_files.map(x => x.name), + this.timeouts.run, + this.memory_limits.run + ); + + this.logger.debug('Analyzing') + + + return { + output, + language: this.runtime.language, + tool: this.tool, + version: this.runtime.version.raw + } + } + async execute() { if (this.state !== job_states.PRIMED) { throw new Error( diff --git a/api/src/runtime.ts b/api/src/runtime.ts index ae2c445..1072f4e 100644 --- a/api/src/runtime.ts +++ b/api/src/runtime.ts @@ -12,6 +12,7 @@ export const runtimes: Runtime[] = []; export class Runtime { language: string; + tooling: string[]; version: SemVer; aliases: string[]; pkgdir: string; @@ -26,10 +27,11 @@ export class Runtime { _env_vars?: Record; constructor(o: { language: string; + tooling?: string[]; version: SemVer; aliases: string[]; pkgdir: string; - runtime?: any; + runtime?: string; timeouts: { run: number; compile: number }; memory_limits: { run: number; compile: number }; max_process_count: number; @@ -38,6 +40,7 @@ export class Runtime { output_max_size: number; }) { this.language = o.language; + this.tooling = o.tooling; this.version = o.version; this.aliases = o.aliases || []; this.pkgdir = o.pkgdir; @@ -128,6 +131,7 @@ export class Runtime { aliases, provides, limit_overrides, + tooling } = info; const version = parse(_version); @@ -145,6 +149,7 @@ export class Runtime { new Runtime({ language: lang.language, aliases: lang.aliases, + tooling, version, pkgdir: package_dir, runtime: language, @@ -160,6 +165,7 @@ export class Runtime { new Runtime({ language, version, + tooling, aliases, pkgdir: package_dir, ...Runtime.compute_all_limits(language, limit_overrides), diff --git a/api/src/types.ts b/api/src/types.ts index 68eb36a..73d6910 100644 --- a/api/src/types.ts +++ b/api/src/types.ts @@ -1,6 +1,7 @@ export interface Metadata { language: string; version: string; + tooling?: string[]; aliases?: string[]; dependencies?: Record; provides: { @@ -23,6 +24,8 @@ export type Limit = export type Limits = Record; +export type LimitObject = { compile: number; run: number; }; + export type LanguageMetadata = { language: string; version: string; @@ -37,6 +40,7 @@ export type File = { }; export type RequestBody = { language: string; + tool?: string; version: string; files: Array; stdin?: string; diff --git a/packages/.gitignore b/packages/.gitignore index d98ab0b..707d48d 100644 --- a/packages/.gitignore +++ b/packages/.gitignore @@ -5,4 +5,5 @@ !*/*/environment !*/*/run !*/*/compile -!*/*/test.* \ No newline at end of file +!*/*/test.* +!*/*/tooling.* diff --git a/packages/python/3.10.0/build.sh b/packages/python/3.10.0/build.sh index 00c839a..e5d415a 100755 --- a/packages/python/3.10.0/build.sh +++ b/packages/python/3.10.0/build.sh @@ -18,4 +18,4 @@ cd .. rm -rf build -bin/pip3 install numpy scipy pandas pycrypto whoosh bcrypt passlib sympy +bin/pip3 install numpy scipy pandas pycrypto whoosh bcrypt passlib sympy flake8 mypy ruff diff --git a/packages/python/3.10.0/metadata.json b/packages/python/3.10.0/metadata.json index b767f8d..4ac5ce7 100644 --- a/packages/python/3.10.0/metadata.json +++ b/packages/python/3.10.0/metadata.json @@ -1,5 +1,6 @@ { "language": "python", "version": "3.10.0", - "aliases": ["py", "py3", "python3", "python3.10"] + "aliases": ["py", "py3", "python3", "python3.10"], + "tooling": ["flake8", "mypy", "ruff"] }