Last active
May 8, 2017 20:40
-
-
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
This file contains 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
/* | |
@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