CB-6698: Support library references for Android via the framework tag
Project: http://git-wip-us.apache.org/repos/asf/cordova-lib/repo Commit: http://git-wip-us.apache.org/repos/asf/cordova-lib/commit/513967db Tree: http://git-wip-us.apache.org/repos/asf/cordova-lib/tree/513967db Diff: http://git-wip-us.apache.org/repos/asf/cordova-lib/diff/513967db Branch: refs/heads/master Commit: 513967dbc8f983ebfe08ba7b96cfc4211ffd7515 Parents: 71e7ba3 Author: Martin Bektchiev <[email protected]> Authored: Tue May 27 14:53:19 2014 +0300 Committer: Mark Koudritsky <[email protected]> Committed: Tue Jun 3 14:44:03 2014 -0400 ---------------------------------------------------------------------- cordova-lib/npm-shrinkwrap.json | 3 + cordova-lib/package.json | 49 ++++---- .../spec-plugman/platforms/android.spec.js | 91 +++++++++++++++ .../spec-plugman/util/action-stack.spec.js | 14 +-- cordova-lib/src/plugman/install.js | 2 +- cordova-lib/src/plugman/platforms/android.js | 115 ++++++++++++++++++- cordova-lib/src/plugman/platforms/ios.js | 2 +- cordova-lib/src/plugman/util/config-changes.js | 22 ++-- 8 files changed, 252 insertions(+), 46 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/513967db/cordova-lib/npm-shrinkwrap.json ---------------------------------------------------------------------- diff --git a/cordova-lib/npm-shrinkwrap.json b/cordova-lib/npm-shrinkwrap.json index 2debb81..3e53081 100644 --- a/cordova-lib/npm-shrinkwrap.json +++ b/cordova-lib/npm-shrinkwrap.json @@ -464,6 +464,9 @@ } } }, + "properties-parser": { + "from": "git://github.com/mbektchiev/node-properties-parser#cordova-lib" + }, "q": { "version": "0.9.7", "from": "q@~0.9" http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/513967db/cordova-lib/package.json ---------------------------------------------------------------------- diff --git a/cordova-lib/package.json b/cordova-lib/package.json index 77344bd..fda29d6 100644 --- a/cordova-lib/package.json +++ b/cordova-lib/package.json @@ -8,36 +8,37 @@ "url": "git://git-wip-us.apache.org/repos/asf/cordova-lib.git" }, "bugs": { - "url" : "https://issues.apache.org/jira/browse/CB", - "email" : "[email protected]" + "url": "https://issues.apache.org/jira/browse/CB", + "email": "[email protected]" }, "main": "cordova-lib.js", "engines": { "node": ">=0.9.9" }, - "engineStrict":true, + "engineStrict": true, "dependencies": { - "glob": "3.2.x", + "bplist-parser": "0.0.x", + "dep-graph": "1.1.0", "elementtree": "0.1.5", - "xcode": "0.6.6", + "glob": "3.2.x", + "mime": "~1.2.11", + "npm": "1.3.4", + "npmconf": "0.1.x", + "osenv": "0.0.x", "plist-with-patches": "0.5.x", - "bplist-parser": "0.0.x", - "shelljs": "0.1.x", - "underscore":"1.4.4", - "dep-graph":"1.1.0", - "semver": "2.0.x", + "properties-parser": "git://github.com/mbektchiev/node-properties-parser", "q": "~0.9", - "npm": "1.3.4", "rc": "0.3.0", - "tar": "0.1.x", "request": "2.22.0", - "npmconf": "0.1.x", - "mime": "~1.2.11", - "osenv": "0.0.x" + "semver": "2.0.x", + "shelljs": "0.1.x", + "tar": "0.1.x", + "underscore": "1.4.4", + "xcode": "0.6.6" }, "devDependencies": { "temp": "0.6.x", - "jasmine-node": "~1" + "jasmine-node": "~1" }, "scripts": { "test": "jasmine-node --color spec-plugman spec-cordova" @@ -136,26 +137,26 @@ "email": "[email protected]" }, { - "name":"Jesse", - "email":"[email protected]" + "name": "Jesse", + "email": "[email protected]" }, { - "name":"Anis Kadri" + "name": "Anis Kadri" }, { - "name":"Ryan Willoughby" + "name": "Ryan Willoughby" }, { - "name":"Brett Rudd" + "name": "Brett Rudd" }, { - "name":"Shazron Abdullah" + "name": "Shazron Abdullah" }, { - "name":"Steve Gill" + "name": "Steve Gill" }, { - "name":"Jesse MacFadyen" + "name": "Jesse MacFadyen" } ] } http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/513967db/cordova-lib/spec-plugman/platforms/android.spec.js ---------------------------------------------------------------------- diff --git a/cordova-lib/spec-plugman/platforms/android.spec.js b/cordova-lib/spec-plugman/platforms/android.spec.js index 1bacc27..df11ec7 100644 --- a/cordova-lib/spec-plugman/platforms/android.spec.js +++ b/cordova-lib/spec-plugman/platforms/android.spec.js @@ -6,6 +6,7 @@ var android = require('../../src/plugman/platforms/android'), shell = require('shelljs'), et = require('elementtree'), os = require('osenv'), + _ = require('underscore'), temp = path.join(os.tmpdir(), 'plugman'), plugins_dir = path.join(temp, 'cordova', 'plugins'), xml_helpers = require('../../src/util/xml-helpers'), @@ -113,6 +114,75 @@ describe('android project handler', function() { }).toThrow('"' + target + '" already exists!'); }); }); + describe('of <framework> elements', function() { + afterEach(function() { + android.purgeProjectFileCache(temp); + }); + it('should update the main and library projects', function() { + var frameworkElement = { attrib: { src: "LibraryPath", custom: true } }; + var subDir = path.resolve(temp, frameworkElement.attrib.src); + var mainProjectPropsFile = path.resolve(temp, "project.properties"); + var subProjectPropsFile = path.resolve(subDir, "project.properties"); + + var existsSync = spyOn( fs, 'existsSync').andReturn(true); + var writeFileSync = spyOn(fs, 'writeFileSync'); + var readFileSync = spyOn(fs, 'readFileSync').andCallFake(function (file) { + if (path.normalize(file) === mainProjectPropsFile) { + return '#Some comment\ntarget=android-19\nandroid.library.reference.1=ExistingLibRef1\nandroid.library.reference.2=ExistingLibRef2'; + } else if (path.normalize(file) === subProjectPropsFile) { + return '#Some comment\ntarget=android-17\nandroid.library=true'; + } else { + throw new Error("Trying to read from an unexpected file " + file); + } + }) + var exec = spyOn(shell, 'exec'); + + android['framework'].install(frameworkElement, dummyplugin, temp); + android.parseProjectFile(temp).write(); + + expect(_.any(writeFileSync.argsForCall, function (callArgs) { + return callArgs[0] === mainProjectPropsFile && callArgs[1].indexOf('\nandroid.library.reference.3=LibraryPath\n') > -1; + })).toBe(true, 'Reference to library not added'); + expect(_.any(writeFileSync.argsForCall, function (callArgs) { + return callArgs[0] === subProjectPropsFile && callArgs[1].indexOf('\ntarget=android-19\n') > -1; + })).toBe(true, 'target SDK version not copied to library'); + expect(exec).toHaveBeenCalledWith('android update lib-project --path ' + subDir); + }); + it('with custom=false should update the main and library projects', function() { + var frameworkElement = { attrib: { src: "extras/android/support/v7/appcompat" } }; + var subDir = path.resolve("~/android-sdk", frameworkElement.attrib.src); + var localPropsFile = path.resolve(temp, "local.properties"); + var mainProjectPropsFile = path.resolve(temp, "project.properties"); + var subProjectPropsFile = path.resolve(subDir, "project.properties"); + + var existsSync = spyOn( fs, 'existsSync').andReturn(true); + var writeFileSync = spyOn(fs, 'writeFileSync'); + var readFileSync = spyOn(fs, 'readFileSync').andCallFake(function (file) { + if (path.normalize(file) === mainProjectPropsFile) { + return '#Some comment\ntarget=android-19\nandroid.library.reference.1=ExistingLibRef1\nandroid.library.reference.2=ExistingLibRef2'; + } else if (path.normalize(file) === subProjectPropsFile) { + return '#Some comment\ntarget=android-17\nandroid.library=true'; + } else if (path.normalize(file) === localPropsFile) { + return "sdk.dir=~/android-sdk"; + } else { + throw new Error("Trying to read from an unexpected file " + file); + } + }) + var exec = spyOn(shell, 'exec'); + + android['framework'].install(frameworkElement, dummyplugin, temp); + android.parseProjectFile(temp).write(); + + var relativePath = android.getRelativeLibraryPath(temp, subDir); + expect(_.any(writeFileSync.argsForCall, function (callArgs) { + return callArgs[0] === mainProjectPropsFile && callArgs[1].indexOf('\nandroid.library.reference.3=' + relativePath + '\n') > -1; + })).toBe(true, 'Reference to library not added'); + expect(_.any(writeFileSync.argsForCall, function (callArgs) { + return callArgs[0] === subProjectPropsFile && callArgs[1].indexOf('\ntarget=android-19\n') > -1; + })).toBe(true, 'target SDK version not copied to library'); + expect(exec).toHaveBeenCalledWith('android update lib-project --path ' + subDir); + }); + }); }); describe('uninstallation', function() { @@ -152,5 +222,26 @@ describe('android project handler', function() { }); }); }); + describe('of <framework> elements', function() { + it('should remove library reference from the main project', function() { + var frameworkElement = { attrib: { src: "LibraryPath" } }; + var sub_dir = path.resolve(temp, frameworkElement.attrib.src); + var mainProjectProps = path.resolve(temp, "project.properties"); + var existsSync = spyOn( fs, 'existsSync').andReturn(true); + var writeFileSync = spyOn(fs, 'writeFileSync'); + var readFileSync = spyOn(fs, 'readFileSync').andCallFake(function (file) { + if (path.normalize(file) === mainProjectProps) + return '#Some comment\ntarget=android-19\nandroid.library.reference.1=ExistingLibRef1\nandroid.library.reference.2=LibraryPath\nandroid.library.reference.3=ExistingLibRef2\n'; + }) + var exec = spyOn(shell, 'exec'); + + android['framework'].uninstall(frameworkElement, temp); + android.parseProjectFile(temp).write(); + + expect(_.any(writeFileSync.argsForCall, function (callArgs) { + return callArgs[0] === mainProjectProps && callArgs[1].indexOf('\nandroid.library.reference.2=ExistingLibRef2\n') > -1; + })).toBe(true, 'Reference to library not removed'); + }); + }); }); }); http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/513967db/cordova-lib/spec-plugman/util/action-stack.spec.js ---------------------------------------------------------------------- diff --git a/cordova-lib/spec-plugman/util/action-stack.spec.js b/cordova-lib/spec-plugman/util/action-stack.spec.js index ef59164..b55ce73 100644 --- a/cordova-lib/spec-plugman/util/action-stack.spec.js +++ b/cordova-lib/spec-plugman/util/action-stack.spec.js @@ -18,9 +18,9 @@ describe('action-stack', function() { stack.push(stack.createAction(second_spy, second_args, function(){}, [])); stack.push(stack.createAction(third_spy, third_args, function(){}, [])); stack.process('android', 'blah'); - expect(first_spy).toHaveBeenCalledWith(first_args[0]); - expect(second_spy).toHaveBeenCalledWith(second_args[0]); - expect(third_spy).toHaveBeenCalledWith(third_args[0]); + expect(first_spy).toHaveBeenCalledWith(first_args[0], jasmine.any(Object)); + expect(second_spy).toHaveBeenCalledWith(second_args[0], jasmine.any(Object)); + expect(third_spy).toHaveBeenCalledWith(third_args[0], jasmine.any(Object)); }); it('should revert processed actions if an exception occurs', function() { spyOn(console, 'log'); @@ -47,11 +47,11 @@ describe('action-stack', function() { runs(function() { expect(error).toEqual(process_err); // first two actions should have been called, but not the third - expect(first_spy).toHaveBeenCalledWith(first_args[0]); - expect(second_spy).toHaveBeenCalledWith(second_args[0]); - expect(third_spy).not.toHaveBeenCalledWith(third_args[0]); + expect(first_spy).toHaveBeenCalledWith(first_args[0], jasmine.any(Object)); + expect(second_spy).toHaveBeenCalledWith(second_args[0], jasmine.any(Object)); + expect(third_spy).not.toHaveBeenCalledWith(third_args[0], jasmine.any(Object)); // first reverter should have been called after second action exploded - expect(first_reverter).toHaveBeenCalledWith(first_reverter_args[0]); + expect(first_reverter).toHaveBeenCalledWith(first_reverter_args[0], jasmine.any(Object)); }); }); }); http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/513967db/cordova-lib/src/plugman/install.js ---------------------------------------------------------------------- diff --git a/cordova-lib/src/plugman/install.js b/cordova-lib/src/plugman/install.js index d779989..2fbbe50 100644 --- a/cordova-lib/src/plugman/install.js +++ b/cordova-lib/src/plugman/install.js @@ -493,7 +493,7 @@ function handleInstall(actions, pluginInfo, platform, project_dir, plugins_dir, var sourceFiles = platformTag.findall('./source-file'), headerFiles = platformTag.findall('./header-file'), resourceFiles = platformTag.findall('./resource-file'), - frameworkFiles = platformTag.findall('./framework[@custom="true"]'), // CB-5238 adding only custom frameworks + frameworkFiles = platformTag.findall('./framework'), libFiles = platformTag.findall('./lib-file'); http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/513967db/cordova-lib/src/plugman/platforms/android.js ---------------------------------------------------------------------- diff --git a/cordova-lib/src/plugman/platforms/android.js b/cordova-lib/src/plugman/platforms/android.js index 0147d08..e105c5b 100644 --- a/cordova-lib/src/plugman/platforms/android.js +++ b/cordova-lib/src/plugman/platforms/android.js @@ -21,7 +21,11 @@ var fs = require('fs') // use existsSync in 0.6.x , path = require('path') , common = require('./common') , events = require('../events') - , xml_helpers = require(path.join(__dirname, '..', '..', 'util', 'xml-helpers')); + , xml_helpers = require(path.join(__dirname, '..', '..', 'util', 'xml-helpers')) + , properties_parser = require('properties-parser') + , shell = require('shelljs'); + +var projectFileCache = {}; module.exports = { www_dir:function(project_dir) { @@ -80,10 +84,115 @@ module.exports = { }, "framework": { install:function(source_el, plugin_dir, project_dir, plugin_id) { - events.emit('verbose', 'framework.install is not supported for android'); + var src = source_el.attrib.src; + var custom = source_el.attrib.custom; + if (!src) throw new Error('src not specified in framework element'); + + events.emit('verbose', "Installing Android library: " + src); + var parent = source_el.attrib.parent; + var parentDir = parent ? path.resolve(project_dir, parent) : project_dir; + var subDir; + if (custom) { + subDir = path.resolve(project_dir, src); + } else { + var localProperties = properties_parser.createEditor(path.resolve(project_dir, "local.properties")); + subDir = path.resolve(localProperties.get("sdk.dir"), src); + } + var projectConfig = module.exports.parseProjectFile(project_dir); + projectConfig.addSubProject(parentDir, subDir); }, uninstall:function(source_el, project_dir, plugin_id) { - events.emit('verbose', 'framework.uninstall is not supported for android'); + var src = source_el.attrib.src; + if (!src) throw new Error('src not specified in framework element'); + + events.emit('verbose', "Uninstalling Android library: " + src); + var parent = source_el.attrib.parent; + var parentDir = parent ? path.resolve(project_dir, parent) : project_dir; + var subDir = path.resolve(project_dir, src); + var projectConfig = module.exports.parseProjectFile(project_dir); + projectConfig.removeSubProject(parentDir, subDir); } + }, + parseProjectFile: function(project_dir){ + if (!projectFileCache[project_dir]) { + projectFileCache[project_dir] = { + propertiesEditors: {}, + subProjectDirs : {}, + addSubProject: function(parentDir, subDir) { + var subProjectFile = path.resolve(subDir, "project.properties"); + if (!fs.existsSync(subProjectFile)) throw new Error('cannot find "' + subProjectFile + '" referenced in <framework>'); + + var parentProjectFile = path.resolve(parentDir, "project.properties"); + var parentProperties = this._getPropertiesFile(parentProjectFile); + addLibraryReference(parentProperties, module.exports.getRelativeLibraryPath(parentDir, subDir)); + + var subProperties = this._getPropertiesFile(subProjectFile); + subProperties.set("target", parentProperties.get("target")); + + this.subProjectDirs[subDir] = true; + this._dirty = true; + }, + removeSubProject: function(parentDir, subDir) { + var parentProjectFile = path.resolve(parentDir, "project.properties"); + var parentProperties = this._getPropertiesFile(parentProjectFile); + removeLibraryReference(parentProperties, module.exports.getRelativeLibraryPath(parentDir, subDir)); + delete this.subProjectDirs[subDir]; + this._dirty = true; + }, + write: function () { + if (!this._dirty) return; + + for (var filename in this.propertiesEditors) { + fs.writeFileSync(filename, this.propertiesEditors[filename].toString()); + } + + for (var sub_dir in this.subProjectDirs) + { + shell.exec("android update lib-project --path " + sub_dir); + } + this._dirty = false; + }, + _dirty : false, + _getPropertiesFile: function (filename) { + if (!this.propertiesEditors[filename]) + this.propertiesEditors[filename] = properties_parser.createEditor(filename); + + return this.propertiesEditors[filename]; + } + }; + } + + return projectFileCache[project_dir]; + }, + purgeProjectFileCache:function(project_dir) { + delete projectFileCache[project_dir]; + }, + getRelativeLibraryPath: function (parentDir, subDir) { + var libraryPath = path.relative(parentDir, subDir); + return (path.sep == '\\') ? libraryPath.replace(/\\/g, '/') : libraryPath; } }; + +function addLibraryReference(projectProperties, libraryPath) { + var i = 1; + while (projectProperties.get("android.library.reference." + i)) + i++; + + projectProperties.set("android.library.reference." + i, libraryPath); +} + +function removeLibraryReference(projectProperties, libraryPath) { + var i = 1; + var currentLib; + while (currentLib = projectProperties.get("android.library.reference." + i)) { + if (currentLib === libraryPath) { + while (currentLib = projectProperties.get("android.library.reference." + (i + 1))) { + projectProperties.set("android.library.reference." + i, currentLib); + i++; + } + projectProperties.set("android.library.reference." + i); + break; + } + i++; + } +} http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/513967db/cordova-lib/src/plugman/platforms/ios.js ---------------------------------------------------------------------- diff --git a/cordova-lib/src/plugman/platforms/ios.js b/cordova-lib/src/plugman/platforms/ios.js index 4f570c3..1c5d8ea 100644 --- a/cordova-lib/src/plugman/platforms/ios.js +++ b/cordova-lib/src/plugman/platforms/ios.js @@ -125,7 +125,7 @@ module.exports = { custom = framework_el.attrib['custom'], srcFile = path.resolve(plugin_dir, src), targetDir = path.resolve(project.plugins_dir, plugin_id, path.basename(src)); - if (!custom) throw new Error('cannot add non custom frameworks.'); + if (!custom) return; //non-custom frameworks are processed in config-changes.js if (!fs.existsSync(srcFile)) throw new Error('cannot find "' + srcFile + '" ios <framework>'); if (fs.existsSync(targetDir)) throw new Error('target destination "' + targetDir + '" already exists'); shell.mkdir('-p', path.dirname(targetDir)); http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/513967db/cordova-lib/src/plugman/util/config-changes.js ---------------------------------------------------------------------- diff --git a/cordova-lib/src/plugman/util/config-changes.js b/cordova-lib/src/plugman/util/config-changes.js index bc24a7f..8e3b614 100644 --- a/cordova-lib/src/plugman/util/config-changes.js +++ b/cordova-lib/src/plugman/util/config-changes.js @@ -305,16 +305,18 @@ function generate_plugin_config_munge(plugin_dir, vars) { // note down pbxproj framework munges in special section of munge obj // CB-5238 this is only for systems frameworks - var frameworks = platformTag.findall('framework'); - frameworks.forEach(function(f) { - var custom = f.attrib['custom']; - if(!custom) { - var file = f.attrib['src']; - var weak = ('true' == f.attrib['weak']).toString(); - - deep_add(munge, 'framework', file, { xml: weak, count: 1 }); - } - }); + if (self.platform === 'ios') { + var frameworks = platformTag.findall('framework'); + frameworks.forEach(function (f) { + var custom = f.attrib['custom']; + if (!custom) { + var file = f.attrib['src']; + var weak = ('true' == f.attrib['weak']).toString(); + + deep_add(munge, 'framework', file, { xml: weak, count: 1 }); + } + }); + } } changes.forEach(function(change) {
