From 71118fb3a24b3b5db31f83444d463540759a0599 Mon Sep 17 00:00:00 2001 From: Anis Kadri Date: Mon, 11 Nov 2013 17:21:01 -0800 Subject: [PATCH] Issue #17 adding/removing custom frameworks --- lib/pbxFile.js | 18 ++++++--- lib/pbxProject.js | 78 ++++++++++++++++++++++++++++++++---- test/FrameworkSearchPaths.js | 46 +++++++++++++++++++++ test/addFramework.js | 54 +++++++++++++++++++++++++ test/removeFramework.js | 51 +++++++++++++++++++++++ 5 files changed, 235 insertions(+), 12 deletions(-) create mode 100644 test/FrameworkSearchPaths.js diff --git a/lib/pbxFile.js b/lib/pbxFile.js index f71d55e..9e04504 100644 --- a/lib/pbxFile.js +++ b/lib/pbxFile.js @@ -36,13 +36,13 @@ function detectLastType(path) { } function fileEncoding(file) { - if (file.lastType != BUNDLE) { + if (file.lastType != BUNDLE && !file.customFramework) { return DEFAULT_FILE_ENCODING; } } function defaultSourceTree(file) { - if (file.lastType == DYLIB || file.lastType == FRAMEWORK) { + if (( file.lastType == DYLIB || file.lastType == FRAMEWORK ) && !file.customFramework) { return 'SDKROOT'; } else { return DEFAULT_SOURCE_TREE; @@ -50,10 +50,12 @@ function defaultSourceTree(file) { } function correctPath(file, filepath) { - if (file.lastType == FRAMEWORK) { + if (file.lastType == FRAMEWORK && !file.customFramework) { return 'System/Library/Frameworks/' + filepath; } else if (file.lastType == DYLIB) { return 'usr/lib/' + filepath; + } else if (file.customFramework == true) { + return file.basename; } else { return filepath; } @@ -62,7 +64,7 @@ function correctPath(file, filepath) { function correctGroup(file) { if (file.lastType == SOURCE_FILE) { return 'Sources'; - } else if (file.lastType == DYLIB || file.lastType == ARCHIVE) { + } else if (file.lastType == DYLIB || file.lastType == ARCHIVE || file.lastType == FRAMEWORK) { return 'Frameworks'; } else { return 'Resources'; @@ -74,8 +76,14 @@ function pbxFile(filepath, opt) { this.lastType = opt.lastType || detectLastType(filepath); - this.path = correctPath(this, filepath); + // for custom frameworks + if(opt.customFramework == true) { + this.customFramework = true; + this.dirname = path.dirname(filepath); + } + this.basename = path.basename(filepath); + this.path = correctPath(this, filepath); this.group = correctGroup(this); this.sourceTree = opt.sourceTree || defaultSourceTree(this); diff --git a/lib/pbxProject.js b/lib/pbxProject.js index 620aa3b..71b89ce 100644 --- a/lib/pbxProject.js +++ b/lib/pbxProject.js @@ -181,9 +181,8 @@ pbxProject.prototype.removeResourceFile = function (path, opt) { return file; } -pbxProject.prototype.addFramework = function (path, opt) { - var file = new pbxFile(path, opt); - +pbxProject.prototype.addFramework = function (fpath, opt) { + var file = new pbxFile(fpath, opt); // catch duplicates if (this.hasFile(file.path)) return false; @@ -194,17 +193,25 @@ pbxProject.prototype.addFramework = function (path, opt) { this.addToPbxFileReferenceSection(file); // PBXFileReference this.addToFrameworksPbxGroup(file); // PBXGroup this.addToPbxFrameworksBuildPhase(file); // PBXFrameworksBuildPhase + + if(opt && opt.customFramework == true) { + this.addToFrameworkSearchPaths(file); + } return file; } -pbxProject.prototype.removeFramework = function (path, opt) { - var file = new pbxFile(path, opt); +pbxProject.prototype.removeFramework = function (fpath, opt) { + var file = new pbxFile(fpath, opt); this.removeFromPbxBuildFileSection(file); // PBXBuildFile this.removeFromPbxFileReferenceSection(file); // PBXFileReference this.removeFromFrameworksPbxGroup(file); // PBXGroup this.removeFromPbxFrameworksBuildPhase(file); // PBXFrameworksBuildPhase + + if(opt && opt.customFramework) { + this.removeFromFrameworkSearchPaths(path.dirname(fpath)); + } return file; } @@ -443,6 +450,54 @@ pbxProject.prototype.updateProductName = function(name) { propReplace(config, 'PRODUCT_NAME', '"' + name + '"'); } +pbxProject.prototype.removeFromFrameworkSearchPaths = function (file) { + var configurations = nonComments(this.pbxXCBuildConfigurationSection()), + INHERITED = '"$(inherited)"', + SEARCH_PATHS = 'FRAMEWORK_SEARCH_PATHS', + config, buildSettings, searchPaths; + var new_path = searchPathForFile(file, this); + + for (config in configurations) { + buildSettings = configurations[config].buildSettings; + + if (unquote(buildSettings['PRODUCT_NAME']) != this.productName) + continue; + + searchPaths = buildSettings[SEARCH_PATHS]; + + if (searchPaths) { + var matches = searchPaths.filter(function(p) { + return p.indexOf(new_path) > -1; + }); + matches.forEach(function(m) { + var idx = searchPaths.indexOf(m); + searchPaths.splice(idx, 1); + }); + } + + } +} + +pbxProject.prototype.addToFrameworkSearchPaths = function (file) { + var configurations = nonComments(this.pbxXCBuildConfigurationSection()), + INHERITED = '"$(inherited)"', + config, buildSettings, searchPaths; + + for (config in configurations) { + buildSettings = configurations[config].buildSettings; + + if (unquote(buildSettings['PRODUCT_NAME']) != this.productName) + continue; + + if (!buildSettings['FRAMEWORK_SEARCH_PATHS'] + || buildSettings['FRAMEWORK_SEARCH_PATHS'] === INHERITED) { + buildSettings['FRAMEWORK_SEARCH_PATHS'] = [INHERITED]; + } + + buildSettings['FRAMEWORK_SEARCH_PATHS'].push(searchPathForFile(file, this)); + } +} + pbxProject.prototype.removeFromLibrarySearchPaths = function (file) { var configurations = nonComments(this.pbxXCBuildConfigurationSection()), INHERITED = '"$(inherited)"', @@ -552,10 +607,8 @@ pbxProject.prototype.__defineGetter__("productName", function () { pbxProject.prototype.hasFile = function (filePath) { var files = nonComments(this.pbxFileReferenceSection()), file, id; - for (id in files) { file = files[id]; - if (file.path == filePath || file.path == ('"' + filePath + '"')) { return true; } @@ -655,6 +708,15 @@ function correctForResourcesPath(file, project) { return file; } +function correctForFrameworksPath(file, project) { + var r_resources_dir = /^Frameworks\//; + + if (project.pbxGroupByName('Frameworks').path) + file.path = file.path.replace(r_resources_dir, ''); + + return file; +} + function searchPathForFile(file, proj) { var plugins = proj.pbxGroupByName('Plugins') pluginsPath = plugins ? plugins.path : null, @@ -668,6 +730,8 @@ function searchPathForFile(file, proj) { if (file.plugin && pluginsPath) { return '"\\"$(SRCROOT)/' + unquote(pluginsPath) + '\\""'; + } else if (file.customFramework && file.dirname) { + return '"' + file.dirname + '"' } else { return '"\\"$(SRCROOT)/' + proj.productName + fileDir + '\\""'; } diff --git a/test/FrameworkSearchPaths.js b/test/FrameworkSearchPaths.js new file mode 100644 index 0000000..d717416 --- /dev/null +++ b/test/FrameworkSearchPaths.js @@ -0,0 +1,46 @@ +var fullProject = require('./fixtures/full-project') + fullProjectStr = JSON.stringify(fullProject), + pbx = require('../lib/pbxProject'), + pbxFile = require('../lib/pbxFile'), + proj = new pbx('.'); + +var pbxFile = { + path:'some/path/include', + dirname: 'some/path', + customFramework: true +} +function cleanHash() { + return JSON.parse(fullProjectStr); +} + +exports.setUp = function (callback) { + proj.hash = cleanHash(); + callback(); +} + +var PRODUCT_NAME = '"KitchenSinktablet"'; + +exports.addAndRemoveToFromFrameworkSearchPaths = { + 'add should add the path to each configuration section':function(test) { + proj.addToFrameworkSearchPaths(pbxFile); + var config = proj.pbxXCBuildConfigurationSection(); + for (var ref in config) { + if (ref.indexOf('_comment') > -1 || config[ref].buildSettings.PRODUCT_NAME != PRODUCT_NAME) continue; + var lib = config[ref].buildSettings.FRAMEWORK_SEARCH_PATHS; + test.ok(lib[1].indexOf('some/path') > -1); + } + test.done(); + }, + 'remove should remove from the path to each configuration section':function(test) { + proj.addToFrameworkSearchPaths(pbxFile); + proj.removeFromFrameworkSearchPaths(pbxFile); + var config = proj.pbxXCBuildConfigurationSection(); + for (var ref in config) { + if (ref.indexOf('_comment') > -1 || config[ref].buildSettings.PRODUCT_NAME != PRODUCT_NAME) continue; + var lib = config[ref].buildSettings.FRAMEWORK_SEARCH_PATHS; + test.ok(lib.length === 1); + test.ok(lib[0].indexOf('some/path') == -1); + } + test.done(); + } +} diff --git a/test/addFramework.js b/test/addFramework.js index 0c6e19c..2bfb6f2 100644 --- a/test/addFramework.js +++ b/test/addFramework.js @@ -13,9 +13,39 @@ exports.setUp = function (callback) { callback(); } +function nonComments(obj) { + var keys = Object.keys(obj), + newObj = {}, i = 0; + + for (i; i < keys.length; i++) { + if (!/_comment$/.test(keys[i])) { + newObj[keys[i]] = obj[keys[i]]; + } + } + + return newObj; +} + +function frameworkSearchPaths(proj) { + var configs = nonComments(proj.pbxXCBuildConfigurationSection()), + allPaths = [], + ids = Object.keys(configs), i, buildSettings; + + for (i = 0; i< ids.length; i++) { + buildSettings = configs[ids[i]].buildSettings; + + if (buildSettings['FRAMEWORK_SEARCH_PATHS']) { + allPaths.push(buildSettings['FRAMEWORK_SEARCH_PATHS']); + } + } + + return allPaths; +} + exports.addFramework = { 'should return a pbxFile': function (test) { var newFile = proj.addFramework('libsqlite3.dylib'); + console.log(newFile); test.equal(newFile.constructor, pbxFile); test.done() @@ -141,5 +171,29 @@ exports.addFramework = { test.ok(!proj.addFramework('libsqlite3.dylib')); test.done(); } + }, + 'should pbxFile correctly for custom frameworks': function (test) { + var newFile = proj.addFramework('/path/to/Custom.framework', {customFramework: true}); + + test.ok(newFile.customFramework); + test.ok(!newFile.fileEncoding); + test.equal(newFile.sourceTree, '""'); + test.equal(newFile.group, 'Frameworks'); + test.equal(newFile.basename, 'Custom.framework'); + test.equal(newFile.dirname, '/path/to'); + // XXX framework has to be copied over to PROJECT root. That is what XCode does when you drag&drop + test.equal(newFile.path, 'Custom.framework'); + + + // should add path to framework search path + var frameworkPaths = frameworkSearchPaths(proj); + expectedPath = '"/path/to"'; + + for (i = 0; i < frameworkPaths.length; i++) { + var current = frameworkPaths[i]; + test.ok(current.indexOf('"$(inherited)"') >= 0); + test.ok(current.indexOf(expectedPath) >= 0); + } + test.done(); } } diff --git a/test/removeFramework.js b/test/removeFramework.js index 869752d..20667f7 100644 --- a/test/removeFramework.js +++ b/test/removeFramework.js @@ -13,6 +13,35 @@ exports.setUp = function (callback) { callback(); } +function nonComments(obj) { + var keys = Object.keys(obj), + newObj = {}, i = 0; + + for (i; i < keys.length; i++) { + if (!/_comment$/.test(keys[i])) { + newObj[keys[i]] = obj[keys[i]]; + } + } + + return newObj; +} + +function frameworkSearchPaths(proj) { + var configs = nonComments(proj.pbxXCBuildConfigurationSection()), + allPaths = [], + ids = Object.keys(configs), i, buildSettings; + + for (i = 0; i< ids.length; i++) { + buildSettings = configs[ids[i]].buildSettings; + + if (buildSettings['FRAMEWORK_SEARCH_PATHS']) { + allPaths.push(buildSettings['FRAMEWORK_SEARCH_PATHS']); + } + } + + return allPaths; +} + exports.removeFramework = { 'should return a pbxFile': function (test) { var newFile = proj.addFramework('libsqlite3.dylib'); @@ -98,6 +127,28 @@ exports.removeFramework = { test.equal(frameworks.files.length, 15); + test.done(); + }, + 'should remove custom frameworks': function (test) { + var newFile = proj.addFramework('/path/to/Custom.framework'), + frameworks = proj.pbxFrameworksBuildPhaseObj(); + + test.equal(frameworks.files.length, 16); + + var deletedFile = proj.removeFramework('/path/to/Custom.framework'), + frameworks = proj.pbxFrameworksBuildPhaseObj(); + + test.equal(frameworks.files.length, 15); + + var frameworkPaths = frameworkSearchPaths(proj); + expectedPath = '"/path/to"'; + + for (i = 0; i < frameworkPaths.length; i++) { + var current = frameworkPaths[i]; + test.ok(current.indexOf('"$(inherited)"') == -1); + test.ok(current.indexOf(expectedPath) == -1); + } + test.done(); } }