Shuffle files around
This commit is contained in:
parent
fb3f4a04a4
commit
cfda844339
18 changed files with 704 additions and 738 deletions
115
src/runner/actions.ts
Normal file
115
src/runner/actions.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import { getParentDir } from '../fs/filesystem';
|
||||
import { spawnOpenSCAD } from "./openscad-runner";
|
||||
import { processMergedOutputs } from "./output-parser";
|
||||
import { AbortablePromise, turnIntoDelayableExecution } from '../utils';
|
||||
|
||||
const syntaxDelay = 300;
|
||||
|
||||
type SyntaxCheckOutput = {logText: string, markers: monaco.editor.IMarkerData[]};
|
||||
export const checkSyntax =
|
||||
turnIntoDelayableExecution(syntaxDelay, (source: string, sourcePath: string) => {
|
||||
// const timestamp = Date.now();
|
||||
|
||||
source = '$preview=true;\n' + source;
|
||||
|
||||
const job = spawnOpenSCAD({
|
||||
workingDir: getParentDir(sourcePath),
|
||||
inputs: [[sourcePath, source + '\n']],
|
||||
args: [sourcePath, "-o", "out.ast"],
|
||||
});
|
||||
|
||||
return AbortablePromise<SyntaxCheckOutput>((res, rej) => {
|
||||
(async () => {
|
||||
try {
|
||||
const result = await job;
|
||||
// console.log(result);
|
||||
res(processMergedOutputs(result.mergedOutputs, {shiftSourceLines: {
|
||||
[sourcePath]: 1,
|
||||
[sourcePath.split('/').slice(-1)[0]]: 1
|
||||
}}));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
rej(e);
|
||||
}
|
||||
})()
|
||||
return () => job.kill();
|
||||
});
|
||||
});
|
||||
|
||||
var renderDelay = 1000;
|
||||
export type RenderOutput = {stlFile: File, logText: string, markers: monaco.editor.IMarkerData[], elapsedMillis: number}
|
||||
|
||||
export type RenderArgs = {
|
||||
source: string,
|
||||
sourcePath: string,
|
||||
features?: string[],
|
||||
extraArgs?: string[],
|
||||
isPreview: boolean
|
||||
}
|
||||
export const render =
|
||||
turnIntoDelayableExecution(renderDelay, (params: RenderArgs) => {
|
||||
const args = [
|
||||
params.sourcePath,
|
||||
"-o", "/home/out.stl",
|
||||
"--export-format=binstl",
|
||||
...(params.features ?? []).map(f => `--enable=${f}`),
|
||||
...(params.extraArgs ?? [])
|
||||
]
|
||||
|
||||
const prefixLines: string[] = [];
|
||||
if (params.isPreview) {
|
||||
prefixLines.push('$preview=true;');
|
||||
}
|
||||
const source = [...prefixLines, params.source].join('\n');
|
||||
|
||||
const job = spawnOpenSCAD({
|
||||
// wasmMemory,
|
||||
workingDir: getParentDir(params.sourcePath),
|
||||
inputs: [[params.sourcePath, source]],
|
||||
args,
|
||||
outputPaths: ['/home/out.stl']
|
||||
});
|
||||
|
||||
return AbortablePromise<RenderOutput>((resolve, reject) => {
|
||||
(async () => {
|
||||
try {
|
||||
const result = await job;
|
||||
console.log(result);
|
||||
|
||||
const {logText, markers} = processMergedOutputs(result.mergedOutputs, {
|
||||
shiftSourceLines: {
|
||||
[params.sourcePath]: prefixLines.length,
|
||||
[params.sourcePath.split('/').slice(-1)[0]]: prefixLines.length
|
||||
}
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
reject(result.error);
|
||||
}
|
||||
|
||||
const [output] = result.outputs ?? [];
|
||||
if (!output) {
|
||||
reject(new Error('No output from runner!'));
|
||||
return;
|
||||
}
|
||||
const [filePath, content] = output;
|
||||
const filePathFragments = filePath.split('/');
|
||||
const fileName = filePathFragments[filePathFragments.length - 1];
|
||||
|
||||
// TODO: have the runner accept and return files.
|
||||
const blob = new Blob([content], { type: "application/octet-stream" });
|
||||
// console.log(new TextDecoder().decode(content));
|
||||
const stlFile = new File([blob], fileName);
|
||||
resolve({stlFile, logText, markers, elapsedMillis: result.elapsedMillis});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
reject(e);
|
||||
}
|
||||
})();
|
||||
|
||||
return () => job.kill()
|
||||
});
|
||||
});
|
||||
65
src/runner/openscad-runner.ts
Normal file
65
src/runner/openscad-runner.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import { MergedOutputs } from "./openscad-worker";
|
||||
import { AbortablePromise } from "../utils";
|
||||
|
||||
export function createWasmMemory({maximumMegabytes, maximumBytes}: {maximumMegabytes: number, maximumBytes: number}) {
|
||||
const pageSize = 64 * 1024; // 64KB
|
||||
if (!maximumBytes) {
|
||||
maximumBytes = maximumMegabytes * 1024 * 1024;
|
||||
}
|
||||
return new WebAssembly.Memory({
|
||||
initial: Math.floor(maximumBytes / 2 / pageSize),
|
||||
maximum: Math.floor(maximumBytes / pageSize),
|
||||
shared: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Output is {outputs: [name, content][], mergedOutputs: [{(stderr|stdout|error)?: string}], exitCode: number}
|
||||
export type OpenSCADInvocation = {
|
||||
wasmMemory?: WebAssembly.Memory,
|
||||
workingDir: string,
|
||||
inputs?: [string, string][],
|
||||
args: string[],
|
||||
outputPaths?: string[],
|
||||
}
|
||||
export type OpenSCADInvocationResults = {
|
||||
exitCode: number,
|
||||
error?: string,
|
||||
outputs?: [string, string][],
|
||||
mergedOutputs: MergedOutputs,
|
||||
elapsedMillis: number,
|
||||
}
|
||||
|
||||
export function spawnOpenSCAD({inputs, args, outputPaths}: OpenSCADInvocation): AbortablePromise<OpenSCADInvocationResults> {
|
||||
var worker: Worker | null;
|
||||
var rejection: (err: any) => void;
|
||||
|
||||
function terminate() {
|
||||
if (!worker) {
|
||||
return;
|
||||
}
|
||||
worker.terminate();
|
||||
worker = null;
|
||||
}
|
||||
|
||||
return AbortablePromise<OpenSCADInvocationResults>((resolve, reject) => {
|
||||
worker = new Worker('./openscad-worker.js');//, {type: "module"})
|
||||
// if (navigator.userAgent.indexOf(' Chrome/') < 0) {
|
||||
// worker = new Worker('./openscad-worker-firefox.js'); // {'type': 'module'}
|
||||
// } else {
|
||||
// worker = new Worker('./openscad-worker.js', {'type': 'module'});
|
||||
// }
|
||||
rejection = reject;
|
||||
worker.onmessage = (e: {data: OpenSCADInvocationResults}) => {
|
||||
resolve(e.data);
|
||||
terminate();
|
||||
}
|
||||
worker.postMessage({inputs, args, outputPaths})
|
||||
|
||||
return () => {
|
||||
// rejection({error: 'Terminated'});
|
||||
terminate();
|
||||
};
|
||||
});
|
||||
}
|
||||
101
src/runner/openscad-worker.ts
Normal file
101
src/runner/openscad-worker.ts
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
||||
|
||||
import OpenSCAD from "../wasm/openscad.js";
|
||||
|
||||
import { createEditorFS, getBrowserFSLibrariesMounts, getParentDir, symlinkLibraries } from "../fs/filesystem";
|
||||
import { OpenSCADInvocation, OpenSCADInvocationResults } from "./openscad-runner";
|
||||
import { zipArchives } from "../fs/zip-archives";
|
||||
declare var BrowserFS: BrowserFSInterface
|
||||
|
||||
importScripts("browserfs.min.js");
|
||||
// importScripts("https://cdnjs.cloudflare.com/ajax/libs/BrowserFS/2.0.0/browserfs.min.js");
|
||||
|
||||
const allArchiveNames = Object.keys(zipArchives)
|
||||
const allZipMountsPromise = getBrowserFSLibrariesMounts(allArchiveNames);
|
||||
|
||||
export type MergedOutputs = {stdout?: string, stderr?: string, error?: string}[];
|
||||
|
||||
addEventListener('message', async (e) => {
|
||||
|
||||
const { inputs, args, outputPaths, wasmMemory, workingDir } = e.data as OpenSCADInvocation;
|
||||
|
||||
const mergedOutputs: MergedOutputs = [];
|
||||
try {
|
||||
const instance = await OpenSCAD({
|
||||
wasmMemory,
|
||||
buffer: wasmMemory && wasmMemory.buffer,
|
||||
noInitialRun: true,
|
||||
'print': (text: string) => {
|
||||
console.debug('stdout: ' + text);
|
||||
mergedOutputs.push({ stdout: text })
|
||||
},
|
||||
'printErr': (text: string) => {
|
||||
console.debug('stderr: ' + text);
|
||||
mergedOutputs.push({ stderr: text })
|
||||
},
|
||||
ENV: {
|
||||
OPENSCADPATH: '/home'
|
||||
}
|
||||
});
|
||||
|
||||
const fs = await createEditorFS('/home');
|
||||
|
||||
// https://github.com/emscripten-core/emscripten/issues/10061
|
||||
const BFS = new BrowserFS.EmscriptenFS(
|
||||
instance.FS,
|
||||
instance.PATH ?? {
|
||||
join2: (a: string, b: string) => `${a}/${b}`,
|
||||
join: (...args: string[]) => args.join('/'),
|
||||
}, instance.ERRNO_CODES ?? {});
|
||||
instance.FS.mount(BFS, {root: '/home'}, '/home');
|
||||
|
||||
// await symlinkLibraries(allArchiveNames, instance.FS, '/home/libraries', '/home');
|
||||
|
||||
instance.FS.chdir('/home');
|
||||
|
||||
if (inputs) {
|
||||
for (const [path, content] of inputs) {
|
||||
try {
|
||||
// const parent = getParentDir(path);
|
||||
// instance.FS.writeFile(path, content);
|
||||
fs.writeFile(path, content);
|
||||
} catch (e) {
|
||||
console.error(`Error while trying to write ${path}`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Calling main ', args)
|
||||
const start = performance.now();
|
||||
const exitCode = instance.callMain(args);
|
||||
const end = performance.now();
|
||||
|
||||
const outputs: [string, string][] = [];
|
||||
for (const path of (outputPaths ?? [])) {
|
||||
try {
|
||||
const content = instance.FS.readFile(path);
|
||||
outputs.push([path, content]);
|
||||
} catch (e) {
|
||||
console.trace(`Failed to read output file ${path}`, e);
|
||||
}
|
||||
}
|
||||
const result: OpenSCADInvocationResults = {
|
||||
outputs,
|
||||
mergedOutputs,
|
||||
exitCode,
|
||||
elapsedMillis: end - start
|
||||
}
|
||||
|
||||
console.debug(result);
|
||||
|
||||
postMessage(result);
|
||||
} catch (e) {
|
||||
console.trace(e);//, e instanceof Error ? e.stack : '');
|
||||
const error = `${e}`;
|
||||
mergedOutputs.push({ error });
|
||||
postMessage({
|
||||
error,
|
||||
mergedOutputs,
|
||||
} as OpenSCADInvocationResults);
|
||||
}
|
||||
});
|
||||
88
src/runner/output-parser.ts
Normal file
88
src/runner/output-parser.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import { MergedOutputs } from "./openscad-worker";
|
||||
|
||||
const ignoredLogs = new Set([
|
||||
'Could not initialize localization.'
|
||||
]);
|
||||
|
||||
type MergedOutputsOptions = {
|
||||
shiftSourceLines: {[path: string]: number}
|
||||
}
|
||||
export const processMergedOutputs = (outputs: MergedOutputs, opts: MergedOutputsOptions) => ({
|
||||
logText: joinMergedOutputs(outputs, opts),
|
||||
markers: parseMergedOutputs(outputs, opts)
|
||||
});
|
||||
|
||||
export function joinMergedOutputs(mergedOutputs: MergedOutputs, opts: MergedOutputsOptions) {
|
||||
let allLines = [];
|
||||
for (const {stderr, stdout, error} of mergedOutputs){
|
||||
const line = stderr ?? stdout ?? `EXCEPTION: ${error}`;
|
||||
if (ignoredLogs.has(line)) {
|
||||
continue;
|
||||
}
|
||||
allLines.push(line);
|
||||
}
|
||||
|
||||
return allLines.join("\n");
|
||||
}
|
||||
|
||||
export function parseMergedOutputs(mergedOutputs: MergedOutputs, opts: MergedOutputsOptions): monaco.editor.IMarkerData[] {
|
||||
let unmatchedLines = [];
|
||||
|
||||
const markers = [];
|
||||
let warningCount = 0, errorCount = 0;
|
||||
const addError = (error: string, file: string, line: number) => {
|
||||
markers.push({
|
||||
startLineNumber: Number(line),
|
||||
startColumn: 1,
|
||||
endLineNumber: Number(line),
|
||||
endColumn: 1000,
|
||||
message: error,
|
||||
severity: monaco.MarkerSeverity.Error
|
||||
})
|
||||
}
|
||||
const getLine = (path: string, lineStr: string) => {
|
||||
const shift = opts.shiftSourceLines[path] ?? 0;
|
||||
return Number(lineStr) - shift;
|
||||
}
|
||||
for (const {stderr, stdout, error} of mergedOutputs){
|
||||
if (stderr) {
|
||||
if (stderr.startsWith('ERROR:')) errorCount++;
|
||||
if (stderr.startsWith('WARNING:')) warningCount++;
|
||||
|
||||
let m = /^ERROR: Parser error in file "([^"]+)", line (\d+): (.*)$/.exec(stderr)
|
||||
if (m) {
|
||||
const [_, file, line, error] = m
|
||||
addError(error, file, getLine(file, line));
|
||||
continue;
|
||||
}
|
||||
|
||||
m = /^ERROR: Parser error: (.*?) in file ([^",]+), line (\d+)$/.exec(stderr)
|
||||
if (m) {
|
||||
const [_, error, file, line] = m
|
||||
addError(error, file, getLine(file, line));
|
||||
continue;
|
||||
}
|
||||
|
||||
m = /^WARNING: (.*?),? in file ([^,]+), line (\d+)\.?/.exec(stderr);
|
||||
if (m) {
|
||||
const [_, warning, file, line] = m
|
||||
markers.push({
|
||||
startLineNumber: getLine(file, line),
|
||||
startColumn: 1,
|
||||
endLineNumber: getLine(file, line),
|
||||
endColumn: 1000,
|
||||
message: warning,
|
||||
severity: monaco.MarkerSeverity.Warning
|
||||
})
|
||||
continue;
|
||||
}
|
||||
}
|
||||
unmatchedLines.push(stderr ?? stdout ?? `EXCEPTION: ${error}`);
|
||||
}
|
||||
if (errorCount || warningCount) unmatchedLines = [`${errorCount} errors, ${warningCount} warnings!`, '', ...unmatchedLines];
|
||||
|
||||
// logsElement.innerText = unmatchedLines.join("\n")
|
||||
|
||||
return markers;
|
||||
}
|
||||
Loading…
Reference in a new issue