Skip to content

Instantly share code, notes, and snippets.

@galloscript
Last active May 8, 2017 20:40
Show Gist options
  • Save galloscript/1bd87ab1db26b8d825f80e301c2bec8f to your computer and use it in GitHub Desktop.
Save galloscript/1bd87ab1db26b8d825f80e301c2bec8f to your computer and use it in GitHub Desktop.
WIP Builder script to compile and link c++ projects without dependencies with IDE project. Can be launched from an IDE with custom build tool target, but all the dependencies must be handled by the target.js file and builder.js
/*
@file builder.js (WIP)
@author David Gallardo @galloscript
@brief Builder script to compile and link c++ projects without dependencies with IDE project.
Can be launched from an IDE with custom build tool target, but all the dependencies
must be handled by the target.js file and builder.js
*/
/*
Example emtest.target.js
exports.GetTargetData = function(aEnv)
{
var lData = {};
lData.TargetName = 'emtest';
lData.TargetType = 'exe';
lData.SrcPath = '.';
return lData;
};
*/
/*
Example build.sh
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
BUILDER=$DIR"/../builder.js"
node $BUILDER build emtest macosx Release x86_64 $DIR
node $BUILDER build emtest emscripten release wasm $DIR
*/
//- Modules ---------------------------------------
const spawn = require('child_process').spawnSync;
const fs = require('fs');
//- Constants -------------------------------------
const BUILDER_PLATFORM_WINDOWS = 'mswindows'
const BUILDER_PLATFORM_MACOSX = 'macosx'
const BUILDER_PLATFORM_EMSCRIPTEN = 'emscripten'
//Platform exclusive directories
const BUILDER_WINDOWS_SOURCES_DIRNAME = 'win';
const BUILDER_OSX_SOURCES_DIRNAME = 'osx';
const BUILDER_EMSCRIPTEN_SOURCES_DIRNAME = 'ems';
//Target type
const BUILDER_STATIC_LIBRARY = 'lib';
const BUILDER_DYNAMIC_LIBRARY = 'dylib';
const BUILDER_EXECUTABLE = 'exe';
//Configurations
const BUILDER_CONFIGURATIONS =
{
BUILDER_DEBUG : 'debug',
BUILDER_RELEASE : 'release'
};
//Versions
const BUILDER_VC_VERSION = '14';
const BUILDER_OSXSDK_VERSION = '10.11';
//- Global Configuration --------------------------
var GlobalConfig = {};
//- Utility ---------------------------------------
function CreatePathDirectories(aPath)
{
aPath.split('/').reduce((path, folder) =>
{
path += folder + '/';
if (!fs.existsSync(path))
{
fs.mkdirSync(path);
}
return path;
}, '');
}
//Returns a list of relative paths to compilable files
function TraverseSourcesDirectoryTree(aFiles, aDir, aExtensions, aExcluded)
{
//console.log('Traversing '+aDir);
var lList = fs.readdirSync(aDir);
for(var f = 0; f < lList.length; ++f)
{
var lFile = aDir + '/' + lList[f];
var lStat = fs.statSync(lFile);
if (lStat && lStat.isDirectory())
{
TraverseSourcesDirectoryTree(aFiles, lFile, aExtensions, aExcluded);
}
else
{
var lExtension = lFile.replace(/.*\./, '').toLowerCase();
if( (aExtensions.length == 0) || (aExtensions.indexOf(lExtension) != -1) )
{
aFiles.push(lFile);
}
}
}
}
function PrepareCommand(aCommandStr, aOutputParamsObj)
{
//Remove double whitespaces and split into array
aCommandStr = aCommandStr.replace(/ +/g, ' ');
console.log("Command: "+aCommandStr);
//console.log(lSourceFiles[f]);
var lCommandArray = aCommandStr.split(' ');
//Separate command from parameters
var lCommandExecutable = lCommandArray[0];
lCommandArray.shift();
aOutputParamsObj.CommandName = lCommandExecutable;
aOutputParamsObj.CommandParameters = lCommandArray;
}
//- Compiler Configs ------------------------------
function ConfigureShared(aConfiguration, aWorkingDir, aTargetData)
{
GlobalConfig.WorkingDir = aWorkingDir;
GlobalConfig.SourcesExtensions = ['cpp', 'c', 'cc', 'cxx'];
GlobalConfig.ExcludedDirectories =
[
'avoid',
'deprecated',
BUILDER_WINDOWS_SOURCES_DIRNAME,
BUILDER_OSX_SOURCES_DIRNAME
];
GlobalConfig.IntermediatePath = 'intermediate/'+aConfiguration+'/${TARGET_NAME}';
GlobalConfig.IncludePaths = [];
GlobalConfig.LibraryPaths = [ GlobalConfig.WorkingDir+'/bin/'+aConfiguration ];
GlobalConfig.Libraries = [];
GlobalConfig.Frameworks = [];
GlobalConfig.OutputPath = 'bin/'+aConfiguration;
}
function ConfigureVC(aConfiguration, aTargetData)
{
GlobalConfig.ExcludedDirectories.splice(GlobalConfig.ExcludedDirectories.indexOf(BUILDER_WINDOWS_SOURCES_DIRNAME), 1);
GlobalConfig.ObjExtension = '.obj';
GlobalConfig.StaticLibraryExtension = '.lib';
GlobalConfig.DynamicLibraryExtension = '.dll';
GlobalConfig.ExecutableExtension = '.exe';
GlobalConfig.LibraryPrefix = '';
//TODO: config for visual studio c++ compiler & linker
}
function ConfigureOSXClang(aConfiguration, aTargetData)
{
GlobalConfig.SourcesExtensions = GlobalConfig.SourcesExtensions.concat(['m', 'mm']);
GlobalConfig.ExcludedDirectories.splice(GlobalConfig.ExcludedDirectories.indexOf(BUILDER_OSX_SOURCES_DIRNAME), 1);
GlobalConfig.ObjExtension = '.o';
GlobalConfig.StaticLibraryExtension = '.a';
GlobalConfig.DynamicLibraryExtension = '.dylib';
GlobalConfig.ExecutableExtension = '';
GlobalConfig.LibraryPrefix = 'lib';
GlobalConfig.CompilerCommand = 'clang++ ${SOURCE_FILE} -c -g -Wall -arch x86_64 -mmacosx-version-min='+BUILDER_OSXSDK_VERSION+' -std=gnu++11 -stdlib=libc++ ${COMPILER_PARAMS} -o ${OBJECT_FILE}';
GlobalConfig.CompilerAppendIncludePath = ' -I${PATH}';
switch(aConfiguration)
{
case BUILDER_CONFIGURATIONS.BUILDER_DEBUG:
GlobalConfig.CompilerParams = ' -O0 -DDEBUG=1';
break;
case BUILDER_CONFIGURATIONS.BUILDER_RELEASE:
default:
GlobalConfig.CompilerParams = ' -Os';
break;
};
GlobalConfig.CompilerExtraParams = ' -Werror -Wno-missing-field-initializers -Wno-missing-prototypes -Werror=return-type '+
' -Wdocumentation -Wunreachable-code -Werror=deprecated-objc-isa-usage '+
' -Werror=objc-root-class -Wno-missing-braces -Wparentheses -Wswitch -Wunused-function '+
' -Wno-unused-label -Wno-unused-parameter -Wunused-variable -Wunused-value '+
' -Wempty-body -Wconditional-uninitialized -Wno-unknown-pragmas -Wshadow '+
' -Wno-four-char-constants -Wno-conversion -Wconstant-conversion -Wint-conversion '+
' -Wbool-conversion -Wenum-conversion -Wassign-enum -Wsign-compare -Wshorten-64-to-32 '+
' -Wpointer-sign -Wnewline-eof';
GlobalConfig.LinkerAppendLibrary = ' -l${LIBRARY_NAME}';
GlobalConfig.LinkerAppendLibraryPath = ' -L${LIBRARY_PATH}';
GlobalConfig.LinkerAppendFramework = ' -framework ${FRAMEWORK_NAME}';
GlobalConfig.StaticLibraryCommand = 'Libtool -static ${OBJECT_FILE_LIST} ${DEPENCENCIES} -o ${LIBRARY_FILE}';
GlobalConfig.DynamicLibraryCommand = 'Libtool -dynamic ${OBJECT_FILE_LIST} ${DEPENCENCIES} -o ${LIBRARY_FILE}';
var lSysRoot = '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX'+BUILDER_OSXSDK_VERSION+'.sdk';
GlobalConfig.ExecutableCommand = 'clang++ ${OBJECT_FILE_LIST} -arch x86_64 -isysroot '+lSysRoot+' -mmacosx-version-min='+BUILDER_OSXSDK_VERSION+' ${DEPENCENCIES} -o ${EXECUTABLE_FILE}';
}
function ConfigureEmscripten(aConfiguration, aTargetData)
{
GlobalConfig.ObjExtension = '.bc';
GlobalConfig.StaticLibraryExtension = '.bc';
GlobalConfig.DynamicLibraryExtension = '.bc';
GlobalConfig.ExecutableExtension = '.html';
GlobalConfig.LibraryPrefix = 'lib';
GlobalConfig.CompilerCommand = 'emcc ${SOURCE_FILE} -c -g -Wall ${COMPILER_PARAMS} -o ${OBJECT_FILE}';
GlobalConfig.CompilerAppendIncludePath = ' -I${PATH}';
switch(aConfiguration)
{
case BUILDER_CONFIGURATIONS.BUILDER_DEBUG:
GlobalConfig.CompilerParams = ' -O0 -DDEBUG=1';
break;
case BUILDER_CONFIGURATIONS.BUILDER_RELEASE:
default:
GlobalConfig.CompilerParams = ' -Os';
break;
};
GlobalConfig.CompilerExtraParams = ' -Werror -Wno-missing-field-initializers -Wno-missing-prototypes -Werror=return-type '+
' -Wdocumentation -Wunreachable-code -Werror=deprecated-objc-isa-usage '+
' -Werror=objc-root-class -Wno-missing-braces -Wparentheses -Wswitch -Wunused-function '+
' -Wno-unused-label -Wno-unused-parameter -Wunused-variable -Wunused-value '+
' -Wempty-body -Wconditional-uninitialized -Wno-unknown-pragmas -Wshadow '+
' -Wno-four-char-constants -Wno-conversion -Wconstant-conversion -Wint-conversion '+
' -Wbool-conversion -Wenum-conversion -Wassign-enum -Wsign-compare -Wshorten-64-to-32 '+
' -Wpointer-sign -Wnewline-eof';
GlobalConfig.LinkerAppendLibrary = ' -l${LIBRARY_NAME}';
GlobalConfig.LinkerAppendLibraryPath = ' -L${LIBRARY_PATH}';
GlobalConfig.LinkerAppendFramework = ' -framework ${FRAMEWORK_NAME}';
GlobalConfig.StaticLibraryCommand = 'emcc ${OBJECT_FILE_LIST} ${DEPENCENCIES} -o ${LIBRARY_FILE}';
GlobalConfig.DynamicLibraryCommand = 'emcc ${OBJECT_FILE_LIST} ${DEPENCENCIES} -o ${LIBRARY_FILE}';
GlobalConfig.ExecutableCommand = 'emcc ${OBJECT_FILE_LIST} ${DEPENCENCIES} -o ${EXECUTABLE_FILE}';
}
//- Build -----------------------------------------
function Build(aTargetData, aConfiguration, aArchitecture, aPlatform)
{
//Compiler
var lCompilerCommand = GlobalConfig.CompilerCommand;
var lCompilerParams = '';
var lIntermediatePath = GlobalConfig.IntermediatePath.replace('${TARGET_NAME}', aTargetData.TargetName);
//Appen Include paths
for(var i = 0; i < GlobalConfig.IncludePaths.length; ++i)
{
lCompilerParams += GlobalConfig.CompilerAppendIncludePath.replace('${PATH}', GlobalConfig.IncludePaths[i]);
}
//Append extra configuracion params
lCompilerParams += GlobalConfig.CompilerParams;
lCompilerParams += GlobalConfig.CompilerExtraParams;
//Replace params
lCompilerCommand = lCompilerCommand.replace('${COMPILER_PARAMS}', lCompilerParams);
//Iterate files
var lSourceFiles = [];
var lObjectFiles = [];
TraverseSourcesDirectoryTree(lSourceFiles, GlobalConfig.WorkingDir+'/'+aTargetData.SrcPath, GlobalConfig.SourcesExtensions, GlobalConfig.ExcludedDirectories);
if(lSourceFiles.length == 0)
{
console.error('[builder.js] No source files found.');
return 1;
}
var lOk = true;
for(var f = 0; f < lSourceFiles.length; ++f)
{
//filename withoud working dir path
var lRelativePath = lSourceFiles[f].replace(GlobalConfig.WorkingDir+'/', '');
//Object file path
var lIntermediateFilePath = GlobalConfig.WorkingDir+'/'+lIntermediatePath+'/'+lRelativePath+GlobalConfig.ObjExtension;
var lIntermediateFileDirectory = lIntermediateFilePath.substring(0, lIntermediateFilePath.lastIndexOf("/"));
lObjectFiles.push(lIntermediateFilePath);
// Create Intermediate directory if it doens't exist
CreatePathDirectories(lIntermediateFileDirectory);
//Create File command
var lFileCommand = lCompilerCommand.replace('${SOURCE_FILE}', GlobalConfig.WorkingDir+'/'+lRelativePath).replace('${OBJECT_FILE}', lIntermediateFilePath);
var lCommandData = {CommandName : '', CommandParameters : []};
PrepareCommand(lFileCommand, lCommandData);
//TODO: Exclude older object files
//TODO: Multythread AND/OR Unity build
//Run the compiler command
var s = spawn(lCommandData.CommandName, lCommandData.CommandParameters, { stdio: 'inherit' });
lOk = lOk && (s.status == 0);
}
if(!lOk) return 2;
//Create Library
if(BUILDER_STATIC_LIBRARY == aTargetData.TargetType)
{
var lLibraryOutputPath = GlobalConfig.WorkingDir+
'/'+GlobalConfig.OutputPath+
'/'+GlobalConfig.LibraryPrefix
+aTargetData.TargetName
+GlobalConfig.StaticLibraryExtension;
var lLibraryOutputDirectory = lLibraryOutputPath.substring(0, lLibraryOutputPath.lastIndexOf("/"));
var lLinkCommand = GlobalConfig.StaticLibraryCommand
.replace('${OBJECT_FILE_LIST}', lObjectFiles.join(' '))
.replace('${DEPENCENCIES}', '')
.replace('${LIBRARY_FILE}', lLibraryOutputPath);
var lCommandData = {CommandName : '', CommandParameters : []};
PrepareCommand(lLinkCommand, lCommandData);
// Create Output directory if it doens't exist
CreatePathDirectories(lLibraryOutputDirectory);
//Run the libtool command
var s = spawn(lCommandData.CommandName, lCommandData.CommandParameters, { stdio: 'inherit' }); //(s.status == 0);
}
//Create Executable
else if(BUILDER_EXECUTABLE == aTargetData.TargetType)
{
var lDepencencies = '';
for(var i = 0; i < GlobalConfig.LibraryPaths.length; ++i)
{
lDepencencies += GlobalConfig.LinkerAppendLibraryPath.replace('${LIBRARY_PATH}', GlobalConfig.LibraryPaths[i]);
}
for(var i = 0; i < GlobalConfig.Libraries.length; ++i)
{
lDepencencies += GlobalConfig.LinkerAppendLibrary.replace('${LIBRARY_NAME}', GlobalConfig.Libraries[i]);
}
//TODO: append frameworks
var lExecutableOutputPath = GlobalConfig.WorkingDir+'/'+GlobalConfig.OutputPath+'/'+aTargetData.TargetName+GlobalConfig.ExecutableExtension;
var lExecutableOutputDirectory = lExecutableOutputPath.substring(0, lExecutableOutputPath.lastIndexOf("/"));
var lLinkCommand = GlobalConfig.ExecutableCommand
.replace('${OBJECT_FILE_LIST}', lObjectFiles.join(' '))
.replace('${DEPENCENCIES}', lDepencencies)
.replace('${EXECUTABLE_FILE}', lExecutableOutputPath);
var lCommandData = {CommandName : '', CommandParameters : []};
PrepareCommand(lLinkCommand, lCommandData);
// Create Output directory if it doens't exist
CreatePathDirectories(lExecutableOutputDirectory);
//Run the linker command
var s = spawn(lCommandData.CommandName, lCommandData.CommandParameters, { stdio: 'inherit' }); //(s.status == 0);
}
}
function Main()
{
var lAction = process.argv[2];
var lTargetName = process.argv[3];
var lPlatform = process.argv[4];
var lConfiguration = process.argv[5];
var lArchitecture = process.argv[6];
var lWorkingDir = process.argv[7] || process.pwd();
console.log(lWorkingDir);
if(!lArchitecture)
{
console.error('[builder.js] Incorrect number of arguments.');
console.error('[builder.js] node builder.js action[build|clean] target_name platform[macosx|mswindows|emscripten] configuration[debug|release] architecture[x86|x86_64]');
console.error('Current arguments: ');
process.argv.forEach(function (val, index, array)
{
console.error(index + ': ' + val);
});
return 1;
}
lAction = lAction.toLowerCase();
lPlatform = lPlatform.toLowerCase();
lConfiguration = lConfiguration.toLowerCase();
lArchitecture = lArchitecture.toLowerCase();
//Target data
var lTargetFileName = lWorkingDir+'/'+lTargetName+'.target.js';
var lTargetData = require(lTargetFileName).GetTargetData();
//Shared configuration
ConfigureShared(lConfiguration, lWorkingDir, lTargetData);
switch(lPlatform)
{
case BUILDER_PLATFORM_WINDOWS: ConfigureVC(lConfiguration, lTargetData); break;
case BUILDER_PLATFORM_MACOSX: ConfigureOSXClang(lConfiguration, lTargetData); break;
case BUILDER_PLATFORM_EMSCRIPTEN: ConfigureEmscripten(lConfiguration, lTargetData); break;
}
return Build(lTargetData, lConfiguration, lArchitecture, lPlatform);
}
var r = Main();
process.exit(r);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment