Created
December 31, 2015 16:19
-
-
Save eljefedelrodeodeljefe/4df0f651aedc90329192 to your computer and use it in GitHub Desktop.
Proposal for a node-native build toolchain 'platform_tools'
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
const spawn = require('child_process').spawn; | |
const fs = require('fs'); | |
const util = require('util'); | |
const debug = util.debuglog('platform_tools'); | |
exports.configure = function configure(cb) { | |
if (!cb) { | |
return | |
} | |
return cb(null) | |
} | |
let possibleCompilers = [ | |
{name: 'gcc', path: '/usr/bin/gcc' }, | |
{name: 'g++', path: '/usr/bin/g++' }, | |
{name: 'clang', path: '/usr/bin/clang' }, | |
{name: 'vsc++', path: process.env['ProgramFiles'] + '\\Microsoft^ Visual^ Studio^ 14.0\\VC\\bin\\cl.exe'} | |
] | |
/** | |
* Return a list of compilers, just for say: command line use, on unix the `win32` specific ENV variable gets replaced /w the win32 notation for the path, just for the curious dev | |
* @return {Array} | |
*/ | |
exports.possibleCompilers = function returnPossibleCompilers () { | |
let compilerList = possibleCompilers | |
if (!process.env['ProgramFiles']) { | |
// this is just for pretty output on unix | |
compilerList.forEach(function (el) { | |
if (el.name === 'vsc++') { | |
el.path = '%programfiles(x86)%' + '\\Microsoft^ Visual^ Studio^ 14.0\\VC\\bin\\cl.exe' | |
} | |
}) | |
} | |
return compilerList | |
} | |
function lookUpCompilers(cb) { | |
// normalizing compiler access here | |
let len = possibleCompilers.length | |
let availableCompilers = [] | |
possibleCompilers.forEach(function lookup(el, index) { | |
fs.stat(el.path, function (err, stats) { | |
if (err) debug(err) | |
if (stats && stats.isFile()) { | |
availableCompilers.push(el) | |
} | |
if (index + 1 >= len) { | |
return cb(null, availableCompilers) | |
} | |
}) | |
}) | |
} | |
function resolveCompilerCmd (compiler) { | |
let cmd = '' | |
possibleCompilers.forEach(function (el) { | |
if (el.name === compiler) { | |
cmd = el.path | |
} | |
}) | |
return cmd | |
} | |
/** | |
* Escape a windows command in form of an absolute path for command line usage | |
* @param {String} cmd Windows string of the plain absolute path to the e.g. cl.exe | |
* @return {String} Escape for spaces in abosulte path for [...]\cl.exe | |
*/ | |
function escapeWinCmd (cmd) { | |
return cmd.replace(/\s/g,"^ "); | |
} | |
/** | |
* Build the array we pass into the 'child_process' for compilation | |
* This will normalize to default also, and construct the actual array based on the OS | |
* @param {Object} options object of already pre-processed options object by `.compile()` | |
* @return {Array} | |
*/ | |
function buildSpawnArgs (options) { | |
// defaults here | |
let version = options.showVersion || false | |
let args = [] | |
switch (process.platform) { | |
case 'darwin': | |
version ? args.push('-v') : 0; | |
args.push(options.file_name) | |
args.push('-o' + options.output) | |
options.includes ? options.includes.forEach(function (el) { | |
args.push('-I' + el) | |
}) : 0; | |
options.suppressWarnings ? args.push('-w') : 0; | |
break; | |
case 'linux': | |
version ? args.push('-v') : 0; | |
args.push(options.file_name) | |
args.push('-o' + options.output) | |
options.includes ? options.includes.forEach(function (el) { | |
args.push('-I' + el) | |
}) : 0; | |
options.suppressWarnings ? args.push('-w') : 0; | |
break; | |
case 'win32': | |
args.push('/FE' + options.output) | |
options.includes ? options.includes.forEach(function (el) { | |
args.push('/I' + el) | |
}) : 0; | |
args.push(options.file_name) | |
options.suppressWarnings ? args.push('/w') : 0; | |
break; | |
} | |
return args | |
} | |
/** | |
* Executes and returns the actual stream from `.spawn()` | |
* @param {String} cmd string of which compiler to use | |
* @param {Object} options | |
* @return {Stream} Stream from `.spawns()` stdio pipe | |
*/ | |
function spawnCompilation (compiler, options) { | |
debug(options) | |
let params = [] | |
let cmd = '' | |
cmd = resolveCompilerCmd(compiler) | |
if (compiler === 'vsc++') { | |
cmd = escapeWinCmd(cmd) | |
} | |
// go down the help- and no-arguments-route here | |
if (!options) { | |
let helpFlag = '' | |
if (compiler === 'vsc++') { | |
helpFlag = '/HELP' | |
} else { | |
helpFlag = '--help' | |
} | |
params = [helpFlag] | |
options = {} | |
} else { | |
params = buildSpawnArgs(options) | |
} | |
let wd = options.cwd || '.' | |
let stdio = options.io || 'inherit' | |
return spawn(cmd, params, { cwd: wd, stdio: stdio }) | |
} | |
/** | |
* Get a list of available compilers, and how you can access them. | |
* @param {Function} cb callback | |
* @return {Function} error first callback, second parameter is the array of available compilers | |
*/ | |
exports.compilers = function compilers (cb) { | |
if (!cb) { | |
let err = new Error('You need to pass in a callback to this function.') | |
throw err | |
} | |
lookUpCompilers(function lookUpCompilersCb() { | |
return cb(null, compilers) | |
}) | |
} | |
/** | |
* Choose a compiler or use the platform's default and compile source code | |
* @example | |
* const tools = require('platform_tools') | |
* // options: | |
* // below are the defaults | |
* let options = { | |
* showVersion: false, // shows the compiler version, when starting. win32 this will be shown in any case | |
* verbose: false, | |
* includes: [ | |
* // paths to includes | |
* ], // default: none; win: concat of /I, unix: concat of -I | |
* suppressWarnings: false, // win: /w, unix: -w | |
* suppressAll: false, | |
* showAllWarnings: false, // win: /Wall, unix: -Wall | |
* optimizations: { | |
* forSmallCode: false, // win: /O1 | |
* forFastCode: false, // win: /O2 | |
* disableOptimization: false, // win: /Od | |
* favorsSmallCode: false, // win: /Os | |
* favorsFastCode: false, // win: /Ot | |
* maxOptimization: false // win: /Ox | |
* } | |
* } | |
* // further description of the compiler flags see: | |
* // win: [MSDN reference](https://msdn.microsoft.com/de-de/library/19z1t1wy.aspx), clang: [clang.llvm.org](http://clang.llvm.org/docs/UsersManual.html#command-line-options) | |
* | |
* tools.compile('clang', 'plattform_tools_test_c.c', 'b.out', options, function () { | |
* console.log('did compile') | |
* }) | |
* | |
* @param {String} compiler string of the compiler name: `gcc` on 'linux' etc. (will become `clang` on `darwin`), `clang` for 'darwin' and `vsc++` for Visual Studios `cl.exe` on 'win32' | |
* @param {String} filename a string of the path to a compilable file. When this is not a path, it will look for the specified name in the current working directory | |
* @param {String} output String for the output file, on win32 this should be: 'RESULT' in order to get get `RESULT.exe` | |
* @param {Object} options as described in examples | |
* @param {Function} cb [description] | |
* @return {Stream|Function} will return either a stream or a callback function, depending on whether a callback as last param is specified | |
*/ | |
exports.compile = function compile (compiler, filename, output, options, cb) { | |
let args = Array.prototype.slice.call(arguments) | |
let len = args.length | |
let defaultOut = 'a.out' | |
compiler === 'vsc++' ? defaultOut = a.exe : 0; | |
let opts = {} | |
// immediate bail-out | |
if (len <= 1) { | |
throw new Error('Not enough arguments.') | |
return | |
} | |
// fastlane, return the stream | |
if (len === 4) { | |
if (!(compiler && typeof (compiler) === 'string') || !(filename && typeof (filename) === 'string') || !(output && typeof (output) === 'string')) { | |
throw new Error('When passed four parameters, you must pass \'compiler\', \'filename, \'output name\' and a valid options object') | |
return | |
} | |
opts = options | |
opts.compiler = compiler | |
return spawnCompilation(compiler, opts) | |
} | |
// return the stream, sets the defaults first | |
if (len > 1) { | |
if (!(typeof (compiler) === 'string') || !(typeof (filename) === 'string')) { | |
throw new Error('When passed two parameters, you must pass \'compiler\', \'filename\'') | |
return | |
} | |
opts.compiler = compiler | |
opts.file_name = filename | |
opts.output = defaultOut | |
} | |
// return the stream, set some user data also first | |
if (len > 2) { | |
if (!(compiler && typeof (compiler) === 'string') || !(filename && typeof (filename) === 'string') || !(output && typeof (output) === 'string')) { | |
throw new Error('When passed three parameters, you must pass \'compiler\', \'filename, \'output name\'') | |
} | |
opts.output = output | |
} | |
// return a callback, which will be invoked open events: close, end, error | |
if (len > 4) { | |
// if (!(compiler && typeof (compiler) === 'string') || !(filename && typeof (filename) === 'string') || !(output && typeof (output) === 'string') || !(cb && typeof (cb) === 'function')) { | |
// throw new Error('When passed five parameters, you must pass \'compiler\', \'filename, \'output name\', a valid options object and a callback function') | |
// } | |
// merge options object | |
if (options) { | |
for (var prop in options) { | |
if (options.hasOwnProperty(prop)) { | |
opts[prop] = options[prop] | |
} | |
} | |
} | |
spawnCompilation(compiler, opts) | |
.on('error', function () { | |
let error = new Error('Error while compiling specified source code.') | |
return cb(err) | |
}) | |
.on('end', function () { | |
return cb(null) | |
}) | |
.on('close', function () { | |
return cb(null) | |
}) | |
} else { | |
// default case; making sure function does not return, when streams a | |
// invoked to get the callback | |
return spawnCompilation(compiler, opts) | |
} | |
} | |
/** | |
* Currently this is noop just in order to have API sugar of `.compiler.help()` | |
* | |
*/ | |
exports.compiler = function compiler() {} | |
/** | |
* Call the compilers help / usage functionalities directly | |
* @param {String} compiler choose compiler | |
* @param {Function} cb callback | |
* @return {Function} callback | |
*/ | |
exports.compiler.help = function help (compiler, cb) { | |
spawnCompilation('gcc', null) | |
.on('error', function () { | |
let error = new Error('Error while running help command.') | |
return cb(err) | |
}) | |
.on('end', function () { | |
return cb(null) | |
}) | |
.on('close', function () { | |
return cb(null) | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment