CB-7330 Don't run "android update" during creation Instead, have the build script copy do the equivalent logic on each build.
Advantages: - Scripts run much faster - No more duplicate CordovaLib entries in project.properties - Building is more independent from create/update script (more robust) Project: http://git-wip-us.apache.org/repos/asf/cordova-android/repo Commit: http://git-wip-us.apache.org/repos/asf/cordova-android/commit/dfa66b9d Tree: http://git-wip-us.apache.org/repos/asf/cordova-android/tree/dfa66b9d Diff: http://git-wip-us.apache.org/repos/asf/cordova-android/diff/dfa66b9d Branch: refs/heads/4.0.x Commit: dfa66b9dd44e52e96fc2c8b4d1c8661c8b0074bb Parents: d56ea25 Author: Andrew Grieve <[email protected]> Authored: Mon Aug 18 23:21:26 2014 -0400 Committer: Andrew Grieve <[email protected]> Committed: Mon Aug 18 23:24:29 2014 -0400 ---------------------------------------------------------------------- bin/lib/check_reqs.js | 68 ++++++++++++--------- bin/lib/create.js | 51 ++++++++++++---- bin/templates/cordova/lib/build.js | 87 ++++++++++++++++++++------- bin/templates/project/project.properties | 15 +++++ 4 files changed, 159 insertions(+), 62 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-android/blob/dfa66b9d/bin/lib/check_reqs.js ---------------------------------------------------------------------- diff --git a/bin/lib/check_reqs.js b/bin/lib/check_reqs.js index 7794594..40f3ade 100644 --- a/bin/lib/check_reqs.js +++ b/bin/lib/check_reqs.js @@ -67,7 +67,18 @@ module.exports.get_target = function() { // Returns a promise. Called only by build and clean commands. module.exports.check_ant = function() { return tryCommand('ant -version', 'Failed to run "ant -version", make sure you have ant installed and added to your PATH.'); -} +}; + +// Returns a promise. Called only by build and clean commands. +module.exports.check_gradle = function() { + var sdkDir = process.env['ANDROID_HOME']; + var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper'); + if (!fs.existsSync(wrapperDir)) { + return Q.reject(new Error('Could not find gradle wrapper within android sdk. Might need to update your Android SDK.\n' + + 'Looked here: ' + wrapperDir)); + } + return Q.when(); +}; // Returns a promise. module.exports.check_java = function() { @@ -126,41 +137,44 @@ module.exports.check_java = function() { // Returns a promise. module.exports.check_android = function() { - var androidCmdPath = forgivingWhichSync('android'); - var adbInPath = !!forgivingWhichSync('adb'); - var hasAndroidHome = !!process.env['ANDROID_HOME'] && fs.existsSync(process.env['ANDROID_HOME']); - if (hasAndroidHome && !androidCmdPath) { - process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'tools'); - } - if (androidCmdPath && !hasAndroidHome) { - var parentDir = path.dirname(androidCmdPath); - if (path.basename(parentDir) == 'tools') { - process.env['ANDROID_HOME'] = path.dirname(parentDir); - hasAndroidHome = true; + return Q().then(function() { + var androidCmdPath = forgivingWhichSync('android'); + var adbInPath = !!forgivingWhichSync('adb'); + var hasAndroidHome = !!process.env['ANDROID_HOME'] && fs.existsSync(process.env['ANDROID_HOME']); + if (hasAndroidHome && !androidCmdPath) { + process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'tools'); } - } - if (hasAndroidHome && !adbInPath) { - process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'platform-tools'); - } - - var valid_target = this.get_target(); - var msg = 'Failed to run "android". Make sure you have the latest Android SDK installed, and that the "android" command (inside the tools/ folder) is added to your PATH.'; - return tryCommand('android list targets', msg) - .then(function(output) { - if (!output.match(valid_target)) { - return Q.reject(new Error('Please install Android target ' + valid_target.split('-')[1] + - ' (the Android newest SDK). Make sure you have the latest Android tools installed as well.' + - ' Run "android" from your command-line to install/update any missing SDKs or tools.')); + if (androidCmdPath && !hasAndroidHome) { + var parentDir = path.dirname(androidCmdPath); + if (path.basename(parentDir) == 'tools') { + process.env['ANDROID_HOME'] = path.dirname(parentDir); + hasAndroidHome = true; + } + } + if (hasAndroidHome && !adbInPath) { + process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'platform-tools'); } - }).then(function() { if (!process.env['ANDROID_HOME']) { throw new Error('ANDROID_HOME is not set and "android" command not in your PATH. You must fulfill at least one of these conditions.'); } if (!fs.existsSync(process.env['ANDROID_HOME'])) { throw new Error('ANDROID_HOME is set to a non-existant path: ' + process.env['ANDROID_HOME']); } + // Check that the target sdk level is installed. + return module.exports.check_android_target(module.exports.get_target()); }); -} +}; + +module.exports.check_android_target = function(valid_target) { + var msg = 'Failed to run "android". Make sure you have the latest Android SDK installed, and that the "android" command (inside the tools/ folder) is added to your PATH.'; + return tryCommand('android list targets', msg) + .then(function(output) { + if (!output.match(valid_target)) { + throw new Error('Please install Android target "' + valid_target + '".\n' + + 'Hint: Run "android" from your command-line to open the SDK manager.'); + } + }); +}; // Returns a promise. module.exports.run = function() { http://git-wip-us.apache.org/repos/asf/cordova-android/blob/dfa66b9d/bin/lib/create.js ---------------------------------------------------------------------- diff --git a/bin/lib/create.js b/bin/lib/create.js index cc42f19..13447da 100755 --- a/bin/lib/create.js +++ b/bin/lib/create.js @@ -83,9 +83,38 @@ function copyJsAndLibrary(projectPath, shared, projectName) { } } -function runAndroidUpdate(projectPath, target_api, shared) { - var targetFrameworkDir = getFrameworkDir(projectPath, shared); - return exec('android update project --subprojects --path "' + projectPath + '" --target ' + target_api + ' --library "' + path.relative(projectPath, targetFrameworkDir) + '"'); +function extractSubProjectPaths(data) { + var ret = {}; + var r = /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg + var m; + while (m = r.exec(data)) { + ret[m[1]] = 1; + } + return Object.keys(ret); +} + +function writeProjectProperties(projectPath, target_api, shared) { + var dstPath = path.join(projectPath, 'project.properties'); + var templatePath = path.join(ROOT, 'bin', 'templates', 'project', 'project.properties'); + var srcPath = fs.existsSync(dstPath) ? dstPath : templatePath; + var data = fs.readFileSync(srcPath, 'utf8'); + data = data.replace(/^target=.*/m, 'target=' + target_api); + var subProjects = extractSubProjectPaths(data); + subProjects = subProjects.filter(function(p) { + return !(/^CordovaLib$/m.exec(p) || + /[\\\/]cordova-android[\\\/]framework$/m.exec(p) || + /^(\.\.[\\\/])+framework$/m.exec(p) + ); + }); + subProjects.unshift(shared ? path.relative(projectPath, path.join(ROOT, 'framework')) : 'CordovaLib'); + data = data.replace(/^\s*android\.library\.reference\.\d+=.*\n/mg, ''); + if (!/\n$/.exec(data)) { + data += '\n'; + } + for (var i = 0; i < subProjects.length; ++i) { + data += 'android.library.reference.' + (i+1) + '=' + subProjects[i] + '\n'; + } + fs.writeFileSync(dstPath, data); } function copyBuildRules(projectPath) { @@ -251,7 +280,7 @@ exports.createProject = function(project_path, package_name, project_name, proje copyBuildRules(project_path); }); // Link it to local android install. - return runAndroidUpdate(project_path, target_api, use_shared_project); + writeProjectProperties(project_path, target_api); }).then(function() { console.log('Project successfully created.'); }); @@ -274,22 +303,20 @@ function extractProjectNameFromManifest(projectPath) { } // Returns a promise. -exports.updateProject = function(projectPath) { - var version = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf-8').trim(); +exports.updateProject = function(projectPath, shared) { + var newVersion = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf-8').trim(); // Check that requirements are met and proper targets are installed return check_reqs.run() .then(function() { var projectName = extractProjectNameFromManifest(projectPath); var target_api = check_reqs.get_target(); - copyJsAndLibrary(projectPath, false, projectName); + copyJsAndLibrary(projectPath, shared, projectName); copyScripts(projectPath); copyBuildRules(projectPath); removeDebuggableFromManifest(projectPath); - return runAndroidUpdate(projectPath, target_api, false) - .then(function() { - console.log('Android project is now at version ' + version); - console.log('If you updated from a pre-3.2.0 version and use an IDE, we now require that you import the "CordovaLib" library project.'); - }); + writeProjectProperties(projectPath, target_api, shared); + console.log('Android project is now at version ' + newVersion); + console.log('If you updated from a pre-3.2.0 version and use an IDE, we now require that you import the "CordovaLib" library project.'); }); }; http://git-wip-us.apache.org/repos/asf/cordova-android/blob/dfa66b9d/bin/templates/cordova/lib/build.js ---------------------------------------------------------------------- diff --git a/bin/templates/cordova/lib/build.js b/bin/templates/cordova/lib/build.js index 071990f..7830bd6 100644 --- a/bin/templates/cordova/lib/build.js +++ b/bin/templates/cordova/lib/build.js @@ -27,9 +27,9 @@ var shell = require('shelljs'), ROOT = path.join(__dirname, '..', '..'); var check_reqs = require('./check_reqs'); -// Globals -var build_type, - build_method; +var LOCAL_PROPERTIES_TEMPLATE = + '# This file is automatically generated.\n' + + '# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n'; function find_files(directory, predicate) { if (fs.existsSync(directory)) { @@ -51,22 +51,25 @@ function hasCustomRules() { return fs.existsSync(path.join(ROOT, 'custom_rules.xml')); } -// Copy the gradle wrapper files on each build so that: -// A) We don't require the Android SDK at project creation time, and -// B) So that they are always up-to-date. -function copyGradleWrapper() { - var projectPath = ROOT; - // check_reqs ensures that this is set. - var sdkDir = process.env['ANDROID_HOME']; - var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper'); - if (process.platform == 'win32') { - shell.cp('-f', path.join(wrapperDir, 'gradlew.bat'), projectPath); - } else { - shell.cp('-f', path.join(wrapperDir, 'gradlew'), projectPath); +function extractProjectNameFromManifest(projectPath) { + var manifestPath = path.join(projectPath, 'AndroidManifest.xml'); + var manifestData = fs.readFileSync(manifestPath, 'utf8'); + var m = /<activity[\s\S]*?android:name\s*=\s*"(.*?)"/i.exec(manifestData); + if (!m) { + throw new Error('Could not find activity name in ' + manifestPath); + } + return m[1]; +} + +function extractSubProjectPaths() { + var data = fs.readFileSync(path.join(ROOT, 'project.properties'), 'utf8'); + var ret = {}; + var r = /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg + var m; + while (m = r.exec(data)) { + ret[m[1]] = 1; } - shell.rm('-rf', path.join(projectPath, 'gradle', 'wrapper')); - shell.mkdir('-p', path.join(projectPath, 'gradle')); - shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(projectPath, 'gradle')); + return Object.keys(ret); } var builders = { @@ -83,6 +86,23 @@ var builders = { prepEnv: function() { return check_reqs.check_ant() .then(function() { + // Copy in build.xml on each build so that: + // A) we don't require the Android SDK at project creation time, and + // B) we always use the SDK's latest version of it. + var sdkDir = process.env['ANDROID_HOME']; + var buildTemplate = fs.readFileSync(path.join(sdkDir, 'tools', 'lib', 'build.template'), 'utf8'); + function writeBuildXml(projectPath) { + var newData = buildTemplate.replace('PROJECT_NAME', extractProjectNameFromManifest(ROOT)); + fs.writeFileSync(path.join(projectPath, 'build.xml'), newData); + if (!fs.existsSync(path.join(projectPath, 'local.properties'))) { + fs.writeFileSync(path.join(projectPath, 'local.properties'), LOCAL_PROPERTIES_TEMPLATE); + } + } + var subProjects = extractSubProjectPaths(); + writeBuildXml(ROOT); + for (var i = 0; i < subProjects.length; ++i) { + writeBuildXml(path.join(ROOT, subProjects[i])); + } }); }, @@ -156,7 +176,24 @@ var builders = { }, prepEnv: function() { - return Q(); + return check_reqs.check_gradle() + .then(function() { + // Copy the gradle wrapper on each build so that: + // A) we don't require the Android SDK at project creation time, and + // B) we always use the SDK's latest version of it. + var projectPath = ROOT; + // check_reqs ensures that this is set. + var sdkDir = process.env['ANDROID_HOME']; + var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper'); + if (process.platform == 'win32') { + shell.cp('-f', path.join(wrapperDir, 'gradlew.bat'), projectPath); + } else { + shell.cp('-f', path.join(wrapperDir, 'gradlew'), projectPath); + } + shell.rm('-rf', path.join(projectPath, 'gradle', 'wrapper')); + shell.mkdir('-p', path.join(projectPath, 'gradle')); + shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(projectPath, 'gradle')); + }); }, /* @@ -182,8 +219,6 @@ var builders = { copyGradleWrapper(); return Q().then(function() { return spawn(wrapper, args); - }).then(function() { - return builder.getOutputFiles(build_type); }); }, @@ -261,9 +296,11 @@ function parseOpts(options) { module.exports.runClean = function(options) { var opts = parseOpts(options); var builder = builders[opts.buildMethod]; - return builder.prepEnv(opts.buildType) + return builder.prepEnv() .then(function() { return builder.clean(); + }).then(function() { + shell.rm('-rf', path.join(ROOT, 'out')); }); }; @@ -275,10 +312,13 @@ module.exports.run = function(options) { var opts = parseOpts(options); var builder = builders[opts.buildMethod]; - return builder.prepEnv(opts.buildType) + return builder.prepEnv() .then(function() { return builder.build(opts.buildType); }).then(function(apkFiles) { + // TODO: Rather than copy apks to out, it might be better to + // just write out what the last .apk build was. These files + // are used by get_apk(). var outputDir = path.join(ROOT, 'out'); shell.mkdir('-p', outputDir); for (var i=0; i < apkFiles.length; ++i) { @@ -299,6 +339,7 @@ module.exports.get_apk = function(build_type) { console.error('ERROR : No .apk found in ' + outputDir + ' directory'); process.exit(2); } + // TODO: Use build_type here. console.log('Using apk: ' + candidates[0]); return candidates[0]; }; http://git-wip-us.apache.org/repos/asf/cordova-android/blob/dfa66b9d/bin/templates/project/project.properties ---------------------------------------------------------------------- diff --git a/bin/templates/project/project.properties b/bin/templates/project/project.properties new file mode 100644 index 0000000..ddd3a06 --- /dev/null +++ b/bin/templates/project/project.properties @@ -0,0 +1,15 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +android.library.reference.1=CordovaLib +# Project target. +target=This_gets_replaced
