Browse Source

Use promises, move to express, combine lots of endpoints into client-options.js

dev/git-series/gccdum
Matt Godbolt 8 years ago
parent
commit
9223f04295
  1. 385
      app.js
  2. 3
      package.json
  3. 4
      static/compiler.js
  4. 48
      static/gcc.js

385
app.js

@ -27,13 +27,14 @@
var nopt = require('nopt'),
os = require('os'),
props = require('./lib/properties'),
connect = require('connect'),
express = require('express'),
child_process = require('child_process'),
temp = require('temp'),
path = require('path'),
async = require('async'),
LRU = require('lru-cache'),
fs = require('fs-extra');
fs = require('fs-extra'),
Promise = require('promise');
var opts = nopt({
'env': [String],
@ -50,7 +51,6 @@ var rootDir = opts.rootDir || './etc';
props.initialize(rootDir + '/config', propHierarchy);
var port = props.get('gcc-explorer', 'port', 10240);
var compilers = [];
var compilersByExe = {};
var compilerExecutables;
@ -103,129 +103,135 @@ function cacheStats() {
console.log("Cache stats: " + cacheHits + " hits, " + cacheMisses + " misses");
}
function compile(req, res) {
var source = req.body.source;
var compiler = req.body.compiler;
if (getCompilerExecutables().indexOf(compiler) < 0) {
return res.end(JSON.stringify({code: -1, stderr: "bad compiler " + compiler}));
}
var compilerInfo = compilersByExe[compiler];
if (!compilerInfo) {
return res.end(JSON.stringify({code: -1, stderr: "bad compiler " + compiler}));
}
var options = req.body.options.split(' ').filter(function (x) {
return x !== "";
function compileHandler(compilers) {
var compilersByExe = {};
compilers.forEach(function (compiler) {
compilersByExe[compiler.exe] = compiler; // todo: kill global
});
var filters = req.body.filters;
var optionsErr = checkOptions(options);
if (optionsErr) {
return res.end(JSON.stringify({code: -1, stderr: optionsErr}));
}
var sourceErr = checkSource(source);
if (sourceErr) {
return res.end(JSON.stringify({code: -1, stderr: sourceErr}));
}
var key = compiler + " | " + source + " | " + options + " | " + filters.intel;
var cached = cache.get(key);
if (cached) {
cacheHits++;
cacheStats();
res.end(cached);
return;
}
cacheMisses++;
return function compile(req, res) {
var source = req.body.source;
var compiler = req.body.compiler;
if (getCompilerExecutables().indexOf(compiler) < 0) {
return res.end(JSON.stringify({code: -1, stderr: "bad compiler " + compiler}));
}
var compilerInfo = compilersByExe[compiler];
if (!compilerInfo) {
return res.end(JSON.stringify({code: -1, stderr: "bad compiler " + compiler}));
}
var options = req.body.options.split(' ').filter(function (x) {
return x !== "";
});
var filters = req.body.filters;
var optionsErr = checkOptions(options);
if (optionsErr) {
return res.end(JSON.stringify({code: -1, stderr: optionsErr}));
}
var sourceErr = checkSource(source);
if (sourceErr) {
return res.end(JSON.stringify({code: -1, stderr: sourceErr}));
}
var compileTask = function (taskFinished) {
temp.mkdir('gcc-explorer-compiler', function (err, dirPath) {
if (err) {
return res.end(JSON.stringify({code: -1, stderr: "Unable to open temp file: " + err}));
}
var postProcess = props.get("gcc-explorer", "postProcess");
var inputFilename = path.join(dirPath, props.get("gcc-explorer", "compileFilename"));
var outputFilename = path.join(dirPath, 'output.S');
if (compilerInfo.supportedOpts['-masm']) {
var syntax = '-masm=att'; // default at&t
if (filters.intel == "true") syntax = '-masm=intel';
options = options.concat([syntax]);
}
var compileToAsm = props.get("gcc-explorer", "compileToAsm", "-S").split(" ");
options = options.concat(['-g', '-o', outputFilename]).concat(compileToAsm).concat([inputFilename]);
var file = fs.openSync(inputFilename, "w");
fs.writeSync(file, source);
fs.closeSync(file);
var okToCache = true;
var compilerWrapper = props.get("gcc-explorer", "compiler-wrapper");
if (compilerWrapper) {
options = [compiler].concat(options);
compiler = compilerWrapper;
}
var child = child_process.spawn(
compiler,
options,
{detached: true}
);
var stdout = "";
var stderr = "";
var timeout = setTimeout(function () {
okToCache = false;
child.kill();
stderr += "\nKilled - processing time exceeded";
}, props.get("gcc-explorer", "compileTimeoutMs", 100));
child.stdout.on('data', function (data) {
stdout += data;
});
child.stderr.on('data', function (data) {
stderr += data;
});
child.on('exit', function (code) {
clearTimeout(timeout);
var maxSize = props.get("gcc-explorer", "max-asm-size", 8 * 1024 * 1024);
var key = compiler + " | " + source + " | " + options + " | " + filters.intel;
var cached = cache.get(key);
if (cached) {
cacheHits++;
cacheStats();
res.end(cached);
return;
}
cacheMisses++;
function complete(data) {
var result = JSON.stringify({
stdout: stdout,
stderr: stderr,
asm: data,
code: code
});
if (okToCache) {
cache.set(key, result);
cacheStats();
}
res.end(result);
fs.remove(dirPath);
taskFinished();
var compileTask = function (taskFinished) {
temp.mkdir('gcc-explorer-compiler', function (err, dirPath) {
if (err) {
return res.end(JSON.stringify({code: -1, stderr: "Unable to open temp file: " + err}));
}
if (code !== 0) {
return complete("<Compilation failed>");
var postProcess = props.get("gcc-explorer", "postProcess");
var inputFilename = path.join(dirPath, props.get("gcc-explorer", "compileFilename"));
var outputFilename = path.join(dirPath, 'output.S');
if (compilerInfo.supportedOpts['-masm']) {
var syntax = '-masm=att'; // default at&t
if (filters.intel == "true") syntax = '-masm=intel';
options = options.concat([syntax]);
}
try {
var size = fs.statSync(outputFilename).size;
if (size >= maxSize) {
return complete("<No output: generated assembly was too large (" + size + " > " + maxSize + " bytes)>");
}
} catch (e) {
return complete("<No output file>");
var compileToAsm = props.get("gcc-explorer", "compileToAsm", "-S").split(" ");
options = options.concat(['-g', '-o', outputFilename]).concat(compileToAsm).concat([inputFilename]);
var file = fs.openSync(inputFilename, "w");
fs.writeSync(file, source);
fs.closeSync(file);
var okToCache = true;
var compilerWrapper = props.get("gcc-explorer", "compiler-wrapper");
if (compilerWrapper) {
options = [compiler].concat(options);
compiler = compilerWrapper;
}
var child = child_process.spawn(
compiler,
options,
{detached: true}
);
var stdout = "";
var stderr = "";
var timeout = setTimeout(function () {
okToCache = false;
child.kill();
stderr += "\nKilled - processing time exceeded";
}, props.get("gcc-explorer", "compileTimeoutMs", 100));
child.stdout.on('data', function (data) {
stdout += data;
});
child.stderr.on('data', function (data) {
stderr += data;
});
child.on('exit', function (code) {
clearTimeout(timeout);
var maxSize = props.get("gcc-explorer", "max-asm-size", 8 * 1024 * 1024);
function complete(data) {
var result = JSON.stringify({
stdout: stdout,
stderr: stderr,
asm: data,
code: code
});
if (okToCache) {
cache.set(key, result);
cacheStats();
}
res.end(result);
fs.remove(dirPath);
taskFinished();
}
child_process.exec('cat "' + outputFilename + '" | ' + postProcess,
{maxBuffer: maxSize},
function (err, filt_stdout, filt_stderr) {
var data = filt_stdout;
if (err) {
if ("")
data = '<No output: ' + err + '>';
if (code !== 0) {
return complete("<Compilation failed>");
}
try {
var size = fs.statSync(outputFilename).size;
if (size >= maxSize) {
return complete("<No output: generated assembly was too large (" + size + " > " + maxSize + " bytes)>");
}
complete(data);
});
} catch (e) {
return complete("<No output file>");
}
child_process.exec('cat "' + outputFilename + '" | ' + postProcess,
{maxBuffer: maxSize},
function (err, filt_stdout, filt_stderr) {
var data = filt_stdout;
if (err) {
if ("")
data = '<No output: ' + err + '>';
}
complete(data);
});
});
child.stdin.end();
});
child.stdin.end();
});
};
};
compileQueue.push({task: compileTask});
compileQueue.push({task: compileTask});
};
}
function loadSources() {
@ -256,20 +262,6 @@ function compareOn(key) {
};
}
function getSources(req, res) {
var sources = fileSources.map(function (source) {
return {name: source.name, urlpart: source.urlpart};
});
res.end(JSON.stringify(sources.sort(compareOn("name"))));
}
function getInfo(req, res) {
res.end(JSON.stringify({
language: props.get("gcc-explorer", "language"),
options: props.get("gcc-explorer", "options"),
}));
}
function getSource(req, res, next) {
var bits = req.url.split("/");
var handler = sourceToHandler[bits[1]];
@ -323,11 +315,11 @@ function getCompilerExecutables() {
return exes;
}
function getCompilers(req, res) {
res.end(JSON.stringify(compilers));
}
function getClientOptions(req, res) {
function clientOptionsHandler(compilers, fileSources) {
var sources = fileSources.map(function (source) {
return {name: source.name, urlpart: source.urlpart};
});
sources = sources.sort(compareOn("name"));
var options = {
google_analytics_account: props.get('gcc-explorer', 'clientGoogleAnalyticsAccount', 'UA-55180-6'),
google_analytics_enabled: props.get('gcc-explorer', 'clientGoogleAnalyticsEnabled', true),
@ -335,73 +327,72 @@ function getClientOptions(req, res) {
github_ribbon_enabled: props.get('gcc-explorer', 'clientGitHubRibbonEnabled', true),
urlshortener: props.get('gcc-explorer', 'clientURLShortener', 'google'),
defaultCompiler: props.get('gcc-explorer', 'defaultCompiler', ''),
defaultSource: props.get('gcc-explorer', 'defaultSource', '')
defaultSource: props.get('gcc-explorer', 'defaultSource', ''),
compilers: compilers,
language: props.get("gcc-explorer", "language"),
compileOptions: props.get("gcc-explorer", "options"),
sources: sources
};
var text = "var OPTIONS = " + JSON.stringify(options) + ";";
return function getClientOptions(req, res) {
res.set('Content-Type', 'application/javascript');
res.end(text);
};
res.end("var OPTIONS = " + JSON.stringify(options) + ";");
}
function findCompilers() {
async.map(getCompilerExecutables(),
function (compiler, callback) {
fs.stat(compiler, function (err, result) {
if (err) return callback(null, null);
child_process.exec(compiler + ' --version', function (err, output) {
if (err) return callback(null, null);
var version = output.split('\n')[0];
child_process.exec(compiler + ' --target-help', function (err, output) {
var options = {};
if (!err) {
var splitness = /--?[-a-zA-Z]+( ?[-a-zA-Z]+)/;
output.split('\n').forEach(function (line) {
var match = line.match(splitness);
if (!match) return;
options[match[0]] = true;
});
}
callback(null, {exe: compiler, version: version, supportedOpts: options});
function getCompilerInfo(compiler) {
return new Promise(function (resolve, reject) {
child_process.exec(compiler + ' --version', function (err, output) {
if (err) return resolve(null);
var version = output.split('\n')[0];
child_process.exec(compiler + ' --target-help', function (err, output) {
var options = {};
if (!err) {
var splitness = /--?[-a-zA-Z]+( ?[-a-zA-Z]+)/;
output.split('\n').forEach(function (line) {
var match = line.match(splitness);
if (!match) return;
options[match[0]] = true;
});
});
});
},
function (err, all) {
all = all.filter(function (x) {
return x !== null;
});
compilers = all.sort(function (x, y) {
return x.version < y.version ? -1 : x.version > y.version ? 1 : 0;
});
compilersByExe = {};
compilers.forEach(function (compiler) {
compilersByExe[compiler.exe] = compiler;
}
resolve({exe: compiler, version: version, supportedOpts: options});
});
}
);
});
});
}
findCompilers();
// WebServer.
var webServer = connect(),
sFavicon = require('serve-favicon'),
sStatic = require('serve-static'),
bodyParser = require('body-parser'),
logger = require('morgan');
webServer
.use(logger('combined'))
.use(sFavicon('static/favicon.ico'))
.use(sStatic('static'))
.use(bodyParser.urlencoded({extended:true}))
.use('/client-options.js', getClientOptions)
.use('/info', getInfo)
.use('/sources', getSources)
.use('/source', getSource)
.use('/compilers', getCompilers)
.use('/compile', compile);
function findCompilers() {
var compilers = getCompilerExecutables().map(getCompilerInfo);
return Promise.all(compilers).then(function (compilers) {
compilers = compilers.filter(function (x) {
return x !== null
});
compilers = compilers.sort(function (x, y) {
return x.version < y.version ? -1 : x.version > y.version ? 1 : 0;
});
return compilers;
});
}
// GO!
console.log("=======================================");
console.log("Listening on http://" + os.hostname() + ":" + port + "/");
console.log("=======================================");
webServer.listen(port);
findCompilers().then(function (compilers) {
var webServer = express(),
sFavicon = require('serve-favicon'),
sStatic = require('serve-static'),
bodyParser = require('body-parser'),
logger = require('morgan');
webServer
.use(logger('combined'))
.use(sFavicon('static/favicon.ico'))
.use(sStatic('static'))
.use(bodyParser.urlencoded({extended: true}))
.get('/client-options.js', clientOptionsHandler(compilers, fileSources))
.use('/source', getSource)
.post('/compile', compileHandler(compilers));
// GO!
console.log("=======================================");
console.log("Listening on http://" + os.hostname() + ":" + port + "/");
console.log("=======================================");
webServer.listen(port);
});

3
package.json

@ -18,10 +18,11 @@
"dependencies": {
"async": "0.9.x",
"body-parser": "1.10.x",
"connect": "3.3.x",
"express": "4.11.x",
"fs-extra": "0.8.x",
"lru-cache": "2.5.x",
"morgan": "1.5.x",
"promise": "6.1.x",
"nopt": "3.0.x",
"serve-favicon": "2.2.x",
"serve-static": "1.8.x",

4
static/compiler.js

@ -267,7 +267,7 @@ function Compiler(domRoot, origFilters, windowLocalPrefix, onChangeCallback, cmM
domRoot.find('.filter button.btn[value="intel"]').toggleClass("disabled", !compiler.supportedOpts["-masm"]);
}
function setCompilers(compilers) {
function setCompilers(compilers, defaultCompiler) {
domRoot.find('.compiler option').remove();
compilersByExe = {};
$.each(compilers, function (index, arg) {
@ -275,7 +275,7 @@ function Compiler(domRoot, origFilters, windowLocalPrefix, onChangeCallback, cmM
domRoot.find('.compiler').append($('<option value="' + arg.exe + '">' + arg.version + '</option>'));
});
var defaultCompiler = getSetting('compiler');
if (!defaultCompiler) defaultCompiler = OPTIONS.defaultCompiler;
if (!defaultCompiler) defaultCompiler = defaultCompiler;
if (defaultCompiler) {
domRoot.find('.compiler').val(defaultCompiler);
}

48
static/gcc.js

@ -191,28 +191,19 @@ function deserialiseState(state) {
return true;
}
function initialise() {
function initialise(options) {
var defaultFilters = JSON.stringify(getAsmFilters());
var actualFilters = $.parseJSON(window.localStorage.filter || defaultFilters);
setFilterUi(actualFilters);
// Synchronous request here to make the whole race condition problem of
// getting language and compiler options after we've set local overrides.
var languageType = "text/x-c++src";
$.ajax({
url: "/info",
dataType: "json",
async: false,
success: function (results) {
$(".language-name").text(results.language);
if (results.language == "Rust") {
languageType = "text/x-rustsrc";
} else if (results.language == "D") {
languageType = "text/x-d";
}
$(".compiler_options").val(results.options);
}
}); // must be ahead of the compiler creation. This is all terrible.
$(".language-name").text(options.language);
if (options.language == "Rust") {
languageType = "text/x-rustsrc";
} else if (options.language == "D") {
languageType = "text/x-d";
}
$(".compiler_options").val(options.compileoptions);
var compiler = new Compiler($('body'), actualFilters, "a", function () {
hidePermalink();
@ -224,24 +215,19 @@ function initialise() {
return false;
});
$('.files .source').change(onSourceChange);
$.getJSON("/compilers", function (results) {
compilersByExe = {};
$.each(results, function (index, arg) {
compilersByExe[arg.exe] = arg;
});
compiler.setCompilers(results);
});
$.getJSON("/sources", function (results) {
compiler.setCompilers(options.compilers, options.defaultCompiler);
function setSources(sources, defaultSource) {
$('.source option').remove();
var source = window.localStorage.source || OPTIONS.defaultSource;
$.each(results, function (index, arg) {
$.each(sources, function (index, arg) {
$('.files .source').append($('<option value="' + arg.urlpart + '">' + arg.name + '</option>'));
if (source == arg.urlpart) {
if (defaultSource == arg.urlpart) {
$('.files .source').val(arg.urlpart);
}
});
onSourceChange();
});
}
setSources(options.sources, window.localStorage.source || options.defaultSource);
$('.files .load').click(function () {
loadFile();
return false;
@ -304,4 +290,6 @@ function setFilterUi(asmFilters) {
});
}
$(initialise);
$(function () {
initialise(OPTIONS)
});

Loading…
Cancel
Save