Skip to content

Commit 27044d9

Browse files
committed
Bring compiling/running code parts from tracers repository
1 parent b539553 commit 27044d9

File tree

14 files changed

+141
-36
lines changed

14 files changed

+141
-36
lines changed

src/backend/common/config.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1-
export {};
1+
const memoryLimit = 256; // in megabytes
2+
const timeLimit = 5000; // in milliseconds
3+
4+
export {
5+
memoryLimit,
6+
timeLimit,
7+
};

src/backend/common/util.js

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import fs from 'fs-extra';
55
import removeMarkdown from 'remove-markdown';
66

77
const execute = (command, cwd, { stdout = process.stdout, stderr = process.stderr } = {}) => new Promise((resolve, reject) => {
8-
if (!cwd) return reject(new Error('CWD Not Specified'));
98
const child = child_process.exec(command, { cwd }, (error, stdout, stderr) => {
109
if (error) return reject(error.code ? new Error(stderr) : error);
1110
resolve(stdout);

src/backend/controllers/auth.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ router.route('/response')
2929
router.route('/destroy')
3030
.get(destroy);
3131

32-
export default router;
32+
export default router;

src/backend/controllers/tracers.js

+54-30
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,78 @@
11
import express from 'express';
22
import fs from 'fs-extra';
3+
import Promise from 'bluebird';
34
import uuid from 'uuid';
45
import path from 'path';
56
import { GitHubApi } from '/apis';
67
import { execute } from '/common/util';
78
import webhook from '/common/webhook';
9+
import { ImageBuilder, WorkerBuilder } from '/tracers';
10+
import { memoryLimit, timeLimit } from '/common/config';
811

912
const router = express.Router();
1013

11-
const repoPath = path.resolve(__dirname, '..', 'public', 'tracers');
12-
const getCodesPath = (...args) => path.resolve(__dirname, '..', 'public', 'codes', ...args);
13-
14-
const getJsWorker = (req, res, next) => {
15-
res.sendFile(path.resolve(repoPath, 'src', 'languages', 'js', 'tracers', 'build', 'tracers.js'));
16-
};
17-
1814
const trace = lang => (req, res, next) => {
1915
const { code } = req.body;
20-
const tempPath = getCodesPath(uuid.v4());
16+
const tempPath = path.resolve(__dirname, '..', 'public', 'codes', uuid.v4());
17+
const tracesPath = path.resolve(tempPath, 'traces.json');
2118
fs.outputFile(path.resolve(tempPath, `Main.${lang}`), code)
22-
.then(() => execute(`./bin/compile ${lang} ${tempPath}`, repoPath, { stdout: null, stderr: null }))
23-
.then(() => execute(`./bin/run ${lang} ${tempPath}`, repoPath, { stdout: null, stderr: null }))
24-
.then(() => res.sendFile(path.resolve(tempPath, 'traces.json')))
19+
.then(() => {
20+
const builder = builderMap[lang];
21+
const containerName = uuid.v4();
22+
let killed = false;
23+
const timer = setTimeout(() => {
24+
execute(`docker kill ${containerName}`).then(() => {
25+
killed = true;
26+
});
27+
}, timeLimit);
28+
return execute([
29+
'docker run --rm',
30+
`--name=${containerName}`,
31+
'-w=/usr/tracer',
32+
`-v=${tempPath}:/usr/tracer:rw`,
33+
`-m=${memoryLimit}m`,
34+
builder.imageName,
35+
].join(' ')).catch(error => {
36+
if (killed) throw new Error('Time Limit Exceeded');
37+
throw error;
38+
}).finally(() => clearTimeout(timer));
39+
})
40+
.then(() => fs.pathExists(tracesPath))
41+
.then(exists => {
42+
if (!exists) throw new Error('Traces Not Found');
43+
res.sendFile(tracesPath);
44+
})
2545
.catch(next)
2646
.finally(() => fs.remove(tempPath));
2747
};
2848

29-
const buildRelease = release => (
30-
fs.pathExistsSync(repoPath) ?
31-
execute(`git fetch && ! git diff-index --quiet ${release.target_commitish} -- ':!package-lock.json'`, repoPath) :
32-
execute(`git clone https://github.com/algorithm-visualizer/tracers.git ${repoPath}`, __dirname)
33-
).then(() => execute(`git reset --hard ${release.target_commitish} && npm install && npm run build && ./bin/build`, repoPath));
49+
const builderMap = {
50+
js: new WorkerBuilder(),
51+
cpp: new ImageBuilder('cpp'),
52+
java: new ImageBuilder('java'),
53+
};
3454

35-
GitHubApi.getLatestRelease('algorithm-visualizer', 'tracers').then(buildRelease).catch(console.error);
55+
Promise.map(Object.keys(builderMap), lang => {
56+
const builder = builderMap[lang];
57+
return GitHubApi.getLatestRelease('algorithm-visualizer', `tracers.${lang}`).then(builder.build);
58+
}).catch(console.error);
3659

37-
webhook.on('tracers', (event, data) => {
38-
switch (event) {
39-
case 'release':
40-
buildRelease(data.release).catch(console.error);
41-
break;
60+
webhook.on('release', (repo, data) => {
61+
const result = /^tracers\.(\w+)$/.exec(repo);
62+
if (result) {
63+
const [, lang] = result;
64+
const builder = builderMap[lang];
65+
builder.build(data.release).catch(console.error);
4266
}
4367
});
4468

45-
router.route('/js')
46-
.get(getJsWorker);
47-
48-
router.route('/java')
49-
.post(trace('java'));
50-
51-
router.route('/cpp')
52-
.post(trace('cpp'));
69+
Object.keys(builderMap).forEach(lang => {
70+
const builder = builderMap[lang];
71+
if (builder instanceof ImageBuilder) {
72+
router.post(`/${lang}`, trace(lang));
73+
} else if (builder instanceof WorkerBuilder) {
74+
router.get(`/${lang}`, (req, res) => res.sendFile(builder.workerPath));
75+
}
76+
});
5377

5478
export default router;

src/backend/tracers/ImageBuilder.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import path from 'path';
2+
import { execute } from '/common/util';
3+
4+
class ImageBuilder {
5+
constructor(lang) {
6+
this.lang = lang;
7+
this.directory = path.resolve(__dirname, lang);
8+
this.imageName = `tracer-${this.lang}`;
9+
10+
this.build = this.build.bind(this);
11+
}
12+
13+
build(release) {
14+
const { tag_name } = release;
15+
return execute(`docker build -t ${this.imageName} . --build-arg tag_name=${tag_name}`, this.directory);
16+
}
17+
}
18+
19+
export default ImageBuilder;

src/backend/tracers/WorkerBuilder.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import path from 'path';
2+
3+
class WorkerBuilder {
4+
constructor() {
5+
this.workerPath = path.resolve(__dirname, 'js', 'worker.js');
6+
7+
this.build = this.build.bind(this);
8+
}
9+
10+
build(release) {
11+
}
12+
}
13+
14+
export default WorkerBuilder;

src/backend/tracers/cpp/Dockerfile

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
FROM rikorose/gcc-cmake
2+
3+
ARG tag_name
4+
5+
RUN curl --create-dirs -o /usr/local/include/nlohmann/json.hpp -L "https://github.com/nlohmann/json/releases/download/v3.1.2/json.hpp" \
6+
&& curl --create-dirs -o /usr/tmp/algorithm-visualizer.tar.gz -L "https://github.com/algorithm-visualizer/tracers.cpp/archive/${tag_name}.tar.gz" \
7+
&& cd /usr/tmp \
8+
&& mkdir algorithm-visualizer \
9+
&& tar xvzf algorithm-visualizer.tar.gz -C algorithm-visualizer --strip-components=1 \
10+
&& cd /usr/tmp/algorithm-visualizer \
11+
&& mkdir build \
12+
&& cd build \
13+
&& cmake .. \
14+
&& make install
15+
16+
CMD g++ Main.cpp -o Main -O2 -std=c++11 \
17+
&& ./Main

src/backend/tracers/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as ImageBuilder } from './ImageBuilder';
2+
export { default as WorkerBuilder } from './WorkerBuilder';

src/backend/tracers/java/Dockerfile

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FROM openjdk:8
2+
3+
ARG tag_name
4+
5+
RUN curl --create-dirs -o /usr/local/lib/algorithm-visualizer.jar -L "https://github.com/algorithm-visualizer/tracers.java/releases/download/${tag_name}/algorithm-visualizer.jar"
6+
7+
CMD javac -cp /usr/local/lib/algorithm-visualizer.jar Main.java \
8+
&& java -cp /usr/local/lib/algorithm-visualizer.jar:. Main

src/backend/tracers/js/worker.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
importScripts('https://unpkg.com/algorithm-visualizer/dist/algorithm-visualizer.js');
2+
importScripts('https://unpkg.com/babel-standalone@6/babel.min.js');
3+
4+
const sandbox = code => {
5+
const require = name => ({ 'algorithm-visualizer': AlgorithmVisualizer }[name]); // fake require
6+
eval(code);
7+
};
8+
9+
onmessage = e => { // TODO: stop after the first delay() on the initial run
10+
const lines = e.data.split('\n').map((line, i) => line.replace(/(.+\. *delay *)(\( *\))/g, `$1(${i})`));
11+
const { code } = Babel.transform(lines.join('\n'), { presets: ['es2015'] });
12+
sandbox(code);
13+
postMessage(AlgorithmVisualizer.Tracer.traces);
14+
};

src/frontend/components/BaseComponent/index.jsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ class BaseComponent extends React.Component {
88
}
99

1010
handleError(error) {
11-
console.error(error);
1211
if (error.response) {
1312
const { data, statusText } = error.response;
1413
const message = data ? typeof data === 'string' ? data : JSON.stringify(data) : statusText;
14+
console.error(message);
1515
this.props.showErrorToast(message);
1616
} else {
17+
console.error(error.message);
1718
this.props.showErrorToast(error.message);
1819
}
1920
}

src/frontend/components/Player/index.jsx

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ class Player extends BaseComponent {
9090
this.handleError(error);
9191
});
9292
} else {
93+
this.setState({ building: false });
9394
this.handleError(new Error('Language Not Supported'));
9495
}
9596
}

src/frontend/files/skeletons/code.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#include "LogTracer.h"
1+
#include "algorithm-visualizer/LogTracer.h"
22

33
int main() {
44
LogTracer logTracer = LogTracer("Scratch Paper");

src/frontend/files/skeletons/code.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import org.algorithm_visualizer.tracers.*;
1+
import org.algorithm_visualizer.*;
22

33
class Main {
44
public static void main(String[] args) {

0 commit comments

Comments
 (0)