diff --git a/commandLine/src/main.cpp b/commandLine/src/main.cpp index 7a742565..3b600923 100644 --- a/commandLine/src/main.cpp +++ b/commandLine/src/main.cpp @@ -1,7 +1,8 @@ #include "ofMain.h" #include "optionparser.h" #include "defines.h" -enum optionIndex { UNKNOWN, HELP, PLUS, RECURSIVE, LISTTEMPLATES, PLATFORMS, ADDONS, OFPATH, VERBOSE, TEMPLATE, DRYRUN, VERSION }; +enum optionIndex { UNKNOWN, HELP, PLUS, RECURSIVE, LISTTEMPLATES, PLATFORMS, ADDONS, OFPATH, VERBOSE, TEMPLATE, DRYRUN, SRCEXTERNAL, VERSION}; + constexpr option::Descriptor usage[] = { {UNKNOWN, 0, "", "",option::Arg::None, "Options:\n" }, @@ -14,7 +15,8 @@ constexpr option::Descriptor usage[] = {VERBOSE, 0,"v","verbose",option::Arg::None, " --verbose, -v \trun verbose" }, {TEMPLATE, 0,"t","template",option::Arg::Optional, " --template, -t \tproject template" }, {DRYRUN, 0,"d","dryrun",option::Arg::None, " --dryrun, -d \tdry run, don't change files" }, - {VERSION, 0, "w", "version", option::Arg::None, " --version, -d \treturn the current version"}, + {SRCEXTERNAL, 0,"s","source",option::Arg::Optional, " --source, -s \trelative or absolute path to source or include folders external to the project (such as ../../../../common_utils/" }, + {VERSION, 0, "w", "version", option::Arg::None, " --version, -w \treturn the current version"}, {0,0,0,0,0,0} }; @@ -58,6 +60,7 @@ std::string directoryForRecursion; std::string projectPath; std::string ofPath; std::vector <std::string> addons; +std::vector <std::string> srcPaths; std::vector <ofTargetPlatform> targets; std::string ofPathEnv; std::string currentWorkingDirectory; @@ -263,6 +266,12 @@ void updateProject(std::string path, ofTargetPlatform target, bool bConsiderPara ofLogNotice() << "parsing addons.make"; project->parseAddons(); } + + if(!bDryRun){ + for(auto & srcPath : srcPaths){ + project->addSrcRecursively(srcPath); + } + } if (!bDryRun) project->save(); } @@ -415,7 +424,12 @@ int main(int argc, char* argv[]){ } } - + if (options[SRCEXTERNAL].count() > 0){ + if (options[SRCEXTERNAL].arg != NULL){ + std::string srcString(options[SRCEXTERNAL].arg); + srcPaths = ofSplitString(srcString, ",", true, true); + } + } if (options[OFPATH].count() > 0){ if (options[OFPATH].arg != NULL){ @@ -571,6 +585,9 @@ int main(int argc, char* argv[]){ for(auto & addon: addons){ project->addAddon(addon); } + for(auto & srcPath : srcPaths){ + project->addSrcRecursively(srcPath); + } } if (!bDryRun) project->save(); diff --git a/frontend/app.js b/frontend/app.js index edb2f304..e7aed63a 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -28,7 +28,7 @@ var isFirstTimeSierra = false; var bVerbose = false; var localAddons = []; - +var numAddedSrcPaths = 1; //----------------------------------------------------------------------------------- // IPC @@ -92,6 +92,12 @@ ipc.on('setProjectPath', function(arg) { $("#projectName").trigger('change'); // checks if we need to be in update or generate mode }); +//------------------------------------------- +ipc.on('setSourceExtraPath', function(arg, index) { + checkAddSourcePath(index); + $("#sourceExtra-"+index).val(arg); +}); + //------------------------------------------- ipc.on('setGenerateMode', function(arg) { switchGenerateMode(arg); @@ -914,12 +920,23 @@ function generate() { addonValueArray.push(localAddons[i]); } + // extra source locations + var srcExtraArr = ""; + for(var i = 0; i < numAddedSrcPaths; i++){ + var srcExtra = $("#sourceExtra-"+i).val(); + if( srcExtra != '' ){ + srcExtraArr += ", " + srcExtra; + } + } + + var lengthOfPlatforms = platformValueArray.length; var gen = {}; gen['projectName'] = $("#projectName").val(); gen['projectPath'] = $("#projectPath").val(); + gen['sourcePath'] = srcExtraArr; gen['platformList'] = platformValueArray; gen['templateList'] = templateValueArray; gen['addonList'] = addonValueArray; //$("#addonsDropdown").val(); @@ -997,12 +1014,16 @@ function switchGenerateMode(mode) { console.log('Switching GenerateMode to Update...'); clearAddonSelection(); + clearExtraSourceList(); } // [default]: switch to createMode (generate new projects) else { // if previously in update mode, deselect Addons - if( $("#updateButton").is(":visible") ){ clearAddonSelection(); } + if( $("#updateButton").is(":visible") ){ + clearAddonSelection(); + clearExtraSourceList(); + } $("#generateButton").show(); $("#updateButton").hide(); @@ -1032,14 +1053,14 @@ function enableAdvancedMode(isAdvanced) { $('#platformsDropdown').removeClass("disabled"); $("body").addClass('advanced'); $('a.updateMultiMenuOption').show(); - + $('#sourceExtraSection').show(); $('#templateSection').show(); $('#templateSectionMulti').show(); } else { $('#platformsDropdown').addClass("disabled"); $('#platformsDropdown').dropdown('set exactly', defaultSettings['defaultPlatform']); - + $('#sourceExtraSection').hide(); $('#templateSection').hide(); $('#templateSectionMulti').hide(); $('#templateDropdown').dropdown('set exactly', ''); @@ -1114,7 +1135,6 @@ function browseOfPath() { } function browseProjectPath() { - var path = $("#projectPath").val(); if (path === ''){ path = $("#ofPath").val(); @@ -1122,6 +1142,37 @@ function browseProjectPath() { ipc.send('pickProjectPath', path); // current path could go here } +function clearExtraSourceList(){ + $("#sourceExtraSection").empty(); + $("#sourceExtraSection").append("<label>Additional source folders:</label>"); + + checkAddSourcePath(-1); + numAddedSrcPaths = 1; +} + +function checkAddSourcePath(index){ + //if we don't have another field below us - add one + var nextFieldId = '#sourceExtra-'+(index+1); + if( $(nextFieldId).length == 0 ){ + var nextIndex = index+1; + var extrafield = '<div class="field"> \ + <div class="ui icon input fluid"> \ + <input type="text" placeholder="Extra source path..." id="sourceExtra-'+nextIndex+'"> \ + <i class="search link icon" onclick="browseSourcePath('+nextIndex+')"></i> \ + </div> \ + </div>'; + + $("#sourceExtraSection").append(extrafield); + numAddedSrcPaths++; + } +} + +function browseSourcePath(index) { + var path = $("#ofPath").val(); + ipc.send('pickSourcePath', path, index); // current path could go here +} + + function browseImportProject() { var path = $("#projectPath").val(); if (path === ''){ diff --git a/frontend/index.html b/frontend/index.html index 1b9df434..26bbb7d3 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -150,6 +150,15 @@ <i class="search link icon" onclick="browseProjectPath()"></i> </div> </div> + <div id="sourceExtraSection" class="field"> + <label>Additional source folders:</label> + <div class="field"> + <div class="ui icon input fluid"> + <input type="text" placeholder="Extra source path..." id="sourceExtra-0"> + <i class="search link icon" onclick="browseSourcePath(0)"></i> + </div> + </div> + </div> <div class="field"> <label> <a class="tooltip" href="http://www.ofxaddons.com/" data-toggle="external_target" data-content="Get more addons at ofxAddons.com" data-position="right center" id="ofx-addons-link">Addons</a>: diff --git a/frontend/index.js b/frontend/index.js index 17207574..42750413 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -497,6 +497,7 @@ ipc.on('isOFProjectFolder', function(event, project) { // todo: also check for config.make & addons.make ? var foundSrcFolder = false; var foundAddons = false; + var foundConfig = false; tmpFiles.forEach(function(el, i) { if (el == 'src') { foundSrcFolder = true; @@ -504,6 +505,9 @@ ipc.on('isOFProjectFolder', function(event, project) { if (el == 'addons.make') { foundAddons = true; } + if(el == 'config.make'){ + foundConfig = true; + } }); if (foundSrcFolder) { @@ -532,6 +536,57 @@ ipc.on('isOFProjectFolder', function(event, project) { } else { event.sender.send('selectAddons', {}); } + + if(foundConfig){ + var projectExtra = fsTemp.readFileSync(pathTemp.resolve(folder, 'config.make')).toString().split("\n"); + projectExtra = projectExtra.filter(function(el) { + if (el === '' || el[0] === '#') { + return false; + } // eleminates these items + else { + console.log("got a good element " + el ); + return true; + } + }); + + //read the valid lines + var extraSrcPathsCount = 0; + + projectExtra.forEach(function(el, i) { + //remove spaces + var line = el.replace(/ /g, ''); + + //split either on = or += + var splitter = "+="; + var n = line.indexOf(splitter); + var macro, value; + + if( n != -1 ){ + var macro = line.substr(0, n); + var value = line.substr(n + splitter.length); + }else{ + splitter = "="; + n = line.indexOf(splitter); + if( n != -1 ){ + macro = line.substr(0, n); + value = line.substr(n + splitter.length); + } + } + + if( macro.length && value.length){ + // this is where you can do things with the macro/values from the config.make file + + console.log("Reading config pair. Macro: " + macro + " Value: " + value); + + if(macro.startsWith('PROJECT_EXTERNAL_SOURCE_PATHS')){ + event.sender.send('setSourceExtraPath', value, extraSrcPathsCount); + extraSrcPathsCount++; + } + } + }); + + } + } else { event.sender.send('setGenerateMode', 'createMode'); } @@ -740,7 +795,7 @@ ipc.on('generate', function(event, arg) { var templateString = ""; var verboseString = ""; var rootPath = defaultOfPath; - + var sourceExtraString = ""; if (generate['platformList'] !== null) { platformString = "-p\"" + generate['platformList'].join(",") + "\""; @@ -761,6 +816,10 @@ ipc.on('generate', function(event, arg) { pathString = "-o\"" + generate['ofPath'] + "\""; rootPath = generate['ofPath']; } + + if( generate['sourcePath'] != null && generate['sourcePath'].length >0 ){ + sourceExtraString = " -s\"" + generate['sourcePath'] + "\""; + } if (generate['verbose'] === true) { verboseString = "-v"; @@ -785,7 +844,7 @@ ipc.on('generate', function(event, arg) { pgApp = pgApp = "\"" + pgApp + "\""; } - var wholeString = pgApp + " " + verboseString + " " + pathString + " " + addonString + " " + platformString + " " + templateString + " " + projectString; + var wholeString = pgApp + " " + verboseString + " " + pathString + " " + addonString + " " + platformString + sourceExtraString + " " + templateString + " " + projectString; exec(wholeString, {maxBuffer : Infinity}, function callback(error, stdout, stderr) { @@ -885,6 +944,19 @@ ipc.on('pickProjectPath', function(event, arg) { }); }); +ipc.on('pickSourcePath', function(event, arg, index) { + path = dialog.showOpenDialog({ + title: 'select extra source or include folder paths to add to project', + properties: ['openDirectory'], + filters: [], + defaultPath: arg + }, function(filenames) { + if (filenames !== undefined && filenames.length > 0) { + event.sender.send('setSourceExtraPath', filenames[0], index); + } + }); +}); + ipc.on('checkMultiUpdatePath', function(event, arg) { diff --git a/ofxProjectGenerator/src/projects/baseProject.cpp b/ofxProjectGenerator/src/projects/baseProject.cpp index 1d60d662..551236fa 100644 --- a/ofxProjectGenerator/src/projects/baseProject.cpp +++ b/ofxProjectGenerator/src/projects/baseProject.cpp @@ -109,6 +109,7 @@ vector<baseProject::Template> baseProject::listAvailableTemplates(std::string ta bool baseProject::create(string path, std::string templateName){ templatePath = getPlatformTemplateDir(); addons.clear(); + extSrcPaths.clear(); if(!ofFilePath::isAbsolute(path)){ path = (std::filesystem::current_path() / std::filesystem::path(path)).string(); @@ -228,7 +229,34 @@ bool baseProject::save(){ addonsMake << addons[i].name << endl; } } - + + //save out params which the PG knows about to config.make + //we mostly use this right now for storing the external source paths + auto buffer = ofBufferFromFile(ofFilePath::join(projectDir,"config.make")); + if( buffer.size() ){ + ofFile saveConfig(ofFilePath::join(projectDir,"config.make"), ofFile::WriteOnly); + + for(auto line : buffer.getLines()){ + string str = line; + + //add the of root path + if( str.rfind("# OF_ROOT =", 0) == 0 ){ + saveConfig << "OF_ROOT = " + getOFRoot() << endl; + } + // replace this section with our external paths + else if( extSrcPaths.size() && str.rfind("# PROJECT_EXTERNAL_SOURCE_PATHS =", 0) == 0 ){ + + for(int d = 0; d < extSrcPaths.size(); d++){ + ofLog(OF_LOG_VERBOSE) << " adding PROJECT_EXTERNAL_SOURCE_PATHS to config" << extSrcPaths[d] << endl; + saveConfig << "PROJECT_EXTERNAL_SOURCE_PATHS" << (d == 0 ? " = " : " += ") << extSrcPaths[d] << endl; + } + + }else{ + saveConfig << str << endl; + } + } + } + return saveProjectFile(); } @@ -294,6 +322,84 @@ void baseProject::addAddon(std::string addonName){ } } +void baseProject::addSrcRecursively(std::string srcPath){ + extSrcPaths.push_back(srcPath); + vector <std::string> srcFilesToAdd; + + //so we can just pass through the file paths + ofDisableDataPath(); + getFilesRecursively(srcPath, srcFilesToAdd); + ofEnableDataPath(); + + //if the files being added are inside the OF root folder, make them relative to the folder. + bool bMakeRelative = false; + if( srcPath.find_first_of(getOFRoot()) == 0 ){ + bMakeRelative = true; + } + + //need this for absolute paths so we can subtract this path from each file path + //say we add this path: /user/person/documents/shared_of_code + //we want folders added for shared_of_code/ and any subfolders, but not folders added for /user/ /user/person/ etc + string parentFolder = ofFilePath::getEnclosingDirectory(ofFilePath::removeTrailingSlash(srcPath)); + + std::map <std::string, std::string> uniqueIncludeFolders; + for( auto & fileToAdd : srcFilesToAdd){ + + //if it is an absolute path it is easy - add the file and enclosing folder to the project + if( ofFilePath::isAbsolute(fileToAdd) && !bMakeRelative ){ + string folder = ofFilePath::getEnclosingDirectory(fileToAdd,false); + string absFolder = folder; + + auto pos = folder.find_first_of(parentFolder); + + //just to be 100% sure - check if the parent folder path is at the beginning of the file path + //then remove it so we just get the folder structure of the actual src files being added and not the full path + if( pos == 0 && parentFolder.size() < folder.size() ){ + folder = folder.substr(parentFolder.size()); + } + + folder = ofFilePath::removeTrailingSlash(folder); + + ofLogVerbose() << " adding file " << fileToAdd << " in folder " << folder << " to project "; + addSrc(fileToAdd, folder); + uniqueIncludeFolders[absFolder] = absFolder; + }else{ + + auto absPath = fileToAdd; + + //if it is a realtive path make the file relative to the project folder + if( !ofFilePath::isAbsolute(absPath) ){ + absPath = ofFilePath::getAbsolutePath( ofFilePath::join(ofFilePath::getCurrentExeDir(), fileToAdd) ); + } + auto canPath = std::filesystem::canonical(absPath); //resolves the ./ and ../ to be the most minamlist absolute path + + //get the file path realtive to the project + auto projectPath = ofFilePath::getAbsolutePath( projectDir ); + auto relPathPathToAdd = ofFilePath::makeRelative(projectPath, canPath); + + //get the folder from the path and clean it up + string folder = ofFilePath::getEnclosingDirectory(relPathPathToAdd,false); + string includeFolder = folder; + + ofStringReplace(folder, "../", ""); +#ifdef TARGET_WIN32 + ofStringReplace(folder, "..\\", ""); //do both just incase someone has used linux paths on windows +#endif + folder = ofFilePath::removeTrailingSlash(folder); + + ofLogVerbose() << " adding file " << fileToAdd << " in folder " << folder << " to project "; + addSrc(relPathPathToAdd, folder); + uniqueIncludeFolders[includeFolder] = includeFolder; + } + } + + //do it this way so we don't try and add a include folder for each file ( as it checks if they are already added ) so should be faster + for(auto & includeFolder : uniqueIncludeFolders){ + ofLogVerbose() << " adding search include paths for folder " << includeFolder.second; + addInclude(includeFolder.second); + } +} + void baseProject::addAddon(ofAddon & addon){ for(int i=0;i<(int)addons.size();i++){ if(addons[i].name==addon.name) return; diff --git a/ofxProjectGenerator/src/projects/baseProject.h b/ofxProjectGenerator/src/projects/baseProject.h index c4048b10..6729a691 100644 --- a/ofxProjectGenerator/src/projects/baseProject.h +++ b/ofxProjectGenerator/src/projects/baseProject.h @@ -67,6 +67,7 @@ class baseProject { virtual void addAddon(std::string addon); virtual void addAddon(ofAddon & addon); + virtual void addSrcRecursively(std::string srcPath); std::string getName() { return projectName;} std::string getPath() { return projectDir; } @@ -87,6 +88,7 @@ class baseProject { void recursiveCopyContents(const ofDirectory & srcDir, ofDirectory & destDir); std::vector<ofAddon> addons; + std::vector<std::string> extSrcPaths; }; diff --git a/ofxProjectGenerator/src/projects/xcodeProject.cpp b/ofxProjectGenerator/src/projects/xcodeProject.cpp index 46fe139d..18b48064 100644 --- a/ofxProjectGenerator/src/projects/xcodeProject.cpp +++ b/ofxProjectGenerator/src/projects/xcodeProject.cpp @@ -218,6 +218,7 @@ STRINGIFY( xcodeProject::xcodeProject(std::string target) :baseProject(target){ if( target == "osx" ){ + projRootUUID = "E4B69B4A0A3A1720003C02F2"; srcUUID = "E4B69E1C0A3A1BDC003C02F2"; addonUUID = "BB4B014C10F69532006C3DED"; localAddonUUID = "6948EE371B920CB800B5AC1A"; @@ -229,6 +230,7 @@ xcodeProject::xcodeProject(std::string target) frameworksBuildPhaseUUID = "E4328149138ABC9F0047C5CB"; }else{ + projRootUUID = "29B97314FDCFA39411CA2CEA"; srcUUID = "E4D8936A11527B74007E1F53"; addonUUID = "BB16F26B0F2B646B00518274"; localAddonUUID = "6948EE371B920CB800B5AC1A"; @@ -875,8 +877,8 @@ void xcodeProject::addSrc(std::string srcFile, std::string folder, SrcType type) std::vector < std::string > folders = ofSplitString(folder, "/", true); - if (folders.size() > 1){ - if (folders[0] == "src"){ + if (folders.size()){ + if (folders.size() > 1 && folders[0] == "src"){ std::string xmlStr = "//key[contains(.,'"+srcUUID+"')]/following-sibling::node()[1]"; folders.erase(folders.begin()); @@ -884,7 +886,7 @@ void xcodeProject::addSrc(std::string srcFile, std::string folder, SrcType type) pugi::xml_node nodeToAddTo = findOrMakeFolderSet( node, folders, "src"); nodeToAddTo.child("array").append_child("string").append_child(pugi::node_pcdata).set_value(UUID.c_str()); - } else if (folders[0] == "addons"){ + } else if (folders.size() > 1 && folders[0] == "addons"){ std::string xmlStr = "//key[contains(.,'"+addonUUID+"')]/following-sibling::node()[1]"; folders.erase(folders.begin()); @@ -893,7 +895,7 @@ void xcodeProject::addSrc(std::string srcFile, std::string folder, SrcType type) nodeToAddTo.child("array").append_child("string").append_child(pugi::node_pcdata).set_value(UUID.c_str()); - } else if (folders[0] == "local_addons"){ + } else if (folders.size() > 1 && folders[0] == "local_addons"){ std::string xmlStr = "//key[contains(.,'"+localAddonUUID+"')]/following-sibling::node()[1]"; folders.erase(folders.begin()); @@ -903,13 +905,15 @@ void xcodeProject::addSrc(std::string srcFile, std::string folder, SrcType type) nodeToAddTo.child("array").append_child("string").append_child(pugi::node_pcdata).set_value(UUID.c_str()); } else { - std::string xmlStr = "//key[contains(.,'"+srcUUID+"')]/following-sibling::node()[1]"; + std::string xmlStr = "//key[contains(.,'"+projRootUUID+"')]/following-sibling::node()[1]"; pugi::xml_node node = doc.select_single_node(xmlStr.c_str()).node(); + pugi::xml_node nodeToAddTo = findOrMakeFolderSet( node, folders, folders[0]); + + nodeToAddTo.child("array").append_child("string").append_child(pugi::node_pcdata).set_value(UUID.c_str()); - // I'm not sure the best way to proceed; - // we should maybe find the rootest level and add it there. - // TODO: fix this. + // This should add any files not in src/ addons/ or local_addons/ + // to the root of the project hierarchy } }; diff --git a/ofxProjectGenerator/src/projects/xcodeProject.h b/ofxProjectGenerator/src/projects/xcodeProject.h index 2878f3df..41f496d5 100644 --- a/ofxProjectGenerator/src/projects/xcodeProject.h +++ b/ofxProjectGenerator/src/projects/xcodeProject.h @@ -40,6 +40,7 @@ class xcodeProject : public baseProject { void saveScheme(); void renameProject(); + std::string projRootUUID; std::string srcUUID; std::string addonUUID; std::string localAddonUUID; diff --git a/ofxProjectGenerator/src/utils/Utils.cpp b/ofxProjectGenerator/src/utils/Utils.cpp index adda7add..64002e4a 100644 --- a/ofxProjectGenerator/src/utils/Utils.cpp +++ b/ofxProjectGenerator/src/utils/Utils.cpp @@ -290,6 +290,8 @@ void getPropsRecursively(const std::string & path, std::vector < std::string > & for (auto & temp : dir) { if (temp.isDirectory()) { + //skip example directories - this is needed as we are search all folders in the addons root path + if( temp.getFileName().rfind("example", 0) == 0) continue; getPropsRecursively(temp.path(), props, platform); } else {