From 9c239804d3ab8fdbbbc76808eef2f50cc113516b Mon Sep 17 00:00:00 2001 From: Joe Bowser Date: Thu, 14 Aug 2014 14:29:54 -0700 Subject: [PATCH 01/17] Change in test's AndroidManifest.xml needed for the test to run properly --- .../src/org/apache/cordova/test/junit/IntentUriOverrideTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/test/src/org/apache/cordova/test/junit/IntentUriOverrideTest.java b/test/src/org/apache/cordova/test/junit/IntentUriOverrideTest.java index aea06afe..381e0edd 100644 --- a/test/src/org/apache/cordova/test/junit/IntentUriOverrideTest.java +++ b/test/src/org/apache/cordova/test/junit/IntentUriOverrideTest.java @@ -25,6 +25,7 @@ public class IntentUriOverrideTest extends ActivityInstrumentationTestCase2 Date: Thu, 14 Aug 2014 14:30:31 -0700 Subject: [PATCH 02/17] Change in test's AndroidManifest.xml needed for the test to run properly. Forgot the manifest. --- test/AndroidManifest.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/AndroidManifest.xml b/test/AndroidManifest.xml index 6dec1aec..e058a420 100755 --- a/test/AndroidManifest.xml +++ b/test/AndroidManifest.xml @@ -255,5 +255,15 @@ + + + + + + From 4319447cb5157e5137465bcdc16bd6e3dcd99570 Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Fri, 15 Aug 2014 13:42:43 -0400 Subject: [PATCH 03/17] CB-7044, CB-7299 Fix up PATH problems when possible. Uses heuristics: - Adds javac to PATH based on default install paths on Windows - Adds javac to PATH based on JAVA_HOME - Adds android and adb to PATH based on ANDROID_HOME - Sets ANDROID_HOME based on location of "android" --- bin/lib/check_reqs.js | 100 +++++++++++++++++++++++------------- bin/lib/create.js | 2 +- bin/templates/cordova/build | 4 +- 3 files changed, 67 insertions(+), 39 deletions(-) diff --git a/bin/lib/check_reqs.js b/bin/lib/check_reqs.js index a4ad3ad6..17a3bd4f 100644 --- a/bin/lib/check_reqs.js +++ b/bin/lib/check_reqs.js @@ -19,26 +19,37 @@ under the License. */ -var shell = require('shelljs'), +var shelljs = require('shelljs'), child_process = require('child_process'), Q = require('q'), path = require('path'), fs = require('fs'), ROOT = path.join(__dirname, '..', '..'); +var isWindows = process.platform == 'win32'; + +function tryCommand(cmd, errMsg) { + var d = Q.defer(); + child_process.exec(cmd, function(err, stdout, stderr) { + if (err) d.reject(new Error(errMsg)); + else d.resolve(stdout); + }); + return d.promise; +} + // Get valid target from framework/project.properties module.exports.get_target = function() { if(fs.existsSync(path.join(ROOT, 'framework', 'project.properties'))) { - var target = shell.grep(/target=android-[\d+]/, path.join(ROOT, 'framework', 'project.properties')); + var target = shelljs.grep(/target=android-[\d+]/, path.join(ROOT, 'framework', 'project.properties')); return target.split('=')[1].replace('\n', '').replace('\r', '').replace(' ', ''); } else if (fs.existsSync(path.join(ROOT, 'project.properties'))) { // if no target found, we're probably in a project and project.properties is in ROOT. // this is called on the project itself, and can support Google APIs AND Vanilla Android - var target = shell.grep(/target=android-[\d+]/, path.join(ROOT, 'project.properties')) || - shell.grep(/target=Google Inc.:Google APIs:[\d+]/, path.join(ROOT, 'project.properties')); + var target = shelljs.grep(/target=android-[\d+]/, path.join(ROOT, 'project.properties')) || + shelljs.grep(/target=Google Inc.:Google APIs:[\d+]/, path.join(ROOT, 'project.properties')); if(target == "" || !target) { // Try Google Glass APIs - target = shell.grep(/target=Google Inc.:Glass Development Kit Preview:[\d+]/, path.join(ROOT, 'project.properties')); + target = shelljs.grep(/target=Google Inc.:Glass Development Kit Preview:[\d+]/, path.join(ROOT, 'project.properties')); } return target.split('=')[1].replace('\n', '').replace('\r', ''); } @@ -46,49 +57,66 @@ module.exports.get_target = function() { // Returns a promise. module.exports.check_ant = function() { - var d = Q.defer(); - child_process.exec('ant -version', function(err, stdout, stderr) { - if (err) d.reject(new Error('ERROR : executing command \'ant\', make sure you have ant installed and added to your path.')); - else d.resolve(); - }); - return d.promise; + return tryCommand('ant -version', 'Failed to run "ant -version", make sure you have ant installed and added to your PATH.'); } // Returns a promise. module.exports.check_java = function() { - var d = Q.defer(); - child_process.exec('java -version', function(err, stdout, stderr) { - if(err) { - var msg = - 'Failed to run \'java -version\', make sure your java environment is set up\n' + - 'including JDK and JRE.\n' + - 'Your JAVA_HOME variable is ' + process.env.JAVA_HOME + '\n'; - d.reject(new Error(msg + err)); + var javacInPath = !!shelljs.which('javac'); + var hasJavaHome = !!process.env.JAVA_HOME; + // Windows java installer doesn't add javac to PATH, nor set JAVA_HOME (ugh). + if (hasJavaHome && !javacInPath) { + process.env.PATH += path.delimiter + path.join(process.env.JAVA_HOME, 'bin'); + } else if (isWindows && (!hasJavaHome || !javacInPath)) { + // Try to auto-detect java in the default install paths. + var firstJdkDir = + shelljs.ls(process.env.ProgramFiles + '\\java\\jdk*')[0] || + shelljs.ls('C:\\Program Files\\java\\jdk*')[0] || + shelljs.ls('C:\\Program Files (x86)\\java\\jdk*')[0]; + if (firstJdkDir) { + // shelljs always uses / in paths. + firstJdkDir = firstJdkDir.replace(/\//g, path.sep); + if (!javacInPath) { + process.env.PATH += path.delimiter + path.join(firstJdkDir, 'bin'); + } + process.env.JAVA_HOME = firstJdkDir; } - else d.resolve(); + } + var msg = + 'Failed to run "java -version", make sure your java environment is set up\n' + + 'including JDK and JRE.\n' + + 'Your JAVA_HOME variable is: ' + process.env.JAVA_HOME; + return tryCommand('java -version', msg) + .then(function() { + msg = 'Failed to run "javac -version", make sure you have a Java JDK (not just a JRE) installed.'; + return tryCommand('javac -version', msg) }); - return d.promise; } // Returns a promise. module.exports.check_android = function() { - var valid_target = this.get_target(); - var d = Q.defer(); - child_process.exec('android list targets', function(err, stdout, stderr) { - if (err) d.reject(stderr); - else d.resolve(stdout); - }); + var androidCmdPath = !!shelljs.which('android'); + var adbInPath = !!shelljs.which('adb'); + var hasAndroidHome = !!process.env.ANDROID_HOME; + if (hasAndroidHome && !androidCmdPath) { + process.env.PATH += path.delimiter + path.join(process.env.ANDROID_HOME, 'tools'); + } + if (androidCmdPath && !hasAndroidHome) { + process.env.ANDROID_HOME = path.dirname(path.dirname(androidCmdPath)); + hasAndroidHome = true; + } + if (hasAndroidHome && !adbInPath) { + process.env.PATH += path.delimiter + path.join(process.env.ANDROID_HOME, 'platform-tools'); + } - return d.promise.then(function(output) { + 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.')); - } - return Q(); - }, function(stderr) { - if (stderr.match(/command\snot\sfound/) || stderr.match(/is not recognized as an internal or external command/)) { - return Q.reject(new Error('The command \"android\" failed. Make sure you have the latest Android SDK installed, and the \"android\" command (inside the tools/ folder) is added to your path.')); - } else { - return Q.reject(new Error('An error occurred while listing Android targets. Error: ' + stderr )); + 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.')); } }); } diff --git a/bin/lib/create.js b/bin/lib/create.js index b2490f24..78345817 100755 --- a/bin/lib/create.js +++ b/bin/lib/create.js @@ -196,7 +196,7 @@ exports.createProject = function(project_path, package_name, project_name, proje }) // Check that requirements are met and proper targets are installed .then(function() { - check_reqs.run(); + return check_reqs.run(); }).then(function() { // Log the given values for the project console.log('Creating Cordova project for the Android platform:'); diff --git a/bin/templates/cordova/build b/bin/templates/cordova/build index a38f3b63..2d589018 100755 --- a/bin/templates/cordova/build +++ b/bin/templates/cordova/build @@ -28,9 +28,9 @@ if(args[2] == '--help' || args[2] == '/?' || args[2] == '-h' || args[2] == 'help' || args[2] == '-help' || args[2] == '/help') { build.help(); } else { - reqs.run().then(function() { + reqs.run().done(function() { return build.run(args[2]); - }).done(null, function(err) { + }, function(err) { console.error(err); process.exit(2); }); From 95aa5c9f1c45b79151dc9650447faeaf12a75d96 Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Fri, 15 Aug 2014 13:58:53 -0400 Subject: [PATCH 04/17] CB-7321 Don't require ant for create script --- bin/lib/check_reqs.js | 4 ++-- bin/templates/cordova/lib/build.js | 6 +++++- bin/templates/cordova/lib/clean.js | 6 +++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/bin/lib/check_reqs.js b/bin/lib/check_reqs.js index 17a3bd4f..db454749 100644 --- a/bin/lib/check_reqs.js +++ b/bin/lib/check_reqs.js @@ -55,7 +55,7 @@ module.exports.get_target = function() { } } -// Returns a promise. +// 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.'); } @@ -123,6 +123,6 @@ module.exports.check_android = function() { // Returns a promise. module.exports.run = function() { - return Q.all([this.check_ant(), this.check_java(), this.check_android()]); + return Q.all([this.check_java(), this.check_android()]); } diff --git a/bin/templates/cordova/lib/build.js b/bin/templates/cordova/lib/build.js index 6336ecf5..5134f412 100644 --- a/bin/templates/cordova/lib/build.js +++ b/bin/templates/cordova/lib/build.js @@ -25,6 +25,7 @@ var shell = require('shelljs'), path = require('path'), fs = require('fs'), ROOT = path.join(__dirname, '..', '..'); +var check_reqs = require('./check_reqs'); function hasCustomRules() { @@ -60,9 +61,12 @@ module.exports.run = function(build_type) { return Q.reject('Build option \'' + build_type + '\' not recognized.'); } // Without our custom_rules.xml, we need to clean before building. - var ret = Q(); + var ret; if (!hasCustomRules()) { + // clean will call check_ant() for us. ret = require('./clean').run(); + } else { + ret = check_reqs.check_ant(); } return ret.then(function() { return spawn('ant', args); diff --git a/bin/templates/cordova/lib/clean.js b/bin/templates/cordova/lib/clean.js index 0a2e0ce0..f2158290 100644 --- a/bin/templates/cordova/lib/clean.js +++ b/bin/templates/cordova/lib/clean.js @@ -22,6 +22,7 @@ var build = require('./build'), spawn = require('./spawn'), path = require('path'); +var check_reqs = require('./check_reqs'); /* * Cleans the project using ant @@ -29,7 +30,10 @@ var build = require('./build'), */ module.exports.run = function() { var args = build.getAntArgs('clean'); - return spawn('ant', args); + return check_reqs.check_ant() + .then(function() { + return spawn('ant', args); + }); } module.exports.help = function() { From 8aa813b8625ec771e05977ca366220fb8f2388e2 Mon Sep 17 00:00:00 2001 From: Ian Clelland Date: Mon, 18 Aug 2014 09:40:33 -0400 Subject: [PATCH 05/17] CB-3445: Add an initial set of Gradle build scripts These scripts will build an android project, in debug and release mode. They also support additional library projects, such as Crosswalk, being added to libraries.gradle (and settings.gradle). A flag can be set in libraries.gradle to enable multi-architecture builds. --- bin/lib/create.js | 12 +- bin/templates/project/build.gradle | 81 +++++++++ .../gradle/wrapper/gradle-wrapper.properties | 6 + bin/templates/project/gradlew | 164 ++++++++++++++++++ bin/templates/project/gradlew.bat | 90 ++++++++++ bin/templates/project/libraries.gradle | 7 + bin/templates/project/settings.gradle | 1 + framework/build.gradle | 33 ++++ 8 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 bin/templates/project/build.gradle create mode 100644 bin/templates/project/gradle/wrapper/gradle-wrapper.properties create mode 100755 bin/templates/project/gradlew create mode 100644 bin/templates/project/gradlew.bat create mode 100644 bin/templates/project/libraries.gradle create mode 100644 bin/templates/project/settings.gradle create mode 100644 framework/build.gradle diff --git a/bin/lib/create.js b/bin/lib/create.js index 78345817..ad3c21c0 100755 --- a/bin/lib/create.js +++ b/bin/lib/create.js @@ -71,6 +71,7 @@ function copyJsAndLibrary(projectPath, shared, projectName) { shell.mkdir('-p', nestedCordovaLibPath); shell.cp('-f', path.join(ROOT, 'framework', 'AndroidManifest.xml'), nestedCordovaLibPath); shell.cp('-f', path.join(ROOT, 'framework', 'project.properties'), nestedCordovaLibPath); + shell.cp('-f', path.join(ROOT, 'framework', 'build.gradle'), nestedCordovaLibPath); shell.cp('-r', path.join(ROOT, 'framework', 'src'), nestedCordovaLibPath); // Create an eclipse project file and set the name of it to something unique. // Without this, you can't import multiple CordovaLib projects into the same workspace. @@ -89,7 +90,9 @@ function runAndroidUpdate(projectPath, target_api, shared) { function copyAntRules(projectPath) { var srcDir = path.join(ROOT, 'bin', 'templates', 'project'); - shell.cp('-f', path.join(srcDir, 'custom_rules.xml'), projectPath); + if (fs.existsSync(path.join(srcDir, 'custom_rules.xml'))) { + shell.cp('-f', path.join(srcDir, 'custom_rules.xml'), projectPath); + } } function copyScripts(projectPath) { @@ -213,6 +216,13 @@ exports.createProject = function(project_path, package_name, project_name, proje shell.cp('-r', path.join(project_template_dir, 'res'), project_path); shell.cp('-r', path.join(ROOT, 'framework', 'res', 'xml'), path.join(project_path, 'res')); + shell.cp('-f', path.join(project_template_dir, 'build.gradle'), project_path); + shell.cp('-f', path.join(project_template_dir, 'libraries.gradle'), project_path); + shell.cp('-f', path.join(project_template_dir, 'settings.gradle'), project_path); + shell.cp('-f', path.join(project_template_dir, 'gradlew'), project_path); + shell.cp('-f', path.join(project_template_dir, 'gradlew.bat'), project_path); + shell.cp('-r', path.join(project_template_dir, 'gradle'), project_path); + // Manually create directories that would be empty within the template (since git doesn't track directories). shell.mkdir(path.join(project_path, 'libs')); diff --git a/bin/templates/project/build.gradle b/bin/templates/project/build.gradle new file mode 100644 index 00000000..4b34e48f --- /dev/null +++ b/bin/templates/project/build.gradle @@ -0,0 +1,81 @@ +import java.util.regex.Pattern + +apply plugin: 'android' + +buildscript { + repositories { + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:0.10.+' + } +} + +ext.multiarch=false + +dependencies { + compile project(':CordovaLib') +} +apply from: 'libraries.gradle' + +android { + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + resources.srcDirs = ['src'] + aidl.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + res.srcDirs = ['res'] + assets.srcDirs = ['assets'] + } + } + + defaultConfig { + versionCode Integer.parseInt("" + getVersionCodeFromManifest() + "0") + } + + compileSdkVersion 19 + buildToolsVersion "19.0.0" + + if (multiarch) { + productFlavors { + armv7 { + versionCode defaultConfig.versionCode + 2 + ndk { + abiFilters "armeabi-v7a", "" + } + } + x86 { + versionCode defaultConfig.versionCode + 4 + ndk { + abiFilters "x86", "" + } + } + all { + ndk { + abiFilters "all", "" + } + } + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } + +} + +task wrapper(type: Wrapper) { + gradleVersion = '1.12' +} + +def getVersionCodeFromManifest() { + def manifestFile = file(android.sourceSets.main.manifest.srcFile) + def pattern = Pattern.compile("versionCode=\"(\\d+)\"") + def matcher = pattern.matcher(manifestFile.getText()) + matcher.find() + return Integer.parseInt(matcher.group(1)) +} diff --git a/bin/templates/project/gradle/wrapper/gradle-wrapper.properties b/bin/templates/project/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..497dc9bf --- /dev/null +++ b/bin/templates/project/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 13 09:52:43 EDT 2014 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-1.12-bin.zip diff --git a/bin/templates/project/gradlew b/bin/templates/project/gradlew new file mode 100755 index 00000000..91a7e269 --- /dev/null +++ b/bin/templates/project/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/bin/templates/project/gradlew.bat b/bin/templates/project/gradlew.bat new file mode 100644 index 00000000..aec99730 --- /dev/null +++ b/bin/templates/project/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/bin/templates/project/libraries.gradle b/bin/templates/project/libraries.gradle new file mode 100644 index 00000000..f4975435 --- /dev/null +++ b/bin/templates/project/libraries.gradle @@ -0,0 +1,7 @@ +dependencies { +// This file contains no plugins by default. +// To add a third-party library project, add a line to this file like +// compile project(':library_dir_name') +} +// If multiple ndk architectures are provided, uncomment this line +//ext.multiarch=true diff --git a/bin/templates/project/settings.gradle b/bin/templates/project/settings.gradle new file mode 100644 index 00000000..b938ccc5 --- /dev/null +++ b/bin/templates/project/settings.gradle @@ -0,0 +1 @@ +include ':CordovaLib', ':' diff --git a/framework/build.gradle b/framework/build.gradle new file mode 100644 index 00000000..82c1ac29 --- /dev/null +++ b/framework/build.gradle @@ -0,0 +1,33 @@ +buildscript { + repositories { + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:0.10.+' + } +} + +apply plugin: 'android-library' + +android { + compileSdkVersion 19 + buildToolsVersion "19.0.0" + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + resources.srcDirs = ['src'] + aidl.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + res.srcDirs = ['res'] + assets.srcDirs = ['assets'] + } + } +} From 7d6ac8703378cfd58b175de8483a2bf26242f799 Mon Sep 17 00:00:00 2001 From: Ian Clelland Date: Mon, 18 Aug 2014 09:44:00 -0400 Subject: [PATCH 06/17] CB-3445: Add option to build and install with gradle This gives build/cordova two new command-line arguments: --ant and --gradle, and will select the build type from those. As a fallback for the Cordova CLI, the environment variable ANDROID_BUILD can also be used, set to either "ant" or "gradle". The default is currently "ant", but it is intended for this to change in the future. --- bin/templates/cordova/build | 10 +- bin/templates/cordova/lib/build.js | 231 ++++++++++++++++++++++------- 2 files changed, 184 insertions(+), 57 deletions(-) diff --git a/bin/templates/cordova/build b/bin/templates/cordova/build index 2d589018..367bf7d2 100755 --- a/bin/templates/cordova/build +++ b/bin/templates/cordova/build @@ -24,12 +24,16 @@ var build = require('./lib/build'), args = process.argv; // Support basic help commands -if(args[2] == '--help' || args[2] == '/?' || args[2] == '-h' || - args[2] == 'help' || args[2] == '-help' || args[2] == '/help') { +if(args[2] == '--help' || + args[2] == '/?' || + args[2] == '-h' || + args[2] == 'help' || + args[2] == '-help' || + args[2] == '/help') { build.help(); } else { reqs.run().done(function() { - return build.run(args[2]); + return build.run.call(build.run, args.slice(2)); }, function(err) { console.error(err); process.exit(2); diff --git a/bin/templates/cordova/lib/build.js b/bin/templates/cordova/lib/build.js index 5134f412..3d0ae872 100644 --- a/bin/templates/cordova/lib/build.js +++ b/bin/templates/cordova/lib/build.js @@ -27,39 +27,164 @@ var shell = require('shelljs'), ROOT = path.join(__dirname, '..', '..'); var check_reqs = require('./check_reqs'); +// Globals +var build_type, + build_method; + +function find_files(directory, predicate) { + if (fs.existsSync(directory)) { + var candidates = fs.readdirSync(directory).filter(predicate).map(function(p) { + p = path.join(directory, p); + return { p: p, t: fs.statSync(p).mtime }; + }).sort(function(a,b) { + return a.t > b.t ? -1 : + a.t < b.t ? 1 : 0; + }).map(function(p) { return p.p; }); + return candidates; + } else { + console.error('ERROR : unable to find project ' + directory + ' directory, could not locate .apk'); + process.exit(2); + } +} function hasCustomRules() { return fs.existsSync(path.join(ROOT, 'custom_rules.xml')); } -module.exports.getAntArgs = function(cmd) { - var args = [cmd, '-f', path.join(ROOT, 'build.xml')]; - // custom_rules.xml is required for incremental builds. - if (hasCustomRules()) { - args.push('-Dout.dir=ant-build', '-Dgen.absolute.dir=ant-gen'); + +module.exports.builders = { + ant: { + getArgs: function(cmd) { + var args = [cmd, '-f', path.join(ROOT, 'build.xml')]; + // custom_rules.xml is required for incremental builds. + if (hasCustomRules()) { + args.push('-Dout.dir=ant-build', '-Dgen.absolute.dir=ant-gen'); + } + try { + // Specify sdk dir in case local properties are missing + args.push('-Dsdk.dir='+path.join(which.sync('android'), '../..')); + } catch(e) { + // Can't find android; don't push arg: assume all is okay + } + return args; + }, + + /* + * Builds the project with ant. + * Returns a promise. + */ + build: function(build_type) { + var builder = this; + var args = builder.getArgs(build_type == "debug" ? 'debug' : 'release'); + return Q().then(function() { + return spawn('ant', args); + }).then(function() { + return builder.getOutputFiles(); + }); + }, + + // Find the recently-generated output APK files + // Ant only generates one output file; return it. + getOutputFiles: function() { + var binDir; + if(hasCustomRules()) { + binDir = path.join(ROOT, 'ant-build'); + } else { + binDir = path.join(ROOT, 'bin'); + } + var candidates = find_files(binDir, function(candidate) { return path.extname(candidate) == '.apk'; }); + if (candidates.length === 0) { + console.error('ERROR : No .apk found in ' + binDir + ' directory'); + process.exit(2); + } + console.log('Using apk: ' + candidates[0]); + return [candidates[0]]; + } + }, + gradle: { + getArgs: function(cmd) { + var args = [cmd, '-b', path.join(ROOT, 'build.gradle')]; + return args; + }, + + /* + * Builds the project with gradle. + * Returns a promise. + */ + build: function(build_type) { + var builder = this; + var wrapper = path.join(ROOT, 'gradlew'); + var args = builder.getArgs('build'); + return Q().then(function() { + return spawn(wrapper, args); + }).then(function() { + return builder.getOutputFiles(build_type); + }); + }, + + // Find the recently-generated output APK files + // Gradle can generate multiple output files; return all of them. + getOutputFiles: function(build_type) { + var binDir = path.join(ROOT, 'build', 'apk'); + var candidates = find_files(binDir, function(candidate) { + // Need to choose between release and debug .apk. + if (build_type === 'debug') { + return (path.extname(candidate) == '.apk' && candidate.indexOf('-debug-') >= 0); + } + if (build_type === 'release') { + return (path.extname(candidate) == '.apk' && candidate.indexOf('-release-') >= 0); + } + return path.extname(candidate) == '.apk'; + }); + return candidates; + } } - return args; }; /* - * Builds the project with ant. + * Builds the project with the specifed options * Returns a promise. */ -module.exports.run = function(build_type) { - //default build type - build_type = typeof build_type !== 'undefined' ? build_type : "--debug"; - var args = module.exports.getAntArgs('debug'); - switch(build_type) { - case '--debug' : - break; - case '--release' : - args[0] = 'release'; - break; - case '--nobuild' : - console.log('Skipping build...'); - return Q(); - default : - return Q.reject('Build option \'' + build_type + '\' not recognized.'); +module.exports.run = function(options) { + + // Backwards-compatibility: Allow a single string argument + if (typeof options == "string") options = [options]; + + // Iterate through command line options + for (var i=0; options && (i < options.length); ++i) { + if (options[i].substring && options[i].substring(0,2) == "--") { + var option = options[i].substring(2); + switch(option) { + case 'debug': + case 'release': + if (build_type) { + return Q.reject('Multiple build types (' + build_type + ' and ' + option + ') specified.'); + } + build_type = option; + break; + case 'ant': + case 'gradle': + if (build_method) { + return Q.reject('Multiple build methods (' + build_method + ' and ' + option + ') specified.'); + } + build_method = option; + break; + case 'nobuild' : + console.log('Skipping build...'); + return Q(); + default : + return Q.reject('Build option \'' + options[i] + '\' not recognized.'); + } + } else { + return Q.reject('Build option \'' + options[i] + '\' not recognized.'); + } } + // Defaults + build_type = build_type || "debug"; + build_method = build_method || process.env.ANDROID_BUILD || "ant"; + + // Get the builder + var builder = module.exports.builders[build_method]; + // Without our custom_rules.xml, we need to clean before building. var ret; if (!hasCustomRules()) { @@ -68,50 +193,48 @@ module.exports.run = function(build_type) { } else { ret = check_reqs.check_ant(); } + + // Return a promise for the actual build return ret.then(function() { - return spawn('ant', args); + return builder.build.call(builder, build_type); + }).then(function(apkFiles) { + var outputDir = path.join(ROOT, 'out'); + try { + fs.mkdirSync(outputDir); + } catch (e) { + if (e.code != "EEXIST") { + throw e; + } + } + for (var i=0; i < apkFiles.length; ++i) { + shell.cp('-f', apkFiles[i], path.join(outputDir, path.basename(apkFiles[i]))); + } }); -} +}; /* * Gets the path to the apk file, if not such file exists then * the script will error out. (should we error or just return undefined?) + * This is called by the run script to install the apk to the device */ -module.exports.get_apk = function() { - var binDir = ''; - if(!hasCustomRules()) { - binDir = path.join(ROOT, 'bin'); - } else { - binDir = path.join(ROOT, 'ant-build'); - } - if (fs.existsSync(binDir)) { - var candidates = fs.readdirSync(binDir).filter(function(p) { - // Need to choose between release and debug .apk. - return path.extname(p) == '.apk'; - }).map(function(p) { - p = path.join(binDir, p); - return { p: p, t: fs.statSync(p).mtime }; - }).sort(function(a,b) { - return a.t > b.t ? -1 : - a.t < b.t ? 1 : 0; - }); - if (candidates.length === 0) { - console.error('ERROR : No .apk found in ' + binDir + ' directory'); - process.exit(2); - } - console.log('Using apk: ' + candidates[0].p); - return candidates[0].p; - } else { - console.error('ERROR : unable to find project ' + binDir + ' directory, could not locate .apk'); +module.exports.get_apk = function(build_type) { + var outputDir = path.join(ROOT, 'out'); + var candidates = find_files(outputDir, function() { return true; }); + if (candidates.length === 0) { + console.error('ERROR : No .apk found in ' + outputDir + ' directory'); process.exit(2); } -} + console.log('Using apk: ' + candidates[0]); + return candidates[0]; +}; module.exports.help = function() { console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'build')) + ' [build_type]'); console.log('Build Types : '); - console.log(' \'--debug\': Default build, will build project in using ant debug'); - console.log(' \'--release\': will build project using ant release'); + console.log(' \'--debug\': Default build, will build project in debug mode'); + console.log(' \'--release\': will build project for release'); + console.log(' \'--ant\': Default build, will build project with ant'); + console.log(' \'--gradle\': will build project with gradle'); console.log(' \'--nobuild\': will skip build process (can be used with run command)'); process.exit(0); -} +}; From fd6a1e5ed00baba902279dbc52cb9639b0e78eb3 Mon Sep 17 00:00:00 2001 From: Ian Clelland Date: Fri, 15 Aug 2014 13:35:50 -0400 Subject: [PATCH 07/17] CB-3445: Add which to checked-in node_modules --- bin/node_modules/which/LICENSE | 23 ++++++ bin/node_modules/which/README.md | 5 ++ bin/node_modules/which/bin/which | 14 ++++ bin/node_modules/which/package.json | 31 +++++++++ bin/node_modules/which/which.js | 104 ++++++++++++++++++++++++++++ 5 files changed, 177 insertions(+) create mode 100644 bin/node_modules/which/LICENSE create mode 100644 bin/node_modules/which/README.md create mode 100755 bin/node_modules/which/bin/which create mode 100644 bin/node_modules/which/package.json create mode 100644 bin/node_modules/which/which.js diff --git a/bin/node_modules/which/LICENSE b/bin/node_modules/which/LICENSE new file mode 100644 index 00000000..05a40109 --- /dev/null +++ b/bin/node_modules/which/LICENSE @@ -0,0 +1,23 @@ +Copyright 2009, 2010, 2011 Isaac Z. Schlueter. +All rights reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/bin/node_modules/which/README.md b/bin/node_modules/which/README.md new file mode 100644 index 00000000..ff1eb531 --- /dev/null +++ b/bin/node_modules/which/README.md @@ -0,0 +1,5 @@ +The "which" util from npm's guts. + +Finds the first instance of a specified executable in the PATH +environment variable. Does not cache the results, so `hash -r` is not +needed when the PATH changes. diff --git a/bin/node_modules/which/bin/which b/bin/node_modules/which/bin/which new file mode 100755 index 00000000..8432ce2f --- /dev/null +++ b/bin/node_modules/which/bin/which @@ -0,0 +1,14 @@ +#!/usr/bin/env node +var which = require("../") +if (process.argv.length < 3) { + console.error("Usage: which ") + process.exit(1) +} + +which(process.argv[2], function (er, thing) { + if (er) { + console.error(er.message) + process.exit(er.errno || 127) + } + console.log(thing) +}) diff --git a/bin/node_modules/which/package.json b/bin/node_modules/which/package.json new file mode 100644 index 00000000..6c5ccb34 --- /dev/null +++ b/bin/node_modules/which/package.json @@ -0,0 +1,31 @@ +{ + "author": { + "name": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me" + }, + "name": "which", + "description": "Like which(1) unix command. Find the first instance of an executable in the PATH.", + "version": "1.0.5", + "repository": { + "type": "git", + "url": "git://github.com/isaacs/node-which.git" + }, + "main": "which.js", + "bin": { + "which": "./bin/which" + }, + "engines": { + "node": "*" + }, + "dependencies": {}, + "devDependencies": {}, + "readme": "The \"which\" util from npm's guts.\n\nFinds the first instance of a specified executable in the PATH\nenvironment variable. Does not cache the results, so `hash -r` is not\nneeded when the PATH changes.\n", + "readmeFilename": "README.md", + "bugs": { + "url": "https://github.com/isaacs/node-which/issues" + }, + "homepage": "https://github.com/isaacs/node-which", + "_id": "which@1.0.5", + "_from": "which@" +} diff --git a/bin/node_modules/which/which.js b/bin/node_modules/which/which.js new file mode 100644 index 00000000..db7e8f74 --- /dev/null +++ b/bin/node_modules/which/which.js @@ -0,0 +1,104 @@ +module.exports = which +which.sync = whichSync + +var path = require("path") + , fs + , COLON = process.platform === "win32" ? ";" : ":" + , isExe + +try { + fs = require("graceful-fs") +} catch (ex) { + fs = require("fs") +} + +if (process.platform == "win32") { + // On windows, there is no good way to check that a file is executable + isExe = function isExe () { return true } +} else { + isExe = function isExe (mod, uid, gid) { + //console.error(mod, uid, gid); + //console.error("isExe?", (mod & 0111).toString(8)) + var ret = (mod & 0001) + || (mod & 0010) && process.getgid && gid === process.getgid() + || (mod & 0100) && process.getuid && uid === process.getuid() + //console.error("isExe?", ret) + return ret + } +} + + + +function which (cmd, cb) { + if (isAbsolute(cmd)) return cb(null, cmd) + var pathEnv = (process.env.PATH || "").split(COLON) + , pathExt = [""] + if (process.platform === "win32") { + pathEnv.push(process.cwd()) + pathExt = (process.env.PATHEXT || ".EXE").split(COLON) + if (cmd.indexOf(".") !== -1) pathExt.unshift("") + } + //console.error("pathEnv", pathEnv) + ;(function F (i, l) { + if (i === l) return cb(new Error("not found: "+cmd)) + var p = path.resolve(pathEnv[i], cmd) + ;(function E (ii, ll) { + if (ii === ll) return F(i + 1, l) + var ext = pathExt[ii] + //console.error(p + ext) + fs.stat(p + ext, function (er, stat) { + if (!er && + stat && + stat.isFile() && + isExe(stat.mode, stat.uid, stat.gid)) { + //console.error("yes, exe!", p + ext) + return cb(null, p + ext) + } + return E(ii + 1, ll) + }) + })(0, pathExt.length) + })(0, pathEnv.length) +} + +function whichSync (cmd) { + if (isAbsolute(cmd)) return cmd + var pathEnv = (process.env.PATH || "").split(COLON) + , pathExt = [""] + if (process.platform === "win32") { + pathEnv.push(process.cwd()) + pathExt = (process.env.PATHEXT || ".EXE").split(COLON) + if (cmd.indexOf(".") !== -1) pathExt.unshift("") + } + for (var i = 0, l = pathEnv.length; i < l; i ++) { + var p = path.join(pathEnv[i], cmd) + for (var j = 0, ll = pathExt.length; j < ll; j ++) { + var cur = p + pathExt[j] + var stat + try { stat = fs.statSync(cur) } catch (ex) {} + if (stat && + stat.isFile() && + isExe(stat.mode, stat.uid, stat.gid)) return cur + } + } + throw new Error("not found: "+cmd) +} + +var isAbsolute = process.platform === "win32" ? absWin : absUnix + +function absWin (p) { + if (absUnix(p)) return true + // pull off the device/UNC bit from a windows path. + // from node's lib/path.js + var splitDeviceRe = + /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?([\\\/])?/ + , result = splitDeviceRe.exec(p) + , device = result[1] || '' + , isUnc = device && device.charAt(1) !== ':' + , isAbsolute = !!result[2] || isUnc // UNC paths are always absolute + + return isAbsolute +} + +function absUnix (p) { + return p.charAt(0) === "/" || p === "" +} From a91bd095b0ebe1e4ed85c69248f376df7cbc626a Mon Sep 17 00:00:00 2001 From: Ian Clelland Date: Thu, 19 Jun 2014 16:12:40 -0400 Subject: [PATCH 08/17] CB-3445: android: Copy Gradle wrapper from Android SDK rather than bundling a JAR --- bin/lib/check_reqs.js | 18 ++ bin/lib/create.js | 16 +- .../gradle/wrapper/gradle-wrapper.properties | 6 - bin/templates/project/gradlew | 164 ------------------ bin/templates/project/gradlew.bat | 90 ---------- 5 files changed, 31 insertions(+), 263 deletions(-) delete mode 100644 bin/templates/project/gradle/wrapper/gradle-wrapper.properties delete mode 100755 bin/templates/project/gradlew delete mode 100644 bin/templates/project/gradlew.bat diff --git a/bin/lib/check_reqs.js b/bin/lib/check_reqs.js index db454749..f6160354 100644 --- a/bin/lib/check_reqs.js +++ b/bin/lib/check_reqs.js @@ -24,6 +24,7 @@ var shelljs = require('shelljs'), Q = require('q'), path = require('path'), fs = require('fs'), + which = require('which'), ROOT = path.join(__dirname, '..', '..'); var isWindows = process.platform == 'win32'; @@ -55,6 +56,23 @@ module.exports.get_target = function() { } } +// Returns a promise. +module.exports.sdk_dir = function() { + var d = Q.defer(); + which('android', function(err, path) { + if (err) { + d.reject(new Error('ERROR: Cannot find Android SDK. android command not found.')); + } else { + var toolsDir = path.substring(0, path.lastIndexOf('/')); + if (toolsDir.substring(toolsDir.length-6) != "/tools") { + d.reject(new Error('ERROR: Cannot find Android SDK. android command not found in tools dir.')); + } + d.resolve(toolsDir.substring(0, toolsDir.length-6)); + } + }); + return d.promise; +}; + // 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.'); diff --git a/bin/lib/create.js b/bin/lib/create.js index ad3c21c0..312881cd 100755 --- a/bin/lib/create.js +++ b/bin/lib/create.js @@ -109,6 +109,13 @@ function copyScripts(projectPath) { shell.cp(path.join(ROOT, 'bin', 'lib', 'android_sdk_version.js'), path.join(projectPath, 'cordova', 'lib', 'android_sdk_version.js')); } +function copyGradleWrapper(sdkPath, projectPath) { + var wrapperDir = path.join(sdkPath, 'tools', 'templates','gradle','wrapper'); + shell.cp(path.join(wrapperDir, 'gradlew'), projectPath); + shell.cp(path.join(wrapperDir, 'gradlew.bat'), projectPath); + shell.cp('-r', path.join(wrapperDir, 'gradle'), projectPath); +} + /** * Test whether a package name is acceptable for use as an android project. * Returns a promise, fulfilled if the package name is acceptable; rejected @@ -219,9 +226,12 @@ exports.createProject = function(project_path, package_name, project_name, proje shell.cp('-f', path.join(project_template_dir, 'build.gradle'), project_path); shell.cp('-f', path.join(project_template_dir, 'libraries.gradle'), project_path); shell.cp('-f', path.join(project_template_dir, 'settings.gradle'), project_path); - shell.cp('-f', path.join(project_template_dir, 'gradlew'), project_path); - shell.cp('-f', path.join(project_template_dir, 'gradlew.bat'), project_path); - shell.cp('-r', path.join(project_template_dir, 'gradle'), project_path); + check_reqs.sdk_dir().then(function(dir) { + console.log("Copying Gradle wrapper from " + dir); + copyGradleWrapper(dir, project_path); + }).catch(function(err) { + console.log("Cannot find Android SDK. Not installing Gradle wrapper."); + }); // Manually create directories that would be empty within the template (since git doesn't track directories). shell.mkdir(path.join(project_path, 'libs')); diff --git a/bin/templates/project/gradle/wrapper/gradle-wrapper.properties b/bin/templates/project/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 497dc9bf..00000000 --- a/bin/templates/project/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Fri Jun 13 09:52:43 EDT 2014 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-1.12-bin.zip diff --git a/bin/templates/project/gradlew b/bin/templates/project/gradlew deleted file mode 100755 index 91a7e269..00000000 --- a/bin/templates/project/gradlew +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/env bash - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn ( ) { - echo "$*" -} - -die ( ) { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; -esac - -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/bin/templates/project/gradlew.bat b/bin/templates/project/gradlew.bat deleted file mode 100644 index aec99730..00000000 --- a/bin/templates/project/gradlew.bat +++ /dev/null @@ -1,90 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega From effffcba1db5aa847b2dc9d382d8680f6873ce29 Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Mon, 18 Aug 2014 14:16:27 -0400 Subject: [PATCH 09/17] CB-3445 Speed up gradle building (incremental builds go from 10s -> 1.5s for me) Biggest win is disabling the linter. --- bin/templates/cordova/lib/build.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/bin/templates/cordova/lib/build.js b/bin/templates/cordova/lib/build.js index 3d0ae872..1f0cd23f 100644 --- a/bin/templates/cordova/lib/build.js +++ b/bin/templates/cordova/lib/build.js @@ -102,7 +102,22 @@ module.exports.builders = { }, gradle: { getArgs: function(cmd) { + var lintSteps = [ + 'lint', + 'lintVitalRelease', + 'compileLint', + 'copyReleaseLint', + 'copyDebugLint' + ]; var args = [cmd, '-b', path.join(ROOT, 'build.gradle')]; + // 10 seconds -> 6 seconds + args.push('-Dorg.gradle.daemon=true'); + // Excluding lint: 6s-> 1.6s + for (var i = 0; i < lintSteps.length; ++i) { + args.push('-x', lintSteps[i]); + } + // Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet): + // args.push('-Dorg.gradle.parallel=true'); return args; }, From 7133576fe9ca8e8a3c91938eba524a843ccf8365 Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Mon, 18 Aug 2014 14:45:23 -0400 Subject: [PATCH 10/17] CB-7044 Add JAVA_HOME when not set. Be stricter about ANDROID_HOME Also switches to using the which module over shelljs.which (better support for .bat files) --- bin/lib/check_reqs.js | 102 +++++++++++++++++++++++++++++------------- 1 file changed, 71 insertions(+), 31 deletions(-) diff --git a/bin/lib/check_reqs.js b/bin/lib/check_reqs.js index f6160354..6f20c192 100644 --- a/bin/lib/check_reqs.js +++ b/bin/lib/check_reqs.js @@ -29,6 +29,14 @@ var shelljs = require('shelljs'), var isWindows = process.platform == 'win32'; +function forgivingWhichSync(cmd) { + try { + return which.sync(path); + } catch (e) { + return ''; + } +} + function tryCommand(cmd, errMsg) { var d = Q.defer(); child_process.exec(cmd, function(err, stdout, stderr) { @@ -80,32 +88,54 @@ module.exports.check_ant = function() { // Returns a promise. module.exports.check_java = function() { - var javacInPath = !!shelljs.which('javac'); - var hasJavaHome = !!process.env.JAVA_HOME; - // Windows java installer doesn't add javac to PATH, nor set JAVA_HOME (ugh). - if (hasJavaHome && !javacInPath) { - process.env.PATH += path.delimiter + path.join(process.env.JAVA_HOME, 'bin'); - } else if (isWindows && (!hasJavaHome || !javacInPath)) { - // Try to auto-detect java in the default install paths. - var firstJdkDir = - shelljs.ls(process.env.ProgramFiles + '\\java\\jdk*')[0] || - shelljs.ls('C:\\Program Files\\java\\jdk*')[0] || - shelljs.ls('C:\\Program Files (x86)\\java\\jdk*')[0]; - if (firstJdkDir) { - // shelljs always uses / in paths. - firstJdkDir = firstJdkDir.replace(/\//g, path.sep); - if (!javacInPath) { - process.env.PATH += path.delimiter + path.join(firstJdkDir, 'bin'); + var javacPath = forgivingWhichSync('javac'); + var hasJavaHome = !!process.env['JAVA_HOME']; + return Q().then(function() { + if (hasJavaHome) { + // Windows java installer doesn't add javac to PATH, nor set JAVA_HOME (ugh). + if (!javacPath) { + process.env['PATH'] += path.delimiter + path.join(process.env['JAVA_HOME'], 'bin'); + } + } else { + if (javacPath) { + // OS X has a command for finding JAVA_HOME. + if (fs.existsSync('/usr/libexec/java_home')) { + return tryCommand('/usr/libexec/java_home', 'Failed to run: /usr/libexec/java_home') + .then(function(stdout) { + process.env['JAVA_HOME'] = stdout.trim(); + }); + } else { + // See if we can derive it from javac's location. + var maybeJavaHome = path.dirname(path.dirname(javacPath)); + if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) { + process.env['JAVA_HOME'] = maybeJavaHome; + } else { + throw new Error('Could not find JAVA_HOME. Try setting the environment variable manually'); + } + } + } else if (isWindows) { + // Try to auto-detect java in the default install paths. + var firstJdkDir = + shelljs.ls(process.env['ProgramFiles'] + '\\java\\jdk*')[0] || + shelljs.ls('C:\\Program Files\\java\\jdk*')[0] || + shelljs.ls('C:\\Program Files (x86)\\java\\jdk*')[0]; + if (firstJdkDir) { + // shelljs always uses / in paths. + firstJdkDir = firstJdkDir.replace(/\//g, path.sep); + if (!javacPath) { + process.env['PATH'] += path.delimiter + path.join(firstJdkDir, 'bin'); + } + process.env['JAVA_HOME'] = firstJdkDir; + } } - process.env.JAVA_HOME = firstJdkDir; } - } - var msg = - 'Failed to run "java -version", make sure your java environment is set up\n' + - 'including JDK and JRE.\n' + - 'Your JAVA_HOME variable is: ' + process.env.JAVA_HOME; - return tryCommand('java -version', msg) - .then(function() { + }).then(function() { + var msg = + 'Failed to run "java -version", make sure your java environment is set up\n' + + 'including JDK and JRE.\n' + + 'Your JAVA_HOME variable is: ' + process.env['JAVA_HOME']; + return tryCommand('java -version', msg) + }).then(function() { msg = 'Failed to run "javac -version", make sure you have a Java JDK (not just a JRE) installed.'; return tryCommand('javac -version', msg) }); @@ -113,18 +143,21 @@ module.exports.check_java = function() { // Returns a promise. module.exports.check_android = function() { - var androidCmdPath = !!shelljs.which('android'); - var adbInPath = !!shelljs.which('adb'); - var hasAndroidHome = !!process.env.ANDROID_HOME; + 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'); + process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'tools'); } if (androidCmdPath && !hasAndroidHome) { - process.env.ANDROID_HOME = path.dirname(path.dirname(androidCmdPath)); - hasAndroidHome = true; + 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'); + process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'platform-tools'); } var valid_target = this.get_target(); @@ -136,6 +169,13 @@ module.exports.check_android = function() { ' (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.')); } + }).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']); + } }); } From 36eab713a1837f315dc1719314d8370f1d5207f3 Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Mon, 18 Aug 2014 14:50:27 -0400 Subject: [PATCH 11/17] CB-3445 Add .gradle template files for "update" as well as "create" --- bin/lib/create.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/bin/lib/create.js b/bin/lib/create.js index 312881cd..d1f43e67 100755 --- a/bin/lib/create.js +++ b/bin/lib/create.js @@ -88,11 +88,13 @@ function runAndroidUpdate(projectPath, target_api, shared) { return exec('android update project --subprojects --path "' + projectPath + '" --target ' + target_api + ' --library "' + path.relative(projectPath, targetFrameworkDir) + '"'); } -function copyAntRules(projectPath) { +function copyBuildRules(projectPath) { var srcDir = path.join(ROOT, 'bin', 'templates', 'project'); - if (fs.existsSync(path.join(srcDir, 'custom_rules.xml'))) { - shell.cp('-f', path.join(srcDir, 'custom_rules.xml'), projectPath); - } + shell.cp('-f', path.join(srcDir, 'custom_rules.xml'), projectPath); + + shell.cp('-f', path.join(srcDir, 'build.gradle'), projectPath); + shell.cp('-f', path.join(srcDir, 'libraries.gradle'), projectPath); + shell.cp('-f', path.join(srcDir, 'settings.gradle'), projectPath); } function copyScripts(projectPath) { @@ -263,7 +265,7 @@ exports.createProject = function(project_path, package_name, project_name, proje shell.sed('-i', /__PACKAGE__/, package_name, manifest_path); shell.sed('-i', /__APILEVEL__/, target_api.split('-')[1], manifest_path); copyScripts(project_path); - copyAntRules(project_path); + copyBuildRules(project_path); }); // Link it to local android install. return runAndroidUpdate(project_path, target_api, use_shared_project); @@ -298,7 +300,7 @@ exports.updateProject = function(projectPath) { var target_api = check_reqs.get_target(); copyJsAndLibrary(projectPath, false, projectName); copyScripts(projectPath); - copyAntRules(projectPath); + copyBuildRules(projectPath); removeDebuggableFromManifest(projectPath); return runAndroidUpdate(projectPath, target_api, false) .then(function() { From ca8bb75b4021566667743ac2b8a3da268b05d6be Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Mon, 18 Aug 2014 14:51:10 -0400 Subject: [PATCH 12/17] CB-3445 Copy gradle wrapper in build instead of create This should play nicer with updates to the android SDK. --- bin/lib/check_reqs.js | 17 ----------------- bin/lib/create.js | 17 ----------------- bin/templates/cordova/lib/build.js | 19 +++++++++++++++++++ 3 files changed, 19 insertions(+), 34 deletions(-) diff --git a/bin/lib/check_reqs.js b/bin/lib/check_reqs.js index 6f20c192..61bd4494 100644 --- a/bin/lib/check_reqs.js +++ b/bin/lib/check_reqs.js @@ -64,23 +64,6 @@ module.exports.get_target = function() { } } -// Returns a promise. -module.exports.sdk_dir = function() { - var d = Q.defer(); - which('android', function(err, path) { - if (err) { - d.reject(new Error('ERROR: Cannot find Android SDK. android command not found.')); - } else { - var toolsDir = path.substring(0, path.lastIndexOf('/')); - if (toolsDir.substring(toolsDir.length-6) != "/tools") { - d.reject(new Error('ERROR: Cannot find Android SDK. android command not found in tools dir.')); - } - d.resolve(toolsDir.substring(0, toolsDir.length-6)); - } - }); - return d.promise; -}; - // 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.'); diff --git a/bin/lib/create.js b/bin/lib/create.js index d1f43e67..cc42f194 100755 --- a/bin/lib/create.js +++ b/bin/lib/create.js @@ -111,13 +111,6 @@ function copyScripts(projectPath) { shell.cp(path.join(ROOT, 'bin', 'lib', 'android_sdk_version.js'), path.join(projectPath, 'cordova', 'lib', 'android_sdk_version.js')); } -function copyGradleWrapper(sdkPath, projectPath) { - var wrapperDir = path.join(sdkPath, 'tools', 'templates','gradle','wrapper'); - shell.cp(path.join(wrapperDir, 'gradlew'), projectPath); - shell.cp(path.join(wrapperDir, 'gradlew.bat'), projectPath); - shell.cp('-r', path.join(wrapperDir, 'gradle'), projectPath); -} - /** * Test whether a package name is acceptable for use as an android project. * Returns a promise, fulfilled if the package name is acceptable; rejected @@ -225,16 +218,6 @@ exports.createProject = function(project_path, package_name, project_name, proje shell.cp('-r', path.join(project_template_dir, 'res'), project_path); shell.cp('-r', path.join(ROOT, 'framework', 'res', 'xml'), path.join(project_path, 'res')); - shell.cp('-f', path.join(project_template_dir, 'build.gradle'), project_path); - shell.cp('-f', path.join(project_template_dir, 'libraries.gradle'), project_path); - shell.cp('-f', path.join(project_template_dir, 'settings.gradle'), project_path); - check_reqs.sdk_dir().then(function(dir) { - console.log("Copying Gradle wrapper from " + dir); - copyGradleWrapper(dir, project_path); - }).catch(function(err) { - console.log("Cannot find Android SDK. Not installing Gradle wrapper."); - }); - // Manually create directories that would be empty within the template (since git doesn't track directories). shell.mkdir(path.join(project_path, 'libs')); diff --git a/bin/templates/cordova/lib/build.js b/bin/templates/cordova/lib/build.js index 1f0cd23f..80e2282b 100644 --- a/bin/templates/cordova/lib/build.js +++ b/bin/templates/cordova/lib/build.js @@ -51,6 +51,24 @@ 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); + } + 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')); +} + module.exports.builders = { ant: { getArgs: function(cmd) { @@ -129,6 +147,7 @@ module.exports.builders = { var builder = this; var wrapper = path.join(ROOT, 'gradlew'); var args = builder.getArgs('build'); + copyGradleWrapper(); return Q().then(function() { return spawn(wrapper, args); }).then(function() { From c91b272648493cfaaad972b42c4868650323ad97 Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Mon, 18 Aug 2014 15:26:05 -0400 Subject: [PATCH 13/17] CB-7044 Fix typo in prev commit causing check_reqs to always fail. --- bin/lib/check_reqs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/lib/check_reqs.js b/bin/lib/check_reqs.js index 61bd4494..7794594e 100644 --- a/bin/lib/check_reqs.js +++ b/bin/lib/check_reqs.js @@ -31,7 +31,7 @@ var isWindows = process.platform == 'win32'; function forgivingWhichSync(cmd) { try { - return which.sync(path); + return which.sync(cmd); } catch (e) { return ''; } From d56ea25816a72b21e7a15d88ae0dfcb62742a66d Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Mon, 18 Aug 2014 16:19:40 -0400 Subject: [PATCH 14/17] CB-3445 Add gradle support clean command (plus some code cleanup) * Don't run ant clean for gradle ever * Don't set sdk.dir since ANDROID_HOME is not always set * Don't export builders --- bin/templates/cordova/build | 2 +- bin/templates/cordova/clean | 20 +++-- bin/templates/cordova/lib/build.js | 137 +++++++++++++++++++---------- bin/templates/cordova/lib/clean.js | 43 --------- 4 files changed, 104 insertions(+), 98 deletions(-) delete mode 100644 bin/templates/cordova/lib/clean.js diff --git a/bin/templates/cordova/build b/bin/templates/cordova/build index 367bf7d2..3c3aee4e 100755 --- a/bin/templates/cordova/build +++ b/bin/templates/cordova/build @@ -33,7 +33,7 @@ if(args[2] == '--help' || build.help(); } else { reqs.run().done(function() { - return build.run.call(build.run, args.slice(2)); + return build.run(args.slice(2)); }, function(err) { console.error(err); process.exit(2); diff --git a/bin/templates/cordova/clean b/bin/templates/cordova/clean index 4e0808bf..d9a7d490 100755 --- a/bin/templates/cordova/clean +++ b/bin/templates/cordova/clean @@ -19,18 +19,26 @@ under the License. */ -var clean = require('./lib/clean'), +var build = require('./lib/build'), reqs = require('./lib/check_reqs'), args = process.argv; +var path = require('path'); -// Usage support for when args are given -if(args.length > 2) { - clean.help(); +// Support basic help commands +if(args[2] == '--help' || + args[2] == '/?' || + args[2] == '-h' || + args[2] == 'help' || + args[2] == '-help' || + args[2] == '/help') { + console.log('Usage: ' + path.relative(process.cwd(), process.argv[1])); + console.log('Cleans the project directory.'); + process.exit(0); } else { reqs.run().done(function() { - return clean.run(); + return build.runClean(args.slice(2)); }, function(err) { - console.error('ERROR: ' + err); + console.error(err); process.exit(2); }); } diff --git a/bin/templates/cordova/lib/build.js b/bin/templates/cordova/lib/build.js index 80e2282b..071990f6 100644 --- a/bin/templates/cordova/lib/build.js +++ b/bin/templates/cordova/lib/build.js @@ -69,7 +69,7 @@ function copyGradleWrapper() { shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(projectPath, 'gradle')); } -module.exports.builders = { +var builders = { ant: { getArgs: function(cmd) { var args = [cmd, '-f', path.join(ROOT, 'build.xml')]; @@ -77,29 +77,45 @@ module.exports.builders = { if (hasCustomRules()) { args.push('-Dout.dir=ant-build', '-Dgen.absolute.dir=ant-gen'); } - try { - // Specify sdk dir in case local properties are missing - args.push('-Dsdk.dir='+path.join(which.sync('android'), '../..')); - } catch(e) { - // Can't find android; don't push arg: assume all is okay - } return args; }, + prepEnv: function() { + return check_reqs.check_ant() + .then(function() { + }); + }, + /* * Builds the project with ant. * Returns a promise. */ build: function(build_type) { + // Without our custom_rules.xml, we need to clean before building. + var ret = Q(); + if (!hasCustomRules()) { + // clean will call check_ant() for us. + ret = this.clean(); + } + var builder = this; - var args = builder.getArgs(build_type == "debug" ? 'debug' : 'release'); - return Q().then(function() { + var args = this.getArgs(build_type == 'debug' ? 'debug' : 'release'); + return check_reqs.check_ant() + .then(function() { return spawn('ant', args); }).then(function() { return builder.getOutputFiles(); }); }, + clean: function() { + var args = this.getArgs('clean'); + return check_reqs.check_ant() + .then(function() { + return spawn('ant', args); + }); + }, + // Find the recently-generated output APK files // Ant only generates one output file; return it. getOutputFiles: function() { @@ -139,6 +155,10 @@ module.exports.builders = { return args; }, + prepEnv: function() { + return Q(); + }, + /* * Builds the project with gradle. * Returns a promise. @@ -155,6 +175,18 @@ module.exports.builders = { }); }, + clean: function() { + var builder = this; + var wrapper = path.join(ROOT, 'gradlew'); + var args = builder.getArgs('clean'); + copyGradleWrapper(); + return Q().then(function() { + return spawn(wrapper, args); + }).then(function() { + return builder.getOutputFiles(build_type); + }); + }, + // Find the recently-generated output APK files // Gradle can generate multiple output files; return all of them. getOutputFiles: function(build_type) { @@ -171,18 +203,31 @@ module.exports.builders = { }); return candidates; } + }, + + none: { + prepEnv: function() { + return Q(); + }, + build: function() { + console.log('Skipping build...'); + return Q(); + }, + clean: function() { + return Q(); + }, } }; -/* - * Builds the project with the specifed options - * Returns a promise. - */ -module.exports.run = function(options) { - +function parseOpts(options) { // Backwards-compatibility: Allow a single string argument if (typeof options == "string") options = [options]; + var ret = { + buildType: 'debug', + buildMethod: process.env['ANDROID_BUILD'] || 'ant' + }; + // Iterate through command line options for (var i=0; options && (i < options.length); ++i) { if (options[i].substring && options[i].substring(0,2) == "--") { @@ -190,21 +235,15 @@ module.exports.run = function(options) { switch(option) { case 'debug': case 'release': - if (build_type) { - return Q.reject('Multiple build types (' + build_type + ' and ' + option + ') specified.'); - } - build_type = option; + ret.buildType = option; break; case 'ant': case 'gradle': - if (build_method) { - return Q.reject('Multiple build methods (' + build_method + ' and ' + option + ') specified.'); - } - build_method = option; + ret.buildMethod = option; break; case 'nobuild' : - console.log('Skipping build...'); - return Q(); + ret.buildMethod = 'none'; + break; default : return Q.reject('Build option \'' + options[i] + '\' not recognized.'); } @@ -212,34 +251,36 @@ module.exports.run = function(options) { return Q.reject('Build option \'' + options[i] + '\' not recognized.'); } } - // Defaults - build_type = build_type || "debug"; - build_method = build_method || process.env.ANDROID_BUILD || "ant"; + return ret; +} - // Get the builder - var builder = module.exports.builders[build_method]; +/* + * Builds the project with the specifed options + * Returns a promise. + */ +module.exports.runClean = function(options) { + var opts = parseOpts(options); + var builder = builders[opts.buildMethod]; + return builder.prepEnv(opts.buildType) + .then(function() { + return builder.clean(); + }); +}; - // Without our custom_rules.xml, we need to clean before building. - var ret; - if (!hasCustomRules()) { - // clean will call check_ant() for us. - ret = require('./clean').run(); - } else { - ret = check_reqs.check_ant(); - } +/* + * Builds the project with the specifed options + * Returns a promise. + */ +module.exports.run = function(options) { + var opts = parseOpts(options); - // Return a promise for the actual build - return ret.then(function() { - return builder.build.call(builder, build_type); + var builder = builders[opts.buildMethod]; + return builder.prepEnv(opts.buildType) + .then(function() { + return builder.build(opts.buildType); }).then(function(apkFiles) { var outputDir = path.join(ROOT, 'out'); - try { - fs.mkdirSync(outputDir); - } catch (e) { - if (e.code != "EEXIST") { - throw e; - } - } + shell.mkdir('-p', outputDir); for (var i=0; i < apkFiles.length; ++i) { shell.cp('-f', apkFiles[i], path.join(outputDir, path.basename(apkFiles[i]))); } diff --git a/bin/templates/cordova/lib/clean.js b/bin/templates/cordova/lib/clean.js deleted file mode 100644 index f2158290..00000000 --- a/bin/templates/cordova/lib/clean.js +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env node - -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. -*/ - -var build = require('./build'), - spawn = require('./spawn'), - path = require('path'); -var check_reqs = require('./check_reqs'); - -/* - * Cleans the project using ant - * Returns a promise. - */ -module.exports.run = function() { - var args = build.getAntArgs('clean'); - return check_reqs.check_ant() - .then(function() { - return spawn('ant', args); - }); -} - -module.exports.help = function() { - console.log('Usage: ' + path.relative(process.cwd(), process.argv[1])); - console.log('Cleans the project directory.'); - process.exit(0); -} From dfa66b9dd44e52e96fc2c8b4d1c8661c8b0074bb Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Mon, 18 Aug 2014 23:21:26 -0400 Subject: [PATCH 15/17] 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) --- 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(-) create mode 100644 bin/templates/project/project.properties diff --git a/bin/lib/check_reqs.js b/bin/lib/check_reqs.js index 7794594e..40f3ade3 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() { diff --git a/bin/lib/create.js b/bin/lib/create.js index cc42f194..13447da7 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.'); }); }; diff --git a/bin/templates/cordova/lib/build.js b/bin/templates/cordova/lib/build.js index 071990f6..7830bd6e 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 = / Date: Tue, 19 Aug 2014 11:53:53 -0400 Subject: [PATCH 16/17] CB-7330 Fix dangling function call in last commit (broke gradle builds) --- bin/templates/cordova/lib/build.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/bin/templates/cordova/lib/build.js b/bin/templates/cordova/lib/build.js index 7830bd6e..1f4810d0 100644 --- a/bin/templates/cordova/lib/build.js +++ b/bin/templates/cordova/lib/build.js @@ -204,7 +204,6 @@ var builders = { var builder = this; var wrapper = path.join(ROOT, 'gradlew'); var args = builder.getArgs('build'); - copyGradleWrapper(); return Q().then(function() { return spawn(wrapper, args); }).then(function() { @@ -216,7 +215,6 @@ var builders = { var builder = this; var wrapper = path.join(ROOT, 'gradlew'); var args = builder.getArgs('clean'); - copyGradleWrapper(); return Q().then(function() { return spawn(wrapper, args); }); From 71e72f215dd7472ac2be4aabd0154b41a24df6e5 Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Tue, 19 Aug 2014 11:59:18 -0400 Subject: [PATCH 17/17] CB-7335 Add a .gitignore to android project template --- bin/lib/create.js | 1 + bin/templates/project/gitignore | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 bin/templates/project/gitignore diff --git a/bin/lib/create.js b/bin/lib/create.js index 13447da7..0e929e3a 100755 --- a/bin/lib/create.js +++ b/bin/lib/create.js @@ -246,6 +246,7 @@ exports.createProject = function(project_path, package_name, project_name, proje shell.cp('-r', path.join(project_template_dir, 'assets'), project_path); shell.cp('-r', path.join(project_template_dir, 'res'), project_path); shell.cp('-r', path.join(ROOT, 'framework', 'res', 'xml'), path.join(project_path, 'res')); + shell.cp(path.join(project_template_dir, 'gitignore'), path.join(project_path, '.gitignore')); // Manually create directories that would be empty within the template (since git doesn't track directories). shell.mkdir(path.join(project_path, 'libs')); diff --git a/bin/templates/project/gitignore b/bin/templates/project/gitignore new file mode 100644 index 00000000..a1c8ff71 --- /dev/null +++ b/bin/templates/project/gitignore @@ -0,0 +1,14 @@ +# Non-project-specific build files: +build.xml +local.properties +/gradlew +/gradlew.bat +/gradle +# Ant builds +ant-built +ant-gen +# Eclipse builds +gen +out +# Gradle builds +/build