Skip to content

Instantly share code, notes, and snippets.

@eljefedelrodeodeljefe
Created December 31, 2015 16:19
Show Gist options
  • Save eljefedelrodeodeljefe/4df0f651aedc90329192 to your computer and use it in GitHub Desktop.
Save eljefedelrodeodeljefe/4df0f651aedc90329192 to your computer and use it in GitHub Desktop.
Proposal for a node-native build toolchain 'platform_tools'
'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