diff options
| author | Rene Ae <aehtyb@gmail.com> | 2015-12-04 20:32:44 -0600 |
|---|---|---|
| committer | Rene Ae <aehtyb@gmail.com> | 2015-12-04 20:32:44 -0600 |
| commit | 10efb0f7b426426057fed757fe3c851a249358dd (patch) | |
| tree | b80e285251d30fbca36220c932ef180c29c55dcf /StoneIsland/platforms/android/cordova/lib | |
| parent | 015b58ff6845b5cb79b13fec109a37b4c10c7813 (diff) | |
android build
Diffstat (limited to 'StoneIsland/platforms/android/cordova/lib')
24 files changed, 2493 insertions, 0 deletions
diff --git a/StoneIsland/platforms/android/cordova/lib/android_sdk_version.js b/StoneIsland/platforms/android/cordova/lib/android_sdk_version.js new file mode 100755 index 00000000..79af2727 --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/android_sdk_version.js @@ -0,0 +1,64 @@ +#!/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 child_process = require('child_process'), + Q = require('q'); + +var get_highest_sdk = function(results){ + var reg = /\d+/; + var apiLevels = []; + for(var i=0;i<results.length;i++){ + apiLevels[i] = parseInt(results[i].match(reg)[0]); + } + apiLevels.sort(function(a,b){return b-a;}); + console.log(apiLevels[0]); +}; + +var get_sdks = function() { + var d = Q.defer(); + child_process.exec('android list targets', function(err, stdout, stderr) { + if (err) d.reject(stderr); + else d.resolve(stdout); + }); + + return d.promise.then(function(output) { + var reg = /android-\d+/gi; + var results = output.match(reg); + if(results.length===0){ + return Q.reject(new Error('No android sdks installed.')); + }else{ + get_highest_sdk(results); + } + + return Q(); + }, function(stderr) { + if (stderr.match(/command\snot\sfound/) || stderr.match(/'android' is not recognized/)) { + 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')); + } + }); +}; + +module.exports.run = function() { + return Q.all([get_sdks()]); +}; + diff --git a/StoneIsland/platforms/android/cordova/lib/appinfo.js b/StoneIsland/platforms/android/cordova/lib/appinfo.js new file mode 100755 index 00000000..080c2ba8 --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/appinfo.js @@ -0,0 +1,41 @@ +#!/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 path = require('path'); +var fs = require('fs'); +var cachedAppInfo = null; + +function readAppInfoFromManifest() { + var manifestPath = path.join(__dirname, '..', '..', 'AndroidManifest.xml'); + var manifestData = fs.readFileSync(manifestPath, {encoding:'utf8'}); + var packageName = /\bpackage\s*=\s*"(.+?)"/.exec(manifestData); + if (!packageName) throw new Error('Could not find package name within ' + manifestPath); + var activityTag = /<activity\b[\s\S]*<\/activity>/.exec(manifestData); + if (!activityTag) throw new Error('Could not find <activity> within ' + manifestPath); + var activityName = /\bandroid:name\s*=\s*"(.+?)"/.exec(activityTag); + if (!activityName) throw new Error('Could not find android:name within ' + manifestPath); + + return packageName[1] + '/.' + activityName[1]; +} + +exports.getActivityName = function() { + return (cachedAppInfo = cachedAppInfo || readAppInfoFromManifest()); +}; diff --git a/StoneIsland/platforms/android/cordova/lib/build.js b/StoneIsland/platforms/android/cordova/lib/build.js new file mode 100755 index 00000000..aa9f3d01 --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/build.js @@ -0,0 +1,717 @@ +#!/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. +*/ + +/* jshint sub:true */ + +var shell = require('shelljs'), + spawn = require('./spawn'), + Q = require('q'), + path = require('path'), + fs = require('fs'), + os = require('os'), + ROOT = path.join(__dirname, '..', '..'); +var check_reqs = require('./check_reqs'); +var exec = require('./exec'); + + +var SIGNING_PROPERTIES = '-signing.properties'; +var MARKER = 'YOUR CHANGES WILL BE ERASED!'; +var TEMPLATE = + '# This file is automatically generated.\n' + + '# Do not modify this file -- ' + MARKER + '\n'; + +function findApks(directory) { + var ret = []; + if (fs.existsSync(directory)) { + fs.readdirSync(directory).forEach(function(p) { + if (path.extname(p) == '.apk') { + ret.push(path.join(directory, p)); + } + }); + } + return ret; +} + +function sortFilesByDate(files) { + return files.map(function(p) { + return { p: p, t: fs.statSync(p).mtime }; + }).sort(function(a, b) { + var timeDiff = b.t - a.t; + return timeDiff === 0 ? a.p.length - b.p.length : timeDiff; + }).map(function(p) { return p.p; }); +} + +function isAutoGenerated(file) { + if(fs.existsSync(file)) { + var fileContents = fs.readFileSync(file, 'utf8'); + return fileContents.indexOf(MARKER) > 0; + } + return false; +} + +function findOutputApksHelper(dir, build_type, arch) { + var ret = findApks(dir).filter(function(candidate) { + // Need to choose between release and debug .apk. + if (build_type === 'debug') { + return /-debug/.exec(candidate) && !/-unaligned|-unsigned/.exec(candidate); + } + if (build_type === 'release') { + return /-release/.exec(candidate) && !/-unaligned/.exec(candidate); + } + return true; + }); + ret = sortFilesByDate(ret); + if (ret.length === 0) { + return ret; + } + // Assume arch-specific build if newest apk has -x86 or -arm. + var archSpecific = !!/-x86|-arm/.exec(ret[0]); + // And show only arch-specific ones (or non-arch-specific) + ret = ret.filter(function(p) { + /*jshint -W018 */ + return !!/-x86|-arm/.exec(p) == archSpecific; + /*jshint +W018 */ + }); + if (archSpecific && ret.length > 1) { + ret = ret.filter(function(p) { + return p.indexOf('-' + arch) != -1; + }); + } + + return ret; +} + +function hasCustomRules() { + return fs.existsSync(path.join(ROOT, 'custom_rules.xml')); +} + +function extractRealProjectNameFromManifest(projectPath) { + var manifestPath = path.join(projectPath, 'AndroidManifest.xml'); + var manifestData = fs.readFileSync(manifestPath, 'utf8'); + var m = /<manifest[\s\S]*?package\s*=\s*"(.*?)"/i.exec(manifestData); + if (!m) { + throw new Error('Could not find package name in ' + manifestPath); + } + + var packageName=m[1]; + var lastDotIndex = packageName.lastIndexOf('.'); + return packageName.substring(lastDotIndex + 1); +} + +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 findAllUniq(data, r) { + var s = {}; + var m; + while ((m = r.exec(data))) { + s[m[1]] = 1; + } + return Object.keys(s); +} + +function readProjectProperties() { + var data = fs.readFileSync(path.join(ROOT, 'project.properties'), 'utf8'); + return { + libs: findAllUniq(data, /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg), + gradleIncludes: findAllUniq(data, /^\s*cordova\.gradle\.include\.\d+=(.*)(?:\s|$)/mg), + systemLibs: findAllUniq(data, /^\s*cordova\.system\.library\.\d+=(.*)(?:\s|$)/mg) + }; +} + +var builders = { + ant: { + getArgs: function(cmd, opts) { + 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'); + } + if(opts.packageInfo) { + args.push('-propertyfile=' + path.join(ROOT, opts.buildType + SIGNING_PROPERTIES)); + } + return args; + }, + + prepEnv: function(opts) { + 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'), TEMPLATE); + } + } + writeBuildXml(ROOT); + var propertiesObj = readProjectProperties(); + var subProjects = propertiesObj.libs; + for (var i = 0; i < subProjects.length; ++i) { + writeBuildXml(path.join(ROOT, subProjects[i])); + } + if (propertiesObj.systemLibs.length > 0) { + throw new Error('Project contains at least one plugin that requires a system library. This is not supported with ANT. Please build using gradle.'); + } + + var propertiesFile = opts.buildType + SIGNING_PROPERTIES; + var propertiesFilePath = path.join(ROOT, propertiesFile); + if (opts.packageInfo) { + fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties()); + } else if(isAutoGenerated(propertiesFilePath)) { + shell.rm('-f', propertiesFilePath); + } + }); + }, + + /* + * Builds the project with ant. + * Returns a promise. + */ + build: function(opts) { + // 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(opts); + } + + var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts); + return check_reqs.check_ant() + .then(function() { + console.log('Executing: ant ' + args.join(' ')); + return spawn('ant', args); + }); + }, + + clean: function(opts) { + var args = this.getArgs('clean', opts); + return check_reqs.check_ant() + .then(function() { + return spawn('ant', args); + }); + }, + + findOutputApks: function(build_type) { + var binDir = path.join(ROOT, hasCustomRules() ? 'ant-build' : 'bin'); + return findOutputApksHelper(binDir, build_type, null); + } + }, + gradle: { + getArgs: function(cmd, opts) { + if (cmd == 'release') { + cmd = 'cdvBuildRelease'; + } else if (cmd == 'debug') { + cmd = 'cdvBuildDebug'; + } + var args = [cmd, '-b', path.join(ROOT, 'build.gradle')]; + if (opts.arch) { + args.push('-PcdvBuildArch=' + opts.arch); + } + + // 10 seconds -> 6 seconds + args.push('-Dorg.gradle.daemon=true'); + args.push.apply(args, opts.extraArgs); + // Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet): + // args.push('-Dorg.gradle.parallel=true'); + return args; + }, + + // Makes the project buildable, minus the gradle wrapper. + prepBuildFiles: function() { + var projectPath = ROOT; + // Update the version of build.gradle in each dependent library. + var pluginBuildGradle = path.join(projectPath, 'cordova', 'lib', 'plugin-build.gradle'); + var propertiesObj = readProjectProperties(); + var subProjects = propertiesObj.libs; + for (var i = 0; i < subProjects.length; ++i) { + if (subProjects[i] !== 'CordovaLib') { + shell.cp('-f', pluginBuildGradle, path.join(ROOT, subProjects[i], 'build.gradle')); + } + } + + var name = extractRealProjectNameFromManifest(ROOT); + //Remove the proj.id/name- prefix from projects: https://issues.apache.org/jira/browse/CB-9149 + var settingsGradlePaths = subProjects.map(function(p){ + var realDir=p.replace(/[/\\]/g, ':'); + var libName=realDir.replace(name+'-',''); + var str='include ":'+libName+'"\n'; + if(realDir.indexOf(name+'-')!==-1) + str+='project(":'+libName+'").projectDir = new File("'+p+'")\n'; + return str; + }); + + // Write the settings.gradle file. + fs.writeFileSync(path.join(projectPath, 'settings.gradle'), + '// GENERATED FILE - DO NOT EDIT\n' + + 'include ":"\n' + settingsGradlePaths.join('')); + // Update dependencies within build.gradle. + var buildGradle = fs.readFileSync(path.join(projectPath, 'build.gradle'), 'utf8'); + var depsList = ''; + subProjects.forEach(function(p) { + var libName=p.replace(/[/\\]/g, ':').replace(name+'-',''); + depsList += ' debugCompile project(path: "' + libName + '", configuration: "debug")\n'; + depsList += ' releaseCompile project(path: "' + libName + '", configuration: "release")\n'; + }); + // For why we do this mapping: https://issues.apache.org/jira/browse/CB-8390 + var SYSTEM_LIBRARY_MAPPINGS = [ + [/^\/?extras\/android\/support\/(.*)$/, 'com.android.support:support-$1:+'], + [/^\/?google\/google_play_services\/libproject\/google-play-services_lib\/?$/, 'com.google.android.gms:play-services:+'] + ]; + propertiesObj.systemLibs.forEach(function(p) { + var mavenRef; + // It's already in gradle form if it has two ':'s + if (/:.*:/.exec(p)) { + mavenRef = p; + } else { + for (var i = 0; i < SYSTEM_LIBRARY_MAPPINGS.length; ++i) { + var pair = SYSTEM_LIBRARY_MAPPINGS[i]; + if (pair[0].exec(p)) { + mavenRef = p.replace(pair[0], pair[1]); + break; + } + } + if (!mavenRef) { + throw new Error('Unsupported system library (does not work with gradle): ' + p); + } + } + depsList += ' compile "' + mavenRef + '"\n'; + }); + buildGradle = buildGradle.replace(/(SUB-PROJECT DEPENDENCIES START)[\s\S]*(\/\/ SUB-PROJECT DEPENDENCIES END)/, '$1\n' + depsList + ' $2'); + var includeList = ''; + propertiesObj.gradleIncludes.forEach(function(includePath) { + includeList += 'apply from: "' + includePath + '"\n'; + }); + buildGradle = buildGradle.replace(/(PLUGIN GRADLE EXTENSIONS START)[\s\S]*(\/\/ PLUGIN GRADLE EXTENSIONS END)/, '$1\n' + includeList + '$2'); + fs.writeFileSync(path.join(projectPath, 'build.gradle'), buildGradle); + }, + + prepEnv: function(opts) { + var self = this; + return check_reqs.check_gradle() + .then(function() { + return self.prepBuildFiles(); + }).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.rm('-f', path.join(projectPath, 'gradlew.bat')); + shell.cp(path.join(wrapperDir, 'gradlew.bat'), projectPath); + } else { + shell.rm('-f', path.join(projectPath, 'gradlew')); + shell.cp(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')); + + // If the gradle distribution URL is set, make sure it points to version we want. + // If it's not set, do nothing, assuming that we're using a future version of gradle that we don't want to mess with. + // For some reason, using ^ and $ don't work. This does the job, though. + var distributionUrlRegex = /distributionUrl.*zip/; + var distributionUrl = 'distributionUrl=http\\://services.gradle.org/distributions/gradle-2.2.1-all.zip'; + var gradleWrapperPropertiesPath = path.join(projectPath, 'gradle', 'wrapper', 'gradle-wrapper.properties'); + shell.chmod('u+w', gradleWrapperPropertiesPath); + shell.sed('-i', distributionUrlRegex, distributionUrl, gradleWrapperPropertiesPath); + + var propertiesFile = opts.buildType + SIGNING_PROPERTIES; + var propertiesFilePath = path.join(ROOT, propertiesFile); + if (opts.packageInfo) { + fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties()); + } else if (isAutoGenerated(propertiesFilePath)) { + shell.rm('-f', propertiesFilePath); + } + }); + }, + + /* + * Builds the project with gradle. + * Returns a promise. + */ + build: function(opts) { + var wrapper = path.join(ROOT, 'gradlew'); + var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts); + return Q().then(function() { + console.log('Running: ' + wrapper + ' ' + args.join(' ')); + return spawn(wrapper, args); + }); + }, + + clean: function(opts) { + var builder = this; + var wrapper = path.join(ROOT, 'gradlew'); + var args = builder.getArgs('clean', opts); + return Q().then(function() { + console.log('Running: ' + wrapper + ' ' + args.join(' ')); + return spawn(wrapper, args); + }); + }, + + findOutputApks: function(build_type, arch) { + var binDir = path.join(ROOT, 'build', 'outputs', 'apk'); + return findOutputApksHelper(binDir, build_type, arch); + } + }, + + none: { + prepEnv: function() { + return Q(); + }, + build: function() { + console.log('Skipping build...'); + return Q(null); + }, + clean: function() { + return Q(); + }, + findOutputApks: function(build_type, arch) { + return sortFilesByDate(builders.ant.findOutputApks(build_type, arch).concat(builders.gradle.findOutputApks(build_type, arch))); + } + } +}; + +module.exports.isBuildFlag = function(flag) { + return /^--(debug|release|ant|gradle|nobuild|versionCode=|minSdkVersion=|gradleArg=|keystore=|alias=|password=|storePassword=|keystoreType=|buildConfig=)/.exec(flag); +}; + +function parseOpts(options, resolvedTarget) { + // Backwards-compatibility: Allow a single string argument + if (typeof options == 'string') options = [options]; + + var ret = { + buildType: 'debug', + buildMethod: process.env['ANDROID_BUILD'] || 'gradle', + arch: null, + extraArgs: [] + }; + + var multiValueArgs = { + 'versionCode': true, + 'minSdkVersion': true, + 'gradleArg': true, + 'keystore' : true, + 'alias' : true, + 'password' : true, + 'storePassword' : true, + 'keystoreType' : true, + 'buildConfig' : true + }; + var packageArgs = {}; + var buildConfig; + // Iterate through command line options + for (var i=0; options && (i < options.length); ++i) { + if (/^--/.exec(options[i])) { + var keyValue = options[i].substring(2).split('='); + var flagName = keyValue.shift(); + var flagValue = keyValue.join('='); + if (multiValueArgs[flagName] && !flagValue) { + flagValue = options[i + 1]; + ++i; + } + switch(flagName) { + case 'debug': + case 'release': + ret.buildType = flagName; + break; + case 'ant': + case 'gradle': + ret.buildMethod = flagName; + break; + case 'device': + case 'emulator': + // Don't need to do anything special to when building for device vs emulator. + // iOS uses this flag to switch on architecture. + break; + case 'prepenv' : + ret.prepEnv = true; + break; + case 'nobuild' : + ret.buildMethod = 'none'; + break; + case 'versionCode': + ret.extraArgs.push('-PcdvVersionCode=' + flagValue); + break; + case 'minSdkVersion': + ret.extraArgs.push('-PcdvMinSdkVersion=' + flagValue); + break; + case 'gradleArg': + ret.extraArgs.push(flagValue); + break; + case 'keystore': + packageArgs.keystore = path.relative(ROOT, path.resolve(flagValue)); + break; + case 'alias': + case 'storePassword': + case 'password': + case 'keystoreType': + packageArgs[flagName] = flagValue; + break; + case 'buildConfig': + buildConfig = flagValue; + break; + default : + console.warn('Build option --\'' + flagName + '\' not recognized (ignoring).'); + } + } else { + console.warn('Build option \'' + options[i] + '\' not recognized (ignoring).'); + } + } + + // If some values are not specified as command line arguments - use build config to supplement them. + // Command line arguemnts have precedence over build config. + if (buildConfig) { + if (!fs.existsSync(buildConfig)) { + throw new Error('Specified build config file does not exist: ' + buildConfig); + } + console.log('Reading build config file: '+ path.resolve(buildConfig)); + var config = JSON.parse(fs.readFileSync(buildConfig, 'utf8')); + if (config.android && config.android[ret.buildType]) { + var androidInfo = config.android[ret.buildType]; + if(androidInfo.keystore && !packageArgs.keystore) { + if(path.isAbsolute(androidInfo.keystore)) { + packageArgs.keystore = androidInfo.keystore; + } else { + packageArgs.keystore = path.relative(ROOT, path.join(path.dirname(buildConfig), androidInfo.keystore)); + } + } + + ['alias', 'storePassword', 'password','keystoreType'].forEach(function (key){ + packageArgs[key] = packageArgs[key] || androidInfo[key]; + }); + } + } + if (packageArgs.keystore && packageArgs.alias) { + ret.packageInfo = new PackageInfo(packageArgs.keystore, packageArgs.alias, packageArgs.storePassword, + packageArgs.password, packageArgs.keystoreType); + } + + if(!ret.packageInfo) { + if(Object.keys(packageArgs).length > 0) { + console.warn('\'keystore\' and \'alias\' need to be specified to generate a signed archive.'); + } + } + ret.arch = resolvedTarget && resolvedTarget.arch; + + return ret; +} + +/* + * 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) + .then(function() { + return builder.clean(opts); + }).then(function() { + shell.rm('-rf', path.join(ROOT, 'out')); + + ['debug', 'release'].forEach(function(config) { + var propertiesFilePath = path.join(ROOT, config + SIGNING_PROPERTIES); + if(isAutoGenerated(propertiesFilePath)){ + shell.rm('-f', propertiesFilePath); + } + }); + }); +}; + +/* + * Builds the project with the specifed options + * Returns a promise. + */ +module.exports.run = function(options, optResolvedTarget) { + var opts = parseOpts(options, optResolvedTarget); + var builder = builders[opts.buildMethod]; + return builder.prepEnv(opts) + .then(function() { + if (opts.prepEnv) { + console.log('Build file successfully prepared.'); + return; + } + return builder.build(opts) + .then(function() { + var apkPaths = builder.findOutputApks(opts.buildType, opts.arch); + console.log('Built the following apk(s):'); + console.log(' ' + apkPaths.join('\n ')); + return { + apkPaths: apkPaths, + buildType: opts.buildType, + buildMethod: opts.buildMethod + }; + }); + }); +}; + +// Called by plugman after installing plugins, and by create script after creating project. +module.exports.prepBuildFiles = function() { + var builder = builders['gradle']; + return builder.prepBuildFiles(); +}; + +/* + * Detects the architecture of a device/emulator + * Returns "arm" or "x86". + */ +module.exports.detectArchitecture = function(target) { + function helper() { + return exec('adb -s ' + target + ' shell cat /proc/cpuinfo', os.tmpdir()) + .then(function(output) { + if (/intel/i.exec(output)) { + return 'x86'; + } + return 'arm'; + }); + } + // It sometimes happens (at least on OS X), that this command will hang forever. + // To fix it, either unplug & replug device, or restart adb server. + return helper().timeout(1000, 'Device communication timed out. Try unplugging & replugging the device.') + .then(null, function(err) { + if (/timed out/.exec('' + err)) { + // adb kill-server doesn't seem to do the trick. + // Could probably find a x-platform version of killall, but I'm not actually + // sure that this scenario even happens on non-OSX machines. + return exec('killall adb') + .then(function() { + console.log('adb seems hung. retrying.'); + return helper() + .then(null, function() { + // The double kill is sadly often necessary, at least on mac. + console.log('Now device not found... restarting adb again.'); + return exec('killall adb') + .then(function() { + return helper() + .then(null, function() { + return Q.reject('USB is flakey. Try unplugging & replugging the device.'); + }); + }); + }); + }, function() { + // For non-killall OS's. + return Q.reject(err); + }); + } + throw err; + }); +}; + +module.exports.findBestApkForArchitecture = function(buildResults, arch) { + var paths = buildResults.apkPaths.filter(function(p) { + if (buildResults.buildType == 'debug') { + return /-debug/.exec(p); + } + return !/-debug/.exec(p); + }); + var archPattern = new RegExp('-' + arch); + var hasArchPattern = /-x86|-arm/; + for (var i = 0; i < paths.length; ++i) { + if (hasArchPattern.exec(paths[i])) { + if (archPattern.exec(paths[i])) { + return paths[i]; + } + } else { + return paths[i]; + } + } + throw new Error('Could not find apk architecture: ' + arch + ' build-type: ' + buildResults.buildType); +}; + +function PackageInfo(keystore, alias, storePassword, password, keystoreType) { + this.keystore = { + 'name': 'key.store', + 'value': keystore + }; + this.alias = { + 'name': 'key.alias', + 'value': alias + }; + if (storePassword) { + this.storePassword = { + 'name': 'key.store.password', + 'value': storePassword + }; + } + if (password) { + this.password = { + 'name': 'key.alias.password', + 'value': password + }; + } + if (keystoreType) { + this.keystoreType = { + 'name': 'key.store.type', + 'value': keystoreType + }; + } +} + +PackageInfo.prototype = { + toProperties: function() { + var self = this; + var result = ''; + Object.keys(self).forEach(function(key) { + result += self[key].name; + result += '='; + result += self[key].value.replace(/\\/g, '\\\\'); + result += '\n'; + }); + return result; + } +}; + +module.exports.help = function() { + console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'build')) + ' [flags] [Signed APK flags]'); + console.log('Flags:'); + console.log(' \'--debug\': will build project in debug mode (default)'); + console.log(' \'--release\': will build project for release'); + console.log(' \'--ant\': will build project with ant'); + console.log(' \'--gradle\': will build project with gradle (default)'); + console.log(' \'--nobuild\': will skip build process (useful when using run command)'); + console.log(' \'--prepenv\': don\'t build, but copy in build scripts where necessary'); + console.log(' \'--versionCode=#\': Override versionCode for this build. Useful for uploading multiple APKs. Requires --gradle.'); + console.log(' \'--minSdkVersion=#\': Override minSdkVersion for this build. Useful for uploading multiple APKs. Requires --gradle.'); + console.log(' \'--gradleArg=<gradle command line arg>\': Extra args to pass to the gradle command. Use one flag per arg. Ex. --gradleArg=-PcdvBuildMultipleApks=true'); + console.log(''); + console.log('Signed APK flags (overwrites debug/release-signing.proprties) :'); + console.log(' \'--keystore=<path to keystore>\': Key store used to build a signed archive. (Required)'); + console.log(' \'--alias=\': Alias for the key store. (Required)'); + console.log(' \'--storePassword=\': Password for the key store. (Optional - prompted)'); + console.log(' \'--password=\': Password for the key. (Optional - prompted)'); + console.log(' \'--keystoreType\': Type of the keystore. (Optional)'); + process.exit(0); +}; diff --git a/StoneIsland/platforms/android/cordova/lib/check_reqs.js b/StoneIsland/platforms/android/cordova/lib/check_reqs.js new file mode 100755 index 00000000..9d251596 --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/check_reqs.js @@ -0,0 +1,327 @@ +#!/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. +*/ + +/* jshint sub:true */ + +var shelljs = require('shelljs'), + child_process = require('child_process'), + Q = require('q'), + path = require('path'), + fs = require('fs'), + which = require('which'), + ROOT = path.join(__dirname, '..', '..'); + +var isWindows = process.platform == 'win32'; + +function forgivingWhichSync(cmd) { + try { + // TODO: Should use shelljs.which() here to have one less dependency. + return fs.realpathSync(which.sync(cmd)); + } catch (e) { + return ''; + } +} + +function tryCommand(cmd, errMsg, catchStderr) { + var d = Q.defer(); + child_process.exec(cmd, function(err, stdout, stderr) { + if (err) d.reject(new Error(errMsg)); + // Sometimes it is necessary to return an stderr instead of stdout in case of success, since + // some commands prints theirs output to stderr instead of stdout. 'javac' is the example + else d.resolve((catchStderr ? stderr : stdout).trim()); + }); + return d.promise; +} + +// Get valid target from framework/project.properties +module.exports.get_target = function() { + function extractFromFile(filePath) { + var target = shelljs.grep(/\btarget=/, filePath); + if (!target) { + throw new Error('Could not find android target within: ' + filePath); + } + return target.split('=')[1].trim(); + } + if (fs.existsSync(path.join(ROOT, 'framework', 'project.properties'))) { + return extractFromFile(path.join(ROOT, 'framework', 'project.properties')); + } + if (fs.existsSync(path.join(ROOT, 'project.properties'))) { + // if no target found, we're probably in a project and project.properties is in ROOT. + return extractFromFile(path.join(ROOT, 'project.properties')); + } + throw new Error('Could not find android target. File missing: ' + path.join(ROOT, 'project.properties')); +}; + +// 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.') + .then(function (output) { + // Parse Ant version from command output + return /version ((?:\d+\.)+(?:\d+))/i.exec(output)[1]; + }); +}; + +// Returns a promise. Called only by build and clean commands. +module.exports.check_gradle = function() { + var sdkDir = process.env['ANDROID_HOME']; + if (!sdkDir) + return Q.reject('Could not find gradle wrapper within Android SDK. Could not find Android SDK directory.\n' + + 'Might need to install Android SDK or set up \'ANDROID_HOME\' env variable.'); + + 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() { + 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) { + var msg = 'Failed to find \'JAVA_HOME\' environment variable. Try setting setting it manually.'; + // OS X has a command for finding JAVA_HOME. + if (fs.existsSync('/usr/libexec/java_home')) { + return tryCommand('/usr/libexec/java_home', msg) + .then(function(stdout) { + process.env['JAVA_HOME'] = stdout.trim(); + }); + } else { + // See if we can derive it from javac's location. + // fs.realpathSync is require on Ubuntu, which symplinks from /usr/bin -> JDK + 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(msg); + } + } + } else if (isWindows) { + // Try to auto-detect java in the default install paths. + var oldSilent = shelljs.config.silent; + shelljs.config.silent = true; + 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]; + shelljs.config.silent = oldSilent; + 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; + } + } + } + }).then(function() { + var msg = + 'Failed to run "java -version", make sure that you have a JDK installed.\n' + + 'You can get it from: http://www.oracle.com/technetwork/java/javase/downloads.\n'; + if (process.env['JAVA_HOME']) { + msg += 'Your JAVA_HOME is invalid: ' + process.env['JAVA_HOME'] + '\n'; + } + return tryCommand('java -version', msg) + .then(function() { + // We use tryCommand with catchStderr = true, because + // javac writes version info to stderr instead of stdout + return tryCommand('javac -version', msg, true); + }).then(function (output) { + var match = /javac ((?:\d+\.)+(?:\d+))/i.exec(output)[1]; + return match && match[1]; + }); + }); +}; + +// Returns a promise. +module.exports.check_android = function() { + return Q().then(function() { + var androidCmdPath = forgivingWhichSync('android'); + var adbInPath = !!forgivingWhichSync('adb'); + var hasAndroidHome = !!process.env['ANDROID_HOME'] && fs.existsSync(process.env['ANDROID_HOME']); + function maybeSetAndroidHome(value) { + if (!hasAndroidHome && fs.existsSync(value)) { + hasAndroidHome = true; + process.env['ANDROID_HOME'] = value; + } + } + if (!hasAndroidHome && !androidCmdPath) { + if (isWindows) { + // Android Studio 1.0 installer + maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'sdk')); + maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'sdk')); + // Android Studio pre-1.0 installer + maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-studio', 'sdk')); + maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-studio', 'sdk')); + // Stand-alone installer + maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-sdk')); + maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-sdk')); + } else if (process.platform == 'darwin') { + // Android Studio 1.0 installer + maybeSetAndroidHome(path.join(process.env['HOME'], 'Library', 'Android', 'sdk')); + // Android Studio pre-1.0 installer + maybeSetAndroidHome('/Applications/Android Studio.app/sdk'); + // Stand-alone zip file that user might think to put under /Applications + maybeSetAndroidHome('/Applications/android-sdk-macosx'); + maybeSetAndroidHome('/Applications/android-sdk'); + } + if (process.env['HOME']) { + // Stand-alone zip file that user might think to put under their home directory + maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk-macosx')); + maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk')); + } + } + if (hasAndroidHome && !androidCmdPath) { + process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'tools'); + } + if (androidCmdPath && !hasAndroidHome) { + var parentDir = path.dirname(androidCmdPath); + var grandParentDir = path.dirname(parentDir); + if (path.basename(parentDir) == 'tools') { + process.env['ANDROID_HOME'] = path.dirname(parentDir); + hasAndroidHome = true; + } else if (fs.existsSync(path.join(grandParentDir, 'tools', 'android'))) { + process.env['ANDROID_HOME'] = grandParentDir; + hasAndroidHome = true; + } else { + throw new Error('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' + + 'Detected \'android\' command at ' + parentDir + ' but no \'tools\' directory found near.\n' + + 'Try reinstall Android SDK or update your PATH to include path to valid SDK directory.'); + } + } + if (hasAndroidHome && !adbInPath) { + process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'platform-tools'); + } + if (!process.env['ANDROID_HOME']) { + throw new Error('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' + + 'Failed to find \'android\' command in your \'PATH\'. Try update your \'PATH\' to include path to valid SDK directory.'); + } + if (!fs.existsSync(process.env['ANDROID_HOME'])) { + throw new Error('\'ANDROID_HOME\' environment variable is set to non-existent path: ' + process.env['ANDROID_HOME'] + + '\nTry update it manually to point to valid SDK directory.'); + } + }); +}; + +module.exports.getAbsoluteAndroidCmd = function() { + return forgivingWhichSync('android').replace(/(\s)/g, '\\$1'); +}; + +module.exports.check_android_target = function(valid_target) { + // valid_target can look like: + // android-19 + // android-L + // Google Inc.:Google APIs:20 + // Google Inc.:Glass Development Kit Preview:20 + if (!valid_target) valid_target = module.exports.get_target(); + var msg = 'Android SDK not found. Make sure that it is installed. If it is not at the default location, set the ANDROID_HOME environment variable.'; + return tryCommand('android list targets --compact', msg) + .then(function(output) { + var targets = output.split('\n'); + if (targets.indexOf(valid_target) >= 0) { + return targets; + } + + var androidCmd = module.exports.getAbsoluteAndroidCmd(); + throw new Error('Please install Android target: "' + valid_target + '".\n\n' + + 'Hint: Open the SDK manager by running: ' + androidCmd + '\n' + + 'You will require:\n' + + '1. "SDK Platform" for ' + valid_target + '\n' + + '2. "Android SDK Platform-tools (latest)\n' + + '3. "Android SDK Build-tools" (latest)'); + }); +}; + +// Returns a promise. +module.exports.run = function() { + return Q.all([this.check_java(), this.check_android().then(this.check_android_target)]) + .then(function() { + console.log('ANDROID_HOME=' + process.env['ANDROID_HOME']); + console.log('JAVA_HOME=' + process.env['JAVA_HOME']); + }); +}; + +/** + * Object thar represents one of requirements for current platform. + * @param {String} id The unique identifier for this requirements. + * @param {String} name The name of requirements. Human-readable field. + * @param {String} version The version of requirement installed. In some cases could be an array of strings + * (for example, check_android_target returns an array of android targets installed) + * @param {Boolean} installed Indicates whether the requirement is installed or not + */ +var Requirement = function (id, name, version, installed) { + this.id = id; + this.name = name; + this.installed = installed || false; + this.metadata = { + version: version, + }; +}; + +/** + * Methods that runs all checks one by one and returns a result of checks + * as an array of Requirement objects. This method intended to be used by cordova-lib check_reqs method + * + * @return Promise<Requirement[]> Array of requirements. Due to implementation, promise is always fulfilled. + */ +module.exports.check_all = function() { + + var requirements = [ + new Requirement('java', 'Java JDK'), + new Requirement('androidSdk', 'Android SDK'), + new Requirement('androidTarget', 'Android target'), + new Requirement('gradle', 'Gradle') + ]; + + var checkFns = [ + this.check_java, + this.check_android, + this.check_android_target, + this.check_gradle + ]; + + // Then execute requirement checks one-by-one + return checkFns.reduce(function (promise, checkFn, idx) { + // Update each requirement with results + var requirement = requirements[idx]; + return promise.then(checkFn) + .then(function (version) { + requirement.installed = true; + requirement.metadata.version = version; + }, function (err) { + requirement.metadata.reason = err instanceof Error ? err.message : err; + }); + }, Q()) + .then(function () { + // When chain is completed, return requirements array to upstream API + return requirements; + }); +}; diff --git a/StoneIsland/platforms/android/cordova/lib/device.js b/StoneIsland/platforms/android/cordova/lib/device.js new file mode 100755 index 00000000..c13fdc40 --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/device.js @@ -0,0 +1,121 @@ +#!/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 exec = require('./exec'), + Q = require('q'), + os = require('os'), + build = require('./build'), + appinfo = require('./appinfo'); + +/** + * Returns a promise for the list of the device ID's found + * @param lookHarder When true, try restarting adb if no devices are found. + */ +module.exports.list = function(lookHarder) { + function helper() { + return exec('adb devices', os.tmpdir()) + .then(function(output) { + var response = output.split('\n'); + var device_list = []; + for (var i = 1; i < response.length; i++) { + if (response[i].match(/\w+\tdevice/) && !response[i].match(/emulator/)) { + device_list.push(response[i].replace(/\tdevice/, '').replace('\r', '')); + } + } + return device_list; + }); + } + return helper() + .then(function(list) { + if (list.length === 0 && lookHarder) { + // adb kill-server doesn't seem to do the trick. + // Could probably find a x-platform version of killall, but I'm not actually + // sure that this scenario even happens on non-OSX machines. + return exec('killall adb') + .then(function() { + console.log('Restarting adb to see if more devices are detected.'); + return helper(); + }, function() { + // For non-killall OS's. + return list; + }); + } + return list; + }); +}; + +module.exports.resolveTarget = function(target) { + return this.list(true) + .then(function(device_list) { + if (!device_list || !device_list.length) { + return Q.reject('ERROR: Failed to deploy to device, no devices found.'); + } + // default device + target = target || device_list[0]; + + if (device_list.indexOf(target) < 0) { + return Q.reject('ERROR: Unable to find target \'' + target + '\'.'); + } + + return build.detectArchitecture(target) + .then(function(arch) { + return { target: target, arch: arch, isEmulator: false }; + }); + }); +}; + +/* + * Installs a previously built application on the device + * and launches it. + * Returns a promise. + */ +module.exports.install = function(target, buildResults) { + return Q().then(function() { + if (target && typeof target == 'object') { + return target; + } + return module.exports.resolveTarget(target); + }).then(function(resolvedTarget) { + var apk_path = build.findBestApkForArchitecture(buildResults, resolvedTarget.arch); + var launchName = appinfo.getActivityName(); + console.log('Using apk: ' + apk_path); + console.log('Installing app on device...'); + var cmd = 'adb -s ' + resolvedTarget.target + ' install -r "' + apk_path + '"'; + return exec(cmd, os.tmpdir()) + .then(function(output) { + if (output.match(/Failure/)) return Q.reject('ERROR: Failed to install apk to device: ' + output); + + //unlock screen + var cmd = 'adb -s ' + resolvedTarget.target + ' shell input keyevent 82'; + return exec(cmd, os.tmpdir()); + }, function(err) { return Q.reject('ERROR: Failed to install apk to device: ' + err); }) + .then(function() { + // launch the application + console.log('Launching application...'); + var cmd = 'adb -s ' + resolvedTarget.target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName; + return exec(cmd, os.tmpdir()); + }).then(function() { + console.log('LAUNCH SUCCESS'); + }, function(err) { + return Q.reject('ERROR: Failed to launch application on device: ' + err); + }); + }); +}; diff --git a/StoneIsland/platforms/android/cordova/lib/emulator.js b/StoneIsland/platforms/android/cordova/lib/emulator.js new file mode 100755 index 00000000..e81dd679 --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/emulator.js @@ -0,0 +1,372 @@ +#!/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. +*/ + +/* jshint sub:true */ + +var exec = require('./exec'); +var appinfo = require('./appinfo'); +var retry = require('./retry'); +var build = require('./build'); +var check_reqs = require('./check_reqs'); + +var Q = require('q'); +var os = require('os'); +var child_process = require('child_process'); + +// constants +var ONE_SECOND = 1000; // in milliseconds +var INSTALL_COMMAND_TIMEOUT = 120 * ONE_SECOND; // in milliseconds +var NUM_INSTALL_RETRIES = 3; +var EXEC_KILL_SIGNAL = 'SIGKILL'; + +/** + * Returns a Promise for a list of emulator images in the form of objects + * { + name : <emulator_name>, + path : <path_to_emulator_image>, + target : <api_target>, + abi : <cpu>, + skin : <skin> + } + */ +module.exports.list_images = function() { + return exec('android list avds') + .then(function(output) { + var response = output.split('\n'); + var emulator_list = []; + for (var i = 1; i < response.length; i++) { + // To return more detailed information use img_obj + var img_obj = {}; + if (response[i].match(/Name:\s/)) { + img_obj['name'] = response[i].split('Name: ')[1].replace('\r', ''); + if (response[i + 1].match(/Path:\s/)) { + i++; + img_obj['path'] = response[i].split('Path: ')[1].replace('\r', ''); + } + if (response[i + 1].match(/\(API\slevel\s/)) { + i++; + img_obj['target'] = response[i].replace('\r', ''); + } + if (response[i + 1].match(/ABI:\s/)) { + i++; + img_obj['abi'] = response[i].split('ABI: ')[1].replace('\r', ''); + } + if (response[i + 1].match(/Skin:\s/)) { + i++; + img_obj['skin'] = response[i].split('Skin: ')[1].replace('\r', ''); + } + + emulator_list.push(img_obj); + } + /* To just return a list of names use this + if (response[i].match(/Name:\s/)) { + emulator_list.push(response[i].split('Name: ')[1].replace('\r', ''); + }*/ + + } + return emulator_list; + }); +}; + +/** + * Will return the closest avd to the projects target + * or undefined if no avds exist. + * Returns a promise. + */ +module.exports.best_image = function() { + var project_target = check_reqs.get_target().replace('android-', ''); + return this.list_images() + .then(function(images) { + var closest = 9999; + var best = images[0]; + for (var i in images) { + var target = images[i].target; + if(target) { + var num = target.split('(API level ')[1].replace(')', ''); + if (num == project_target) { + return images[i]; + } else if (project_target - num < closest && project_target > num) { + closest = project_target - num; + best = images[i]; + } + } + } + return best; + }); +}; + +// Returns a promise. +module.exports.list_started = function() { + return exec('adb devices', os.tmpdir()) + .then(function(output) { + var response = output.split('\n'); + var started_emulator_list = []; + for (var i = 1; i < response.length; i++) { + if (response[i].match(/device/) && response[i].match(/emulator/)) { + started_emulator_list.push(response[i].replace(/\tdevice/, '').replace('\r', '')); + } + } + return started_emulator_list; + }); +}; + +// Returns a promise. +module.exports.list_targets = function() { + return exec('android list targets', os.tmpdir()) + .then(function(output) { + var target_out = output.split('\n'); + var targets = []; + for (var i = target_out.length; i >= 0; i--) { + if(target_out[i].match(/id:/)) { + targets.push(targets[i].split(' ')[1]); + } + } + return targets; + }); +}; + +/* + * Starts an emulator with the given ID, + * and returns the started ID of that emulator. + * If no ID is given it will used the first image available, + * if no image is available it will error out (maybe create one?). + * + * Returns a promise. + */ +module.exports.start = function(emulator_ID) { + var self = this; + var emulator_id, num_started, started_emulators; + + return self.list_started() + .then(function(list) { + started_emulators = list; + num_started = started_emulators.length; + if (!emulator_ID) { + return self.list_images() + .then(function(emulator_list) { + if (emulator_list.length > 0) { + return self.best_image() + .then(function(best) { + emulator_ID = best.name; + console.log('WARNING : no emulator specified, defaulting to ' + emulator_ID); + return emulator_ID; + }); + } else { + var androidCmd = check_reqs.getAbsoluteAndroidCmd(); + return Q.reject('ERROR : No emulator images (avds) found.\n' + + '1. Download desired System Image by running: ' + androidCmd + ' sdk\n' + + '2. Create an AVD by running: ' + androidCmd + ' avd\n' + + 'HINT: For a faster emulator, use an Intel System Image and install the HAXM device driver\n'); + } + }); + } else { + return Q(emulator_ID); + } + }).then(function() { + var cmd = 'emulator'; + var args = ['-avd', emulator_ID]; + var proc = child_process.spawn(cmd, args, { stdio: 'inherit', detached: true }); + proc.unref(); // Don't wait for it to finish, since the emulator will probably keep running for a long time. + }).then(function() { + // wait for emulator to start + console.log('Waiting for emulator...'); + return self.wait_for_emulator(num_started); + }).then(function(new_started) { + if (new_started.length > 1) { + for (var i in new_started) { + if (started_emulators.indexOf(new_started[i]) < 0) { + emulator_id = new_started[i]; + } + } + } else { + emulator_id = new_started[0]; + } + if (!emulator_id) return Q.reject('ERROR : Failed to start emulator, could not find new emulator'); + + //wait for emulator to boot up + process.stdout.write('Booting up emulator (this may take a while)...'); + return self.wait_for_boot(emulator_id); + }).then(function() { + console.log('BOOT COMPLETE'); + + //unlock screen + return exec('adb -s ' + emulator_id + ' shell input keyevent 82', os.tmpdir()); + }).then(function() { + //return the new emulator id for the started emulators + return emulator_id; + }); +}; + +/* + * Waits for the new emulator to apear on the started-emulator list. + * Returns a promise with a list of newly started emulators' IDs. + */ +module.exports.wait_for_emulator = function(num_running) { + var self = this; + return self.list_started() + .then(function(new_started) { + if (new_started.length > num_running) { + return new_started; + } else { + return Q.delay(1000).then(function() { + return self.wait_for_emulator(num_running); + }); + } + }); +}; + +/* + * Waits for the boot animation property of the emulator to switch to 'stopped' + */ +module.exports.wait_for_boot = function(emulator_id) { + var self = this; + return exec('adb -s ' + emulator_id + ' shell getprop init.svc.bootanim', os.tmpdir()) + .then(function(output) { + if (output.match(/stopped/)) { + return; + } else { + process.stdout.write('.'); + return Q.delay(3000).then(function() { + return self.wait_for_boot(emulator_id); + }); + } + }); +}; + +/* + * Create avd + * TODO : Enter the stdin input required to complete the creation of an avd. + * Returns a promise. + */ +module.exports.create_image = function(name, target) { + console.log('Creating avd named ' + name); + if (target) { + return exec('android create avd --name ' + name + ' --target ' + target) + .then(null, function(error) { + console.error('ERROR : Failed to create emulator image : '); + console.error(' Do you have the latest android targets including ' + target + '?'); + console.error(error); + }); + } else { + console.log('WARNING : Project target not found, creating avd with a different target but the project may fail to install.'); + return exec('android create avd --name ' + name + ' --target ' + this.list_targets()[0]) + .then(function() { + // TODO: This seems like another error case, even though it always happens. + console.error('ERROR : Unable to create an avd emulator, no targets found.'); + console.error('Please insure you have targets available by running the "android" command'); + return Q.reject(); + }, function(error) { + console.error('ERROR : Failed to create emulator image : '); + console.error(error); + }); + } +}; + +module.exports.resolveTarget = function(target) { + return this.list_started() + .then(function(emulator_list) { + if (emulator_list.length < 1) { + return Q.reject('No started emulators found, please start an emultor before deploying your project.'); + } + + // default emulator + target = target || emulator_list[0]; + if (emulator_list.indexOf(target) < 0) { + return Q.reject('Unable to find target \'' + target + '\'. Failed to deploy to emulator.'); + } + + return build.detectArchitecture(target) + .then(function(arch) { + return {target:target, arch:arch, isEmulator:true}; + }); + }); +}; + +/* + * Installs a previously built application on the emulator and launches it. + * If no target is specified, then it picks one. + * If no started emulators are found, error out. + * Returns a promise. + */ +module.exports.install = function(givenTarget, buildResults) { + + var target; + + // resolve the target emulator + return Q().then(function () { + if (givenTarget && typeof givenTarget == 'object') { + return givenTarget; + } else { + return module.exports.resolveTarget(givenTarget); + } + + // set the resolved target + }).then(function (resolvedTarget) { + target = resolvedTarget; + + // install the app + }).then(function () { + + var apk_path = build.findBestApkForArchitecture(buildResults, target.arch); + var execOptions = { + timeout: INSTALL_COMMAND_TIMEOUT, // in milliseconds + killSignal: EXEC_KILL_SIGNAL + }; + + console.log('Installing app on emulator...'); + console.log('Using apk: ' + apk_path); + + var retriedInstall = retry.retryPromise( + NUM_INSTALL_RETRIES, + exec, 'adb -s ' + target.target + ' install -r -d "' + apk_path + '"', os.tmpdir(), execOptions + ); + + return retriedInstall.then(function (output) { + if (output.match(/Failure/)) { + return Q.reject('Failed to install apk to emulator: ' + output); + } else { + console.log('INSTALL SUCCESS'); + } + }, function (err) { + return Q.reject('Failed to install apk to emulator: ' + err); + }); + + // unlock screen + }).then(function () { + + console.log('Unlocking screen...'); + return exec('adb -s ' + target.target + ' shell input keyevent 82', os.tmpdir()); + + // launch the application + }).then(function () { + + console.log('Launching application...'); + var launchName = appinfo.getActivityName(); + var cmd = 'adb -s ' + target.target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName; + return exec(cmd, os.tmpdir()); + + // report success or failure + }).then(function (output) { + console.log('LAUNCH SUCCESS'); + }, function (err) { + return Q.reject('Failed to launch app on emulator: ' + err); + }); +}; diff --git a/StoneIsland/platforms/android/cordova/lib/exec.js b/StoneIsland/platforms/android/cordova/lib/exec.js new file mode 100755 index 00000000..798a93ba --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/exec.js @@ -0,0 +1,68 @@ +#!/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 child_process = require("child_process"); +var Q = require("q"); + +// constants +var DEFAULT_MAX_BUFFER = 1024000; + +// Takes a command and optional current working directory. +// Returns a promise that either resolves with the stdout, or +// rejects with an error message and the stderr. +// +// WARNING: +// opt_cwd is an artifact of an old design, and must +// be removed in the future; the correct solution is +// to pass the options object the same way that +// child_process.exec expects +// +// NOTE: +// exec documented here - https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback +module.exports = function(cmd, opt_cwd, options) { + + var d = Q.defer(); + + if (typeof options === "undefined") { + options = {}; + } + + // override cwd to preserve old opt_cwd behavior + options.cwd = opt_cwd; + + // set maxBuffer + if (typeof options.maxBuffer === "undefined") { + options.maxBuffer = DEFAULT_MAX_BUFFER; + } + + try { + child_process.exec(cmd, options, function(err, stdout, stderr) { + if (err) d.reject("Error executing \"" + cmd + "\": " + stderr); + else d.resolve(stdout); + }); + } catch(e) { + console.error("error caught: " + e); + d.reject(e); + } + + return d.promise; +}; + diff --git a/StoneIsland/platforms/android/cordova/lib/install-device b/StoneIsland/platforms/android/cordova/lib/install-device new file mode 100755 index 00000000..fc4b7841 --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/install-device @@ -0,0 +1,42 @@ +#!/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 device = require('./device'), + args = process.argv; + +if(args.length > 2) { + var install_target; + if (args[2].substring(0, 9) == '--target=') { + install_target = args[2].substring(9, args[2].length); + device.install(install_target).done(null, function(err) { + console.error('ERROR: ' + err); + process.exit(2); + }); + } else { + console.error('ERROR : argument \'' + args[2] + '\' not recognized.'); + process.exit(2); + } +} else { + device.install().done(null, function(err) { + console.error('ERROR: ' + err); + process.exit(2); + }); +} diff --git a/StoneIsland/platforms/android/cordova/lib/install-device.bat b/StoneIsland/platforms/android/cordova/lib/install-device.bat new file mode 100755 index 00000000..ac7214ac --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/install-device.bat @@ -0,0 +1,26 @@ +:: 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. + +@ECHO OFF +SET script_path="%~dp0install-device" +IF EXIST %script_path% ( + node "%script_path%" %* +) ELSE ( + ECHO. + ECHO ERROR: Could not find 'install-device' script in 'cordova\lib' folder, aborting...>&2 + EXIT /B 1 +)
\ No newline at end of file diff --git a/StoneIsland/platforms/android/cordova/lib/install-emulator b/StoneIsland/platforms/android/cordova/lib/install-emulator new file mode 100755 index 00000000..aa2a34f6 --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/install-emulator @@ -0,0 +1,38 @@ +#!/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 emulator = require('./emulator'), + args = process.argv; + +var install_target; +if(args.length > 2) { + if (args[2].substring(0, 9) == '--target=') { + install_target = args[2].substring(9, args[2].length); + } else { + console.error('ERROR : argument \'' + args[2] + '\' not recognized.'); + process.exit(2); + } +} + +emulator.install(install_target).done(null, function(err) { + console.error('ERROR: ' + err); + process.exit(2); +}); diff --git a/StoneIsland/platforms/android/cordova/lib/install-emulator.bat b/StoneIsland/platforms/android/cordova/lib/install-emulator.bat new file mode 100755 index 00000000..1ec67790 --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/install-emulator.bat @@ -0,0 +1,26 @@ +:: 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. + +@ECHO OFF +SET script_path="%~dp0install-emulator" +IF EXIST %script_path% ( + node "%script_path%" %* +) ELSE ( + ECHO. + ECHO ERROR: Could not find 'install-emulator' script in 'cordova\lib' folder, aborting...>&2 + EXIT /B 1 +)
\ No newline at end of file diff --git a/StoneIsland/platforms/android/cordova/lib/list-devices b/StoneIsland/platforms/android/cordova/lib/list-devices new file mode 100755 index 00000000..e390bff6 --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/list-devices @@ -0,0 +1,33 @@ +#!/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 devices = require('./device'); + +// Usage support for when args are given +devices.list().done(function(device_list) { + device_list && device_list.forEach(function(dev) { + console.log(dev); + }); +}, function(err) { + console.error('ERROR: ' + err); + process.exit(2); +}); + diff --git a/StoneIsland/platforms/android/cordova/lib/list-devices.bat b/StoneIsland/platforms/android/cordova/lib/list-devices.bat new file mode 100755 index 00000000..c0bcdd9a --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/list-devices.bat @@ -0,0 +1,26 @@ +:: 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. + +@ECHO OFF +SET script_path="%~dp0list-devices" +IF EXIST %script_path% ( + node "%script_path%" %* +) ELSE ( + ECHO. + ECHO ERROR: Could not find 'list-devices' script in 'cordova\lib' folder, aborting...>&2 + EXIT /B 1 +)
\ No newline at end of file diff --git a/StoneIsland/platforms/android/cordova/lib/list-emulator-images b/StoneIsland/platforms/android/cordova/lib/list-emulator-images new file mode 100755 index 00000000..996cf555 --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/list-emulator-images @@ -0,0 +1,32 @@ +#!/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 emulators = require('./emulator'); + +// Usage support for when args are given +emulators.list_images().done(function(emulator_list) { + emulator_list && emulator_list.forEach(function(emu) { + console.log(emu.name); + }); +}, function(err) { + console.error('ERROR: ' + err); + process.exit(2); +}); diff --git a/StoneIsland/platforms/android/cordova/lib/list-emulator-images.bat b/StoneIsland/platforms/android/cordova/lib/list-emulator-images.bat new file mode 100755 index 00000000..661cbf95 --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/list-emulator-images.bat @@ -0,0 +1,26 @@ +:: 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. + +@ECHO OFF +SET script_path="%~dp0list-emulator-images" +IF EXIST %script_path% ( + node "%script_path%" %* +) ELSE ( + ECHO. + ECHO ERROR: Could not find 'list-emulator-images' script in 'cordova\lib' folder, aborting...>&2 + EXIT /B 1 +) diff --git a/StoneIsland/platforms/android/cordova/lib/list-started-emulators b/StoneIsland/platforms/android/cordova/lib/list-started-emulators new file mode 100755 index 00000000..2ae8c5a8 --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/list-started-emulators @@ -0,0 +1,32 @@ +#!/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 emulators = require('./emulator'); + +// Usage support for when args are given +emulators.list_started().done(function(emulator_list) { + emulator_list && emulator_list.forEach(function(emu) { + console.log(emu); + }); +}, function(err) { + console.error('ERROR: ' + err); + process.exit(2); +}); diff --git a/StoneIsland/platforms/android/cordova/lib/list-started-emulators.bat b/StoneIsland/platforms/android/cordova/lib/list-started-emulators.bat new file mode 100755 index 00000000..a4e88f7d --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/list-started-emulators.bat @@ -0,0 +1,26 @@ +:: 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. + +@ECHO OFF +SET script_path="%~dp0list-started-emulators" +IF EXIST %script_path% ( + node "%script_path%" %* +) ELSE ( + ECHO. + ECHO ERROR: Could not find 'list-started-emulators' script in 'cordova\lib' folder, aborting...>&2 + EXIT /B 1 +)
\ No newline at end of file diff --git a/StoneIsland/platforms/android/cordova/lib/log.js b/StoneIsland/platforms/android/cordova/lib/log.js new file mode 100755 index 00000000..ebf836d5 --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/log.js @@ -0,0 +1,56 @@ +#!/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 path = require('path'), + os = require('os'), + Q = require('q'), + child_process = require('child_process'), + ROOT = path.join(__dirname, '..', '..'); + +/* + * Starts running logcat in the shell. + * Returns a promise. + */ +module.exports.run = function() { + var d = Q.defer(); + var adb = child_process.spawn('adb', ['logcat'], {cwd: os.tmpdir()}); + + adb.stdout.on('data', function(data) { + var lines = data ? data.toString().split('\n') : []; + var out = lines.filter(function(x) { return x.indexOf('nativeGetEnabledTags') < 0; }); + console.log(out.join('\n')); + }); + + adb.stderr.on('data', console.error); + adb.on('close', function(code) { + if (code > 0) { + d.reject('Failed to run logcat command.'); + } else d.resolve(); + }); + + return d.promise; +}; + +module.exports.help = function() { + console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'log'))); + console.log('Gives the logcat output on the command line.'); + process.exit(0); +}; diff --git a/StoneIsland/platforms/android/cordova/lib/plugin-build.gradle b/StoneIsland/platforms/android/cordova/lib/plugin-build.gradle new file mode 100755 index 00000000..b345b90a --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/plugin-build.gradle @@ -0,0 +1,79 @@ +/* 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. +*/ + +// GENERATED FILE! DO NOT EDIT! + +buildscript { + repositories { + mavenCentral() + } + + // Switch the Android Gradle plugin version requirement depending on the + // installed version of Gradle. This dependency is documented at + // http://tools.android.com/tech-docs/new-build-system/version-compatibility + // and https://issues.apache.org/jira/browse/CB-8143 + if (gradle.gradleVersion >= "2.2") { + dependencies { + classpath 'com.android.tools.build:gradle:1.0.0+' + } + } else if (gradle.gradleVersion >= "2.1") { + dependencies { + classpath 'com.android.tools.build:gradle:0.14.0+' + } + } else { + dependencies { + classpath 'com.android.tools.build:gradle:0.12.0+' + } + } +} + +apply plugin: 'android-library' + +dependencies { + compile fileTree(dir: 'libs', include: '*.jar') + debugCompile project(path: ":CordovaLib", configuration: "debug") + releaseCompile project(path: ":CordovaLib", configuration: "release") +} + +android { + compileSdkVersion cdvCompileSdkVersion + buildToolsVersion cdvBuildToolsVersion + publishNonDefault true + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_6 + targetCompatibility JavaVersion.VERSION_1_6 + } + + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + resources.srcDirs = ['src'] + aidl.srcDirs = ['src'] + renderscript.srcDirs = ['src'] + res.srcDirs = ['res'] + assets.srcDirs = ['assets'] + jniLibs.srcDirs = ['libs'] + } + } +} + +if (file('build-extras.gradle').exists()) { + apply from: 'build-extras.gradle' +} diff --git a/StoneIsland/platforms/android/cordova/lib/retry.js b/StoneIsland/platforms/android/cordova/lib/retry.js new file mode 100755 index 00000000..dc52a7d2 --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/retry.js @@ -0,0 +1,66 @@ +#!/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. +*/ + +/* jshint node: true */ + +"use strict"; + +/* + * Retry a promise-returning function a number of times, propagating its + * results on success or throwing its error on a failed final attempt. + * + * @arg {Number} attemts_left - The number of times to retry the passed call. + * @arg {Function} promiseFunction - A function that returns a promise. + * @arg {...} - Arguments to pass to promiseFunction. + * + * @returns {Promise} + */ +module.exports.retryPromise = function (attemts_left, promiseFunction) { + + // NOTE: + // get all trailing arguments, by skipping the first two (attemts_left and + // promiseFunction) because they shouldn't get passed to promiseFunction + var promiseFunctionArguments = Array.prototype.slice.call(arguments, 2); + + return promiseFunction.apply(undefined, promiseFunctionArguments).then( + + // on success pass results through + function onFulfilled(value) { + return value; + }, + + // on rejection either retry, or throw the error + function onRejected(error) { + + attemts_left -= 1; + + if (attemts_left < 1) { + throw error; + } + + console.log("A retried call failed. Retrying " + attemts_left + " more time(s)."); + + // retry call self again with the same arguments, except attemts_left is now lower + var fullArguments = [attemts_left, promiseFunction].concat(promiseFunctionArguments); + return module.exports.retryPromise.apply(undefined, fullArguments); + } + ); +}; diff --git a/StoneIsland/platforms/android/cordova/lib/run.js b/StoneIsland/platforms/android/cordova/lib/run.js new file mode 100755 index 00000000..7f15448c --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/run.js @@ -0,0 +1,160 @@ +#!/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. +*/ + +/* jshint loopfunc:true */ + +var path = require('path'), + build = require('./build'), + emulator = require('./emulator'), + device = require('./device'), + shell = require('shelljs'), + Q = require('q'); + +/* + * Runs the application on a device if available. + * If no device is found, it will use a started emulator. + * If no started emulators are found it will attempt to start an avd. + * If no avds are found it will error out. + * Returns a promise. + */ + module.exports.run = function(args) { + var buildFlags = []; + var install_target; + var list = false; + + for (var i=2; i<args.length; i++) { + if (build.isBuildFlag(args[i])) { + buildFlags.push(args[i]); + } else if (args[i] == '--device') { + install_target = '--device'; + } else if (args[i] == '--emulator') { + install_target = '--emulator'; + } else if (/^--target=/.exec(args[i])) { + install_target = args[i].substring(9, args[i].length); + } else if (args[i] == '--list') { + list = true; + } else { + console.warn('Option \'' + args[i] + '\' not recognized (ignoring).'); + } + } + + if (list) { + var output = ''; + var temp = ''; + if (!install_target) { + output += 'Available Android Devices:\n'; + temp = shell.exec(path.join(__dirname, 'list-devices'), {silent:true}).output; + temp = temp.replace(/^(?=[^\s])/gm, '\t'); + output += temp; + output += 'Available Android Virtual Devices:\n'; + temp = shell.exec(path.join(__dirname, 'list-emulator-images'), {silent:true}).output; + temp = temp.replace(/^(?=[^\s])/gm, '\t'); + output += temp; + } else if (install_target == '--emulator') { + output += 'Available Android Virtual Devices:\n'; + temp = shell.exec(path.join(__dirname, 'list-emulator-images'), {silent:true}).output; + temp = temp.replace(/^(?=[^\s])/gm, '\t'); + output += temp; + } else if (install_target == '--device') { + output += 'Available Android Devices:\n'; + temp = shell.exec(path.join(__dirname, 'list-devices'), {silent:true}).output; + temp = temp.replace(/^(?=[^\s])/gm, '\t'); + output += temp; + } + console.log(output); + return; + } + + return Q() + .then(function() { + if (!install_target) { + // no target given, deploy to device if available, otherwise use the emulator. + return device.list() + .then(function(device_list) { + if (device_list.length > 0) { + console.log('WARNING : No target specified, deploying to device \'' + device_list[0] + '\'.'); + install_target = device_list[0]; + } else { + console.log('WARNING : No target specified, deploying to emulator'); + install_target = '--emulator'; + } + }); + } + }).then(function() { + if (install_target == '--device') { + return device.resolveTarget(null); + } else if (install_target == '--emulator') { + // Give preference to any already started emulators. Else, start one. + return emulator.list_started() + .then(function(started) { + return started && started.length > 0 ? started[0] : emulator.start(); + }).then(function(emulatorId) { + return emulator.resolveTarget(emulatorId); + }); + } + // They specified a specific device/emulator ID. + return device.list() + .then(function(devices) { + if (devices.indexOf(install_target) > -1) { + return device.resolveTarget(install_target); + } + return emulator.list_started() + .then(function(started_emulators) { + if (started_emulators.indexOf(install_target) > -1) { + return emulator.resolveTarget(install_target); + } + return emulator.list_images() + .then(function(avds) { + // if target emulator isn't started, then start it. + for (var avd in avds) { + if (avds[avd].name == install_target) { + return emulator.start(install_target) + .then(function(emulatorId) { + return emulator.resolveTarget(emulatorId); + }); + } + } + return Q.reject('Target \'' + install_target + '\' not found, unable to run project'); + }); + }); + }); + }).then(function(resolvedTarget) { + return build.run(buildFlags, resolvedTarget).then(function(buildResults) { + if (resolvedTarget.isEmulator) { + return emulator.install(resolvedTarget, buildResults); + } + return device.install(resolvedTarget, buildResults); + }); + }); +}; + +module.exports.help = function(args) { + console.log('Usage: ' + path.relative(process.cwd(), args[1]) + ' [options]'); + console.log('Build options :'); + console.log(' --debug : Builds project in debug mode'); + console.log(' --release : Builds project in release mode'); + console.log(' --nobuild : Runs the currently built project without recompiling'); + console.log('Deploy options :'); + console.log(' --device : Will deploy the built project to a device'); + console.log(' --emulator : Will deploy the built project to an emulator if one exists'); + console.log(' --target=<target_id> : Installs to the target with the specified id.'); + process.exit(0); +}; diff --git a/StoneIsland/platforms/android/cordova/lib/spawn.js b/StoneIsland/platforms/android/cordova/lib/spawn.js new file mode 100755 index 00000000..3e500a09 --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/spawn.js @@ -0,0 +1,50 @@ +#!/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 child_process = require('child_process'), + Q = require('q'); +var isWindows = process.platform.slice(0, 3) == 'win'; + +// Takes a command and optional current working directory. +module.exports = function(cmd, args, opt_cwd) { + var d = Q.defer(); + var opts = { cwd: opt_cwd, stdio: 'inherit' }; + try { + // Work around spawn not being able to find .bat files. + if (isWindows) { + args = [['/s', '/c', '"' + [cmd].concat(args).map(function(a){if (/^[^"].* .*[^"]/.test(a)) return '"' + a + '"'; return a;}).join(' ')+'"'].join(' ')]; + cmd = 'cmd'; + opts.windowsVerbatimArguments = true; + } + var child = child_process.spawn(cmd, args, opts); + child.on('exit', function(code) { + if (code) { + d.reject('Error code ' + code + ' for command: ' + cmd + ' with args: ' + args); + } else { + d.resolve(); + } + }); + } catch(e) { + console.error('error caught: ' + e); + d.reject(e); + } + return d.promise; +}; diff --git a/StoneIsland/platforms/android/cordova/lib/start-emulator b/StoneIsland/platforms/android/cordova/lib/start-emulator new file mode 100755 index 00000000..f96bdc3e --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/start-emulator @@ -0,0 +1,39 @@ +#!/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 emulator = require('./emulator'), + args = process.argv; + +var install_target; +if(args.length > 2) { + if (args[2].substring(0, 9) == '--target=') { + install_target = args[2].substring(9, args[2].length); + } else { + console.error('ERROR : argument \'' + args[2] + '\' not recognized.'); + process.exit(2); + } +} + +emulator.start(install_target).done(null, function(err) { + console.error('ERROR: ' + err); + process.exit(2); +}); + diff --git a/StoneIsland/platforms/android/cordova/lib/start-emulator.bat b/StoneIsland/platforms/android/cordova/lib/start-emulator.bat new file mode 100755 index 00000000..9329d951 --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/start-emulator.bat @@ -0,0 +1,26 @@ +:: 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. + +@ECHO OFF +SET script_path="%~dp0start-emulator" +IF EXIST %script_path% ( + node "%script_path%" %* +) ELSE ( + ECHO. + ECHO ERROR: Could not find 'start-emulator' script in 'cordova\lib' folder, aborting...>&2 + EXIT /B 1 +)
\ No newline at end of file |
