Compare commits

...

98 Commits
4.0.2 ... 5.0.0

Author SHA1 Message Date
Steve Gill
59a0189719 added node_modules back into .gitignore 2015-11-02 17:09:25 -08:00
Steve Gill
43dc7c68a6 checked in missing node_module dependencies that are required 2015-11-02 17:07:38 -08:00
Steve Gill
49870d2d63 Set VERSION to 5.0.0 (via coho) 2015-11-01 23:43:00 -08:00
Steve Gill
278197ae7c Update JS snapshot to version 5.0.0 (via coho) 2015-11-01 23:43:00 -08:00
Steve Gill
dfe468f335 CB-9922 Updated RELEASENOTES and Version for release 5.0.0 2015-11-01 23:41:46 -08:00
Steve Gill
4f7721b405 updated node_modules 2015-10-30 19:43:08 -07:00
TyIsI
3abd12aee3 Update CordovaWebViewEngine.java
Fixed typo. This closes #234
2015-10-30 05:08:18 -07:00
Tim Barham
1c90a77325 CB-9909 Shouldn't escape spaces in paths on Windows.. This closes #237 2015-10-30 04:58:26 -07:00
Steve Gill
671219ae56 CB-9870 updated hello world template 2015-10-29 15:33:43 -07:00
Vladimir Kotikov
78fa7374d9 CB-9880 Fixes platform update failure when upgrading from android@<4.1.0 2015-10-28 12:42:35 +03:00
Vladimir Kotikov
c1b389ad9b CB-9844 Remove old .java after renaming activity 2015-10-27 11:30:58 +03:00
Joe Bowser
5a07a51b5d Checked the notice file, updating to 2015 2015-10-26 16:42:06 -07:00
Dmitry Blotsky
824b9803d2 CB-9800 Fixing contribute link. 2015-10-23 15:16:41 -07:00
Vladimir Kotikov
ab72e48431 CB-9782 Check in cordova-common dependency 2015-10-22 22:43:39 +03:00
Vladimir Kotikov
78b7ae72c9 Adds licence header to Adb to pass rat audit 2015-10-22 14:06:20 +03:00
Vladimir Kotikov
1151856d38 CB-9835 Downgrade properties-parser to prevent failures in Node < 4.x 2015-10-21 13:24:30 +03:00
Steve Gill
12c282ce5c reverted fixed line endings in check_reqs.bat 2015-10-20 16:48:59 -07:00
Steve Gill
0ac822c577 Fixed line endings 2015-10-20 16:15:57 -07:00
Vladimir Kotikov
400282282f CB-9782 Implements PlatformApi contract for Android platform.
This closes #226
2015-10-20 12:21:30 +03:00
Alexander Sorokin
789c505a88 CB-9826 Fixed 'test-build' script on windows. This closes #228 2015-10-20 12:02:27 +03:00
fujunwei
0429bb0ab8 The extra digit need to be added to the end of the version code
The version code of default config is generated by the environment variable
and the value from AndroidManifest.xml.
The test case is using the command line "cordova build -- --versionCode=100".. This closes #211
2015-10-19 11:25:33 -07:00
Joe Bowser
000eb0916e Incrementing Travis to 23 2015-10-19 10:31:56 -07:00
Joe Bowser
4db1fecba8 You can't easily abstract out this variable because this can crash Cordova due to the plugin design. 2015-10-19 10:31:56 -07:00
Joe Bowser
013ad94af0 Refactor of the Cordova Plugin/Permissions API 2015-10-19 10:31:56 -07:00
Joe Bowser
2ceb8030ee Manually updating version to 5.0.0-dev for engine tags 2015-10-19 10:31:55 -07:00
Joe Bowser
47ac514835 Working on getting the Geolocation Plugin to work by default with the default WebView. Crosswalk will need to make similar modifications. 2015-10-19 10:31:55 -07:00
Joe Bowser
5e0c9595c3 Got the PoC working with the Contacts plugin, going to work through all the plugins 2015-10-19 10:31:55 -07:00
Joe Bowser
a0747aa960 Refactoring based on feedback from Andrew 2015-10-19 10:31:55 -07:00
Joe Bowser
07912fdecd Bump up to API level 23 2015-10-19 10:31:54 -07:00
Joe Bowser
9a33943783 Finally got it kinda working, Geolocation is not going to work in browser anymore 2015-10-19 10:31:54 -07:00
Joe Bowser
8981ddb681 Commiting code to handle permissions, and the special case of the Geolocation Plugin 2015-10-19 10:31:54 -07:00
daserge
7d61a79a78 CB-9608 cordova-android no longer builds on Node 0.10 or below
Replaced path.isAbsolute usage with path.resolve. This closes #217
2015-09-29 09:45:48 +03:00
daserge
afa61aeb09 CB-9080 Cordova CLI run for Android versions 4.1.1 and lower throws error 2015-09-23 18:44:41 +03:00
Vladimir Kotikov
bf57aa1df0 CB-9557 Fixes apk install failure when switching from debug to release build 2015-08-27 09:46:58 +03:00
Tommy-Carlos Williams
055e3bf609 CB-9496 removed permissions added for crosswalk
These would better live in the actual crosswalk plugin

https://github.com/crosswalk-project/cordova-plugin-crosswalk-webview

I have also submit a PR to that repo adding them to its `plugin.xml`

github: close #206
2015-08-24 23:17:03 +10:00
Alexander Lebedev
b1dadaf15d CB-9402 Allow to set gradle distubutionUrl via env variable CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL
This closes #205
2015-08-14 11:28:35 -07:00
Joe Bowser
9e400911f5 Make Cordova pass the Lint step 2015-08-10 16:43:41 -07:00
Steve Gill
892b875867 updated release notes 2015-07-31 16:54:52 -07:00
Steve Gill
8f7bc1ffbb CB-9428 update script now bumps up minSdkVersion to 14 if it is less than that. 2015-07-31 16:32:51 -07:00
Vladimir Kotikov
e5506d40bc CB-9430 Fixes check_reqs failure when javac returns an extra line 2015-07-31 16:37:21 +03:00
alsorokin
64f89c5eda CB-9172 Improved emulator deploy stability. This closes #188.
- Use UUID to distinguish between launched emulators
- Wait for android.process.acore instead of init.svc.bootanim on emulator boot
- Increased retry timeout when installing app to the emulator
- If there is already a started/starting emulator, wait for it's boot instead of trying to deploy to it right away
2015-07-31 03:59:53 -07:00
alsorokin
1ad0665eb5 CB-9404 Fixed an exception when path contained -debug or -release 2015-07-23 18:55:11 +03:00
Joe Bowser
37309c23c2 CB-8320: Setting up gradle so we can use CordovaLib as a standard Android Library 2015-07-22 13:23:28 -07:00
alsorokin
8983ddbdcc CB-9185 Fixed an issue when unsigned apks couldn't be found. This closes #202 2015-07-22 11:00:07 -07:00
Vladimir Kotikov
d99a21eb8d CB-9397 Fixes minor issues with cordova requirements android 2015-07-22 16:45:49 +03:00
Vladimir Kotikov
f9ce1c607b CB-9389 Fixes build/check_reqs hang
This removes gradle version check since it requires downloading and
installing of gradle distributive if it is not installed yet.

Partial revert of 4bf705a
2015-07-22 13:20:39 +03:00
Nikhil Khandelwal
847312fcf9 Update JS snapshot to version 4.2.0-dev (via coho) 2015-07-20 09:52:18 -07:00
Nikhil Khandelwal
b770076b7f Set VERSION to 4.2.0-dev (via coho) 2015-07-20 09:52:14 -07:00
Nikhil Khandelwal
dc9413258e CB-9394 Updated RELEASENOTES - Fixing typo 2015-07-20 09:44:06 -07:00
Nikhil Khandelwal
4b574a2863 CB-9394 Updated RELEASENOTES 2015-07-20 09:40:18 -07:00
Jose Pereira
4b3cc67353 CB-9392 Fixed printing flavored versions. This closes #184 2015-07-20 16:52:28 +03:00
sgrebnov
32b72756f3 CB-9382 [Android] Fix KeepRunning setting when Plugin activity is showed. This closes #200 2015-07-20 16:02:21 +03:00
Malte Legenhausen
2fc86e2833 CB-9391 Fixes cdvBuildMultipleApks option casting
This closes #199
2015-07-20 15:59:27 +03:00
Simon Pireyn
fab472859d CB-9343 Split the Content-Type to obtain a clean mimetype
This closes #197
2015-07-20 13:28:40 +03:00
Connor Pearson
92caa3a186 CB-9255 Make getUriType case insensitive.
This closes #186
2015-07-20 13:14:31 +03:00
Vladimir Kotikov
26c7a96255 CB-9149 Fixes JSHint issue introduced by 899daa9 2015-07-20 13:00:17 +03:00
Omar Mefire
e170e463fe CB-9372: Remove unused files: 'main.js' & 'master.css'. This closes #198 2015-07-20 12:42:38 +03:00
Tony Homer
899daa9ea7 CB-9149 Make gradle alias subprojects in order to handle libs that depend on libs. This closes #182 2015-07-17 16:10:16 -04:00
Simon MacDonald
6d334c05e9 Update min SDK version to 14 2015-07-07 14:29:45 -07:00
Nikhil Khandelwal
5ac0cc51d3 Update licenses. This closes #190 2015-07-07 11:38:17 -07:00
Joe Schneider
f93c2b161d CB-9185 Fix signed release build exception. This closes #193. 2015-07-07 11:35:53 -07:00
Vladimir Kotikov
6b071c0fb2 CB-9286 Fixes build failure when ANDROID_HOME is not set. 2015-07-03 09:46:39 +03:00
Nikhil Khandelwal
d3245a43d3 CB-9284 Fix for handling absolute path for keystore in build.json 2015-07-02 15:13:31 -07:00
alsorokin
90a51c2cc1 CB-9260 Install Android-22 on Travis-CI 2015-06-30 11:35:28 +03:00
Dmitry Blotsky
61df5e0a37 Adding .ratignore file. 2015-06-16 21:30:59 -07:00
Dmitry Blotsky
c0312f9b50 CB-9119 Adding lib/retry.js for retrying promise-returning functions. Retrying 'adb install' in emulator.js because it sometimes hangs. 2015-06-12 11:50:15 -07:00
Volker Braun
eb70f05168 CB-9115 android: Grant Lollipop permission req
This patch overrides onPermissionRequest so that getUserMedia can be
used inside the browser.

Since a hybrid app has to request permissions anyways via
AndroidManifest.xml, I think it is unnecessary to have any further
configuration for onPermissionRequest. Anything that the app is allowed
to do should be possible from the JS side. Hence all requests are
granted. This enables getUserMedia (and WebRTC) on Android Lollipop,
without resorting to crosswalk.

The docs say that request.grant has to be called from the UI thread, but
don't explicitly spell out whether onPermissionRequest is called from
the UI thread. I think that this is so, the WebChromeClient of course
makes its calls from the UI thread unless otherwise noted. So there is
no need to post a runnable to the UI thread.

This closes 178
https://github.com/apache/cordova-android/pull/178
2015-06-10 11:53:03 -07:00
Nikhil Khandelwal
505db38232 Remove extra console message 2015-06-05 10:21:53 -07:00
Vladimir Kotikov
096e1e3caa CB-8898 Report expected gradle location properly 2015-06-03 21:57:50 +03:00
Vladimir Kotikov
b5d8b51310 CB-8898 Fixes gradle check failure due to missing quotes 2015-06-03 12:37:25 +03:00
Joe Bowser
c9e7201058 CB-9080: -d option is not supported on Android 4.1.1 and lower, removing 2015-06-02 07:43:33 -07:00
Vladimir Kotikov
4bf705a3d3 CB-8954 Adds requirements command support to check_reqs module 2015-05-29 13:00:38 +03:00
Steve Gill
ce42568721 Update JS snapshot to version 4.1.0-dev (via coho) 2015-05-20 13:12:35 -07:00
Joe Bowser
eb956b2449 Updating Release Notes 2015-05-19 08:14:04 -07:00
Steve Gill
1bf4e93da1 CB-8417 updated platform specific files from cordova.js repo 2015-05-18 18:41:53 -07:00
Joe Bowser
aba0a8421b Adding tests to confirm that preferences aren't changed by Intents 2015-05-15 14:13:18 -07:00
Joe Bowser
b5a58e6ca0 updating existing test code 2015-05-15 14:13:18 -07:00
Joe Bowser
44aa7464e1 Forgot to remove the method that copied over the intent data 2015-05-15 14:13:18 -07:00
Joe Bowser
4ea684dd7a Getting around to removing this old Intent code 2015-05-15 14:13:18 -07:00
Steve Gill
215b7e08f8 Update JS snapshot to version 4.1.0-dev (via coho) 2015-05-08 15:33:05 -07:00
Andrew Grieve
754911f346 Fix CordovaPluginTest on KitKat (start-up events seem to change) 2015-05-06 09:59:22 -04:00
Bochun Bai
9873106785 CB-3360 Allow setting a custom User-Agent (close #162) 2015-05-06 09:59:22 -04:00
Kenneth Chan
d005359f89 CB-8902 Use immersive mode when available when going fullscreen (close #175) 2015-05-06 09:46:35 -04:00
Andrew Grieve
1ce52a2845 Make BridgeMode methods public (they were always supposed to be) 2015-04-23 16:07:20 -04:00
Andrew Grieve
7e480d1ff9 Simplify: EncodingUtils.getBytes(str) -> str.getBytes() 2015-04-23 15:58:56 -04:00
Andrew Grieve
85877d259c Don't show warning when gradlew file is read-only 2015-04-23 15:38:48 -04:00
Andrew Grieve
0b86db8748 Don't show warning when prepEnv copies gradlew and it's read-only 2015-04-23 15:34:25 -04:00
Andrew Grieve
bca7f62efd Make gradle wrapper prepEnv code work even when android-sdk is read-only 2015-04-23 15:18:01 -04:00
Andrew Grieve
4953ae84cd CB-8897 Delete drawable/icon.png since it duplicates drawable-mdpi/icon.png 2015-04-22 21:59:02 -04:00
Joe Bowser
e96a5a0b3e Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/cordova-android 2015-04-22 14:19:03 -07:00
Joe Bowser
e4678f4709 CB-8894: Updating the template to target mininumSdkTarget=14 2015-04-22 14:18:41 -07:00
Joe Bowser
1def13deb3 Updating the template to target mininumSdkTarget=14 2015-04-22 14:15:18 -07:00
Andrew Grieve
bce4283239 CB-8891 Add a note about when the gradle helpers were added 2015-04-22 09:53:13 -04:00
Andrew Grieve
9ff786d021 CB-8891 Add a gradle helper for retrieving config.xml preference values 2015-04-22 09:51:16 -04:00
Andrew Grieve
ee14a67795 CB-8884 Delete Eclipse tweaks from create script 2015-04-21 14:24:15 -04:00
Andrew Grieve
b63a2e37be CB-8834 Don't fail to install on VERSION_DOWNGRADE 2015-04-09 11:28:55 -04:00
Andrew Grieve
84274b4259 Update JS snapshot to version 4.1.0-dev (via coho) 2015-04-09 11:05:48 -04:00
Andrew Grieve
b6bf5298e6 Set VERSION to 4.1.0-dev (via coho) 2015-04-09 11:05:47 -04:00
917 changed files with 90578 additions and 2376 deletions

2
.gitignore vendored
View File

@@ -39,5 +39,5 @@ Desktop.ini
*.iml
.idea
npm-debug.log
/node_modules
/framework/build
node_modules/

4
.ratignore Normal file
View File

@@ -0,0 +1,4 @@
*.properties
bin
gen
proguard-project.txt

View File

@@ -1,6 +1,10 @@
language: android
sudo: false
install: npm install
install:
- "(pushd .. && git clone https://github.com/apache/cordova-lib.git && popd)"
- npm link ../cordova-lib/cordova-common
- npm install
- echo y | android update sdk -u --filter android-23
script:
- npm test
- npm run test-build
- npm run test-build

View File

@@ -27,7 +27,7 @@ There are multiple ways to contribute: report bugs, improve the docs, and
contribute code.
For instructions on this, start with the
[contribution overview](http://cordova.apache.org/#contribute).
[contribution overview](http://cordova.apache.org/contribute/).
The details are explained there, but the important items are:
- Sign and submit an Apache ICLA (Contributor License Agreement).

43
LICENSE
View File

@@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright 2015 Apache Cordova
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -226,11 +226,9 @@ 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.
================================================================================
bin/node_modules/shelljs
================================================================================
Copyright (c) 2012, Artur Adib <aadib@mozilla.com>
All rights reserved.
@@ -247,19 +245,19 @@ modification, are permitted provided that the following conditions are met:
names of the contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL ARTUR ADIB BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================================================
bin/node_modules/shelljs
bin/node_modules/nopt
================================================================================
Copyright 2009, 2010, 2011 Isaac Z. Schlueter.
All rights reserved.
@@ -285,3 +283,32 @@ 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.
================================================================================
bin/node_modules/which
================================================================================
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.

2
NOTICE
View File

@@ -1,5 +1,5 @@
Apache Cordova
Copyright 2014 The Apache Software Foundation
Copyright 2015 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org)

View File

@@ -19,6 +19,104 @@
#
-->
## Release Notes for Cordova (Android) ##
Update these notes using: git log --pretty=format:'* %s' --topo-order --no-merges *remote*/4.1.x...HEAD
### 5.0.0 (Nov 01, 2015)
* Update CordovaWebViewEngine.java
* CB-9909 Shouldn't escape spaces in paths on Windows.
* CB-9870 updated hello world template
* CB-9880 Fixes platform update failure when upgrading from android@<4.1.0
* CB-9844 Remove old .java after renaming activity
* CB-9800 Fixing contribute link.
* CB-9782 Check in `cordova-common` dependency
* Adds licence header to Adb to pass rat audit
* CB-9835 Downgrade `properties-parser` to prevent failures in Node < 4.x
* CB-9782 Implements PlatformApi contract for Android platform.
* CB-9826 Fixed `test-build` script on windows.
* Refactor of the Cordova Plugin/Permissions API
* Manually updating version to 5.0.0-dev for engine tags
* Bump up to API level 23
* Commiting code to handle permissions, and the special case of the Geolocation Plugin
* CB-9608 cordova-android no longer builds on Node 0.10 or below
* CB-9080 Cordova CLI run for Android versions 4.1.1 and lower throws error
* CB-9557 Fixes apk install failure when switching from debug to release build
* CB-9496 removed permissions added for crosswalk
* CB-9402 Allow to set gradle distubutionUrl via env variable CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL
* CB-9428 update script now bumps up minSdkVersion to 14 if it is less than that.
* CB-9430 Fixes check_reqs failure when javac returns an extra line
* CB-9172 Improved emulator deploy stability. This closes #188.
* CB-9404 Fixed an exception when path contained -debug or -release
* CB-8320 Setting up gradle so we can use CordovaLib as a standard Android Library
* CB-9185 Fixed an issue when unsigned apks couldn't be found.
* CB-9397 Fixes minor issues with `cordova requirements android`
* CB-9389 Fixes build/check_reqs hang
### Release 4.1.1 (Aug 2015) ###
* CB-9428 update script now bumps up minSdkVersion to 14 if it is less than that
* CB-9430 Fixes check_reqs failure when javac returns an extra line
### Release 4.1.0 (Jul 2015) ###
* CB-9392 Fixed printing flavored versions. This closes #184.
* CB-9382 [Android] Fix KeepRunning setting when Plugin activity is showed. This closes #200
* CB-9391 Fixes cdvBuildMultipleApks option casting
* CB-9343 Split the Content-Type to obtain a clean mimetype
* CB-9255 Make getUriType case insensitive.
* CB-9149 Fixes JSHint issue introduced by 899daa9
* CB-9372: Remove unused files: 'main.js' & 'master.css'. This closes #198
* CB-9149 Make gradle alias subprojects in order to handle libs that depend on libs. This closes #182
* Update min SDK version to 14
* Update licenses. This closes #190
* CB-9185 Fix signed release build exception. This closes #193.
* CB-9286 Fixes build failure when ANDROID_HOME is not set.
* CB-9284 Fix for handling absolute path for keystore in build.json
* CB-9260 Install Android-22 on Travis-CI
* Adding .ratignore file.
* CB-9119 Adding lib/retry.js for retrying promise-returning functions. Retrying 'adb install' in emulator.js because it sometimes hangs.
* CB-9115 android: Grant Lollipop permission req
* Remove extra console message
* CB-8898 Report expected gradle location properly
* CB-8898 Fixes gradle check failure due to missing quotes
* CB-9080: -d option is not supported on Android 4.1.1 and lower, removing
* CB-8954 Adds `requirements` command support to check_reqs module
* Update JS snapshot to version 4.1.0-dev (via coho)
* CB-8417 updated platform specific files from cordova.js repo
* Adding tests to confirm that preferences aren't changed by Intents
* Forgot to remove the method that copied over the intent data
* Getting around to removing this old Intent code
* Update JS snapshot to version 4.1.0-dev (via coho)
* Fix CordovaPluginTest on KitKat (start-up events seem to change)
* CB-3360 Allow setting a custom User-Agent (close #162)
* CB-8902 Use immersive mode when available when going fullscreen (close #175)
* Make BridgeMode methods public (they were always supposed to be)
* Simplify: EncodingUtils.getBytes(str) -> str.getBytes()
* Don't show warning when gradlew file is read-only
* Don't show warning when prepEnv copies gradlew and it's read-only
* Make gradle wrapper prepEnv code work even when android-sdk is read-only
* CB-8897 Delete drawable/icon.png since it duplicates drawable-mdpi/icon.png
* Updating the template to target mininumSdkTarget=14
* CB-8894: Updating the template to target mininumSdkTarget=14
* CB-8891 Add a note about when the gradle helpers were added
* CB-8891 Add a gradle helper for retrieving config.xml preference values
* CB-8884 Delete Eclipse tweaks from create script
* CB-8834 Don't fail to install on VERSION_DOWNGRADE
* Automated tools fail, and you have to remember all four places where this is set.
* Update the package.json
* CB-9042 coho failed to update version, so here we are
* CB9042 - Updating Release Notes
* Adding tests to confirm that preferences aren't changed by Intents
* updating existing test code
* Forgot to remove the method that copied over the intent data
* Getting around to removing this old Intent code
* CB-8834 Don't fail to install on VERSION_DOWNGRADE
### Release 4.0.2 (May 2015) ###
* Removed Intent Functionality from Preferences - Preferences can no longer be set by intents
### Release 4.0.1 (April 2015) ###
* Bug fixed where platform failed to install on a version downgrade
### Release 4.0.0 (March 2015) ###

View File

@@ -1 +1 @@
4.0.0-dev
5.0.0

View File

@@ -19,7 +19,9 @@
under the License.
*/
var path = require('path');
var create = require('./lib/create');
var ConfigParser = require('cordova-common').ConfigParser;
var Api = require('./templates/cordova/Api');
var argv = require('nopt')({
'help' : Boolean,
'cli' : Boolean,
@@ -39,11 +41,16 @@ if (argv.help || argv.argv.remain.length === 0) {
process.exit(1);
}
var project_path = argv.argv.remain[0];
var package_name = argv.argv.remain[1];
var project_name = argv.argv.remain[2];
var template_path = argv.argv.remain[3];
var activity_name = argv['activity-name'];
var config = new ConfigParser(path.resolve(__dirname, 'templates/project/res/xml/config.xml'));
create.createProject(project_path, package_name, project_name, activity_name, template_path, argv.link || argv.shared, argv.cli).done();
if (argv.argv.remain[1]) config.setPackageName(argv.argv.remain[1]);
if (argv.argv.remain[2]) config.setName(argv.argv.remain[2]);
if (argv['activity-name']) config.setName(argv['activity-name']);
var options = {
link: argv.link || argv.shared,
customTemplate: argv.argv.remain[3],
activityName: argv['activity-name']
};
Api.createPlatform(argv.argv.remain[0], config, options).done();

View File

@@ -26,25 +26,26 @@ var shelljs = require('shelljs'),
Q = require('q'),
path = require('path'),
fs = require('fs'),
which = require('which'),
ROOT = path.join(__dirname, '..', '..');
var CordovaError = require('cordova-common').CordovaError;
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));
return fs.realpathSync(shelljs.which(cmd));
} catch (e) {
return '';
}
}
function tryCommand(cmd, errMsg) {
function tryCommand(cmd, errMsg, catchStderr) {
var d = Q.defer();
child_process.exec(cmd, function(err, stdout, stderr) {
if (err) d.reject(new Error(errMsg));
else d.resolve(stdout);
if (err) d.reject(new CordovaError(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;
}
@@ -70,15 +71,23 @@ 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.');
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(new CordovaError('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' +
return Q.reject(new CordovaError('Could not find gradle wrapper within Android SDK. Might need to update your Android SDK.\n' +
'Looked here: ' + wrapperDir));
}
return Q.when();
@@ -96,9 +105,10 @@ module.exports.check_java = function() {
}
} 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', 'Failed to run: /usr/libexec/java_home')
return tryCommand('/usr/libexec/java_home', msg)
.then(function(stdout) {
process.env['JAVA_HOME'] = stdout.trim();
});
@@ -109,7 +119,7 @@ module.exports.check_java = function() {
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');
throw new CordovaError(msg);
}
}
} else if (isWindows) {
@@ -140,7 +150,12 @@ module.exports.check_java = function() {
}
return tryCommand('java -version', msg)
.then(function() {
return tryCommand('javac -version', msg);
// 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];
});
});
};
@@ -196,25 +211,31 @@ module.exports.check_android = function() {
process.env['ANDROID_HOME'] = grandParentDir;
hasAndroidHome = true;
} else {
throw new Error('ANDROID_HOME is not set and no "tools" directory found at ' + parentDir);
throw new CordovaError('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('ANDROID_HOME is not set and "android" command not in your PATH. You must fulfill at least one of these conditions.');
throw new CordovaError('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 is set to a non-existant path: ' + process.env['ANDROID_HOME']);
throw new CordovaError('\'ANDROID_HOME\' environment variable is set to non-existent path: ' + process.env['ANDROID_HOME'] +
'\nTry update it manually to point to valid SDK directory.');
}
// Check that the target sdk level is installed.
return module.exports.check_android_target(module.exports.get_target());
});
};
module.exports.getAbsoluteAndroidCmd = function() {
return forgivingWhichSync('android').replace(/(\s)/g, '\\$1');
module.exports.getAbsoluteAndroidCmd = function () {
var cmd = forgivingWhichSync('android');
if (process.platform === 'win32') {
return '"' + cmd + '"';
}
return cmd.replace(/(\s)/g, '\\$1');
};
module.exports.check_android_target = function(valid_target) {
@@ -223,27 +244,87 @@ module.exports.check_android_target = function(valid_target) {
// 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) {
if (output.split('\n').indexOf(valid_target) == -1) {
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)');
var targets = output.split('\n');
if (targets.indexOf(valid_target) >= 0) {
return targets;
}
var androidCmd = module.exports.getAbsoluteAndroidCmd();
throw new CordovaError('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()])
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;
});
};

View File

@@ -26,6 +26,11 @@ var shell = require('shelljs'),
check_reqs = require('./check_reqs'),
ROOT = path.join(__dirname, '..', '..');
var MIN_SDK_VERSION = 14;
var CordovaError = require('cordova-common').CordovaError;
var AndroidManifest = require('../templates/cordova/lib/AndroidManifest');
function setShellFatal(value, func) {
var oldVal = shell.config.fatal;
shell.config.fatal = value;
@@ -41,6 +46,16 @@ function copyJsAndLibrary(projectPath, shared, projectName) {
var nestedCordovaLibPath = getFrameworkDir(projectPath, false);
var srcCordovaJsPath = path.join(ROOT, 'bin', 'templates', 'project', 'assets', 'www', 'cordova.js');
shell.cp('-f', srcCordovaJsPath, path.join(projectPath, 'assets', 'www', 'cordova.js'));
// Copy the cordova.js file to platforms/<platform>/platform_www/
// The www dir is nuked on each prepare so we keep cordova.js in platform_www
shell.mkdir('-p', path.join(projectPath, 'platform_www'));
shell.cp('-f', srcCordovaJsPath, path.join(projectPath, 'platform_www'));
// Copy cordova-js-src directory into platform_www directory.
// We need these files to build cordova.js if using browserify method.
shell.cp('-rf', path.join(ROOT, 'cordova-js-src'), path.join(projectPath, 'platform_www'));
// Don't fail if there are no old jars.
setShellFatal(false, function() {
shell.ls(path.join(projectPath, 'libs', 'cordova-*.jar')).forEach(function(oldJar) {
@@ -58,7 +73,7 @@ function copyJsAndLibrary(projectPath, shared, projectName) {
if (shared) {
shell.rm('-rf', nestedCordovaLibPath);
} else if (!wasSymlink) {
// Delete only the src, since eclipse can't handle its .project file being deleted.
// Delete only the src, since Eclipse / Android Studio can't handle their project files being deleted.
shell.rm('-rf', path.join(nestedCordovaLibPath, 'src'));
}
});
@@ -72,13 +87,6 @@ function copyJsAndLibrary(projectPath, shared, projectName) {
shell.cp('-f', path.join(ROOT, 'framework', 'build.gradle'), nestedCordovaLibPath);
shell.cp('-f', path.join(ROOT, 'framework', 'cordova.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.
var eclipseProjectFilePath = path.join(nestedCordovaLibPath, '.project');
if (!fs.existsSync(eclipseProjectFilePath)) {
var data = '<?xml version="1.0" encoding="UTF-8"?><projectDescription><name>' + projectName + '-' + 'CordovaLib</name></projectDescription>';
fs.writeFileSync(eclipseProjectFilePath, data, 'utf8');
}
}
}
@@ -96,6 +104,7 @@ function writeProjectProperties(projectPath, target_api) {
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);
@@ -134,8 +143,8 @@ function copyScripts(projectPath) {
shell.rm('-rf', destScriptsDir);
// Copy in the new ones.
shell.cp('-r', srcScriptsDir, projectPath);
shell.cp('-r', path.join(ROOT, 'bin', 'node_modules'), destScriptsDir);
shell.cp(path.join(ROOT, 'bin', 'check_reqs'), path.join(destScriptsDir, 'check_reqs'));
shell.cp('-r', path.join(ROOT, 'node_modules'), destScriptsDir);
shell.cp(path.join(ROOT, 'bin', 'check_reqs*'), destScriptsDir);
shell.cp(path.join(ROOT, 'bin', 'lib', 'check_reqs.js'), path.join(projectPath, 'cordova', 'lib', 'check_reqs.js'));
shell.cp(path.join(ROOT, 'bin', 'android_sdk_version'), path.join(destScriptsDir, 'android_sdk_version'));
shell.cp(path.join(ROOT, 'bin', 'lib', 'android_sdk_version.js'), path.join(projectPath, 'cordova', 'lib', 'android_sdk_version.js'));
@@ -150,13 +159,14 @@ function validatePackageName(package_name) {
//Make the package conform to Java package types
//http://developer.android.com/guide/topics/manifest/manifest-element.html#package
//Enforce underscore limitation
var msg = 'Error validating package name. ';
if (!/^[a-zA-Z][a-zA-Z0-9_]+(\.[a-zA-Z][a-zA-Z0-9_]*)+$/.test(package_name)) {
return Q.reject('Package name must look like: com.company.Name');
return Q.reject(new CordovaError(msg + 'Package name must look like: com.company.Name'));
}
//Class is a reserved word
if(/\b[Cc]lass\b/.test(package_name)) {
return Q.reject('class is a reserved word');
return Q.reject(new CordovaError(msg + '"class" is a reserved word'));
}
return Q.resolve();
@@ -168,78 +178,78 @@ function validatePackageName(package_name) {
* otherwise.
*/
function validateProjectName(project_name) {
var msg = 'Error validating project name. ';
//Make sure there's something there
if (project_name === '') {
return Q.reject('Project name cannot be empty');
return Q.reject(new CordovaError(msg + 'Project name cannot be empty'));
}
//Enforce stupid name error
if (project_name === 'CordovaActivity') {
return Q.reject('Project name cannot be CordovaActivity');
return Q.reject(new CordovaError(msg + 'Project name cannot be CordovaActivity'));
}
//Classes in Java don't begin with numbers
if (/^[0-9]/.test(project_name)) {
return Q.reject('Project name must not begin with a number');
return Q.reject(new CordovaError(msg + 'Project name must not begin with a number'));
}
return Q.resolve();
}
/**
* $ create [options]
*
* Creates an android application with the given options.
*
* Options:
* @param {String} project_path Path to the new Cordova android project.
* @param {ConfigParser} config Instance of ConfigParser to retrieve basic
* project properties.
* @param {Object} [options={}] Various options
* @param {String} [options.activityName='MainActivity'] Name for the
* activity
* @param {Boolean} [options.link=false] Specifies whether javascript files
* and CordovaLib framework will be symlinked to created application.
* @param {String} [options.customTemplate] Path to project template
* (override)
* @param {EventEmitter} [events] An EventEmitter instance for logging
* events
*
* - `project_path` {String} Path to the new Cordova android project.
* - `package_name`{String} Package name, following reverse-domain style convention.
* - `project_name` {String} Project name.
* - `activity_name` {String} Name for the activity
* - 'project_template_dir' {String} Path to project template (override).
*
* Returns a promise.
* @return {Promise<String>} Directory where application has been created
*/
exports.create = function(project_path, config, options, events) {
options = options || {};
exports.createProject = function(project_path, package_name, project_name, activity_name, project_template_dir, use_shared_project, use_cli_template) {
// Set default values for path, package and name
project_path = typeof project_path !== 'undefined' ? project_path : 'CordovaExample';
project_path = path.relative(process.cwd(), project_path);
package_name = typeof package_name !== 'undefined' ? package_name : 'my.cordova.project';
project_name = typeof project_name !== 'undefined' ? project_name : 'CordovaExample';
project_template_dir = typeof project_template_dir !== 'undefined' ?
project_template_dir :
path.join(ROOT, 'bin', 'templates', 'project');
var package_as_path = package_name.replace(/\./g, path.sep);
var activity_dir = path.join(project_path, 'src', package_as_path);
var safe_activity_name = typeof activity_name !== 'undefined' ? activity_name : 'MainActivity';
var activity_path = path.join(activity_dir, safe_activity_name + '.java');
var target_api = check_reqs.get_target();
var manifest_path = path.join(project_path, 'AndroidManifest.xml');
project_path = path.relative(process.cwd(), (project_path || 'CordovaExample'));
// Check if project already exists
if(fs.existsSync(project_path)) {
return Q.reject('Project already exists! Delete and recreate');
return Q.reject(new CordovaError('Project already exists! Delete and recreate'));
}
var package_name = config.packageName() || 'my.cordova.project';
var project_name = config.name() ?
config.name().replace(/[^\w.]/g,'_') : 'CordovaExample';
var safe_activity_name = config.android_activityName() || options.activityName || 'MainActivity';
var target_api = check_reqs.get_target();
//Make the package conform to Java package types
return validatePackageName(package_name)
.then(function() {
validateProjectName(project_name);
}).then(function() {
// Log the given values for the project
console.log('Creating Cordova project for the Android platform:');
console.log('\tPath: ' + project_path);
console.log('\tPackage: ' + package_name);
console.log('\tName: ' + project_name);
console.log('\tActivity: ' + safe_activity_name);
console.log('\tAndroid target: ' + target_api);
events.emit('log', 'Creating Cordova project for the Android platform:');
events.emit('log', '\tPath: ' + project_path);
events.emit('log', '\tPackage: ' + package_name);
events.emit('log', '\tName: ' + project_name);
events.emit('log', '\tActivity: ' + safe_activity_name);
events.emit('log', '\tAndroid target: ' + target_api);
console.log('Copying template files...');
events.emit('verbose', 'Copying template files...');
setShellFatal(true, function() {
var project_template_dir = options.customTemplate || path.join(ROOT, 'bin', 'templates', 'project');
// copy project template
shell.cp('-r', path.join(project_template_dir, 'assets'), project_path);
shell.cp('-r', path.join(project_template_dir, 'res'), project_path);
@@ -248,40 +258,35 @@ exports.createProject = function(project_path, package_name, project_name, activ
// Manually create directories that would be empty within the template (since git doesn't track directories).
shell.mkdir(path.join(project_path, 'libs'));
// Add in the proper eclipse project file.
if (use_cli_template) {
var note = 'To show `assets/www` or `res/xml/config.xml`, go to:\n' +
' Project -> Properties -> Resource -> Resource Filters\n' +
'And delete the exclusion filter.\n';
shell.cp(path.join(project_template_dir, 'eclipse-project-CLI'), path.join(project_path, '.project'));
fs.writeFileSync(path.join(project_path, 'assets', '_where-is-www.txt'), note);
} else {
shell.cp(path.join(project_template_dir, 'eclipse-project'), path.join(project_path, '.project'));
}
// copy cordova.js, cordova.jar
copyJsAndLibrary(project_path, use_shared_project, safe_activity_name);
copyJsAndLibrary(project_path, options.link, safe_activity_name);
// interpolate the activity name and package
var packagePath = package_name.replace(/\./g, path.sep);
var activity_dir = path.join(project_path, 'src', packagePath);
var activity_path = path.join(activity_dir, safe_activity_name + '.java');
shell.mkdir('-p', activity_dir);
shell.cp('-f', path.join(project_template_dir, 'Activity.java'), activity_path);
shell.sed('-i', /__ACTIVITY__/, safe_activity_name, activity_path);
shell.sed('-i', /__NAME__/, project_name, path.join(project_path, 'res', 'values', 'strings.xml'));
shell.sed('-i', /__NAME__/, project_name, path.join(project_path, '.project'));
shell.sed('-i', /__ID__/, package_name, activity_path);
shell.cp('-f', path.join(project_template_dir, 'AndroidManifest.xml'), manifest_path);
shell.sed('-i', /__ACTIVITY__/, safe_activity_name, manifest_path);
shell.sed('-i', /__PACKAGE__/, package_name, manifest_path);
shell.sed('-i', /__APILEVEL__/, target_api.split('-')[1], manifest_path);
var manifest = new AndroidManifest(path.join(project_template_dir, 'AndroidManifest.xml'));
manifest.setPackageId(package_name)
.setTargetSdkVersion(target_api.split('-')[1])
.getActivity().setName(safe_activity_name);
var manifest_path = path.join(project_path, 'AndroidManifest.xml');
manifest.write(manifest_path);
copyScripts(project_path);
copyBuildRules(project_path);
});
// Link it to local android install.
writeProjectProperties(project_path, target_api);
prepBuildFiles(project_path);
console.log(generateDoneMessage('create', use_shared_project));
});
events.emit('log', generateDoneMessage('create', options.link));
}).thenResolve(project_path);
};
function generateDoneMessage(type, link) {
@@ -293,36 +298,32 @@ function generateDoneMessage(type, link) {
return msg;
}
// Attribute removed in Cordova 4.4 (CB-5447).
function removeDebuggableFromManifest(projectPath) {
var manifestPath = path.join(projectPath, 'AndroidManifest.xml');
shell.sed('-i', /\s*android:debuggable="true"/, '', manifestPath);
}
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];
}
// Returns a promise.
exports.updateProject = function(projectPath, shared) {
exports.update = function(projectPath, options, events) {
options = options || {};
return Q()
.then(function() {
var projectName = extractProjectNameFromManifest(projectPath);
var manifest = new AndroidManifest(path.join(projectPath, 'AndroidManifest.xml'));
if (Number(manifest.getMinSdkVersion()) < MIN_SDK_VERSION) {
events.emit('verbose', 'Updating minSdkVersion to ' + MIN_SDK_VERSION + ' in AndroidManifest.xml');
manifest.setMinSdkVersion(MIN_SDK_VERSION);
}
manifest.setDebuggable(false).write();
var projectName = manifest.getActivity().getName();
var target_api = check_reqs.get_target();
copyJsAndLibrary(projectPath, shared, projectName);
copyJsAndLibrary(projectPath, options.link, projectName);
copyScripts(projectPath);
copyBuildRules(projectPath);
removeDebuggableFromManifest(projectPath);
writeProjectProperties(projectPath, target_api);
prepBuildFiles(projectPath);
console.log(generateDoneMessage('update', shared));
});
events.emit('log', generateDoneMessage('update', options.link));
}).thenResolve(projectPath);
};

View File

@@ -1,32 +0,0 @@
/*
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.
*/
exports.getArgs = function(argv) {
var ret = {};
var posArgs = [];
for (var i = 2, arg; (arg = argv[i] || i < argv.length); ++i) {
if (/^--/.exec(arg)) {
ret[arg] = true;
} else {
posArgs.push(arg);
}
}
ret._ = posArgs;
return ret;
};

1
bin/node_modules/.bin/shjs generated vendored
View File

@@ -1 +0,0 @@
../shelljs/bin/shjs

41
bin/node_modules/nopt/package.json generated vendored

File diff suppressed because one or more lines are too long

40
bin/node_modules/q/CONTRIBUTING.md generated vendored
View File

@@ -1,40 +0,0 @@
For pull requests:
- Be consistent with prevalent style and design decisions.
- Add a Jasmine spec to `specs/q-spec.js`.
- Use `npm test` to avoid regressions.
- Run tests in `q-spec/run.html` in as many supported browsers as you
can find the will to deal with.
- Do not build minified versions; we do this each release.
- If you would be so kind, add a note to `CHANGES.md` in an
appropriate section:
- `Next Major Version` if it introduces backward incompatibilities
to code in the wild using documented features.
- `Next Minor Version` if it adds a new feature.
- `Next Patch Version` if it fixes a bug.
For releases:
- Run `npm test`.
- Run tests in `q-spec/run.html` in a representative sample of every
browser under the sun.
- Run `npm run cover` and make sure you're happy with the results.
- Run `npm run minify` and be sure to commit the resulting `q.min.js`.
- Note the Gzipped size output by the previous command, and update
`README.md` if it has changed to 1 significant digit.
- Stash any local changes.
- Update `CHANGES.md` to reflect all changes in the differences
between `HEAD` and the previous tagged version. Give credit where
credit is due.
- Update `README.md` to address all new, non-experimental features.
- Update the API reference on the Wiki to reflect all non-experimental
features.
- Use `npm version major|minor|patch` to update `package.json`,
commit, and tag the new version.
- Use `npm publish` to send up a new release.
- Send an email to the q-continuum mailing list announcing the new
release and the notes from the change log. This helps folks
maintaining other package ecosystems.

View File

@@ -1,71 +0,0 @@
"use strict";
var Q = require("../q");
var fs = require("fs");
suite("A single simple async operation", function () {
bench("with an immediately-fulfilled promise", function (done) {
Q().then(done);
});
bench("with direct setImmediate usage", function (done) {
setImmediate(done);
});
bench("with direct setTimeout(…, 0)", function (done) {
setTimeout(done, 0);
});
});
suite("A fs.readFile", function () {
var denodeified = Q.denodeify(fs.readFile);
set("iterations", 1000);
set("delay", 1000);
bench("directly, with callbacks", function (done) {
fs.readFile(__filename, done);
});
bench("with Q.nfcall", function (done) {
Q.nfcall(fs.readFile, __filename).then(done);
});
bench("with a Q.denodeify'ed version", function (done) {
denodeified(__filename).then(done);
});
bench("with manual usage of deferred.makeNodeResolver", function (done) {
var deferred = Q.defer();
fs.readFile(__filename, deferred.makeNodeResolver());
deferred.promise.then(done);
});
});
suite("1000 operations in parallel", function () {
function makeCounter(desiredCount, ultimateCallback) {
var soFar = 0;
return function () {
if (++soFar === desiredCount) {
ultimateCallback();
}
};
}
var numberOfOps = 1000;
bench("with immediately-fulfilled promises", function (done) {
var counter = makeCounter(numberOfOps, done);
for (var i = 0; i < numberOfOps; ++i) {
Q().then(counter);
}
});
bench("with direct setImmediate usage", function (done) {
var counter = makeCounter(numberOfOps, done);
for (var i = 0; i < numberOfOps; ++i) {
setImmediate(counter);
}
});
});

View File

@@ -1,36 +0,0 @@
"use strict";
var Q = require("../q");
suite("Chaining", function () {
var numberToChain = 1000;
bench("Chaining many already-fulfilled promises together", function (done) {
var currentPromise = Q();
for (var i = 0; i < numberToChain; ++i) {
currentPromise = currentPromise.then(function () {
return Q();
});
}
currentPromise.then(done);
});
bench("Chaining and then fulfilling the end of the chain", function (done) {
var deferred = Q.defer();
var currentPromise = deferred.promise;
for (var i = 0; i < numberToChain; ++i) {
(function () {
var promiseToReturn = currentPromise;
currentPromise = Q().then(function () {
return promiseToReturn;
});
}());
}
currentPromise.then(done);
deferred.resolve();
});
});

93
bin/node_modules/q/package.json generated vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

5
bin/node_modules/which/README.md generated vendored
View File

@@ -1,5 +0,0 @@
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.

14
bin/node_modules/which/bin/which generated vendored
View File

@@ -1,14 +0,0 @@
#!/usr/bin/env node
var which = require("../")
if (process.argv.length < 3) {
console.error("Usage: which <thing>")
process.exit(1)
}
which(process.argv[2], function (er, thing) {
if (er) {
console.error(er.message)
process.exit(er.errno || 127)
}
console.log(thing)
})

31
bin/node_modules/which/package.json generated vendored
View File

@@ -1,31 +0,0 @@
{
"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@"
}

104
bin/node_modules/which/which.js generated vendored
View File

@@ -1,104 +0,0 @@
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 === ""
}

View File

@@ -0,0 +1,10 @@
{
"node": true
, "bitwise": true
, "undef": true
, "trailing": true
, "quotmark": true
, "indent": 4
, "unused": "vars"
, "latedef": "nofunc"
}

492
bin/templates/cordova/Api.js vendored Normal file
View File

@@ -0,0 +1,492 @@
/**
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 Q = require('q');
var fs = require('fs');
var path = require('path');
var shell = require('shelljs');
var CordovaError = require('cordova-common').CordovaError;
var PlatformJson = require('cordova-common').PlatformJson;
var ActionStack = require('cordova-common').ActionStack;
var AndroidProject = require('./lib/AndroidProject');
var PlatformMunger = require('cordova-common').ConfigChanges.PlatformMunger;
var PluginInfoProvider = require('cordova-common').PluginInfoProvider;
var ConsoleLogger = require('./lib/ConsoleLogger');
var pluginHandlers = require('./lib/pluginHandlers');
var PLATFORM = 'android';
/**
* Class, that acts as abstraction over particular platform. Encapsulates the
* platform's properties and methods.
*
* Platform that implements own PlatformApi instance _should implement all
* prototype methods_ of this class to be fully compatible with cordova-lib.
*
* The PlatformApi instance also should define the following field:
*
* * platform: String that defines a platform name.
*/
function Api(platform, platformRootDir, events) {
this.platform = PLATFORM;
this.root = path.resolve(__dirname, '..');
this.events = events || ConsoleLogger.get();
// NOTE: trick to share one EventEmitter instance across all js code
require('cordova-common').events = this.events;
this._platformJson = PlatformJson.load(this.root, platform);
this._pluginInfoProvider = new PluginInfoProvider();
this._munger = new PlatformMunger(this.platform, this.root, this._platformJson, this._pluginInfoProvider);
var self = this;
this.locations = {
root: self.root,
www: path.join(self.root, 'assets/www'),
platformWww: path.join(self.root, 'platform_www'),
configXml: path.join(self.root, 'res/xml/config.xml'),
defaultConfigXml: path.join(self.root, 'cordova/defaults.xml'),
strings: path.join(self.root, 'res/values/strings.xml'),
manifest: path.join(self.root, 'AndroidManifest.xml'),
// NOTE: Due to platformApi spec we need to return relative paths here
cordovaJs: 'bin/templates/project/assets/www/cordova.js',
cordovaJsSrc: 'cordova-js-src'
};
}
/**
* Installs platform to specified directory and creates a platform project.
*
* @param {String} destination Destination directory, where insatll platform to
* @param {ConfigParser} [config] ConfgiParser instance, used to retrieve
* project creation options, such as package id and project name.
* @param {Object} [options] An options object. The most common options are:
* @param {String} [options.customTemplate] A path to custom template, that
* should override the default one from platform.
* @param {Boolean} [options.link] Flag that indicates that platform's
* sources will be linked to installed platform instead of copying.
* @param {EventEmitter} [events] An EventEmitter instance that will be used for
* logging purposes. If no EventEmitter provided, all events will be logged to
* console
*
* @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi
* instance or rejected with CordovaError.
*/
Api.createPlatform = function (destination, config, options, events) {
return require('../../lib/create')
.create(destination, config, options, events || ConsoleLogger.get())
.then(function (destination) {
var PlatformApi = require(path.resolve(destination, 'cordova/Api'));
return new PlatformApi(PLATFORM, destination, events);
});
};
/**
* Updates already installed platform.
*
* @param {String} destination Destination directory, where platform installed
* @param {Object} [options] An options object. The most common options are:
* @param {String} [options.customTemplate] A path to custom template, that
* should override the default one from platform.
* @param {Boolean} [options.link] Flag that indicates that platform's
* sources will be linked to installed platform instead of copying.
* @param {EventEmitter} [events] An EventEmitter instance that will be used for
* logging purposes. If no EventEmitter provided, all events will be logged to
* console
*
* @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi
* instance or rejected with CordovaError.
*/
Api.updatePlatform = function (destination, options, events) {
return require('../../lib/create')
.update(destination, options, events || ConsoleLogger.get())
.then(function (destination) {
var PlatformApi = require(path.resolve(destination, 'cordova/Api'));
return new PlatformApi('android', destination, events);
});
};
/**
* Gets a CordovaPlatform object, that represents the platform structure.
*
* @return {CordovaPlatform} A structure that contains the description of
* platform's file structure and other properties of platform.
*/
Api.prototype.getPlatformInfo = function () {
var result = {};
result.locations = this.locations;
result.root = this.root;
result.name = this.platform;
result.version = require('./version');
result.projectConfig = this._config;
return result;
};
/**
* Updates installed platform with provided www assets and new app
* configuration. This method is required for CLI workflow and will be called
* each time before build, so the changes, made to app configuration and www
* code, will be applied to platform.
*
* @param {CordovaProject} cordovaProject A CordovaProject instance, that defines a
* project structure and configuration, that should be applied to platform
* (contains project's www location and ConfigParser instance for project's
* config).
*
* @return {Promise} Return a promise either fulfilled, or rejected with
* CordovaError instance.
*/
Api.prototype.prepare = function (cordovaProject) {
return require('./lib/prepare').prepare.call(this, cordovaProject);
};
/**
* Installs a new plugin into platform. This method only copies non-www files
* (sources, libs, etc.) to platform. It also doesn't resolves the
* dependencies of plugin. Both of handling of www files, such as assets and
* js-files and resolving dependencies are the responsibility of caller.
*
* @param {PluginInfo} plugin A PluginInfo instance that represents plugin
* that will be installed.
* @param {Object} installOptions An options object. Possible options below:
* @param {Boolean} installOptions.link: Flag that specifies that plugin
* sources will be symlinked to app's directory instead of copying (if
* possible).
* @param {Object} installOptions.variables An object that represents
* variables that will be used to install plugin. See more details on plugin
* variables in documentation:
* https://cordova.apache.org/docs/en/4.0.0/plugin_ref_spec.md.html
*
* @return {Promise} Return a promise either fulfilled, or rejected with
* CordovaError instance.
*/
Api.prototype.addPlugin = function (plugin, installOptions) {
if (!plugin || plugin.constructor.name !== 'PluginInfo')
return Q.reject(new CordovaError('The parameter is incorrect. The first parameter to addPlugin should be a PluginInfo instance'));
installOptions = installOptions || {};
installOptions.variables = installOptions.variables || {};
var self = this;
var actions = new ActionStack();
var project = AndroidProject.getProjectFile(this.root);
// gather all files needs to be handled during install
plugin.getFilesAndFrameworks(this.platform)
.concat(plugin.getAssets(this.platform))
.concat(plugin.getJsModules(this.platform))
.forEach(function(item) {
actions.push(actions.createAction(
pluginHandlers.getInstaller(item.itemType), [item, plugin, project, installOptions],
pluginHandlers.getUninstaller(item.itemType), [item, plugin, project, installOptions]));
});
// run through the action stack
return actions.process(this.platform)
.then(function () {
if (project) {
project.write();
}
// Add PACKAGE_NAME variable into vars
if (!installOptions.variables.PACKAGE_NAME) {
installOptions.variables.PACKAGE_NAME = project.getPackageName();
}
self._munger
// Ignore passed `is_top_level` option since platform itself doesn't know
// anything about managing dependencies - it's responsibility of caller.
.add_plugin_changes(plugin, installOptions.variables, /*is_top_level=*/true, /*should_increment=*/true)
.save_all();
var targetDir = installOptions.usePlatformWww ?
self.locations.platformWww :
self.locations.www;
self._addModulesInfo(plugin, targetDir);
});
};
/**
* Removes an installed plugin from platform.
*
* Since method accepts PluginInfo instance as input parameter instead of plugin
* id, caller shoud take care of managing/storing PluginInfo instances for
* future uninstalls.
*
* @param {PluginInfo} plugin A PluginInfo instance that represents plugin
* that will be installed.
*
* @return {Promise} Return a promise either fulfilled, or rejected with
* CordovaError instance.
*/
Api.prototype.removePlugin = function (plugin, uninstallOptions) {
if (!plugin || plugin.constructor.name !== 'PluginInfo')
return Q.reject(new CordovaError('The parameter is incorrect. The first parameter to addPlugin should be a PluginInfo instance'));
var self = this;
var actions = new ActionStack();
var project = AndroidProject.getProjectFile(this.root);
// queue up plugin files
plugin.getFilesAndFrameworks(this.platform)
.concat(plugin.getAssets(this.platform))
.concat(plugin.getJsModules(this.platform))
.forEach(function(item) {
actions.push(actions.createAction(
pluginHandlers.getUninstaller(item.itemType), [item, plugin, project, uninstallOptions],
pluginHandlers.getInstaller(item.itemType), [item, plugin, project, uninstallOptions]));
});
// run through the action stack
return actions.process(this.platform)
.then(function() {
if (project) {
project.write();
}
self._munger
// Ignore passed `is_top_level` option since platform itself doesn't know
// anything about managing dependencies - it's responsibility of caller.
.remove_plugin_changes(plugin, /*is_top_level=*/true)
.save_all();
var targetDir = uninstallOptions.usePlatformWww ?
self.locations.platformWww :
self.locations.www;
self._removeModulesInfo(plugin, targetDir);
});
};
/**
* Builds an application package for current platform.
*
* @param {Object} buildOptions A build options. This object's structure is
* highly depends on platform's specific. The most common options are:
* @param {Boolean} buildOptions.debug Indicates that packages should be
* built with debug configuration. This is set to true by default unless the
* 'release' option is not specified.
* @param {Boolean} buildOptions.release Indicates that packages should be
* built with release configuration. If not set to true, debug configuration
* will be used.
* @param {Boolean} buildOptions.device Specifies that built app is intended
* to run on device
* @param {Boolean} buildOptions.emulator: Specifies that built app is
* intended to run on emulator
* @param {String} buildOptions.target Specifies the device id that will be
* used to run built application.
* @param {Boolean} buildOptions.nobuild Indicates that this should be a
* dry-run call, so no build artifacts will be produced.
* @param {String[]} buildOptions.archs Specifies chip architectures which
* app packages should be built for. List of valid architectures is depends on
* platform.
* @param {String} buildOptions.buildConfig The path to build configuration
* file. The format of this file is depends on platform.
* @param {String[]} buildOptions.argv Raw array of command-line arguments,
* passed to `build` command. The purpose of this property is to pass a
* platform-specific arguments, and eventually let platform define own
* arguments processing logic.
*
* @return {Promise<Object[]>} A promise either fulfilled with an array of build
* artifacts (application packages) if package was built successfully,
* or rejected with CordovaError. The resultant build artifact objects is not
* strictly typed and may conatin arbitrary set of fields as in sample below.
*
* {
* architecture: 'x86',
* buildType: 'debug',
* path: '/path/to/build',
* type: 'app'
* }
*
* The return value in most cases will contain only one item but in some cases
* there could be multiple items in output array, e.g. when multiple
* arhcitectures is specified.
*/
Api.prototype.build = function (buildOptions) {
var self = this;
return require('./lib/check_reqs').run()
.then(function () {
return require('./lib/build').run.call(self, buildOptions);
})
.then(function (buildResults) {
// Cast build result to array of build artifacts
return buildResults.apkPaths.map(function (apkPath) {
return {
buildType: buildResults.buildType,
buildMethod: buildResults.buildMethod,
path: apkPath,
type: 'apk'
};
});
});
};
/**
* Builds an application package for current platform and runs it on
* specified/default device. If no 'device'/'emulator'/'target' options are
* specified, then tries to run app on default device if connected, otherwise
* runs the app on emulator.
*
* @param {Object} runOptions An options object. The structure is the same
* as for build options.
*
* @return {Promise} A promise either fulfilled if package was built and ran
* successfully, or rejected with CordovaError.
*/
Api.prototype.run = function(runOptions) {
var self = this;
return require('./lib/check_reqs').run()
.then(function () {
return require('./lib/run').run.call(self, runOptions);
});
};
/**
* Cleans out the build artifacts from platform's directory.
*
* @return {Promise} Return a promise either fulfilled, or rejected with
* CordovaError.
*/
Api.prototype.clean = function(cleanOptions) {
var self = this;
return require('./lib/check_reqs').run()
.then(function () {
return require('./lib/build').runClean.call(self, cleanOptions);
});
};
/**
* Performs a requirements check for current platform. Each platform defines its
* own set of requirements, which should be resolved before platform can be
* built successfully.
*
* @return {Promise<Requirement[]>} Promise, resolved with set of Requirement
* objects for current platform.
*/
Api.prototype.requirements = function() {
return require('./lib/check_reqs').check_all();
};
module.exports = Api;
/**
* Removes the specified modules from list of installed modules and updates
* platform_json and cordova_plugins.js on disk.
*
* @param {PluginInfo} plugin PluginInfo instance for plugin, which modules
* needs to be added.
* @param {String} targetDir The directory, where updated cordova_plugins.js
* should be written to.
*/
Api.prototype._addModulesInfo = function(plugin, targetDir) {
var installedModules = this._platformJson.root.modules || [];
var installedPaths = installedModules.map(function (installedModule) {
return installedModule.file;
});
var modulesToInstall = plugin.getJsModules(this.platform)
.filter(function (moduleToInstall) {
return installedPaths.indexOf(moduleToInstall.file) === -1;
}).map(function (moduleToInstall) {
var moduleName = plugin.id + '.' + ( moduleToInstall.name || moduleToInstall.src.match(/([^\/]+)\.js/)[1] );
var obj = {
file: ['plugins', plugin.id, moduleToInstall.src].join('/'),
id: moduleName
};
if (moduleToInstall.clobbers.length > 0) {
obj.clobbers = moduleToInstall.clobbers.map(function(o) { return o.target; });
}
if (moduleToInstall.merges.length > 0) {
obj.merges = moduleToInstall.merges.map(function(o) { return o.target; });
}
if (moduleToInstall.runs) {
obj.runs = true;
}
return obj;
});
this._platformJson.root.modules = installedModules.concat(modulesToInstall);
this._writePluginModules(targetDir);
this._platformJson.save();
};
/**
* Removes the specified modules from list of installed modules and updates
* platform_json and cordova_plugins.js on disk.
*
* @param {PluginInfo} plugin PluginInfo instance for plugin, which modules
* needs to be removed.
* @param {String} targetDir The directory, where updated cordova_plugins.js
* should be written to.
*/
Api.prototype._removeModulesInfo = function(plugin, targetDir) {
var installedModules = this._platformJson.root.modules || [];
var modulesToRemove = plugin.getJsModules(this.platform)
.map(function (jsModule) {
return ['plugins', plugin.id, jsModule.src].join('/');
});
var updatedModules = installedModules
.filter(function (installedModule) {
return (modulesToRemove.indexOf(installedModule.file) === -1);
});
this._platformJson.root.modules = updatedModules;
this._writePluginModules(targetDir);
this._platformJson.save();
};
/**
* Fetches all installed modules, generates cordova_plugins contents and writes
* it to file.
*
* @param {String} targetDir Directory, where write cordova_plugins.js to.
* Ususally it is either <platform>/www or <platform>/platform_www
* directories.
*/
Api.prototype._writePluginModules = function (targetDir) {
var self = this;
// Write out moduleObjects as JSON wrapped in a cordova module to cordova_plugins.js
var final_contents = 'cordova.define(\'cordova/plugin_list\', function(require, exports, module) {\n';
final_contents += 'module.exports = ' + JSON.stringify(this._platformJson.root.modules, null, ' ') + ';\n';
final_contents += 'module.exports.metadata = \n';
final_contents += '// TOP OF METADATA\n';
var pluginMetadata = Object.keys(this._platformJson.root.installed_plugins)
.reduce(function (metadata, plugin) {
metadata[plugin] = self._platformJson.root.installed_plugins[plugin].version;
return metadata;
}, {});
final_contents += JSON.stringify(pluginMetadata, null, 4) + '\n';
final_contents += '// BOTTOM OF METADATA\n';
final_contents += '});'; // Close cordova.define.
shell.mkdir('-p', targetDir);
fs.writeFileSync(path.join(targetDir, 'cordova_plugins.js'), final_contents, 'utf-8');
};

View File

@@ -19,23 +19,30 @@
under the License.
*/
var build = require('./lib/build'),
reqs = require('./lib/check_reqs'),
args = process.argv;
var args = process.argv;
var Api = require('./Api');
var nopt = require('nopt');
var path = require('path');
// Support basic help commands
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.slice(2));
}, function(err) {
console.error(err);
process.exit(2);
});
}
if(['--help', '/?', '-h', 'help', '-help', '/help'].indexOf(process.argv[2]) >= 0)
require('./lib/build').help();
// Do some basic argument parsing
var buildOpts = nopt({
'verbose' : Boolean,
'silent' : Boolean,
'debug' : Boolean,
'release' : Boolean,
'nobuild': Boolean,
'buildConfig' : path
}, { 'd' : '--verbose' });
// Make buildOptions compatible with PlatformApi build method spec
buildOpts.argv = buildOpts.argv.remain;
new Api().build(buildOpts)
.catch(function(err) {
console.error(err.stack);
process.exit(2);
});

View File

@@ -19,26 +19,18 @@
under the License.
*/
var build = require('./lib/build'),
reqs = require('./lib/check_reqs'),
args = process.argv;
var Api = require('./Api');
var path = require('path');
// Support basic help commands
if(args[2] == '--help' ||
args[2] == '/?' ||
args[2] == '-h' ||
args[2] == 'help' ||
args[2] == '-help' ||
args[2] == '/help') {
if(['--help', '/?', '-h', 'help', '-help', '/help'].indexOf(process.argv[2]) >= 0) {
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 build.runClean(args.slice(2));
}, function(err) {
console.error(err);
process.exit(2);
});
}
new Api().clean({argv: process.argv.slice(2)})
.catch(function(err) {
console.error(err.stack);
process.exit(2);
});

96
bin/templates/cordova/lib/Adb.js vendored Normal file
View File

@@ -0,0 +1,96 @@
/**
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 Q = require('q');
var os = require('os');
var events = require('cordova-common').events;
var spawn = require('cordova-common').superspawn.spawn;
var CordovaError = require('cordova-common').CordovaError;
var Adb = {};
function isDevice(line) {
return line.match(/\w+\tdevice/) && !line.match(/emulator/);
}
function isEmulator(line) {
return line.match(/device/) && line.match(/emulator/);
}
/**
* Lists available/connected devices and emulators
*
* @param {Object} opts Various options
* @param {Boolean} opts.emulators Specifies whether this method returns
* emulators only
*
* @return {Promise<String[]>} list of available/connected
* devices/emulators
*/
Adb.devices = function (opts) {
return spawn('adb', ['devices'], {cwd: os.tmpdir()})
.then(function(output) {
return output.split('\n').filter(function (line) {
// Filter out either real devices or emulators, depending on options
return (line && opts && opts.emulators) ? isEmulator(line) : isDevice(line);
}).map(function (line) {
return line.replace(/\tdevice/, '').replace('\r', '');
});
});
};
Adb.install = function (target, packagePath, opts) {
events.emit('verbose', 'Installing apk ' + packagePath + ' on ' + target + '...');
var args = ['-s', target, 'install'];
if (opts && opts.replace) args.push('-r');
return spawn('adb', args.concat(packagePath), {cwd: os.tmpdir()})
.then(function(output) {
// 'adb install' seems to always returns no error, even if installation fails
// so we catching output to detect installation failure
if (output.match(/Failure/))
return Q.reject(new CordovaError('Failed to install apk to device: ' + output));
});
};
Adb.uninstall = function (target, packageId) {
events.emit('verbose', 'Uninstalling ' + packageId + ' from ' + target + '...');
return spawn('adb', ['-s', target, 'uninstall', packageId], {cwd: os.tmpdir()});
};
Adb.shell = function (target, shellCommand) {
events.emit('verbose', 'Running command "' + shellCommand + '" on ' + target + '...');
var args = ['-s', target, 'shell'];
shellCommand = shellCommand.split(/\s+/);
return spawn('adb', args.concat(shellCommand), {cwd: os.tmpdir()})
.catch(function (output) {
return Q.reject(new CordovaError('Failed to execute shell command "' +
shellCommand + '"" on device: ' + output));
});
};
Adb.start = function (target, activityName) {
events.emit('verbose', 'Starting application "' + activityName + '" on ' + target + '...');
return Adb.shell(target, 'am start -W -a android.intent.action.MAIN -n' + activityName)
.catch(function (output) {
return Q.reject(new CordovaError('Failed to start application "' +
activityName + '"" on device: ' + output));
});
};
module.exports = Adb;

View File

@@ -0,0 +1,161 @@
/**
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 fs = require('fs');
var et = require('elementtree');
var xml= require('cordova-common').xmlHelpers;
var DEFAULT_ORIENTATION = 'default';
/** Wraps an AndroidManifest file */
function AndroidManifest(path) {
this.path = path;
this.doc = xml.parseElementtreeSync(path);
if (this.doc.getroot().tag !== 'manifest') {
throw new Error(path + ' has incorrect root node name (expected "manifest")');
}
}
AndroidManifest.prototype.getVersionName = function() {
return this.doc.getroot().attrib['android:versionName'];
};
AndroidManifest.prototype.setVersionName = function(versionName) {
this.doc.getroot().attrib['android:versionName'] = versionName;
return this;
};
AndroidManifest.prototype.getVersionCode = function() {
return this.doc.getroot().attrib['android:versionCode'];
};
AndroidManifest.prototype.setVersionCode = function(versionCode) {
this.doc.getroot().attrib['android:versionCode'] = versionCode;
return this;
};
AndroidManifest.prototype.getPackageId = function() {
/*jshint -W069 */
return this.doc.getroot().attrib['package'];
/*jshint +W069 */
};
AndroidManifest.prototype.setPackageId = function(pkgId) {
/*jshint -W069 */
this.doc.getroot().attrib['package'] = pkgId;
/*jshint +W069 */
return this;
};
AndroidManifest.prototype.getActivity = function() {
var activity = this.doc.getroot().find('./application/activity');
return {
getName: function () {
return activity.attrib['android:name'];
},
setName: function (name) {
if (!name) {
delete activity.attrib['android:name'];
} else {
activity.attrib['android:name'] = name;
}
return this;
},
getOrientation: function () {
return activity.attrib['android:screenOrientation'];
},
setOrientation: function (orientation) {
if (!orientation || orientation.toLowerCase() === DEFAULT_ORIENTATION) {
delete activity.attrib['android:screenOrientation'];
} else {
activity.attrib['android:screenOrientation'] = orientation;
}
return this;
},
getLaunchMode: function () {
return activity.attrib['android:launchMode'];
},
setLaunchMode: function (launchMode) {
if (!launchMode) {
delete activity.attrib['android:launchMode'];
} else {
activity.attrib['android:launchMode'] = launchMode;
}
return this;
}
};
};
['minSdkVersion', 'maxSdkVersion', 'targetSdkVersion']
.forEach(function(sdkPrefName) {
// Copy variable reference to avoid closure issues
var prefName = sdkPrefName;
AndroidManifest.prototype['get' + capitalize(prefName)] = function() {
var usesSdk = this.doc.getroot().find('./uses-sdk');
return usesSdk && usesSdk.attrib['android:' + prefName];
};
AndroidManifest.prototype['set' + capitalize(prefName)] = function(prefValue) {
var usesSdk = this.doc.getroot().find('./uses-sdk');
if (!usesSdk && prefValue) { // if there is no required uses-sdk element, we should create it first
usesSdk = new et.Element('uses-sdk');
this.doc.getroot().append(usesSdk);
}
if (prefValue) {
usesSdk.attrib['android:' + prefName] = prefValue;
}
return this;
};
});
AndroidManifest.prototype.getDebuggable = function() {
return this.doc.getroot().find('./application').attrib['android:debuggable'] === 'true';
};
AndroidManifest.prototype.setDebuggable = function(value) {
var application = this.doc.getroot().find('./application');
if (value) {
application.attrib['android:debuggable'] = 'true';
} else {
// The default value is "false", so we can remove attribute at all.
delete application.attrib['android:debuggable'];
}
return this;
};
/**
* Writes manifest to disk syncronously. If filename is specified, then manifest
* will be written to that file
*
* @param {String} [destPath] File to write manifest to. If omitted,
* manifest will be written to file it has been read from.
*/
AndroidManifest.prototype.write = function(destPath) {
fs.writeFileSync(destPath || this.path, this.doc.write({indent: 4}), 'utf-8');
};
module.exports = AndroidManifest;
function capitalize (str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}

View File

@@ -0,0 +1,184 @@
/**
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 fs = require('fs');
var path = require('path');
var properties_parser = require('properties-parser');
var AndroidManifest = require('./AndroidManifest');
var projectFileCache = {};
function addToPropertyList(projectProperties, key, value) {
var i = 1;
while (projectProperties.get(key + '.' + i))
i++;
projectProperties.set(key + '.' + i, value);
projectProperties.dirty = true;
}
function removeFromPropertyList(projectProperties, key, value) {
var i = 1;
var currentValue;
while ((currentValue = projectProperties.get(key + '.' + i))) {
if (currentValue === value) {
while ((currentValue = projectProperties.get(key + '.' + (i + 1)))) {
projectProperties.set(key + '.' + i, currentValue);
i++;
}
projectProperties.set(key + '.' + i);
break;
}
i++;
}
projectProperties.dirty = true;
}
function getRelativeLibraryPath (parentDir, subDir) {
var libraryPath = path.relative(parentDir, subDir);
return (path.sep == '\\') ? libraryPath.replace(/\\/g, '/') : libraryPath;
}
function AndroidProject(projectDir) {
this._propertiesEditors = {};
this._subProjectDirs = {};
this._dirty = false;
this.projectDir = projectDir;
this.platformWww = path.join(this.projectDir, 'platform_www');
this.www = path.join(this.projectDir, 'assets/www');
}
AndroidProject.getProjectFile = function (projectDir) {
if (!projectFileCache[projectDir]) {
projectFileCache[projectDir] = new AndroidProject(projectDir);
}
return projectFileCache[projectDir];
};
AndroidProject.purgeCache = function (projectDir) {
if (projectDir) {
delete projectFileCache[projectDir];
} else {
projectFileCache = {};
}
};
/**
* Reads the package name out of the Android Manifest file
*
* @param {String} projectDir The absolute path to the directory containing the project
*
* @return {String} The name of the package
*/
AndroidProject.prototype.getPackageName = function() {
return new AndroidManifest(path.join(this.projectDir, 'AndroidManifest.xml')).getPackageId();
};
AndroidProject.prototype.getCustomSubprojectRelativeDir = function(plugin_id, src) {
// All custom subprojects are prefixed with the last portion of the package id.
// This is to avoid collisions when opening multiple projects in Eclipse that have subprojects with the same name.
var packageName = this.getPackageName();
var lastDotIndex = packageName.lastIndexOf('.');
var prefix = packageName.substring(lastDotIndex + 1);
var subRelativeDir = path.join(plugin_id, prefix + '-' + path.basename(src));
return subRelativeDir;
};
AndroidProject.prototype.addSubProject = function(parentDir, subDir) {
var parentProjectFile = path.resolve(parentDir, 'project.properties');
var subProjectFile = path.resolve(subDir, 'project.properties');
var parentProperties = this._getPropertiesFile(parentProjectFile);
// TODO: Setting the target needs to happen only for pre-3.7.0 projects
if (fs.existsSync(subProjectFile)) {
var subProperties = this._getPropertiesFile(subProjectFile);
subProperties.set('target', parentProperties.get('target'));
subProperties.dirty = true;
this._subProjectDirs[subDir] = true;
}
addToPropertyList(parentProperties, 'android.library.reference', getRelativeLibraryPath(parentDir, subDir));
this._dirty = true;
};
AndroidProject.prototype.removeSubProject = function(parentDir, subDir) {
var parentProjectFile = path.resolve(parentDir, 'project.properties');
var parentProperties = this._getPropertiesFile(parentProjectFile);
removeFromPropertyList(parentProperties, 'android.library.reference', getRelativeLibraryPath(parentDir, subDir));
delete this._subProjectDirs[subDir];
this._dirty = true;
};
AndroidProject.prototype.addGradleReference = function(parentDir, subDir) {
var parentProjectFile = path.resolve(parentDir, 'project.properties');
var parentProperties = this._getPropertiesFile(parentProjectFile);
addToPropertyList(parentProperties, 'cordova.gradle.include', getRelativeLibraryPath(parentDir, subDir));
this._dirty = true;
};
AndroidProject.prototype.removeGradleReference = function(parentDir, subDir) {
var parentProjectFile = path.resolve(parentDir, 'project.properties');
var parentProperties = this._getPropertiesFile(parentProjectFile);
removeFromPropertyList(parentProperties, 'cordova.gradle.include', getRelativeLibraryPath(parentDir, subDir));
this._dirty = true;
};
AndroidProject.prototype.addSystemLibrary = function(parentDir, value) {
var parentProjectFile = path.resolve(parentDir, 'project.properties');
var parentProperties = this._getPropertiesFile(parentProjectFile);
addToPropertyList(parentProperties, 'cordova.system.library', value);
this._dirty = true;
};
AndroidProject.prototype.removeSystemLibrary = function(parentDir, value) {
var parentProjectFile = path.resolve(parentDir, 'project.properties');
var parentProperties = this._getPropertiesFile(parentProjectFile);
removeFromPropertyList(parentProperties, 'cordova.system.library', value);
this._dirty = true;
};
AndroidProject.prototype.write = function() {
if (!this._dirty) {
return;
}
this._dirty = false;
for (var filename in this._propertiesEditors) {
var editor = this._propertiesEditors[filename];
if (editor.dirty) {
fs.writeFileSync(filename, editor.toString());
editor.dirty = false;
}
}
};
AndroidProject.prototype._getPropertiesFile = function (filename) {
if (!this._propertiesEditors[filename]) {
if (fs.existsSync(filename)) {
this._propertiesEditors[filename] = properties_parser.createEditor(filename);
} else {
this._propertiesEditors[filename] = properties_parser.createEditor();
}
}
return this._propertiesEditors[filename];
};
module.exports = AndroidProject;

View File

@@ -0,0 +1,75 @@
/**
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 loggerInstance;
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var CordovaError = require('cordova-common').CordovaError;
/**
* @class ConsoleLogger
* @extends EventEmitter
*
* Implementing basic logging for platform. Inherits regular NodeJS
* EventEmitter. All events, emitted on this class instance are immediately
* logged to console.
*
* Also attaches handler to process' uncaught exceptions, so these exceptions
* logged to console similar to regular error events.
*/
function ConsoleLogger() {
EventEmitter.call(this);
var isVerbose = process.argv.indexOf('-d') >= 0 || process.argv.indexOf('--verbose') >= 0;
// For CordovaError print only the message without stack trace unless we
// are in a verbose mode.
process.on('uncaughtException', function(err){
if ((err instanceof CordovaError) && isVerbose) {
console.error(err.stack);
} else {
console.error(err.message);
}
process.exit(1);
});
this.on('results', console.log);
this.on('verbose', function () {
if (isVerbose)
console.log.apply(console, arguments);
});
this.on('info', console.log);
this.on('log', console.log);
this.on('warn', console.warn);
}
util.inherits(ConsoleLogger, EventEmitter);
/**
* Returns already instantiated/newly created instance of ConsoleLogger class.
* This method should be used instead of creating ConsoleLogger directly,
* otherwise we'll get multiple handlers attached to process'
* uncaughtException
*
* @return {ConsoleLogger} New or already created instance of ConsoleLogger
*/
ConsoleLogger.get = function () {
loggerInstance = loggerInstance || new ConsoleLogger();
return loggerInstance;
};
module.exports = ConsoleLogger;

View File

@@ -1,41 +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 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());
};

View File

@@ -19,464 +19,80 @@
under the License.
*/
/* jshint sub:true */
var shell = require('shelljs'),
spawn = require('./spawn'),
Q = require('q'),
var 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');
nopt = require('nopt');
var Adb = require('./Adb');
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 api 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 (arch && 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 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 subProjectsAsGradlePaths = subProjects.map(function(p) { return ':' + p.replace(/[/\\]/g, ':'); });
// Write the settings.gradle file.
fs.writeFileSync(path.join(projectPath, 'settings.gradle'),
'// GENERATED FILE - DO NOT EDIT\n' +
'include ":"\n' +
'include "' + subProjectsAsGradlePaths.join('"\ninclude "') + '"\n');
// Update dependencies within build.gradle.
var buildGradle = fs.readFileSync(path.join(projectPath, 'build.gradle'), 'utf8');
var depsList = '';
subProjectsAsGradlePaths.forEach(function(p) {
depsList += ' debugCompile project(path: "' + p + '", configuration: "debug")\n';
depsList += ' releaseCompile project(path: "' + p + '", 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.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'));
// 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.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);
};
var builders = require('./builders/builders');
var events = require('cordova-common').events;
var spawn = require('cordova-common').superspawn.spawn;
var CordovaError = require('cordova-common').CordovaError;
function parseOpts(options, resolvedTarget) {
// Backwards-compatibility: Allow a single string argument
if (typeof options == 'string') options = [options];
options = options || {};
options.argv = nopt({
gradle: Boolean,
ant: Boolean,
prepenv: Boolean,
versionCode: String,
minSdkVersion: String,
gradleArg: String,
keystore: path,
alias: String,
storePassword: String,
password: String,
keystoreType: String
}, {}, options.argv, 0);
var ret = {
buildType: 'debug',
buildMethod: process.env['ANDROID_BUILD'] || 'gradle',
arch: null,
buildType: options.release ? 'release' : 'debug',
buildMethod: process.env.ANDROID_BUILD || 'gradle',
prepEnv: options.argv.prepenv,
arch: resolvedTarget && resolvedTarget.arch,
extraArgs: []
};
var multiValueArgs = {
'versionCode': true,
'minSdkVersion': true,
'gradleArg': true,
'keystore' : true,
'alias' : true,
'password' : true,
'storePassword' : true,
'keystoreType' : true,
'buildConfig' : true
};
if (options.argv.ant || options.argv.gradle)
ret.buildMethod = options.argv.ant ? 'ant' : 'gradle';
if (options.nobuild) ret.buildMethod = 'none';
if (options.argv.versionCode)
ret.extraArgs.push('-PcdvVersionCode=' + options.versionCode);
if (options.argv.minSdkVersion)
ret.extraArgs.push('-PcdvMinSdkVersion=' + options.minSdkVersion);
if (options.argv.gradleArg)
ret.extraArgs.push(options.gradleArg);
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 (options.argv.keystore)
packageArgs.keystore = path.relative(this.root, path.resolve(options.argv.keystore));
['alias','storePassword','password','keystoreType'].forEach(function (flagName) {
if (options.argv[flagName])
packageArgs[flagName] = options.argv[flagName];
});
var buildConfig = options.buildConfig;
// 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) {
console.log(path.resolve(buildConfig));
if (!fs.existsSync(buildConfig)) {
throw new Error('Specified build config file does not exist: ' + buildConfig);
}
console.log('Reading build config file: '+ buildConfig);
events.emit('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 = packageArgs.keystore || path.relative(ROOT, path.join(path.dirname(buildConfig), androidInfo.keystore));
if(androidInfo.keystore && !packageArgs.keystore) {
packageArgs.keystore = path.resolve(path.dirname(buildConfig), androidInfo.keystore);
}
['alias', 'storePassword', 'password','keystoreType'].forEach(function (key){
@@ -484,6 +100,7 @@ function parseOpts(options, resolvedTarget) {
});
}
}
if (packageArgs.keystore && packageArgs.alias) {
ret.packageInfo = new PackageInfo(packageArgs.keystore, packageArgs.alias, packageArgs.storePassword,
packageArgs.password, packageArgs.keystoreType);
@@ -491,10 +108,9 @@ function parseOpts(options, resolvedTarget) {
if(!ret.packageInfo) {
if(Object.keys(packageArgs).length > 0) {
console.warn('\'keystore\' and \'alias\' need to be specified to generate a signed archive.');
events.emit('warn', '\'keystore\' and \'alias\' need to be specified to generate a signed archive.');
}
}
ret.arch = resolvedTarget && resolvedTarget.arch;
return ret;
}
@@ -505,40 +121,39 @@ function parseOpts(options, resolvedTarget) {
*/
module.exports.runClean = function(options) {
var opts = parseOpts(options);
var builder = builders[opts.buildMethod];
var builder = builders.getBuilder(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.
/**
* Builds the project with the specifed options.
*
* @param {BuildOptions} options A set of options. See PlatformApi.build
* method documentation for reference.
* @param {Object} optResolvedTarget A deployment target. Used to pass
* target architecture from upstream 'run' call. TODO: remove this option in
* favor of setting buildOptions.archs field.
*
* @return {Promise<Object>} Promise, resolved with built packages
* information.
*/
module.exports.run = function(options, optResolvedTarget) {
var opts = parseOpts(options, optResolvedTarget);
var builder = builders[opts.buildMethod];
var builder = builders.getBuilder(opts.buildMethod);
var self = this;
return builder.prepEnv(opts)
.then(function() {
if (opts.prepEnv) {
console.log('Build file successfully prepared.');
self.events.emit('verbose', '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 '));
self.events.emit('log', 'Built the following apk(s): \n\t' + apkPaths.join('\n\t'));
return {
apkPaths: apkPaths,
buildType: opts.buildType,
@@ -550,8 +165,7 @@ module.exports.run = function(options, optResolvedTarget) {
// Called by plugman after installing plugins, and by create script after creating project.
module.exports.prepBuildFiles = function() {
var builder = builders['gradle'];
return builder.prepBuildFiles();
return builders.getBuilder('gradle').prepBuildFiles();
};
/*
@@ -560,34 +174,32 @@ module.exports.prepBuildFiles = function() {
*/
module.exports.detectArchitecture = function(target) {
function helper() {
return exec('adb -s ' + target + ' shell cat /proc/cpuinfo', os.tmpdir())
return Adb.shell(target, 'cat /proc/cpuinfo')
.then(function(output) {
if (/intel/i.exec(output)) {
return 'x86';
}
return 'arm';
return /intel/i.exec(output) ? 'x86' : '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.')
return helper()
.timeout(1000, new CordovaError('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')
return spawn('killall', ['adb'])
.then(function() {
console.log('adb seems hung. retrying.');
events.emit('verbose', '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')
events.emit('warn', 'Now device not found... restarting adb again.');
return spawn('killall', ['adb'])
.then(function() {
return helper()
.then(null, function() {
return Q.reject('USB is flakey. Try unplugging & replugging the device.');
return Q.reject(new CordovaError('USB is flakey. Try unplugging & replugging the device.'));
});
});
});
@@ -602,16 +214,18 @@ module.exports.detectArchitecture = function(target) {
module.exports.findBestApkForArchitecture = function(buildResults, arch) {
var paths = buildResults.apkPaths.filter(function(p) {
var apkName = path.basename(p);
if (buildResults.buildType == 'debug') {
return /-debug/.exec(p);
return /-debug/.exec(apkName);
}
return !/-debug/.exec(p);
return !/-debug/.exec(apkName);
});
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])) {
var apkName = path.basename(paths[i]);
if (hasArchPattern.exec(apkName)) {
if (archPattern.exec(apkName)) {
return paths[i];
}
} else {
@@ -665,7 +279,7 @@ PackageInfo.prototype = {
};
module.exports.help = function() {
console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'build')) + ' [flags] [Signed APK flags]');
console.log('Usage: ' + path.relative(process.cwd(), path.join('../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');

View File

@@ -0,0 +1,141 @@
/*
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 Q = require('q');
var fs = require('fs');
var path = require('path');
var util = require('util');
var shell = require('shelljs');
var spawn = require('cordova-common').superspawn.spawn;
var CordovaError = require('cordova-common').CordovaError;
var check_reqs = require('../check_reqs');
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';
var GenericBuilder = require('./GenericBuilder');
function AntBuilder (projectRoot) {
GenericBuilder.call(this, projectRoot);
this.binDirs = {ant: this.binDirs.ant};
}
util.inherits(AntBuilder, GenericBuilder);
AntBuilder.prototype.getArgs = function(cmd, opts) {
var args = [cmd, '-f', path.join(this.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(this.root, opts.buildType + SIGNING_PROPERTIES));
}
return args;
};
AntBuilder.prototype.prepEnv = function(opts) {
var self = this;
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.
/*jshint -W069 */
var sdkDir = process.env['ANDROID_HOME'];
/*jshint +W069 */
var buildTemplate = fs.readFileSync(path.join(sdkDir, 'tools', 'lib', 'build.template'), 'utf8');
function writeBuildXml(projectPath) {
var newData = buildTemplate.replace('PROJECT_NAME', self.extractRealProjectNameFromManifest());
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(self.root);
var propertiesObj = self.readProjectProperties();
var subProjects = propertiesObj.libs;
for (var i = 0; i < subProjects.length; ++i) {
writeBuildXml(path.join(self.root, subProjects[i]));
}
if (propertiesObj.systemLibs.length > 0) {
throw new CordovaError('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(self.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.
*/
AntBuilder.prototype.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() {
return spawn('ant', args, {stdio: 'inherit'});
});
};
AntBuilder.prototype.clean = function(opts) {
var args = this.getArgs('clean', opts);
var self = this;
return check_reqs.check_ant()
.then(function() {
return spawn('ant', args, {stdio: 'inherit'});
})
.then(function () {
shell.rm('-rf', path.join(self.root, 'out'));
['debug', 'release'].forEach(function(config) {
var propertiesFilePath = path.join(self.root, config + SIGNING_PROPERTIES);
if(isAutoGenerated(propertiesFilePath)){
shell.rm('-f', propertiesFilePath);
}
});
});
};
module.exports = AntBuilder;
function hasCustomRules(projectRoot) {
return fs.existsSync(path.join(projectRoot, 'custom_rules.xml'));
}
function isAutoGenerated(file) {
return fs.existsSync(file) && fs.readFileSync(file, 'utf8').indexOf(MARKER) > 0;
}

View File

@@ -0,0 +1,138 @@
/*
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 Q = require('q');
var fs = require('fs');
var path = require('path');
var shell = require('shelljs');
var events = require('cordova-common').events;
var CordovaError = require('cordova-common').CordovaError;
function GenericBuilder (projectDir) {
this.root = projectDir || path.resolve(__dirname, '../../..');
this.binDirs = {
ant: path.join(this.root, hasCustomRules(this.root) ? 'ant-build' : 'bin'),
gradle: path.join(this.root, 'build', 'outputs', 'apk')
};
}
function hasCustomRules(projectRoot) {
return fs.existsSync(path.join(projectRoot, 'custom_rules.xml'));
}
GenericBuilder.prototype.prepEnv = function() {
return Q();
};
GenericBuilder.prototype.build = function() {
events.emit('log', 'Skipping build...');
return Q(null);
};
GenericBuilder.prototype.clean = function() {
return Q();
};
GenericBuilder.prototype.findOutputApks = function(build_type, arch) {
var self = this;
return Object.keys(this.binDirs)
.reduce(function (result, builderName) {
var binDir = self.binDirs[builderName];
return result.concat(findOutputApksHelper(binDir, build_type, builderName === 'ant' ? null : arch));
}, [])
.sort(apkSorter);
};
GenericBuilder.prototype.readProjectProperties = function () {
function findAllUniq(data, r) {
var s = {};
var m;
while ((m = r.exec(data))) {
s[m[1]] = 1;
}
return Object.keys(s);
}
var data = fs.readFileSync(path.join(this.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)
};
};
GenericBuilder.prototype.extractRealProjectNameFromManifest = function () {
var manifestPath = path.join(this.root, 'AndroidManifest.xml');
var manifestData = fs.readFileSync(manifestPath, 'utf8');
var m = /<manifest[\s\S]*?package\s*=\s*"(.*?)"/i.exec(manifestData);
if (!m) {
throw new CordovaError('Could not find package name in ' + manifestPath);
}
var packageName=m[1];
var lastDotIndex = packageName.lastIndexOf('.');
return packageName.substring(lastDotIndex + 1);
};
module.exports = GenericBuilder;
function apkSorter(fileA, fileB) {
var timeDiff = fs.statSync(fileA).mtime - fs.statSync(fileB).mtime;
return timeDiff === 0 ? fileA.length - fileB.length : timeDiff;
}
function findOutputApksHelper(dir, build_type, arch) {
var shellSilent = shell.config.silent;
shell.config.silent = true;
var ret = shell.ls(path.join(dir, '*.apk'))
.filter(function(candidate) {
var apkName = path.basename(candidate);
// Need to choose between release and debug .apk.
if (build_type === 'debug') {
return /-debug/.exec(apkName) && !/-unaligned|-unsigned/.exec(apkName);
}
if (build_type === 'release') {
return /-release/.exec(apkName) && !/-unaligned/.exec(apkName);
}
return true;
})
.sort(apkSorter);
shellSilent = shellSilent;
if (ret.length === 0) {
return ret;
}
// Assume arch-specific build if newest apk has -x86 or -arm.
var archSpecific = !!/-x86|-arm/.exec(path.basename(ret[0]));
// And show only arch-specific ones (or non-arch-specific)
ret = ret.filter(function(p) {
/*jshint -W018 */
return !!/-x86|-arm/.exec(path.basename(p)) == archSpecific;
/*jshint +W018 */
});
if (archSpecific && ret.length > 1) {
ret = ret.filter(function(p) {
return path.basename(p).indexOf('-' + arch) != -1;
});
}
return ret;
}

View File

@@ -0,0 +1,213 @@
/*
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 Q = require('q');
var fs = require('fs');
var util = require('util');
var path = require('path');
var shell = require('shelljs');
var spawn = require('cordova-common').superspawn.spawn;
var CordovaError = require('cordova-common').CordovaError;
var check_reqs = require('../check_reqs');
var GenericBuilder = require('./GenericBuilder');
var MARKER = 'YOUR CHANGES WILL BE ERASED!';
var SIGNING_PROPERTIES = '-signing.properties';
var TEMPLATE =
'# This file is automatically generated.\n' +
'# Do not modify this file -- ' + MARKER + '\n';
function GradleBuilder (projectRoot) {
GenericBuilder.call(this, projectRoot);
this.binDirs = {gradle: this.binDirs.gradle};
}
util.inherits(GradleBuilder, GenericBuilder);
GradleBuilder.prototype.getArgs = function(cmd, opts) {
if (cmd == 'release') {
cmd = 'cdvBuildRelease';
} else if (cmd == 'debug') {
cmd = 'cdvBuildDebug';
}
var args = [cmd, '-b', path.join(this.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.
GradleBuilder.prototype.prepBuildFiles = function() {
// Update the version of build.gradle in each dependent library.
var pluginBuildGradle = path.join(this.root, 'cordova', 'lib', 'plugin-build.gradle');
var propertiesObj = this.readProjectProperties();
var subProjects = propertiesObj.libs;
for (var i = 0; i < subProjects.length; ++i) {
if (subProjects[i] !== 'CordovaLib') {
shell.cp('-f', pluginBuildGradle, path.join(this.root, subProjects[i], 'build.gradle'));
}
}
var name = this.extractRealProjectNameFromManifest();
//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(this.root, 'settings.gradle'),
'// GENERATED FILE - DO NOT EDIT\n' +
'include ":"\n' + settingsGradlePaths.join(''));
// Update dependencies within build.gradle.
var buildGradle = fs.readFileSync(path.join(this.root, '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 CordovaError('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(this.root, 'build.gradle'), buildGradle);
};
GradleBuilder.prototype.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.
// check_reqs ensures that this is set.
/*jshint -W069 */
var sdkDir = process.env['ANDROID_HOME'];
/*jshint +W069 */
var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper');
if (process.platform == 'win32') {
shell.rm('-f', path.join(self.root, 'gradlew.bat'));
shell.cp(path.join(wrapperDir, 'gradlew.bat'), self.root);
} else {
shell.rm('-f', path.join(self.root, 'gradlew'));
shell.cp(path.join(wrapperDir, 'gradlew'), self.root);
}
shell.rm('-rf', path.join(self.root, 'gradle', 'wrapper'));
shell.mkdir('-p', path.join(self.root, 'gradle'));
shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(self.root, '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/;
/*jshint -W069 */
var distributionUrl = process.env['CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL'] || 'http\\://services.gradle.org/distributions/gradle-2.2.1-all.zip';
/*jshint +W069 */
var gradleWrapperPropertiesPath = path.join(self.root, 'gradle', 'wrapper', 'gradle-wrapper.properties');
shell.chmod('u+w', gradleWrapperPropertiesPath);
shell.sed('-i', distributionUrlRegex, 'distributionUrl='+distributionUrl, gradleWrapperPropertiesPath);
var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
var propertiesFilePath = path.join(self.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.
*/
GradleBuilder.prototype.build = function(opts) {
var wrapper = path.join(this.root, 'gradlew');
var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
return Q().then(function() {
return spawn(wrapper, args, {stdio: 'inherit'});
});
};
GradleBuilder.prototype.clean = function(opts) {
var builder = this;
var wrapper = path.join(this.root, 'gradlew');
var args = builder.getArgs('clean', opts);
return Q().then(function() {
return spawn(wrapper, args, {stdio: 'inherit'});
})
.then(function () {
shell.rm('-rf', path.join(builder.root, 'out'));
['debug', 'release'].forEach(function(config) {
var propertiesFilePath = path.join(builder.root, config + SIGNING_PROPERTIES);
if(isAutoGenerated(propertiesFilePath)){
shell.rm('-f', propertiesFilePath);
}
});
});
};
module.exports = GradleBuilder;
function isAutoGenerated(file) {
return fs.existsSync(file) && fs.readFileSync(file, 'utf8').indexOf(MARKER) > 0;
}

View File

@@ -0,0 +1,47 @@
/*
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 CordovaError = require('cordova-common').CordovaError;
var knownBuilders = {
ant: 'AntBuilder',
gradle: 'GradleBuilder',
none: 'GenericBuilder'
};
/**
* Helper method that instantiates and returns a builder for specified build
* type.
*
* @param {String} builderType Builder name to construct and return. Must
* be one of 'ant', 'gradle' or 'none'
*
* @return {Builder} A builder instance for specified build type.
*/
module.exports.getBuilder = function (builderType, projectRoot) {
if (!knownBuilders[builderType])
throw new CordovaError('Builder ' + builderType + ' is not supported.');
try {
var Builder = require('./' + knownBuilders[builderType]);
return new Builder(projectRoot);
} catch (err) {
throw new CordovaError('Failed to instantiate ' + knownBuilders[builderType] + ' builder: ' + err);
}
};

View File

@@ -19,40 +19,30 @@
under the License.
*/
var exec = require('./exec'),
Q = require('q'),
os = require('os'),
build = require('./build'),
appinfo = require('./appinfo');
var Q = require('q'),
build = require('./build');
var path = require('path');
var Adb = require('./Adb');
var AndroidManifest = require('./AndroidManifest');
var spawn = require('cordova-common').superspawn.spawn;
var CordovaError = require('cordova-common').CordovaError;
var events = require('cordova-common').events;
/**
* 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()
return Adb.devices()
.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')
return spawn('killall', ['adb'])
.then(function() {
console.log('Restarting adb to see if more devices are detected.');
return helper();
events.emit('verbose', 'Restarting adb to see if more devices are detected.');
return Adb.devices();
}, function() {
// For non-killall OS's.
return list;
@@ -66,7 +56,7 @@ 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.');
return Q.reject(new CordovaError('Failed to deploy to device, no devices found.'));
}
// default device
target = target || device_list[0];
@@ -95,27 +85,22 @@ module.exports.install = function(target, buildResults) {
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); })
var manifest = new AndroidManifest(path.join(__dirname, '../../AndroidManifest.xml'));
var pkgName = manifest.getPackageId();
var launchName = pkgName + '/.' + manifest.getActivity().getName();
events.emit('log', 'Using apk: ' + apk_path);
// This promise is always resolved, even if 'adb uninstall' fails to uninstall app
// or the app doesn't installed at all, so no error catching needed.
return Adb.uninstall(resolvedTarget.target, pkgName)
.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());
return Adb.install(resolvedTarget.target, apk_path, {replace: true});
}).then(function() {
console.log('LAUNCH SUCCESS');
}, function(err) {
return Q.reject('ERROR: Failed to launch application on device: ' + err);
//unlock screen
return Adb.shell(resolvedTarget.target, 'input keyevent 82');
}).then(function() {
return Adb.start(resolvedTarget.target, launchName);
}).then(function() {
events.emit('log', 'LAUNCH SUCCESS');
});
});
};

View File

@@ -21,13 +21,26 @@
/* jshint sub:true */
var exec = require('./exec'),
Q = require('q'),
os = require('os'),
appinfo = require('./appinfo'),
build = require('./build'),
child_process = require('child_process');
var retry = require('./retry');
var build = require('./build');
var check_reqs = require('./check_reqs');
var path = require('path');
var Adb = require('./Adb');
var AndroidManifest = require('./AndroidManifest');
var events = require('cordova-common').events;
var spawn = require('cordova-common').superspawn.spawn;
var CordovaError = require('cordova-common').CordovaError;
var Q = require('q');
var os = require('os');
var child_process = require('child_process');
// constants
var ONE_SECOND = 1000; // in milliseconds
var ONE_MINUTE = 60 * ONE_SECOND; // in milliseconds
var INSTALL_COMMAND_TIMEOUT = 5 * ONE_MINUTE; // 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
@@ -40,7 +53,7 @@ var check_reqs = require('./check_reqs');
}
*/
module.exports.list_images = function() {
return exec('android list avds')
return spawn('android', ['list', 'avds'])
.then(function(output) {
var response = output.split('\n');
var emulator_list = [];
@@ -84,11 +97,14 @@ module.exports.list_images = function() {
* Returns a promise.
*/
module.exports.best_image = function() {
var project_target = check_reqs.get_target().replace('android-', '');
return this.list_images()
.then(function(images) {
// Just return undefined if there is no images
if (images.length === 0) return;
var closest = 9999;
var best = images[0];
var project_target = check_reqs.get_target().replace('android-', '');
for (var i in images) {
var target = images[i].target;
if(target) {
@@ -107,22 +123,12 @@ module.exports.best_image = function() {
// 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;
});
return Adb.devices({emulators: true});
};
// Returns a promise.
module.exports.list_targets = function() {
return exec('android list targets', os.tmpdir())
return spawn('android', ['list', 'targets'], {cwd: os.tmpdir()})
.then(function(output) {
var target_out = output.split('\n');
var targets = [];
@@ -138,101 +144,96 @@ module.exports.list_targets = function() {
/*
* 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 ID is given it will use 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];
}
return Q().then(function() {
if (emulator_ID) return Q(emulator_ID);
return self.best_image()
.then(function(best) {
if (best && best.name) {
events.emit('warn', 'No emulator specified, defaulting to ' + best.name);
return best.name;
}
} else {
emulator_id = new_started[0];
}
if (!emulator_id) return Q.reject('ERROR : Failed to start emulator, could not find new emulator');
var androidCmd = check_reqs.getAbsoluteAndroidCmd();
return Q.reject(new CordovaError('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'));
});
}).then(function(emulatorId) {
var uuid = 'cordova_emulator_' + new Date().getTime();
var uuidProp = 'emu.uuid=' + uuid;
var args = ['-avd', emulatorId, '-prop', uuidProp];
// Don't wait for it to finish, since the emulator will probably keep running for a long time.
child_process
.spawn('emulator', args, { stdio: 'inherit', detached: true })
.unref();
// wait for emulator to start
events.emit('log', 'Waiting for emulator...');
return self.wait_for_emulator(uuid);
}).then(function(emulatorId) {
if (!emulatorId)
return Q.reject(new CordovaError('Failed to start 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;
return self.wait_for_boot(emulatorId)
.then(function() {
events.emit('log','BOOT COMPLETE');
//unlock screen
return Adb.shell(emulatorId, 'input keyevent 82');
}).then(function() {
//return the new emulator id for the started emulators
return emulatorId;
});
});
};
/*
* Waits for the new emulator to apear on the started-emulator list.
* Returns a promise with a list of newly started emulators' IDs.
* Waits for an emulator with given uuid to apear on the started-emulator list.
* Returns a promise with this emulator's ID.
*/
module.exports.wait_for_emulator = function(num_running) {
module.exports.wait_for_emulator = function(uuid) {
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);
});
}
});
var emulator_id = null;
var promises = [];
new_started.forEach(function (emulator) {
promises.push(
Adb.shell(emulator, 'getprop emu.uuid')
.then(function (output) {
if (output.indexOf(uuid) >= 0) {
emulator_id = emulator;
}
})
);
});
return Q.all(promises).then(function () {
return emulator_id || self.wait_for_emulator(uuid);
});
});
};
/*
* Waits for the boot animation property of the emulator to switch to 'stopped'
* Waits for the core android process of the emulator to start
*/
module.exports.wait_for_boot = function(emulator_id) {
var self = this;
return exec('adb -s ' + emulator_id + ' shell getprop init.svc.bootanim', os.tmpdir())
return Adb.shell(emulator_id, 'ps')
.then(function(output) {
if (output.match(/stopped/)) {
if (output.match(/android\.process\.acore/)) {
return;
} else {
process.stdout.write('.');
@@ -251,7 +252,7 @@ module.exports.wait_for_boot = function(emulator_id) {
module.exports.create_image = function(name, target) {
console.log('Creating avd named ' + name);
if (target) {
return exec('android create avd --name ' + name + ' --target ' + target)
return spawn('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 + '?');
@@ -259,7 +260,7 @@ module.exports.create_image = function(name, target) {
});
} 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])
return spawn('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.');
@@ -298,37 +299,74 @@ module.exports.resolveTarget = function(target) {
* If no started emulators are found, error out.
* Returns a promise.
*/
module.exports.install = function(target, buildResults) {
return Q().then(function() {
if (target && typeof target == 'object') {
return target;
module.exports.install = function(givenTarget, buildResults) {
var target;
var manifest = new AndroidManifest(path.join(__dirname, '../../AndroidManifest.xml'));
var pkgName = manifest.getPackageId();
// resolve the target emulator
return Q().then(function () {
if (givenTarget && typeof givenTarget == 'object') {
return givenTarget;
} else {
return module.exports.resolveTarget(givenTarget);
}
return module.exports.resolveTarget(target);
}).then(function(resolvedTarget) {
var apk_path = build.findBestApkForArchitecture(buildResults, resolvedTarget.arch);
console.log('Installing app on emulator...');
console.log('Using apk: ' + apk_path);
return exec('adb -s ' + resolvedTarget.target + ' install -r "' + apk_path + '"', os.tmpdir())
.then(function(output) {
if (output.match(/Failure/)) {
return Q.reject('Failed to install apk to emulator: ' + output);
// set the resolved target
}).then(function (resolvedTarget) {
target = resolvedTarget;
// install the app
}).then(function () {
// This promise is always resolved, even if 'adb uninstall' fails to uninstall app
// or the app doesn't installed at all, so no error catching needed.
return Adb.uninstall(target.target, pkgName)
.then(function() {
var apk_path = build.findBestApkForArchitecture(buildResults, target.arch);
var execOptions = {
cwd: os.tmpdir(),
timeout: INSTALL_COMMAND_TIMEOUT, // in milliseconds
killSignal: EXEC_KILL_SIGNAL
};
events.emit('log', 'Using apk: ' + apk_path);
events.emit('verbose', 'Installing app on emulator...');
function exec(command, opts) {
return Q.promise(function (resolve, reject) {
child_process.exec(command, opts, function(err, stdout, stderr) {
if (err) reject(new CordovaError('Error executing "' + command + '": ' + stderr));
else resolve(stdout);
});
});
}
return Q();
}, function(err) {
return Q.reject('Failed to install apk to emulator: ' + err);
}).then(function() {
//unlock screen
return exec('adb -s ' + resolvedTarget.target + ' shell input keyevent 82', os.tmpdir());
}).then(function() {
// launch the application
console.log('Launching application...');
var launchName = appinfo.getActivityName();
var cmd = 'adb -s ' + resolvedTarget.target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
return exec(cmd, os.tmpdir());
}).then(function(output) {
console.log('LAUNCH SUCCESS');
}, function(err) {
return Q.reject('Failed to launch app on emulator: ' + err);
var retriedInstall = retry.retryPromise(
NUM_INSTALL_RETRIES,
exec, 'adb -s ' + target.target + ' install -r "' + apk_path + '"', execOptions
);
return retriedInstall.then(function (output) {
if (output.match(/Failure/)) {
return Q.reject(new CordovaError('Failed to install apk to emulator: ' + output));
} else {
events.emit('log', 'INSTALL SUCCESS');
}
}, function (err) {
return Q.reject(new CordovaError('Failed to install apk to emulator: ' + err));
});
});
// unlock screen
}).then(function () {
events.emit('verbose', 'Unlocking screen...');
return Adb.shell(target.target, 'input keyevent 82');
}).then(function () {
Adb.start(target.target, pkgName + '/.' + manifest.getActivity().getName());
// report success or failure
}).then(function (output) {
events.emit('log', 'LAUNCH SUCCESS');
});
};

View File

@@ -1,41 +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 child_process = require('child_process'),
Q = require('q');
// 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.
module.exports = function(cmd, opt_cwd) {
var d = Q.defer();
try {
child_process.exec(cmd, {cwd: opt_cwd, maxBuffer: 1024000}, 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;
};

View File

@@ -0,0 +1,252 @@
/*
*
* Copyright 2013 Anis Kadri
*
* Licensed 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 unused: vars */
var fs = require('fs');
var path = require('path');
var shell = require('shelljs');
var events = require('cordova-common').events;
var CordovaError = require('cordova-common').CordovaError;
var handlers = {
'source-file':{
install:function(obj, plugin, project, options) {
if (!obj.src) throw new CordovaError('<source-file> element is missing "src" attribute for plugin: ' + plugin.id);
if (!obj.targetDir) throw new CordovaError('<source-file> element is missing "target-dir" attribute for plugin: ' + plugin.id);
var dest = path.join(obj.targetDir, path.basename(obj.src));
copyNewFile(plugin.dir, obj.src, project.projectDir, dest, options && options.link);
},
uninstall:function(obj, plugin, project, options) {
var dest = path.join(obj.targetDir, path.basename(obj.src));
deleteJava(project.projectDir, dest);
}
},
'lib-file':{
install:function(obj, plugin, project, options) {
var dest = path.join('libs', path.basename(obj.src));
copyFile(plugin.dir, obj.src, project.projectDir, dest, options && options.link);
},
uninstall:function(obj, plugin, project, options) {
var dest = path.join('libs', path.basename(obj.src));
removeFile(project.projectDir, dest);
}
},
'resource-file':{
install:function(obj, plugin, project, options) {
copyFile(plugin.dir, obj.src, project.projectDir, path.normalize(obj.target), options && options.link);
},
uninstall:function(obj, plugin, project, options) {
removeFile(project.projectDir, path.normalize(obj.target));
}
},
'framework': {
install:function(obj, plugin, project, options) {
var src = obj.src;
if (!src) throw new CordovaError('src not specified in <framework> for plugin: ' + plugin.id);
events.emit('verbose', 'Installing Android library: ' + src);
var parentDir = obj.parent ? path.resolve(project.projectDir, obj.parent) : project.projectDir;
var subDir;
if (obj.custom) {
var subRelativeDir = project.getCustomSubprojectRelativeDir(plugin.id, src);
copyNewFile(plugin.dir, src, project.projectDir, subRelativeDir, options && options.link);
subDir = path.resolve(project.projectDir, subRelativeDir);
} else {
obj.type = 'sys';
subDir = src;
}
if (obj.type == 'gradleReference') {
project.addGradleReference(parentDir, subDir);
} else if (obj.type == 'sys') {
project.addSystemLibrary(parentDir, subDir);
} else {
project.addSubProject(parentDir, subDir);
}
},
uninstall:function(obj, plugin, project, options) {
var src = obj.src;
if (!src) throw new CordovaError('src not specified in <framework> for plugin: ' + plugin.id);
events.emit('verbose', 'Uninstalling Android library: ' + src);
var parentDir = obj.parent ? path.resolve(project.projectDir, obj.parent) : project.projectDir;
var subDir;
if (obj.custom) {
var subRelativeDir = project.getCustomSubprojectRelativeDir(plugin.id, src);
removeFile(project.projectDir, subRelativeDir);
subDir = path.resolve(project.projectDir, subRelativeDir);
// If it's the last framework in the plugin, remove the parent directory.
var parDir = path.dirname(subDir);
if (fs.readdirSync(parDir).length === 0) {
fs.rmdirSync(parDir);
}
} else {
obj.type = 'sys';
subDir = src;
}
if (obj.type == 'gradleReference') {
project.removeGradleReference(parentDir, subDir);
} else if (obj.type == 'sys') {
project.removeSystemLibrary(parentDir, subDir);
} else {
project.removeSubProject(parentDir, subDir);
}
}
},
asset:{
install:function(obj, plugin, project, options) {
if (!obj.src) {
throw new CordovaError('<asset> tag without required "src" attribute. plugin=' + plugin.dir);
}
if (!obj.target) {
throw new CordovaError('<asset> tag without required "target" attribute');
}
var www = options.usePlatformWww ? project.platformWww : project.www;
copyFile(plugin.dir, obj.src, www, obj.target);
},
uninstall:function(obj, plugin, project, options) {
var target = obj.target || obj.src;
if (!target) throw new CordovaError('<asset> tag without required "target" attribute');
var www = options.usePlatformWww ? project.platformWww : project.www;
removeFile(www, target);
removeFileF(path.resolve(www, 'plugins', plugin.id));
}
},
'js-module': {
install: function (obj, plugin, project, options) {
// Copy the plugin's files into the www directory.
var moduleSource = path.resolve(plugin.dir, obj.src);
var moduleName = plugin.id + '.' + (obj.name || path.parse(obj.src).name);
// Read in the file, prepend the cordova.define, and write it back out.
var scriptContent = fs.readFileSync(moduleSource, 'utf-8').replace(/^\ufeff/, ''); // Window BOM
if (moduleSource.match(/.*\.json$/)) {
scriptContent = 'module.exports = ' + scriptContent;
}
scriptContent = 'cordova.define("' + moduleName + '", function(require, exports, module) {\n' + scriptContent + '\n});\n';
var www = options.usePlatformWww ? project.platformWww : project.www;
var moduleDestination = path.resolve(www, 'plugins', plugin.id, obj.src);
shell.mkdir('-p', path.dirname(moduleDestination));
fs.writeFileSync(moduleDestination, scriptContent, 'utf-8');
},
uninstall: function (obj, plugin, project, options) {
var pluginRelativePath = path.join('plugins', plugin.id, obj.src);
var www = options.usePlatformWww ? project.platformWww : project.www;
removeFileAndParents(www, pluginRelativePath);
}
}
};
module.exports.getInstaller = function (type) {
if (handlers[type] && handlers[type].install) {
return handlers[type].install;
}
events.emit('verbose', '<' + type + '> is not supported for android plugins');
};
module.exports.getUninstaller = function(type) {
if (handlers[type] && handlers[type].uninstall) {
return handlers[type].uninstall;
}
events.emit('verbose', '<' + type + '> is not supported for android plugins');
};
function copyFile (plugin_dir, src, project_dir, dest, link) {
src = path.resolve(plugin_dir, src);
if (!fs.existsSync(src)) throw new CordovaError('"' + src + '" not found!');
// check that src path is inside plugin directory
var real_path = fs.realpathSync(src);
var real_plugin_path = fs.realpathSync(plugin_dir);
if (real_path.indexOf(real_plugin_path) !== 0)
throw new CordovaError('"' + src + '" not located within plugin!');
dest = path.resolve(project_dir, dest);
// check that dest path is located in project directory
if (dest.indexOf(project_dir) !== 0)
throw new CordovaError('"' + dest + '" not located within project!');
shell.mkdir('-p', path.dirname(dest));
if (link) {
fs.symlinkSync(path.relative(path.dirname(dest), src), dest);
} else if (fs.statSync(src).isDirectory()) {
// XXX shelljs decides to create a directory when -R|-r is used which sucks. http://goo.gl/nbsjq
shell.cp('-Rf', src+'/*', dest);
} else {
shell.cp('-f', src, dest);
}
}
// Same as copy file but throws error if target exists
function copyNewFile (plugin_dir, src, project_dir, dest, link) {
var target_path = path.resolve(project_dir, dest);
if (fs.existsSync(target_path))
throw new CordovaError('"' + target_path + '" already exists!');
copyFile(plugin_dir, src, project_dir, dest, !!link);
}
// checks if file exists and then deletes. Error if doesn't exist
function removeFile (project_dir, src) {
var file = path.resolve(project_dir, src);
shell.rm('-Rf', file);
}
// deletes file/directory without checking
function removeFileF (file) {
shell.rm('-Rf', file);
}
// Sometimes we want to remove some java, and prune any unnecessary empty directories
function deleteJava (project_dir, destFile) {
removeFileAndParents(project_dir, destFile, 'src');
}
function removeFileAndParents (baseDir, destFile, stopper) {
stopper = stopper || '.';
var file = path.resolve(baseDir, destFile);
if (!fs.existsSync(file)) return;
removeFileF(file);
// check if directory is empty
var curDir = path.dirname(file);
while(curDir !== path.resolve(baseDir, stopper)) {
if(fs.existsSync(curDir) && fs.readdirSync(curDir).length === 0) {
fs.rmdirSync(curDir);
curDir = path.resolve(curDir, '..');
} else {
// directory not empty...do nothing
break;
}
}
}

379
bin/templates/cordova/lib/prepare.js vendored Normal file
View File

@@ -0,0 +1,379 @@
/**
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 Q = require('q');
var fs = require('fs');
var path = require('path');
var shell = require('shelljs');
var events = require('cordova-common').events;
var AndroidManifest = require('./AndroidManifest');
var xmlHelpers = require('cordova-common').xmlHelpers;
var CordovaError = require('cordova-common').CordovaError;
var ConfigParser = require('cordova-common').ConfigParser;
module.exports.prepare = function (cordovaProject) {
var self = this;
this._config = updateConfigFilesFrom(cordovaProject.projectConfig,
this._munger, this.locations);
// Update own www dir with project's www assets and plugins' assets and js-files
return Q.when(updateWwwFrom(cordovaProject, this.locations))
.then(function () {
// update project according to config.xml changes.
return updateProjectAccordingTo(self._config, self.locations);
})
.then(function () {
handleIcons(cordovaProject.projectConfig, self.root);
handleSplashes(cordovaProject.projectConfig, self.root);
})
.then(function () {
self.events.emit('verbose', 'updated project successfully');
});
};
/**
* Updates config files in project based on app's config.xml and config munge,
* generated by plugins.
*
* @param {ConfigParser} sourceConfig A project's configuration that will
* be merged into platform's config.xml
* @param {ConfigChanges} configMunger An initialized ConfigChanges instance
* for this platform.
* @param {Object} locations A map of locations for this platform
*
* @return {ConfigParser} An instance of ConfigParser, that
* represents current project's configuration. When returned, the
* configuration is already dumped to appropriate config.xml file.
*/
function updateConfigFilesFrom(sourceConfig, configMunger, locations) {
events.emit('verbose', 'Generating config.xml from defaults for platform "android"');
// First cleanup current config and merge project's one into own
// Overwrite platform config.xml with defaults.xml.
shell.cp('-f', locations.defaultConfigXml, locations.configXml);
// Then apply config changes from global munge to all config files
// in project (including project's config)
configMunger.reapply_global_munge().save_all();
// Merge changes from app's config.xml into platform's one
var config = new ConfigParser(locations.configXml);
xmlHelpers.mergeXml(sourceConfig.doc.getroot(),
config.doc.getroot(), 'android', /*clobber=*/true);
config.write();
return config;
}
/**
* Updates platform 'www' directory by replacing it with contents of
* 'platform_www' and app www. Also copies project's overrides' folder into
* the platform 'www' folder
*
* @param {Object} cordovaProject An object which describes cordova project.
* @param {Object} destinations An object that contains destination
* paths for www files.
*/
function updateWwwFrom(cordovaProject, destinations) {
shell.rm('-rf', destinations.www);
shell.mkdir('-p', destinations.www);
// Copy source files from project's www directory
shell.cp('-rf', path.join(cordovaProject.locations.www, '*'), destinations.www);
// Override www sources by files in 'platform_www' directory
shell.cp('-rf', path.join(destinations.platformWww, '*'), destinations.www);
// If project contains 'merges' for our platform, use them as another overrides
var merges_path = path.join(cordovaProject.root, 'merges', 'android');
if (fs.existsSync(merges_path)) {
events.emit('verbose', 'Found "merges" for android platform. Copying over existing "www" files.');
var overrides = path.join(merges_path, '*');
shell.cp('-rf', overrides, destinations.www);
}
}
/**
* Updates project structure and AndroidManifest according to project's configuration.
*
* @param {ConfigParser} platformConfig A project's configuration that will
* be used to update project
* @param {Object} locations A map of locations for this platform
*/
function updateProjectAccordingTo(platformConfig, locations) {
// Update app name by editing res/values/strings.xml
var name = platformConfig.name();
var strings = xmlHelpers.parseElementtreeSync(locations.strings);
strings.find('string[@name="app_name"]').text = name;
fs.writeFileSync(locations.strings, strings.write({indent: 4}), 'utf-8');
events.emit('verbose', 'Wrote out Android application name to "' + name + '"');
// Java packages cannot support dashes
var pkg = (platformConfig.android_packageName() || platformConfig.packageName()).replace(/-/g, '_');
var manifest = new AndroidManifest(locations.manifest);
var orig_pkg = manifest.getPackageId();
manifest.getActivity()
.setOrientation(findOrientationValue(platformConfig))
.setLaunchMode(findAndroidLaunchModePreference(platformConfig));
manifest.setVersionName(platformConfig.version())
.setVersionCode(platformConfig.android_versionCode() || default_versionCode(platformConfig.version()))
.setPackageId(pkg)
.setMinSdkVersion(platformConfig.getPreference('android-minSdkVersion', 'android'))
.setMaxSdkVersion(platformConfig.getPreference('android-maxSdkVersion', 'android'))
.setTargetSdkVersion(platformConfig.getPreference('android-targetSdkVersion', 'android'))
.write();
var javaPattern = path.join(locations.root, 'src', orig_pkg.replace(/\./g, '/'), '*.java');
var java_files = shell.ls(javaPattern).filter(function(f) {
return shell.grep(/extends\s+CordovaActivity/g, f);
});
if (java_files.length === 0) {
throw new CordovaError('No Java files found which extend CordovaActivity.');
} else if(java_files.length > 1) {
events.emit('log', 'Multiple candidate Java files (.java files which extend CordovaActivity) found. Guessing at the first one, ' + java_files[0]);
}
var destFile = path.join(locations.root, 'src', pkg.replace(/\./g, '/'), path.basename(java_files[0]));
shell.mkdir('-p', path.dirname(destFile));
shell.sed(/package [\w\.]*;/, 'package ' + pkg + ';', java_files[0]).to(destFile);
events.emit('verbose', 'Wrote out Android package name to "' + pkg + '"');
if (orig_pkg !== pkg) {
// If package was name changed we need to remove old java with main activity
shell.rm('-Rf',java_files[0]);
// remove any empty directories
var currentDir = path.dirname(java_files[0]);
var sourcesRoot = path.resolve(locations.root, 'src');
while(currentDir !== sourcesRoot) {
if(fs.existsSync(currentDir) && fs.readdirSync(currentDir).length === 0) {
fs.rmdirSync(currentDir);
currentDir = path.resolve(currentDir, '..');
} else {
break;
}
}
}
}
// Consturct the default value for versionCode as
// PATCH + MINOR * 100 + MAJOR * 10000
// see http://developer.android.com/tools/publishing/versioning.html
function default_versionCode(version) {
var nums = version.split('-')[0].split('.');
var versionCode = 0;
if (+nums[0]) {
versionCode += +nums[0] * 10000;
}
if (+nums[1]) {
versionCode += +nums[1] * 100;
}
if (+nums[2]) {
versionCode += +nums[2];
}
return versionCode;
}
function copyImage(src, resourcesDir, density, name) {
var destFolder = path.join(resourcesDir, (density ? 'drawable-': 'drawable') + density);
var isNinePatch = !!/\.9\.png$/.exec(src);
var ninePatchName = name.replace(/\.png$/, '.9.png');
// default template does not have default asset for this density
if (!fs.existsSync(destFolder)) {
fs.mkdirSync(destFolder);
}
var destFilePath = path.join(destFolder, isNinePatch ? ninePatchName : name);
events.emit('verbose', 'copying image from ' + src + ' to ' + destFilePath);
shell.cp('-f', src, destFilePath);
}
function handleSplashes(projectConfig, platformRoot) {
var resources = projectConfig.getSplashScreens('android');
// if there are "splash" elements in config.xml
if (resources.length > 0) {
deleteDefaultResourceAt(platformRoot, 'screen.png');
events.emit('verbose', 'splash screens: ' + JSON.stringify(resources));
// The source paths for icons and splashes are relative to
// project's config.xml location, so we use it as base path.
var projectRoot = path.dirname(projectConfig.path);
var destination = path.join(platformRoot, 'res');
var hadMdpi = false;
resources.forEach(function (resource) {
if (!resource.density) {
return;
}
if (resource.density == 'mdpi') {
hadMdpi = true;
}
copyImage(path.join(projectRoot, resource.src), destination, resource.density, 'screen.png');
});
// There's no "default" drawable, so assume default == mdpi.
if (!hadMdpi && resources.defaultResource) {
copyImage(path.join(projectRoot, resources.defaultResource.src), destination, 'mdpi', 'screen.png');
}
}
}
function handleIcons(projectConfig, platformRoot) {
var icons = projectConfig.getIcons('android');
// if there are icon elements in config.xml
if (icons.length === 0) {
events.emit('verbose', 'This app does not have launcher icons defined');
return;
}
deleteDefaultResourceAt(platformRoot, 'icon.png');
var android_icons = {};
var default_icon;
// http://developer.android.com/design/style/iconography.html
var sizeToDensityMap = {
36: 'ldpi',
48: 'mdpi',
72: 'hdpi',
96: 'xhdpi',
144: 'xxhdpi',
192: 'xxxhdpi'
};
// find the best matching icon for a given density or size
// @output android_icons
var parseIcon = function(icon, icon_size) {
// do I have a platform icon for that density already
var density = icon.density || sizeToDensityMap[icon_size];
if (!density) {
// invalid icon defition ( or unsupported size)
return;
}
var previous = android_icons[density];
if (previous && previous.platform) {
return;
}
android_icons[density] = icon;
};
// iterate over all icon elements to find the default icon and call parseIcon
for (var i=0; i<icons.length; i++) {
var icon = icons[i];
var size = icon.width;
if (!size) {
size = icon.height;
}
if (!size && !icon.density) {
if (default_icon) {
events.emit('verbose', 'more than one default icon: ' + JSON.stringify(icon));
} else {
default_icon = icon;
}
} else {
parseIcon(icon, size);
}
}
// The source paths for icons and splashes are relative to
// project's config.xml location, so we use it as base path.
var projectRoot = path.dirname(projectConfig.path);
var destination = path.join(platformRoot, 'res');
for (var density in android_icons) {
copyImage(path.join(projectRoot, android_icons[density].src), destination, density, 'icon.png');
}
// There's no "default" drawable, so assume default == mdpi.
if (default_icon && !android_icons.mdpi) {
copyImage(path.join(projectRoot, default_icon.src), destination, 'mdpi', 'icon.png');
}
}
// remove the default resource name from all drawable folders
function deleteDefaultResourceAt(baseDir, resourceName) {
shell.ls(path.join(baseDir, 'res/drawable-*'))
.forEach(function (drawableFolder) {
var imagePath = path.join(drawableFolder, resourceName);
shell.rm('-f', [imagePath, imagePath.replace(/\.png$/, '.9.png')]);
events.emit('verbose', 'Deleted ' + imagePath);
});
}
/**
* Gets and validates 'AndroidLaunchMode' prepference from config.xml. Returns
* preference value and warns if it doesn't seems to be valid
*
* @param {ConfigParser} platformConfig A configParser instance for
* platform.
*
* @return {String} Preference's value from config.xml or
* default value, if there is no such preference. The default value is
* 'singleTop'
*/
function findAndroidLaunchModePreference(platformConfig) {
var launchMode = platformConfig.getPreference('AndroidLaunchMode');
if (!launchMode) {
// Return a default value
return 'singleTop';
}
var expectedValues = ['standard', 'singleTop', 'singleTask', 'singleInstance'];
var valid = expectedValues.indexOf(launchMode) >= 0;
if (!valid) {
// Note: warn, but leave the launch mode as developer wanted, in case the list of options changes in the future
events.emit('warn', 'Unrecognized value for AndroidLaunchMode preference: ' +
launchMode + '. Expected values are: ' + expectedValues.join(', '));
}
return launchMode;
}
/**
* Queries ConfigParser object for the orientation <preference> value. Warns if
* global preference value is not supported by platform.
*
* @param {Object} platformConfig ConfigParser object
*
* @return {String} Global/platform-specific orientation in lower-case
* (or empty string if both are undefined).
*/
function findOrientationValue(platformConfig) {
var ORIENTATION_DEFAULT = 'default';
var orientation = platformConfig.getPreference('orientation');
if (!orientation) {
return ORIENTATION_DEFAULT;
}
var GLOBAL_ORIENTATIONS = ['default', 'portrait','landscape'];
function isSupported(orientation) {
return GLOBAL_ORIENTATIONS.indexOf(orientation.toLowerCase()) >= 0;
}
// Check if the given global orientation is supported
if (orientation && isSupported(orientation)) {
return orientation;
}
events.emit('warn', 'Unsupported global orientation: ' + orientation +
'. Defaulting to value: ' + ORIENTATION_DEFAULT);
return ORIENTATION_DEFAULT;
}

68
bin/templates/cordova/lib/retry.js vendored Normal file
View File

@@ -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.
*/
/* jshint node: true */
'use strict';
var events = require('cordova-common').events;
/*
* 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;
}
events.emit('verbose', '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);
}
);
};

View File

@@ -25,63 +25,25 @@ 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.
/**
* 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.
*
* @param {Object} runOptions various run/build options. See Api.js build/run
* methods for reference.
*
* @return {Promise}
*/
module.exports.run = function(args) {
var buildFlags = [];
var install_target;
var list = false;
module.exports.run = function(runOptions) {
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).');
}
}
var self = this;
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;
}
var install_target = runOptions.device ? '--device' :
runOptions.emulator ? '--emulator' :
runOptions.target;
return Q()
.then(function() {
@@ -90,10 +52,10 @@ var path = require('path'),
return device.list()
.then(function(device_list) {
if (device_list.length > 0) {
console.log('WARNING : No target specified, deploying to device \'' + device_list[0] + '\'.');
self.events.emit('warn', 'No target specified, deploying to device \'' + device_list[0] + '\'.');
install_target = device_list[0];
} else {
console.log('WARNING : No target specified, deploying to emulator');
self.events.emit('warn', 'No target specified, deploying to emulator');
install_target = '--emulator';
}
});
@@ -137,9 +99,17 @@ var path = require('path'),
});
});
}).then(function(resolvedTarget) {
return build.run(buildFlags, resolvedTarget).then(function(buildResults) {
// Better just call self.build, but we're doing some processing of
// build results (according to platformApi spec) so they are in different
// format than emulator.install expects.
// TODO: Update emulator/device.install to handle this change
return build.run.call(self, runOptions, resolvedTarget)
.then(function(buildResults) {
if (resolvedTarget.isEmulator) {
return emulator.install(resolvedTarget, buildResults);
return emulator.wait_for_boot(resolvedTarget.target)
.then(function () {
return emulator.install(resolvedTarget, buildResults);
});
}
return device.install(resolvedTarget, buildResults);
});

View File

@@ -1,50 +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 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;
};

View File

@@ -19,19 +19,33 @@
under the License.
*/
var run = require('./lib/run'),
reqs = require('./lib/check_reqs'),
args = process.argv;
var Api = require('./Api');
var nopt = require('nopt');
var path = require('path');
// Support basic help commands
if (args[2] == '--help' || args[2] == '/?' || args[2] == '-h' ||
args[2] == 'help' || args[2] == '-help' || args[2] == '/help') {
run.help(args);
} else {
reqs.run().done(function() {
return run.run(args);
}, function(err) {
console.error('ERROR: ' + err);
process.exit(2);
});
}
if(['--help', '/?', '-h', 'help', '-help', '/help'].indexOf(process.argv[2]) >= 0)
require('./lib/run').help();
// Do some basic argument parsing
var runOpts = nopt({
'verbose' : Boolean,
'silent' : Boolean,
'debug' : Boolean,
'release' : Boolean,
'nobuild': Boolean,
'buildConfig' : path,
'archs' : String,
'device' : Boolean,
'emulator': Boolean,
'target' : String
}, { 'd' : '--verbose' });
// Make runOptions compatible with PlatformApi run method spec
runOpts.argv = runOpts.argv.remain;
new Api().run(runOpts)
.catch(function(err) {
console.error(err, err.stack);
process.exit(2);
});

View File

@@ -20,6 +20,10 @@
*/
// Coho updates this line:
var VERSION = "4.0.0-dev";
var VERSION = "5.0.0";
console.log(VERSION);
module.exports.version = VERSION;
if (!module.parent) {
console.log(VERSION);
}

View File

@@ -29,8 +29,6 @@
/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<application android:icon="@drawable/icon" android:label="@string/app_name"
android:hardwareAccelerated="true" android:supportsRtl="true">
@@ -47,5 +45,5 @@
</activity>
</application>
<uses-sdk android:minSdkVersion="10" android:targetSdkVersion="__APILEVEL__"/>
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="__APILEVEL__"/>
</manifest>

View File

@@ -1,5 +1,5 @@
// Platform: android
// fc4db9145934bd0053161cbf9ffc0caf83b770c6
// 6b83950ffdd6b84977dfae49d8147ef640b8097f
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
@@ -19,7 +19,7 @@
under the License.
*/
;(function() {
var PLATFORM_VERSION_BUILD_LABEL = '4.0.0-dev';
var PLATFORM_VERSION_BUILD_LABEL = '5.0.0';
// file: src/scripts/require.js
/*jshint -W079 */
@@ -101,10 +101,17 @@ if (typeof module === "object" && typeof require === "function") {
// file: src/cordova.js
define("cordova", function(require, exports, module) {
// Workaround for Windows 10 in hosted environment case
// http://www.w3.org/html/wg/drafts/html/master/browsers.html#named-access-on-the-window-object
if (window.cordova && !(window.cordova instanceof HTMLElement)) {
throw new Error("cordova already defined");
}
var channel = require('cordova/channel');
var platform = require('cordova/platform');
/**
* Intercept calls to addEventListener + removeEventListener and handle deviceready,
* resume, and pause events.
@@ -323,7 +330,7 @@ module.exports = cordova;
});
// file: src/android/android/nativeapiprovider.js
// file: /Users/steveng/repo/cordova/cordova-android/cordova-js-src/android/nativeapiprovider.js
define("cordova/android/nativeapiprovider", function(require, exports, module) {
/**
@@ -346,7 +353,7 @@ module.exports = {
});
// file: src/android/android/promptbasednativeapi.js
// file: /Users/steveng/repo/cordova/cordova-android/cordova-js-src/android/promptbasednativeapi.js
define("cordova/android/promptbasednativeapi", function(require, exports, module) {
/**
@@ -371,7 +378,6 @@ module.exports = {
// file: src/common/argscheck.js
define("cordova/argscheck", function(require, exports, module) {
var exec = require('cordova/exec');
var utils = require('cordova/utils');
var moduleExports = module.exports;
@@ -856,7 +862,7 @@ module.exports = channel;
});
// file: src/android/exec.js
// file: /Users/steveng/repo/cordova/cordova-android/cordova-js-src/exec.js
define("cordova/exec", function(require, exports, module) {
/**
@@ -891,11 +897,7 @@ var cordova = require('cordova'),
// For the ONLINE_EVENT to be viable, it would need to intercept all event
// listeners (both through addEventListener and window.ononline) as well
// as set the navigator property itself.
ONLINE_EVENT: 2,
// Uses reflection to access private APIs of the WebView that can send JS
// to be executed.
// Requires Android 3.2.4 or above.
PRIVATE_API: 3
ONLINE_EVENT: 2
},
jsToNativeBridgeMode, // Set lazily.
nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT,
@@ -1229,6 +1231,7 @@ if (!window.console.warn) {
// Register pause, resume and deviceready channels as events on document.
channel.onPause = cordova.addDocumentEventHandler('pause');
channel.onResume = cordova.addDocumentEventHandler('resume');
channel.onActivated = cordova.addDocumentEventHandler('activated');
channel.onDeviceReady = cordova.addStickyDocumentEventHandler('deviceready');
// Listen for DOMContentLoaded and notify our channel subscribers.
@@ -1290,10 +1293,12 @@ define("cordova/init_b", function(require, exports, module) {
var channel = require('cordova/channel');
var cordova = require('cordova');
var modulemapper = require('cordova/modulemapper');
var platform = require('cordova/platform');
var pluginloader = require('cordova/pluginloader');
var utils = require('cordova/utils');
var platformInitChannelsArray = [channel.onDOMContentLoaded, channel.onNativeReady];
var platformInitChannelsArray = [channel.onDOMContentLoaded, channel.onNativeReady, channel.onPluginsReady];
// setting exec
cordova.exec = require('cordova/exec');
@@ -1356,6 +1361,7 @@ if (!window.console.warn) {
// Register pause, resume and deviceready channels as events on document.
channel.onPause = cordova.addDocumentEventHandler('pause');
channel.onResume = cordova.addDocumentEventHandler('resume');
channel.onActivated = cordova.addDocumentEventHandler('activated');
channel.onDeviceReady = cordova.addStickyDocumentEventHandler('deviceready');
// Listen for DOMContentLoaded and notify our channel subscribers.
@@ -1377,10 +1383,19 @@ if (window._nativeReady) {
// Call the platform-specific initialization.
platform.bootstrap && platform.bootstrap();
// Wrap in a setTimeout to support the use-case of having plugin JS appended to cordova.js.
// The delay allows the attached modules to be defined before the plugin loader looks for them.
setTimeout(function() {
pluginloader.load(function() {
channel.onPluginsReady.fire();
});
}, 0);
/**
* Create all cordova objects once native side is ready.
*/
channel.join(function() {
modulemapper.mapModules(window);
platform.initialize && platform.initialize();
@@ -1499,7 +1514,104 @@ exports.reset();
});
// file: src/android/platform.js
// file: src/common/modulemapper_b.js
define("cordova/modulemapper_b", function(require, exports, module) {
var builder = require('cordova/builder'),
symbolList = [],
deprecationMap;
exports.reset = function() {
symbolList = [];
deprecationMap = {};
};
function addEntry(strategy, moduleName, symbolPath, opt_deprecationMessage) {
symbolList.push(strategy, moduleName, symbolPath);
if (opt_deprecationMessage) {
deprecationMap[symbolPath] = opt_deprecationMessage;
}
}
// Note: Android 2.3 does have Function.bind().
exports.clobbers = function(moduleName, symbolPath, opt_deprecationMessage) {
addEntry('c', moduleName, symbolPath, opt_deprecationMessage);
};
exports.merges = function(moduleName, symbolPath, opt_deprecationMessage) {
addEntry('m', moduleName, symbolPath, opt_deprecationMessage);
};
exports.defaults = function(moduleName, symbolPath, opt_deprecationMessage) {
addEntry('d', moduleName, symbolPath, opt_deprecationMessage);
};
exports.runs = function(moduleName) {
addEntry('r', moduleName, null);
};
function prepareNamespace(symbolPath, context) {
if (!symbolPath) {
return context;
}
var parts = symbolPath.split('.');
var cur = context;
for (var i = 0, part; part = parts[i]; ++i) {
cur = cur[part] = cur[part] || {};
}
return cur;
}
exports.mapModules = function(context) {
var origSymbols = {};
context.CDV_origSymbols = origSymbols;
for (var i = 0, len = symbolList.length; i < len; i += 3) {
var strategy = symbolList[i];
var moduleName = symbolList[i + 1];
var module = require(moduleName);
// <runs/>
if (strategy == 'r') {
continue;
}
var symbolPath = symbolList[i + 2];
var lastDot = symbolPath.lastIndexOf('.');
var namespace = symbolPath.substr(0, lastDot);
var lastName = symbolPath.substr(lastDot + 1);
var deprecationMsg = symbolPath in deprecationMap ? 'Access made to deprecated symbol: ' + symbolPath + '. ' + deprecationMsg : null;
var parentObj = prepareNamespace(namespace, context);
var target = parentObj[lastName];
if (strategy == 'm' && target) {
builder.recursiveMerge(target, module);
} else if ((strategy == 'd' && !target) || (strategy != 'd')) {
if (!(symbolPath in origSymbols)) {
origSymbols[symbolPath] = target;
}
builder.assignOrWrapInDeprecateGetter(parentObj, lastName, module, deprecationMsg);
}
}
};
exports.getOriginalSymbol = function(context, symbolPath) {
var origSymbols = context.CDV_origSymbols;
if (origSymbols && (symbolPath in origSymbols)) {
return origSymbols[symbolPath];
}
var parts = symbolPath.split('.');
var obj = context;
for (var i = 0; i < parts.length; ++i) {
obj = obj && obj[parts[i]];
}
return obj;
};
exports.reset();
});
// file: /Users/steveng/repo/cordova/cordova-android/cordova-js-src/platform.js
define("cordova/platform", function(require, exports, module) {
module.exports = {
@@ -1575,7 +1687,7 @@ function onMessageFromNative(msg) {
});
// file: src/android/plugin/android/app.js
// file: /Users/steveng/repo/cordova/cordova-android/cordova-js-src/plugin/android/app.js
define("cordova/plugin/android/app", function(require, exports, module) {
var exec = require('cordova/exec');
@@ -1778,6 +1890,54 @@ exports.load = function(callback) {
};
});
// file: src/common/pluginloader_b.js
define("cordova/pluginloader_b", function(require, exports, module) {
var modulemapper = require('cordova/modulemapper');
// Handler for the cordova_plugins.js content.
// See plugman's plugin_loader.js for the details of this object.
function handlePluginsObject(moduleList) {
// if moduleList is not defined or empty, we've nothing to do
if (!moduleList || !moduleList.length) {
return;
}
// Loop through all the modules and then through their clobbers and merges.
for (var i = 0, module; module = moduleList[i]; i++) {
if (module.clobbers && module.clobbers.length) {
for (var j = 0; j < module.clobbers.length; j++) {
modulemapper.clobbers(module.id, module.clobbers[j]);
}
}
if (module.merges && module.merges.length) {
for (var k = 0; k < module.merges.length; k++) {
modulemapper.merges(module.id, module.merges[k]);
}
}
// Finally, if runs is truthy we want to simply require() the module.
if (module.runs) {
modulemapper.runs(module.id);
}
}
}
// Loads all plugins' js-modules. Plugin loading is syncronous in browserified bundle
// but the method accepts callback to be compatible with non-browserify flow.
// onDeviceReady is blocked on onPluginsReady. onPluginsReady is fired when there are
// no plugins to load, or they are all done.
exports.load = function(callback) {
var moduleList = require("cordova/plugin_list");
handlePluginsObject(moduleList);
callback();
};
});
// file: src/common/urlutil.js
@@ -1859,15 +2019,14 @@ utils.typeName = function(val) {
/**
* Returns an indication of whether the argument is an array or not
*/
utils.isArray = function(a) {
return utils.typeName(a) == 'Array';
};
utils.isArray = Array.isArray ||
function(a) {return utils.typeName(a) == 'Array';};
/**
* Returns an indication of whether the argument is a Date or not
*/
utils.isDate = function(d) {
return utils.typeName(d) == 'Date';
return (d instanceof Date);
};
/**
@@ -1901,17 +2060,25 @@ utils.clone = function(obj) {
* Returns a wrapped version of the function
*/
utils.close = function(context, func, params) {
if (typeof params == 'undefined') {
return function() {
return func.apply(context, arguments);
};
} else {
return function() {
return func.apply(context, params);
};
}
return function() {
var args = params || arguments;
return func.apply(context, args);
};
};
//------------------------------------------------------------------------------
function UUIDcreatePart(length) {
var uuidpart = "";
for (var i=0; i<length; i++) {
var uuidchar = parseInt((Math.random() * 256), 10).toString(16);
if (uuidchar.length == 1) {
uuidchar = "0" + uuidchar;
}
uuidpart += uuidchar;
}
return uuidpart;
}
/**
* Create a UUID
*/
@@ -1923,6 +2090,7 @@ utils.createUUID = function() {
UUIDcreatePart(6);
};
/**
* Extends a child object from a parent object using classical inheritance
* pattern.
@@ -1932,6 +2100,7 @@ utils.extend = (function() {
var F = function() {};
// extend Child from Parent
return function(Child, Parent) {
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.__super__ = Parent.prototype;
@@ -1951,18 +2120,7 @@ utils.alert = function(msg) {
};
//------------------------------------------------------------------------------
function UUIDcreatePart(length) {
var uuidpart = "";
for (var i=0; i<length; i++) {
var uuidchar = parseInt((Math.random() * 256), 10).toString(16);
if (uuidchar.length == 1) {
uuidchar = "0" + uuidchar;
}
uuidpart += uuidchar;
}
return uuidpart;
}
});

View File

@@ -19,7 +19,16 @@
-->
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: https://ssl.gstatic.com/accessibility/javascript/android/; style-src 'self' 'unsafe-inline'; media-src *">
<!--
Customize this policy to fit your own app's needs. For more guidance, see:
https://github.com/apache/cordova-plugin-whitelist/blob/master/README.md#content-security-policy
Some notes:
* gap: is required only on iOS (when using UIWebView) and is needed for JS->native communication
* https://ssl.gstatic.com is required only on Android and is needed for TalkBack to function properly
* Disables use of inline scripts in order to mitigate risk of XSS vulnerabilities. To change this:
* Enable inline JS: add 'unsafe-inline' to default-src
-->
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">

View File

@@ -1,165 +0,0 @@
/*
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 deviceInfo = function() {
document.getElementById("platform").innerHTML = device.platform;
document.getElementById("version").innerHTML = device.version;
document.getElementById("uuid").innerHTML = device.uuid;
document.getElementById("name").innerHTML = device.name;
document.getElementById("width").innerHTML = screen.width;
document.getElementById("height").innerHTML = screen.height;
document.getElementById("colorDepth").innerHTML = screen.colorDepth;
};
var getLocation = function() {
var suc = function(p) {
alert(p.coords.latitude + " " + p.coords.longitude);
};
var locFail = function() {
};
navigator.geolocation.getCurrentPosition(suc, locFail);
};
var beep = function() {
navigator.notification.beep(2);
};
var vibrate = function() {
navigator.notification.vibrate(0);
};
function roundNumber(num) {
var dec = 3;
var result = Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
return result;
}
var accelerationWatch = null;
function updateAcceleration(a) {
document.getElementById('x').innerHTML = roundNumber(a.x);
document.getElementById('y').innerHTML = roundNumber(a.y);
document.getElementById('z').innerHTML = roundNumber(a.z);
}
var toggleAccel = function() {
if (accelerationWatch !== null) {
navigator.accelerometer.clearWatch(accelerationWatch);
updateAcceleration({
x : "",
y : "",
z : ""
});
accelerationWatch = null;
} else {
var options = {};
options.frequency = 1000;
accelerationWatch = navigator.accelerometer.watchAcceleration(
updateAcceleration, function(ex) {
alert("accel fail (" + ex.name + ": " + ex.message + ")");
}, options);
}
};
var preventBehavior = function(e) {
e.preventDefault();
};
function dump_pic(data) {
var viewport = document.getElementById('viewport');
console.log(data);
viewport.style.display = "";
viewport.style.position = "absolute";
viewport.style.top = "10px";
viewport.style.left = "10px";
document.getElementById("test_img").src = data;
}
function fail(msg) {
alert(msg);
}
function show_pic() {
navigator.camera.getPicture(dump_pic, fail, {
quality : 50
});
}
function close() {
var viewport = document.getElementById('viewport');
viewport.style.position = "relative";
viewport.style.display = "none";
}
function contacts_success(contacts) {
alert(contacts.length
+ ' contacts returned.'
+ (contacts[2] && contacts[2].name ? (' Third contact is ' + contacts[2].name.formatted)
: ''));
}
function get_contacts() {
var obj = new ContactFindOptions();
obj.filter = "";
obj.multiple = true;
navigator.contacts.find(
[ "displayName", "name" ], contacts_success,
fail, obj);
}
function check_network() {
var networkState = navigator.network.connection.type;
var states = {};
states[Connection.UNKNOWN] = 'Unknown connection';
states[Connection.ETHERNET] = 'Ethernet connection';
states[Connection.WIFI] = 'WiFi connection';
states[Connection.CELL_2G] = 'Cell 2G connection';
states[Connection.CELL_3G] = 'Cell 3G connection';
states[Connection.CELL_4G] = 'Cell 4G connection';
states[Connection.NONE] = 'No network connection';
confirm('Connection type:\n ' + states[networkState]);
}
var watchID = null;
function updateHeading(h) {
document.getElementById('h').innerHTML = h.magneticHeading;
}
function toggleCompass() {
if (watchID !== null) {
navigator.compass.clearWatch(watchID);
watchID = null;
updateHeading({ magneticHeading : "Off"});
} else {
var options = { frequency: 1000 };
watchID = navigator.compass.watchHeading(updateHeading, function(e) {
alert('Compass Error: ' + e.code);
}, options);
}
}
function init() {
// the next line makes it impossible to see Contacts on the HTC Evo since it
// doesn't have a scroll button
// document.addEventListener("touchmove", preventBehavior, false);
document.addEventListener("deviceready", deviceInfo, true);
}

View File

@@ -1,116 +0,0 @@
/*
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.
*/
body {
background:#222 none repeat scroll 0 0;
color:#666;
font-family:Helvetica;
font-size:72%;
line-height:1.5em;
margin:0;
border-top:1px solid #393939;
}
#info{
background:#ffa;
border: 1px solid #ffd324;
-webkit-border-radius: 5px;
border-radius: 5px;
clear:both;
margin:15px 6px 0;
width:295px;
padding:4px 0px 2px 10px;
}
#info > h4{
font-size:.95em;
margin:5px 0;
}
#stage.theme{
padding-top:3px;
}
/* Definition List */
#stage.theme > dl{
padding-top:10px;
clear:both;
margin:0;
list-style-type:none;
padding-left:10px;
overflow:auto;
}
#stage.theme > dl > dt{
font-weight:bold;
float:left;
margin-left:5px;
}
#stage.theme > dl > dd{
width:45px;
float:left;
color:#a87;
font-weight:bold;
}
/* Content Styling */
#stage.theme > h1, #stage.theme > h2, #stage.theme > p{
margin:1em 0 .5em 13px;
}
#stage.theme > h1{
color:#eee;
font-size:1.6em;
text-align:center;
margin:0;
margin-top:15px;
padding:0;
}
#stage.theme > h2{
clear:both;
margin:0;
padding:3px;
font-size:1em;
text-align:center;
}
/* Stage Buttons */
#stage.theme a.btn{
border: 1px solid #555;
-webkit-border-radius: 5px;
border-radius: 5px;
text-align:center;
display:block;
float:left;
background:#444;
width:150px;
color:#9ab;
font-size:1.1em;
text-decoration:none;
padding:1.2em 0;
margin:3px 0px 3px 5px;
}
#stage.theme a.btn.large{
width:308px;
padding:1.2em 0;
}

View File

@@ -118,7 +118,7 @@ if (ext.cdvReleaseSigningPropertiesFile == null && file('release-signing.propert
}
// Cast to appropriate types.
ext.cdvBuildMultipleApks = !!cdvBuildMultipleApks;
ext.cdvBuildMultipleApks = cdvBuildMultipleApks == null ? false : cdvBuildMultipleApks.toBoolean();
ext.cdvMinSdkVersion = cdvMinSdkVersion == null ? null : Integer.parseInt('' + cdvMinSdkVersion)
ext.cdvVersionCode = cdvVersionCode == null ? null : Integer.parseInt('' + cdvVersionCode)
@@ -152,11 +152,8 @@ task cdvPrintProps << {
println('cdvDebugSigningPropertiesFile=' + cdvDebugSigningPropertiesFile)
println('cdvBuildArch=' + cdvBuildArch)
println('computedVersionCode=' + android.defaultConfig.versionCode)
if (android.productFlavors.has('armv7')) {
println('computedArmv7VersionCode=' + android.productFlavors.armv7.versionCode)
}
if (android.productFlavors.has('x86')) {
println('computedx86VersionCode=' + android.productFlavors.x86.versionCode)
android.productFlavors.each { flavor ->
println('computed' + flavor.name.capitalize() + 'VersionCode=' + flavor.versionCode)
}
}
@@ -187,13 +184,13 @@ android {
if (Boolean.valueOf(cdvBuildMultipleApks)) {
productFlavors {
armv7 {
versionCode cdvVersionCode ?: defaultConfig.versionCode + 2
versionCode defaultConfig.versionCode + 2
ndk {
abiFilters "armeabi-v7a", ""
}
}
x86 {
versionCode cdvVersionCode ?: defaultConfig.versionCode + 4
versionCode defaultConfig.versionCode + 4
ndk {
abiFilters "x86", ""
}

View File

@@ -1,45 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>__NAME__</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
<filteredResources>
<filter>
<id>1388696068187</id>
<name></name>
<type>10</type>
<matcher>
<id>org.eclipse.ui.ide.multiFilter</id>
<arguments>1.0-name-matches-false-true-CordovaLib|platform_www|cordova</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>

View File

@@ -1,71 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>__NAME__</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
<linkedResources>
<link>
<name>config.xml</name>
<type>1</type>
<locationURI>$%7BPARENT-2-PROJECT_LOC%7D/config.xml</locationURI>
</link>
<link>
<name>www</name>
<type>2</type>
<locationURI>$%7BPARENT-2-PROJECT_LOC%7D/www</locationURI>
</link>
<link>
<name>merges</name>
<type>2</type>
<locationURI>$%7BPARENT-2-PROJECT_LOC%7D/merges</locationURI>
</link>
</linkedResources>
<filteredResources>
<filter>
<id>1390880034107</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.ui.ide.multiFilter</id>
<arguments>1.0-projectRelativePath-matches-false-true-^(build.xml|ant-gen|ant-build|custom_rules.xml|CordovaLib|platform_www|cordova)</arguments>
</matcher>
</filter>
<filter>
<id>1390880034108</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.ui.ide.multiFilter</id>
<arguments>1.0-projectRelativePath-matches-false-true-^(assets/www|res/xml/config.xml)</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -19,13 +19,17 @@
under the License.
*/
var path = require('path');
var create = require('./lib/create');
var args = require('./lib/simpleargs').getArgs(process.argv);
var Api = require('./templates/cordova/Api');
var args = require('nopt')({
'link': Boolean,
'shared': Boolean,
'help': Boolean
});
if (args['--help'] || args._.length === 0) {
if (args.help || args.argv.remain.length === 0) {
console.log('Usage: ' + path.relative(process.cwd(), path.join(__dirname, 'update')) + ' <path_to_project> [--link]');
console.log(' --link will use the CordovaLib project directly instead of making a copy.');
process.exit(1);
}
create.updateProject(args._[0], args['--link'] || args['--shared']).done();
Api.updatePlatform(args.argv.remain[0], {link: (args.link || args.shared)}).done();

View File

@@ -51,11 +51,7 @@ var cordova = require('cordova'),
// For the ONLINE_EVENT to be viable, it would need to intercept all event
// listeners (both through addEventListener and window.ononline) as well
// as set the navigator property itself.
ONLINE_EVENT: 2,
// Uses reflection to access private APIs of the WebView that can send JS
// to be executed.
// Requires Android 3.2.4 or above.
PRIVATE_API: 3
ONLINE_EVENT: 2
},
jsToNativeBridgeMode, // Set lazily.
nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT,

View File

@@ -19,5 +19,5 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.apache.cordova" android:versionName="1.0" android:versionCode="1">
<uses-sdk android:minSdkVersion="10" />
<uses-sdk android:minSdkVersion="14" />
</manifest>

View File

@@ -23,27 +23,20 @@ buildscript {
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+'
}
dependencies {
classpath 'com.android.tools.build:gradle:1.0.0+'
}
}
apply plugin: 'android-library'
ext {
apply from: 'cordova.gradle'
cdvCompileSdkVersion = privateHelpers.getProjectTarget()
cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools()
}
android {
compileSdkVersion cdvCompileSdkVersion
buildToolsVersion cdvBuildToolsVersion

View File

@@ -152,6 +152,26 @@ def doPromptForPassword(msg) {
}
}
def doGetConfigXml() {
def xml = file("res/xml/config.xml").getText()
// Disable namespace awareness since Cordova doesn't use them properly
return new XmlParser(false, false).parseText(xml)
}
def doGetConfigPreference(name, defaultValue) {
name = name.toLowerCase()
def root = doGetConfigXml()
def ret = defaultValue
root.preference.each { it ->
def attrName = it.attribute("name")
if (attrName && attrName.toLowerCase() == name) {
ret = it.attribute("value")
}
}
return ret
}
// Properties exported here are visible to all plugins.
ext {
// These helpers are shared, but are not guaranteed to be stable / unchanged.
@@ -161,5 +181,12 @@ ext {
privateHelpers.extractIntFromManifest = { name -> doExtractIntFromManifest(name) }
privateHelpers.promptForPassword = { msg -> doPromptForPassword(msg) }
privateHelpers.ensureValueExists = { filePath, props, key -> doEnsureValueExists(filePath, props, key) }
// These helpers can be used by plugins / projects and will not change.
cdvHelpers = {}
// Returns a XmlParser for the config.xml. Added in 4.1.0.
cdvHelpers.getConfigXml = { doGetConfigXml() }
// Returns the value for the desired <preference>. Added in 4.1.0.
cdvHelpers.getConfigPreference = { name, defaultValue -> doGetConfigPreference(name, defaultValue) }
}

View File

@@ -10,7 +10,7 @@
# Indicates whether an apk should be generated for each density.
split.density=false
# Project target.
target=android-22
target=android-23
apk-configurations=
renderscript.opt.level=O0
android.library=true

View File

@@ -36,8 +36,8 @@ public class Config {
public static void init(Activity action) {
parser = new ConfigXmlParser();
parser.parse(action);
//TODO: Add feature to bring this back. Some preferences should be overridden by intents, but not all
parser.getPreferences().setPreferencesBundle(action.getIntent().getExtras());
parser.getPreferences().copyIntoIntentExtras(action);
}
// Intended to be used for testing only; creates an empty configuration.

View File

@@ -26,11 +26,13 @@ import org.json.JSONObject;
import android.app.Activity;
import android.app.AlertDialog;
import android.annotation.SuppressLint;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Color;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
@@ -88,6 +90,9 @@ public class CordovaActivity extends Activity {
// when another application (activity) is started.
protected boolean keepRunning = true;
// Flag to keep immersive mode if set to fullscreen
protected boolean immersiveMode;
// Read from config.xml:
protected CordovaPreferences preferences;
protected String launchUrl;
@@ -104,19 +109,21 @@ public class CordovaActivity extends Activity {
// need to activate preferences before super.onCreate to avoid "requestFeature() must be called before adding content" exception
loadConfig();
if(!preferences.getBoolean("ShowTitle", false))
{
if (!preferences.getBoolean("ShowTitle", false)) {
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
}
if(preferences.getBoolean("SetFullscreen", false))
{
if (preferences.getBoolean("SetFullscreen", false)) {
Log.d(TAG, "The SetFullscreen configuration is deprecated in favor of Fullscreen, and will be removed in a future version.");
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
} else if (preferences.getBoolean("Fullscreen", false)) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
preferences.set("Fullscreen", true);
}
if (preferences.getBoolean("Fullscreen", false)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
immersiveMode = true;
} else {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
} else {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
@@ -125,12 +132,11 @@ public class CordovaActivity extends Activity {
super.onCreate(savedInstanceState);
cordovaInterface = makeCordovaInterface();
if(savedInstanceState != null)
{
if (savedInstanceState != null) {
cordovaInterface.restoreInstanceState(savedInstanceState);
}
}
protected void init() {
appView = makeWebView();
createViews();
@@ -152,7 +158,6 @@ public class CordovaActivity extends Activity {
parser.parse(this);
preferences = parser.getPreferences();
preferences.setPreferencesBundle(getIntent().getExtras());
preferences.copyIntoIntentExtras(this);
launchUrl = parser.getLaunchUrl();
pluginEntries = parser.getPluginEntries();
Config.parser = parser;
@@ -180,7 +185,7 @@ public class CordovaActivity extends Activity {
/**
* Construct the default web view object.
*
* <p/>
* Override this to customize the webview that is used.
*/
protected CordovaWebView makeWebView() {
@@ -224,19 +229,22 @@ public class CordovaActivity extends Activity {
LOG.d(TAG, "Paused the activity.");
if (this.appView != null) {
this.appView.handlePause(this.keepRunning);
// CB-9382 If there is an activity that started for result and main activity is waiting for callback
// result, we shoudn't stop WebView Javascript timers, as activity for result might be using them
boolean keepRunning = this.keepRunning || this.cordovaInterface.activityResultCallback != null;
this.appView.handlePause(keepRunning);
}
}
/**
* Called when the activity receives a new intent
**/
*/
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
//Forward to plugins
if (this.appView != null)
this.appView.onNewIntent(intent);
this.appView.onNewIntent(intent);
}
/**
@@ -246,7 +254,7 @@ public class CordovaActivity extends Activity {
protected void onResume() {
super.onResume();
LOG.d(TAG, "Resumed the activity.");
if (this.appView == null) {
return;
}
@@ -298,6 +306,25 @@ public class CordovaActivity extends Activity {
}
}
/**
* Called when view focus is changed
*/
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus && immersiveMode) {
final int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
getWindow().getDecorView().setSystemUiVisibility(uiOptions);
}
}
@SuppressLint("NewApi")
@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
// Capture requestCode here so that it is captured in the setActivityResultCallback() case.
@@ -309,10 +336,10 @@ public class CordovaActivity extends Activity {
* Called when an activity you launched exits, giving you the requestCode you started it with,
* the resultCode it returned, and any additional data from it.
*
* @param requestCode The request code originally supplied to startActivityForResult(),
* allowing you to identify who this result came from.
* @param resultCode The integer result code returned by the child activity through its setResult().
* @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
* @param requestCode The request code originally supplied to startActivityForResult(),
* allowing you to identify who this result came from.
* @param resultCode The integer result code returned by the child activity through its setResult().
* @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
@@ -325,9 +352,9 @@ public class CordovaActivity extends Activity {
* Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
* The errorCode parameter corresponds to one of the ERROR_* constants.
*
* @param errorCode The error code corresponding to an ERROR_* value.
* @param description A String describing the error.
* @param failingUrl The url that failed to load.
* @param errorCode The error code corresponding to an ERROR_* value.
* @param description A String describing the error.
* @param failingUrl The url that failed to load.
*/
public void onReceivedError(final int errorCode, final String description, final String failingUrl) {
final CordovaActivity me = this;
@@ -416,9 +443,9 @@ public class CordovaActivity extends Activity {
/**
* Called when a message is sent to plugin.
*
* @param id The message id
* @param data The message data
* @return Object or null
* @param id The message id
* @param data The message data
* @return Object or null
*/
public Object onMessage(String id, Object data) {
if ("onReceivedError".equals(id)) {
@@ -434,8 +461,7 @@ public class CordovaActivity extends Activity {
return null;
}
protected void onSaveInstanceState(Bundle outState)
{
protected void onSaveInstanceState(Bundle outState) {
cordovaInterface.onSaveInstanceState(outState);
super.onSaveInstanceState(outState);
}
@@ -443,7 +469,7 @@ public class CordovaActivity extends Activity {
/**
* Called by the system when the device configuration changes while your activity is running.
*
* @param newConfig The new device configuration
* @param newConfig The new device configuration
*/
@Override
public void onConfigurationChanged(Configuration newConfig) {
@@ -456,4 +482,27 @@ public class CordovaActivity extends Activity {
pm.onConfigurationChanged(newConfig);
}
}
/**
* Called by the system when the user grants permissions
*
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[],
int[] grantResults) {
try
{
cordovaInterface.onRequestPermissionResult(requestCode, permissions, grantResults);
}
catch (JSONException e)
{
LOG.d(TAG, "JSONException: Parameters fed into the method are not valid");
e.printStackTrace();
}
}
}

View File

@@ -69,4 +69,20 @@ public interface CordovaInterface {
* Returns a shared thread pool that can be used for background tasks.
*/
public ExecutorService getThreadPool();
/**
* Sends a permission request to the activity for one permission.
*/
public void requestPermission(CordovaPlugin plugin, int requestCode, String permission);
/**
* Sends a permission request to the activity for a group of permissions
*/
public void requestPermissions(CordovaPlugin plugin, int requestCode, String [] permissions);
/**
* Check for a permission. Returns true if the permission is granted, false otherwise.
*/
public boolean hasPermission(String permission);
}

View File

@@ -19,11 +19,16 @@
package org.apache.cordova;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import org.json.JSONException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -38,6 +43,7 @@ public class CordovaInterfaceImpl implements CordovaInterface {
protected ActivityResultHolder savedResult;
protected CordovaPlugin activityResultCallback;
protected CordovaPlugin permissionResultCallback;
protected String initCallbackService;
protected int activityResultRequestCode;
@@ -161,4 +167,48 @@ public class CordovaInterfaceImpl implements CordovaInterface {
this.intent = intent;
}
}
/**
* Called by the system when the user grants permissions
*
* @param requestCode
* @param permissions
* @param grantResults
*/
public void onRequestPermissionResult(int requestCode, String[] permissions,
int[] grantResults) throws JSONException {
if(permissionResultCallback != null)
{
permissionResultCallback.onRequestPermissionResult(requestCode, permissions, grantResults);
permissionResultCallback = null;
}
}
public void requestPermission(CordovaPlugin plugin, int requestCode, String permission) {
permissionResultCallback = plugin;
String[] permissions = new String [1];
permissions[0] = permission;
getActivity().requestPermissions(permissions, requestCode);
}
public void requestPermissions(CordovaPlugin plugin, int requestCode, String [] permissions)
{
permissionResultCallback = plugin;
getActivity().requestPermissions(permissions, requestCode);
}
public boolean hasPermission(String permission)
{
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
{
int result = activity.checkSelfPermission(permission);
return PackageManager.PERMISSION_GRANTED == result;
}
else
{
return true;
}
}
}

View File

@@ -26,8 +26,10 @@ import org.json.JSONArray;
import org.json.JSONException;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -359,4 +361,38 @@ public class CordovaPlugin {
*/
public void onConfigurationChanged(Configuration newConfig) {
}
/**
* Called by the Plugin Manager when we need to actually request permissions
*
* @param requestCode Passed to the activity to track the request
*
* @return Returns the permission that was stored in the plugin
*/
public void requestPermissions(int requestCode) {
}
/*
* Called by the WebView implementation to check for geolocation permissions, can be used
* by other Java methods in the event that a plugin is using this as a dependency.
*
* @return Returns true if the plugin has all the permissions it needs to operate.
*/
public boolean hasPermisssion() {
return true;
}
/**
* Called by the system when the user grants permissions
*
* @param requestCode
* @param permissions
* @param grantResults
*/
public void onRequestPermissionResult(int requestCode, String[] permissions,
int[] grantResults) throws JSONException {
}
}

View File

@@ -61,13 +61,6 @@ public class CordovaPreferences {
String value = prefs.get(name);
if (value != null) {
return Boolean.parseBoolean(value);
} else if (preferencesBundleExtras != null) {
Object bundleValue = preferencesBundleExtras.get(name);
if (bundleValue instanceof String) {
return "true".equals(bundleValue);
}
// Gives a nice warning if type is wrong.
return preferencesBundleExtras.getBoolean(name, defaultValue);
}
return defaultValue;
}
@@ -83,13 +76,6 @@ public class CordovaPreferences {
if (value != null) {
// Use Integer.decode() can't handle it if the highest bit is set.
return (int)(long)Long.decode(value);
} else if (preferencesBundleExtras != null) {
Object bundleValue = preferencesBundleExtras.get(name);
if (bundleValue instanceof String) {
return Integer.valueOf((String)bundleValue);
}
// Gives a nice warning if type is wrong.
return preferencesBundleExtras.getInt(name, defaultValue);
}
return defaultValue;
}
@@ -99,13 +85,6 @@ public class CordovaPreferences {
String value = prefs.get(name);
if (value != null) {
return Double.valueOf(value);
} else if (preferencesBundleExtras != null) {
Object bundleValue = preferencesBundleExtras.get(name);
if (bundleValue instanceof String) {
return Double.valueOf((String)bundleValue);
}
// Gives a nice warning if type is wrong.
return preferencesBundleExtras.getDouble(name, defaultValue);
}
return defaultValue;
}
@@ -115,69 +94,8 @@ public class CordovaPreferences {
String value = prefs.get(name);
if (value != null) {
return value;
} else if (preferencesBundleExtras != null && !"errorurl".equals(name)) {
Object bundleValue = preferencesBundleExtras.get(name);
if (bundleValue != null) {
return bundleValue.toString();
}
}
return defaultValue;
}
// Plugins should not rely on values within the intent since this does not work
// for apps with multiple webviews. Instead, they should retrieve prefs from the
// Config object associated with their webview.
public void copyIntoIntentExtras(Activity action) {
for (String name : prefs.keySet()) {
String value = prefs.get(name);
if (value == null) {
continue;
}
if (name.equals("loglevel")) {
LOG.setLogLevel(value);
} else if (name.equals("splashscreen")) {
// Note: We should probably pass in the classname for the variable splash on splashscreen!
int resource = action.getResources().getIdentifier(value, "drawable", action.getClass().getPackage().getName());
if(resource == 0) {
resource = action.getResources().getIdentifier(value, "drawable", action.getPackageName());
}
action.getIntent().putExtra(name, resource);
}
else if(name.equals("backgroundcolor")) {
int asInt = (int)(long)Long.decode(value);
action.getIntent().putExtra(name, asInt);
}
else if(name.equals("loadurltimeoutvalue")) {
int asInt = Integer.decode(value);
action.getIntent().putExtra(name, asInt);
}
else if(name.equals("splashscreendelay")) {
int asInt = Integer.decode(value);
action.getIntent().putExtra(name, asInt);
}
else if(name.equals("keeprunning"))
{
boolean asBool = Boolean.parseBoolean(value);
action.getIntent().putExtra(name, asBool);
}
else if(name.equals("inappbrowserstorageenabled"))
{
boolean asBool = Boolean.parseBoolean(value);
action.getIntent().putExtra(name, asBool);
}
else if(name.equals("disallowoverscroll"))
{
boolean asBool = Boolean.parseBoolean(value);
action.getIntent().putExtra(name, asBool);
}
else
{
action.getIntent().putExtra(name, value);
}
}
// In the normal case, the intent extras are null until the first call to putExtra().
if (preferencesBundleExtras == null) {
preferencesBundleExtras = action.getIntent().getExtras();
}
}
}

View File

@@ -28,8 +28,6 @@ import android.os.Looper;
import android.util.Base64;
import android.webkit.MimeTypeMap;
import org.apache.http.util.EncodingUtils;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -38,6 +36,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.channels.FileChannel;
@@ -105,28 +104,28 @@ public class CordovaResourceApi {
public static int getUriType(Uri uri) {
assertNonRelative(uri);
String scheme = uri.getScheme();
if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(scheme)) {
return URI_TYPE_CONTENT;
}
if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
if (ContentResolver.SCHEME_ANDROID_RESOURCE.equalsIgnoreCase(scheme)) {
return URI_TYPE_RESOURCE;
}
if (ContentResolver.SCHEME_FILE.equals(scheme)) {
if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(scheme)) {
if (uri.getPath().startsWith("/android_asset/")) {
return URI_TYPE_ASSET;
}
return URI_TYPE_FILE;
}
if ("data".equals(scheme)) {
if ("data".equalsIgnoreCase(scheme)) {
return URI_TYPE_DATA;
}
if ("http".equals(scheme)) {
if ("http".equalsIgnoreCase(scheme)) {
return URI_TYPE_HTTP;
}
if ("https".equals(scheme)) {
if ("https".equalsIgnoreCase(scheme)) {
return URI_TYPE_HTTPS;
}
if (PLUGIN_URI_SCHEME.equals(scheme)) {
if (PLUGIN_URI_SCHEME.equalsIgnoreCase(scheme)) {
return URI_TYPE_PLUGIN;
}
return URI_TYPE_UNKNOWN;
@@ -189,7 +188,11 @@ public class CordovaResourceApi {
HttpURLConnection conn = (HttpURLConnection)new URL(uri.toString()).openConnection();
conn.setDoInput(false);
conn.setRequestMethod("HEAD");
return conn.getHeaderField("Content-Type");
String mimeType = conn.getHeaderField("Content-Type");
if (mimeType != null) {
mimeType = mimeType.split(";")[0];
}
return mimeType;
} catch (IOException e) {
}
}
@@ -284,6 +287,9 @@ public class CordovaResourceApi {
HttpURLConnection conn = (HttpURLConnection)new URL(uri.toString()).openConnection();
conn.setDoInput(true);
String mimeType = conn.getHeaderField("Content-Type");
if (mimeType != null) {
mimeType = mimeType.split(";")[0];
}
int length = conn.getContentLength();
InputStream inputStream = conn.getInputStream();
return new OpenForReadResult(uri, inputStream, mimeType, length, null);
@@ -427,7 +433,16 @@ public class CordovaResourceApi {
}
}
String dataPartAsString = uriAsString.substring(commaPos + 1);
byte[] data = base64 ? Base64.decode(dataPartAsString, Base64.DEFAULT) : EncodingUtils.getBytes(dataPartAsString, "UTF-8");
byte[] data;
if (base64) {
data = Base64.decode(dataPartAsString, Base64.DEFAULT);
} else {
try {
data = dataPartAsString.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
data = dataPartAsString.getBytes();
}
}
InputStream inputStream = new ByteArrayInputStream(data);
return new OpenForReadResult(uri, inputStream, contentType, data.length, null);
}

View File

@@ -31,7 +31,7 @@ import android.webkit.WebChromeClient.CustomViewCallback;
* are not expected to implement it.
*/
public interface CordovaWebView {
public static final String CORDOVA_VERSION = "4.0.0-dev";
public static final String CORDOVA_VERSION = "5.0.0";
void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences);

View File

@@ -22,7 +22,7 @@ import android.view.KeyEvent;
import android.view.View;
/**
* Interfcae for all Cordova engines.
* Interface for all Cordova engines.
* No methods will be added to this class (in order to be compatible with existing engines).
* Instead, we will create a new interface: e.g. CordovaWebViewEngineV2
*/

View File

@@ -270,9 +270,9 @@ public class NativeToJsMessageQueue {
}
public static abstract class BridgeMode {
abstract void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue);
void notifyOfFlush(NativeToJsMessageQueue queue, boolean fromOnlineEvent) {}
void reset() {}
public abstract void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue);
public void notifyOfFlush(NativeToJsMessageQueue queue, boolean fromOnlineEvent) {}
public void reset() {}
}
/** Uses JS polls for messages on a timer.. */

View File

@@ -47,6 +47,8 @@ public class PluginManager {
private final CordovaWebView app;
private boolean isInitialized;
private CordovaPlugin permissionRequester;
public PluginManager(CordovaWebView cordovaWebView, CordovaInterface cordova, Collection<PluginEntry> pluginEntries) {
this.ctx = cordova;
this.app = cordovaWebView;
@@ -508,4 +510,5 @@ public class PluginManager {
}
}
}
}

View File

@@ -19,6 +19,7 @@
package org.apache.cordova.engine;
import android.annotation.TargetApi;
import android.os.Build;
import android.webkit.CookieManager;
import android.webkit.WebView;
@@ -30,6 +31,8 @@ class SystemCookieManager implements ICordovaCookieManager {
protected final WebView webView;
private final CookieManager cookieManager;
//Added because lint can't see the conditional RIGHT ABOVE this
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public SystemCookieManager(WebView webview) {
webView = webview;
cookieManager = CookieManager.getInstance();

View File

@@ -18,8 +18,10 @@
*/
package org.apache.cordova.engine;
import java.util.Arrays;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
@@ -36,6 +38,7 @@ import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.PermissionRequest;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
@@ -61,13 +64,15 @@ public class SystemWebChromeClient extends WebChromeClient {
private View mVideoProgressView;
private CordovaDialogsHelper dialogsHelper;
private Context appContext;
private WebChromeClient.CustomViewCallback mCustomViewCallback;
private View mCustomView;
public SystemWebChromeClient(SystemWebViewEngine parentEngine) {
this.parentEngine = parentEngine;
dialogsHelper = new CordovaDialogsHelper(parentEngine.webView.getContext());
appContext = parentEngine.webView.getContext();
dialogsHelper = new CordovaDialogsHelper(appContext);
}
/**
@@ -172,12 +177,21 @@ public class SystemWebChromeClient extends WebChromeClient {
/**
* Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin.
*
* This also checks for the Geolocation Plugin and requests permission from the application to use Geolocation.
*
* @param origin
* @param callback
*/
public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
super.onGeolocationPermissionsShowPrompt(origin, callback);
callback.invoke(origin, true, false);
//Get the plugin, it should be loaded
CordovaPlugin geolocation = parentEngine.pluginManager.getPlugin("Geolocation");
if(geolocation != null && !geolocation.hasPermisssion())
{
geolocation.requestPermissions(0);
}
}
// API level 7 is required for this, see if we could lower this using something else
@@ -266,6 +280,13 @@ public class SystemWebChromeClient extends WebChromeClient {
return true;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void onPermissionRequest(final PermissionRequest request) {
Log.d(LOG_TAG, "onPermissionRequest: " + Arrays.toString(request.getResources()));
request.grant(request.getResources());
}
public void destroyLastDialog(){
dialogsHelper.destroyLastDialog();
}

View File

@@ -60,6 +60,7 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
protected final SystemWebView webView;
protected final SystemCookieManager cookieManager;
protected CordovaPreferences preferences;
protected CordovaBridge bridge;
protected CordovaWebViewEngine.Client client;
protected CordovaWebView parentWebView;
@@ -71,10 +72,15 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
/** Used when created via reflection. */
public SystemWebViewEngine(Context context, CordovaPreferences preferences) {
this(new SystemWebView(context));
this(new SystemWebView(context), preferences);
}
public SystemWebViewEngine(SystemWebView webView) {
this(webView, null);
}
public SystemWebViewEngine(SystemWebView webView, CordovaPreferences preferences) {
this.preferences = preferences;
this.webView = webView;
cookieManager = new SystemCookieManager(webView);
}
@@ -86,6 +92,10 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
if (this.cordova != null) {
throw new IllegalStateException();
}
// Needed when prefs are not passed by the constructor
if (preferences == null) {
preferences = parentWebView.getPreferences();
}
this.parentWebView = parentWebView;
this.cordova = cordova;
this.client = client;
@@ -125,7 +135,7 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
return webView;
}
@SuppressLint("SetJavaScriptEnabled")
@SuppressLint({"NewApi", "SetJavaScriptEnabled"})
@SuppressWarnings("deprecation")
private void initWebViewSettings() {
webView.setInitialScale(0);
@@ -199,7 +209,19 @@ public class SystemWebViewEngine implements CordovaWebViewEngine {
// Fix for CB-1405
// Google issue 4641
settings.getUserAgentString();
String defaultUserAgent = settings.getUserAgentString();
// Fix for CB-3360
String overrideUserAgent = preferences.getString("OverrideUserAgent", null);
if (overrideUserAgent != null) {
settings.setUserAgentString(overrideUserAgent);
} else {
String appendUserAgent = preferences.getString("AppendUserAgent", null);
if (appendUserAgent != null) {
settings.setUserAgentString(defaultUserAgent + " " + appendUserAgent);
}
}
// End CB-3360
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);

24
node_modules/cordova-common/.jscs.json generated vendored Normal file
View File

@@ -0,0 +1,24 @@
{
"disallowMixedSpacesAndTabs": true,
"disallowTrailingWhitespace": true,
"validateLineBreaks": "LF",
"validateIndentation": 4,
"requireLineFeedAtFileEnd": true,
"disallowSpaceAfterPrefixUnaryOperators": true,
"disallowSpaceBeforePostfixUnaryOperators": true,
"requireSpaceAfterLineComment": true,
"requireCapitalizedConstructors": true,
"disallowSpacesInNamedFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"requireSpaceAfterKeywords": [
"if",
"else",
"for",
"while",
"do"
]
}

1
node_modules/cordova-common/.jshintignore generated vendored Normal file
View File

@@ -0,0 +1 @@
spec/fixtures/*

2
node_modules/cordova-common/.npmignore generated vendored Normal file
View File

@@ -0,0 +1,2 @@
spec
coverage

2
node_modules/cordova-common/.ratignore generated vendored Normal file
View File

@@ -0,0 +1,2 @@
fixtures
coverage

149
node_modules/cordova-common/README.md generated vendored Normal file
View File

@@ -0,0 +1,149 @@
<!--
#
# 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.
#
-->
# cordova-common
Expoeses shared functionality used by [cordova-lib](https://github.com/apache/cordova-lib/) and Cordova platforms.
## Exposed APIs
### `events`
Represents special instance of NodeJS EventEmitter which is intended to be used to post events to cordova-lib and cordova-cli
Usage:
```
var events = require('cordova-common').events;
events.emit('warn', 'Some warning message')
```
There are the following events supported by cordova-cli: `verbose`, `log`, `info`, `warn`, `error`.
### `CordovaError`
An error class used by Cordova to throw cordova-specific errors. The CordovaError class is inherited from Error, so CordovaError instances is also valid Error instances (`instanceof` check succeeds).
Usage:
```
var CordovaError = require('cordova-common').CordovaError;
throw new CordovaError('Some error message', SOME_ERR_CODE);
```
See [CordovaError](src/CordovaError/CordovaError.js) for supported error codes.
### `ConfigParser`
Exposes functionality to deal with cordova project `config.xml` files. For ConfigParser API reference check [ConfigParser Readme](src/ConfigParser/README.md).
Usage:
```
var ConfigParser = require('cordova-common').ConfigParser;
var appConfig = new ConfigParser('path/to/cordova-app/config.xml');
console.log(appconfig.name() + ':' + appConfig.version());
```
### `PluginInfoProvider` and `PluginInfo`
`PluginInfo` is a wrapper for cordova plugins' `plugin.xml` files. This class may be instantiated directly or via `PluginInfoProvider`. The difference is that `PluginInfoProvider` caches `PluginInfo` instances based on plugin source directory.
Usage:
```
var PluginInfo: require('cordova-common').PluginInfo;
var PluginInfoProvider: require('cordova-common').PluginInfoProvider;
// The following instances are equal
var plugin1 = new PluginInfo('path/to/plugin_directory');
var plugin2 = new PluginInfoProvider().get('path/to/plugin_directory');
console.log('The plugin ' + plugin1.id + ' has version ' + plugin1.version)
```
### `ActionStack`
Utility module for dealing with sequential tasks. Provides a set of tasks that are needed to be done and reverts all tasks that are already completed if one of those tasks fail to complete. Used internally by cordova-lib and platform's plugin installation routines.
Usage:
```
var ActionStack = require('cordova-common').ActionStack;
var stack = new ActionStack()
var action1 = stack.createAction(task1, [<task parameters>], task1_reverter, [<reverter_parameters>]);
var action2 = stack.createAction(task2, [<task parameters>], task2_reverter, [<reverter_parameters>]);
stack.push(action1);
stack.push(action2);
stack.process()
.then(function() {
// all actions succeded
})
.catch(function(error){
// One of actions failed with error
})
```
### `superspawn`
Module for spawning child processes with some advanced logic.
Usage:
```
var superspawn = require('cordova-common').superspawn;
superspawn.spawn('adb', ['devices'])
.then(function(devices){
// Do something...
})
```
### `xmlHelpers`
A set of utility methods for dealing with xml files.
Usage:
```
var xml = require('cordova-common').xmlHelpers;
var xmlDoc1 = xml.parseElementtreeSync('some/xml/file');
var xmlDoc2 = xml.parseElementtreeSync('another/xml/file');
xml.mergeXml(doc1, doc2); // doc2 now contains all the nodes from doc1
```
### Other APIs
The APIs listed below are also exposed but are intended to be only used internally by cordova plugin installation routines.
```
PlatformJson
ConfigChanges
ConfigKeeper
ConfigFile
mungeUtil
```
## Setup
* Clone this repository onto your local machine
`git clone https://git-wip-us.apache.org/repos/asf/cordova-lib.git`
* In terminal, navigate to the inner cordova-common directory
`cd cordova-lib/cordova-common`
* Install dependencies and npm-link
`npm install && npm link`
* Navigate to cordova-lib directory and link cordova-common
`cd ../cordova-lib && npm link cordova-common && npm install`

34
node_modules/cordova-common/RELEASENOTES.md generated vendored Normal file
View File

@@ -0,0 +1,34 @@
<!--
#
# 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.
#
-->
# Cordova-common Release Notes
### 1.0.0 (Oct 29, 2015)
* CB-9890 Documents cordova-common
* CB-9598 Correct cordova-lib -> cordova-common in README
* Pick ConfigParser changes from apache@0c3614e
* CB-9743 Removes system frameworks handling from ConfigChanges
* CB-9598 Cleans out code which has been moved to `cordova-common`
* Pick ConfigParser changes from apache@ddb027b
* Picking CordovaError changes from apache@a3b1fca
* CB-9598 Adds tests and fixtures based on existing cordova-lib ones
* CB-9598 Initial implementation for cordova-common

42
node_modules/cordova-common/cordova-common.js generated vendored Normal file
View File

@@ -0,0 +1,42 @@
/**
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 */
// For now expose plugman and cordova just as they were in the old repos
exports = module.exports = {
events: require('./src/events'),
superspawn: require('./src/superspawn'),
ActionStack: require('./src/ActionStack'),
CordovaError: require('./src/CordovaError/CordovaError'),
CordovaExternalToolErrorContext: require('./src/CordovaError/CordovaExternalToolErrorContext'),
PlatformJson: require('./src/PlatformJson'),
ConfigParser: require('./src/ConfigParser/ConfigParser.js'),
PluginInfo: require('./src/PluginInfo/PluginInfo.js'),
PluginInfoProvider: require('./src/PluginInfo/PluginInfoProvider.js'),
ConfigChanges: require('./src/ConfigChanges/ConfigChanges.js'),
ConfigKeeper: require('./src/ConfigChanges/ConfigKeeper.js'),
ConfigFile: require('./src/ConfigChanges/ConfigFile.js'),
mungeUtil: require('./src/ConfigChanges/munge-util.js'),
xmlHelpers: require('./src/util/xml-helpers')
};

View File

@@ -0,0 +1,8 @@
/build/*
node_modules
*.node
*.sh
*.swp
.lock*
npm-debug.log
.idea

View File

@@ -0,0 +1,47 @@
bplist-parser
=============
Binary Mac OS X Plist (property list) parser.
## Installation
```bash
$ npm install bplist-parser
```
## Quick Examples
```javascript
var bplist = require('bplist-parser');
bplist.parseFile('myPlist.bplist', function(err, obj) {
if (err) throw err;
console.log(JSON.stringify(obj));
});
```
## License
(The MIT License)
Copyright (c) 2012 Near Infinity Corporation
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.

View File

@@ -0,0 +1,337 @@
'use strict';
// adapted from http://code.google.com/p/plist/source/browse/trunk/src/com/dd/plist/BinaryPropertyListParser.java
var fs = require('fs');
var debug = false;
exports.maxObjectSize = 100 * 1000 * 1000; // 100Meg
exports.maxObjectCount = 32768;
// EPOCH = new SimpleDateFormat("yyyy MM dd zzz").parse("2001 01 01 GMT").getTime();
// ...but that's annoying in a static initializer because it can throw exceptions, ick.
// So we just hardcode the correct value.
var EPOCH = 978307200000;
// UID object definition
var UID = exports.UID = function(id) {
this.UID = id;
}
var parseFile = exports.parseFile = function (fileNameOrBuffer, callback) {
function tryParseBuffer(buffer) {
var err = null;
var result;
try {
result = parseBuffer(buffer);
} catch (ex) {
err = ex;
}
callback(err, result);
}
if (Buffer.isBuffer(fileNameOrBuffer)) {
return tryParseBuffer(fileNameOrBuffer);
} else {
fs.readFile(fileNameOrBuffer, function (err, data) {
if (err) { return callback(err); }
tryParseBuffer(data);
});
}
};
var parseBuffer = exports.parseBuffer = function (buffer) {
var result = {};
// check header
var header = buffer.slice(0, 'bplist'.length).toString('utf8');
if (header !== 'bplist') {
throw new Error("Invalid binary plist. Expected 'bplist' at offset 0.");
}
// Handle trailer, last 32 bytes of the file
var trailer = buffer.slice(buffer.length - 32, buffer.length);
// 6 null bytes (index 0 to 5)
var offsetSize = trailer.readUInt8(6);
if (debug) {
console.log("offsetSize: " + offsetSize);
}
var objectRefSize = trailer.readUInt8(7);
if (debug) {
console.log("objectRefSize: " + objectRefSize);
}
var numObjects = readUInt64BE(trailer, 8);
if (debug) {
console.log("numObjects: " + numObjects);
}
var topObject = readUInt64BE(trailer, 16);
if (debug) {
console.log("topObject: " + topObject);
}
var offsetTableOffset = readUInt64BE(trailer, 24);
if (debug) {
console.log("offsetTableOffset: " + offsetTableOffset);
}
if (numObjects > exports.maxObjectCount) {
throw new Error("maxObjectCount exceeded");
}
// Handle offset table
var offsetTable = [];
for (var i = 0; i < numObjects; i++) {
var offsetBytes = buffer.slice(offsetTableOffset + i * offsetSize, offsetTableOffset + (i + 1) * offsetSize);
offsetTable[i] = readUInt(offsetBytes, 0);
if (debug) {
console.log("Offset for Object #" + i + " is " + offsetTable[i] + " [" + offsetTable[i].toString(16) + "]");
}
}
// Parses an object inside the currently parsed binary property list.
// For the format specification check
// <a href="http://www.opensource.apple.com/source/CF/CF-635/CFBinaryPList.c">
// Apple's binary property list parser implementation</a>.
function parseObject(tableOffset) {
var offset = offsetTable[tableOffset];
var type = buffer[offset];
var objType = (type & 0xF0) >> 4; //First 4 bits
var objInfo = (type & 0x0F); //Second 4 bits
switch (objType) {
case 0x0:
return parseSimple();
case 0x1:
return parseInteger();
case 0x8:
return parseUID();
case 0x2:
return parseReal();
case 0x3:
return parseDate();
case 0x4:
return parseData();
case 0x5: // ASCII
return parsePlistString();
case 0x6: // UTF-16
return parsePlistString(true);
case 0xA:
return parseArray();
case 0xD:
return parseDictionary();
default:
throw new Error("Unhandled type 0x" + objType.toString(16));
}
function parseSimple() {
//Simple
switch (objInfo) {
case 0x0: // null
return null;
case 0x8: // false
return false;
case 0x9: // true
return true;
case 0xF: // filler byte
return null;
default:
throw new Error("Unhandled simple type 0x" + objType.toString(16));
}
}
function parseInteger() {
var length = Math.pow(2, objInfo);
if (length < exports.maxObjectSize) {
return readUInt(buffer.slice(offset + 1, offset + 1 + length));
} else {
throw new Error("To little heap space available! Wanted to read " + length + " bytes, but only " + exports.maxObjectSize + " are available.");
}
}
function parseUID() {
var length = objInfo + 1;
if (length < exports.maxObjectSize) {
return new UID(readUInt(buffer.slice(offset + 1, offset + 1 + length)));
} else {
throw new Error("To little heap space available! Wanted to read " + length + " bytes, but only " + exports.maxObjectSize + " are available.");
}
}
function parseReal() {
var length = Math.pow(2, objInfo);
if (length < exports.maxObjectSize) {
var realBuffer = buffer.slice(offset + 1, offset + 1 + length);
if (length === 4) {
return realBuffer.readFloatBE(0);
}
else if (length === 8) {
return realBuffer.readDoubleBE(0);
}
} else {
throw new Error("To little heap space available! Wanted to read " + length + " bytes, but only " + exports.maxObjectSize + " are available.");
}
}
function parseDate() {
if (objInfo != 0x3) {
console.error("Unknown date type :" + objInfo + ". Parsing anyway...");
}
var dateBuffer = buffer.slice(offset + 1, offset + 9);
return new Date(EPOCH + (1000 * dateBuffer.readDoubleBE(0)));
}
function parseData() {
var dataoffset = 1;
var length = objInfo;
if (objInfo == 0xF) {
var int_type = buffer[offset + 1];
var intType = (int_type & 0xF0) / 0x10;
if (intType != 0x1) {
console.error("0x4: UNEXPECTED LENGTH-INT TYPE! " + intType);
}
var intInfo = int_type & 0x0F;
var intLength = Math.pow(2, intInfo);
dataoffset = 2 + intLength;
if (intLength < 3) {
length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
} else {
length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
}
}
if (length < exports.maxObjectSize) {
return buffer.slice(offset + dataoffset, offset + dataoffset + length);
} else {
throw new Error("To little heap space available! Wanted to read " + length + " bytes, but only " + exports.maxObjectSize + " are available.");
}
}
function parsePlistString (isUtf16) {
isUtf16 = isUtf16 || 0;
var enc = "utf8";
var length = objInfo;
var stroffset = 1;
if (objInfo == 0xF) {
var int_type = buffer[offset + 1];
var intType = (int_type & 0xF0) / 0x10;
if (intType != 0x1) {
console.err("UNEXPECTED LENGTH-INT TYPE! " + intType);
}
var intInfo = int_type & 0x0F;
var intLength = Math.pow(2, intInfo);
var stroffset = 2 + intLength;
if (intLength < 3) {
length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
} else {
length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
}
}
// length is String length -> to get byte length multiply by 2, as 1 character takes 2 bytes in UTF-16
length *= (isUtf16 + 1);
if (length < exports.maxObjectSize) {
var plistString = new Buffer(buffer.slice(offset + stroffset, offset + stroffset + length));
if (isUtf16) {
plistString = swapBytes(plistString);
enc = "ucs2";
}
return plistString.toString(enc);
} else {
throw new Error("To little heap space available! Wanted to read " + length + " bytes, but only " + exports.maxObjectSize + " are available.");
}
}
function parseArray() {
var length = objInfo;
var arrayoffset = 1;
if (objInfo == 0xF) {
var int_type = buffer[offset + 1];
var intType = (int_type & 0xF0) / 0x10;
if (intType != 0x1) {
console.error("0xa: UNEXPECTED LENGTH-INT TYPE! " + intType);
}
var intInfo = int_type & 0x0F;
var intLength = Math.pow(2, intInfo);
arrayoffset = 2 + intLength;
if (intLength < 3) {
length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
} else {
length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
}
}
if (length * objectRefSize > exports.maxObjectSize) {
throw new Error("To little heap space available!");
}
var array = [];
for (var i = 0; i < length; i++) {
var objRef = readUInt(buffer.slice(offset + arrayoffset + i * objectRefSize, offset + arrayoffset + (i + 1) * objectRefSize));
array[i] = parseObject(objRef);
}
return array;
}
function parseDictionary() {
var length = objInfo;
var dictoffset = 1;
if (objInfo == 0xF) {
var int_type = buffer[offset + 1];
var intType = (int_type & 0xF0) / 0x10;
if (intType != 0x1) {
console.error("0xD: UNEXPECTED LENGTH-INT TYPE! " + intType);
}
var intInfo = int_type & 0x0F;
var intLength = Math.pow(2, intInfo);
dictoffset = 2 + intLength;
if (intLength < 3) {
length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
} else {
length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
}
}
if (length * 2 * objectRefSize > exports.maxObjectSize) {
throw new Error("To little heap space available!");
}
if (debug) {
console.log("Parsing dictionary #" + tableOffset);
}
var dict = {};
for (var i = 0; i < length; i++) {
var keyRef = readUInt(buffer.slice(offset + dictoffset + i * objectRefSize, offset + dictoffset + (i + 1) * objectRefSize));
var valRef = readUInt(buffer.slice(offset + dictoffset + (length * objectRefSize) + i * objectRefSize, offset + dictoffset + (length * objectRefSize) + (i + 1) * objectRefSize));
var key = parseObject(keyRef);
var val = parseObject(valRef);
if (debug) {
console.log(" DICT #" + tableOffset + ": Mapped " + key + " to " + val);
}
dict[key] = val;
}
return dict;
}
}
return [ parseObject(topObject) ];
};
function readUInt(buffer, start) {
start = start || 0;
var l = 0;
for (var i = start; i < buffer.length; i++) {
l <<= 8;
l |= buffer[i] & 0xFF;
}
return l;
}
// we're just going to toss the high order bits because javascript doesn't have 64-bit ints
function readUInt64BE(buffer, start) {
var data = buffer.slice(start, start + 8);
return data.readUInt32BE(4, 8);
}
function swapBytes(buffer) {
var len = buffer.length;
for (var i = 0; i < len; i += 2) {
var a = buffer[i];
buffer[i] = buffer[i+1];
buffer[i+1] = a;
}
return buffer;
}

View File

@@ -0,0 +1,36 @@
{
"name": "bplist-parser",
"version": "0.1.0",
"description": "Binary plist parser.",
"main": "bplistParser.js",
"scripts": {
"test": "./node_modules/nodeunit/bin/nodeunit test"
},
"keywords": [
"bplist",
"plist",
"parser"
],
"author": {
"name": "Joe Ferner",
"email": "joe.ferner@nearinfinity.com"
},
"license": "MIT",
"devDependencies": {
"nodeunit": "~0.9.1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/nearinfinity/node-bplist-parser.git"
},
"readme": "bplist-parser\n=============\n\nBinary Mac OS X Plist (property list) parser.\n\n## Installation\n\n```bash\n$ npm install bplist-parser\n```\n\n## Quick Examples\n\n```javascript\nvar bplist = require('bplist-parser');\n\nbplist.parseFile('myPlist.bplist', function(err, obj) {\n if (err) throw err;\n\n console.log(JSON.stringify(obj));\n});\n```\n\n## License\n\n(The MIT License)\n\nCopyright (c) 2012 Near Infinity Corporation\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/nearinfinity/node-bplist-parser/issues"
},
"homepage": "https://github.com/nearinfinity/node-bplist-parser#readme",
"_id": "bplist-parser@0.1.0",
"_shasum": "630823f2056437d4dbefc20e84017f8bac48e008",
"_resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.1.0.tgz",
"_from": "bplist-parser@>=0.1.0 <0.2.0"
}

View File

@@ -0,0 +1 @@
node_modules

View File

@@ -0,0 +1,7 @@
language: node_js
sudo: false
node_js:
- "0.10"
install: npm install
script:
- npm test

View File

@@ -0,0 +1,14 @@
[![Build Status](https://travis-ci.org/stevengill/cordova-registry-mapper.svg?branch=master)](https://travis-ci.org/stevengill/cordova-registry-mapper)
#Cordova Registry Mapper
This module is used to map Cordova plugin ids to package names and vice versa.
When Cordova users add plugins to their projects using ids
(e.g. `cordova plugin add org.apache.cordova.device`),
this module will map that id to the corresponding package name so `cordova-lib` knows what to fetch from **npm**.
This module was created so the Apache Cordova project could migrate its plugins from
the [Cordova Registry](http://registry.cordova.io/)
to [npm](https://registry.npmjs.com/)
instead of having to maintain a registry.

View File

@@ -0,0 +1,199 @@
var map = {
'org.apache.cordova.battery-status':'cordova-plugin-battery-status',
'org.apache.cordova.camera':'cordova-plugin-camera',
'org.apache.cordova.console':'cordova-plugin-console',
'org.apache.cordova.contacts':'cordova-plugin-contacts',
'org.apache.cordova.device':'cordova-plugin-device',
'org.apache.cordova.device-motion':'cordova-plugin-device-motion',
'org.apache.cordova.device-orientation':'cordova-plugin-device-orientation',
'org.apache.cordova.dialogs':'cordova-plugin-dialogs',
'org.apache.cordova.file':'cordova-plugin-file',
'org.apache.cordova.file-transfer':'cordova-plugin-file-transfer',
'org.apache.cordova.geolocation':'cordova-plugin-geolocation',
'org.apache.cordova.globalization':'cordova-plugin-globalization',
'org.apache.cordova.inappbrowser':'cordova-plugin-inappbrowser',
'org.apache.cordova.media':'cordova-plugin-media',
'org.apache.cordova.media-capture':'cordova-plugin-media-capture',
'org.apache.cordova.network-information':'cordova-plugin-network-information',
'org.apache.cordova.splashscreen':'cordova-plugin-splashscreen',
'org.apache.cordova.statusbar':'cordova-plugin-statusbar',
'org.apache.cordova.vibration':'cordova-plugin-vibration',
'org.apache.cordova.test-framework':'cordova-plugin-test-framework',
'com.msopentech.websql' : 'cordova-plugin-websql',
'com.msopentech.indexeddb' : 'cordova-plugin-indexeddb',
'com.microsoft.aad.adal' : 'cordova-plugin-ms-adal',
'com.microsoft.capptain' : 'capptain-cordova',
'com.microsoft.services.aadgraph' : 'cordova-plugin-ms-aad-graph',
'com.microsoft.services.files' : 'cordova-plugin-ms-files',
'om.microsoft.services.outlook' : 'cordova-plugin-ms-outlook',
'com.pbakondy.sim' : 'cordova-plugin-sim',
'android.support.v4' : 'cordova-plugin-android-support-v4',
'android.support.v7-appcompat' : 'cordova-plugin-android-support-v7-appcompat',
'com.google.playservices' : 'cordova-plugin-googleplayservices',
'com.google.cordova.admob' : 'cordova-plugin-admobpro',
'com.rjfun.cordova.extension' : 'cordova-plugin-extension',
'com.rjfun.cordova.plugin.admob' : 'cordova-plugin-admob',
'com.rjfun.cordova.flurryads' : 'cordova-plugin-flurry',
'com.rjfun.cordova.facebookads' : 'cordova-plugin-facebookads',
'com.rjfun.cordova.httpd' : 'cordova-plugin-httpd',
'com.rjfun.cordova.iad' : 'cordova-plugin-iad',
'com.rjfun.cordova.iflyspeech' : 'cordova-plugin-iflyspeech',
'com.rjfun.cordova.lianlianpay' : 'cordova-plugin-lianlianpay',
'com.rjfun.cordova.mobfox' : 'cordova-plugin-mobfox',
'com.rjfun.cordova.mopub' : 'cordova-plugin-mopub',
'com.rjfun.cordova.mmedia' : 'cordova-plugin-mmedia',
'com.rjfun.cordova.nativeaudio' : 'cordova-plugin-nativeaudio',
'com.rjfun.cordova.plugin.paypalmpl' : 'cordova-plugin-paypalmpl',
'com.rjfun.cordova.smartadserver' : 'cordova-plugin-smartadserver',
'com.rjfun.cordova.sms' : 'cordova-plugin-sms',
'com.rjfun.cordova.wifi' : 'cordova-plugin-wifi',
'com.ohh2ahh.plugins.appavailability' : 'cordova-plugin-appavailability',
'org.adapt-it.cordova.fonts' : 'cordova-plugin-fonts',
'de.martinreinhardt.cordova.plugins.barcodeScanner' : 'cordova-plugin-barcodescanner',
'de.martinreinhardt.cordova.plugins.urlhandler' : 'cordova-plugin-urlhandler',
'de.martinreinhardt.cordova.plugins.email' : 'cordova-plugin-email',
'de.martinreinhardt.cordova.plugins.certificates' : 'cordova-plugin-certificates',
'de.martinreinhardt.cordova.plugins.sqlite' : 'cordova-plugin-sqlite',
'fr.smile.cordova.fileopener' : 'cordova-plugin-fileopener',
'org.smile.websqldatabase.initializer' : 'cordova-plugin-websqldatabase-initializer',
'org.smile.websqldatabase.wpdb' : 'cordova-plugin-websqldatabase',
'org.jboss.aerogear.cordova.push' : 'aerogear-cordova-push',
'org.jboss.aerogear.cordova.oauth2' : 'aerogear-cordova-oauth2',
'org.jboss.aerogear.cordova.geo' : 'aerogear-cordova-geo',
'org.jboss.aerogear.cordova.crypto' : 'aerogear-cordova-crypto',
'org.jboss.aerogaer.cordova.otp' : 'aerogear-cordova-otp',
'uk.co.ilee.applewatch' : 'cordova-plugin-apple-watch',
'uk.co.ilee.directions' : 'cordova-plugin-directions',
'uk.co.ilee.gamecenter' : 'cordova-plugin-game-center',
'uk.co.ilee.jailbreakdetection' : 'cordova-plugin-jailbreak-detection',
'uk.co.ilee.nativetransitions' : 'cordova-plugin-native-transitions',
'uk.co.ilee.pedometer' : 'cordova-plugin-pedometer',
'uk.co.ilee.shake' : 'cordova-plugin-shake',
'uk.co.ilee.touchid' : 'cordova-plugin-touchid',
'com.knowledgecode.cordova.websocket' : 'cordova-plugin-websocket',
'com.elixel.plugins.settings' : 'cordova-plugin-settings',
'com.cowbell.cordova.geofence' : 'cordova-plugin-geofence',
'com.blackberry.community.preventsleep' : 'cordova-plugin-preventsleep',
'com.blackberry.community.gamepad' : 'cordova-plugin-gamepad',
'com.blackberry.community.led' : 'cordova-plugin-led',
'com.blackberry.community.thumbnail' : 'cordova-plugin-thumbnail',
'com.blackberry.community.mediakeys' : 'cordova-plugin-mediakeys',
'com.blackberry.community.simplebtlehrplugin' : 'cordova-plugin-bluetoothheartmonitor',
'com.blackberry.community.simplebeaconplugin' : 'cordova-plugin-bluetoothibeacon',
'com.blackberry.community.simplebtsppplugin' : 'cordova-plugin-bluetoothspp',
'com.blackberry.community.clipboard' : 'cordova-plugin-clipboard',
'com.blackberry.community.curl' : 'cordova-plugin-curl',
'com.blackberry.community.qt' : 'cordova-plugin-qtbridge',
'com.blackberry.community.upnp' : 'cordova-plugin-upnp',
'com.blackberry.community.PasswordCrypto' : 'cordova-plugin-password-crypto',
'com.blackberry.community.deviceinfoplugin' : 'cordova-plugin-deviceinfo',
'com.blackberry.community.gsecrypto' : 'cordova-plugin-bb-crypto',
'com.blackberry.community.mongoose' : 'cordova-plugin-mongoose',
'com.blackberry.community.sysdialog' : 'cordova-plugin-bb-sysdialog',
'com.blackberry.community.screendisplay' : 'cordova-plugin-screendisplay',
'com.blackberry.community.messageplugin' : 'cordova-plugin-bb-messageretrieve',
'com.blackberry.community.emailsenderplugin' : 'cordova-plugin-emailsender',
'com.blackberry.community.audiometadata' : 'cordova-plugin-audiometadata',
'com.blackberry.community.deviceemails' : 'cordova-plugin-deviceemails',
'com.blackberry.community.audiorecorder' : 'cordova-plugin-audiorecorder',
'com.blackberry.community.vibration' : 'cordova-plugin-vibrate-intense',
'com.blackberry.community.SMSPlugin' : 'cordova-plugin-bb-sms',
'com.blackberry.community.extractZipFile' : 'cordova-plugin-bb-zip',
'com.blackberry.community.lowlatencyaudio' : 'cordova-plugin-bb-nativeaudio',
'com.blackberry.community.barcodescanner' : 'phonegap-plugin-barcodescanner',
'com.blackberry.app' : 'cordova-plugin-bb-app',
'com.blackberry.bbm.platform' : 'cordova-plugin-bbm',
'com.blackberry.connection' : 'cordova-plugin-bb-connection',
'com.blackberry.identity' : 'cordova-plugin-bb-identity',
'com.blackberry.invoke.card' : 'cordova-plugin-bb-card',
'com.blackberry.invoke' : 'cordova-plugin-bb-invoke',
'com.blackberry.invoked' : 'cordova-plugin-bb-invoked',
'com.blackberry.io.filetransfer' : 'cordova-plugin-bb-filetransfer',
'com.blackberry.io' : 'cordova-plugin-bb-io',
'com.blackberry.notification' : 'cordova-plugin-bb-notification',
'com.blackberry.payment' : 'cordova-plugin-bb-payment',
'com.blackberry.pim.calendar' : 'cordova-plugin-bb-calendar',
'com.blackberry.pim.contacts' : 'cordova-plugin-bb-contacts',
'com.blackberry.pim.lib' : 'cordova-plugin-bb-pimlib',
'com.blackberry.push' : 'cordova-plugin-bb-push',
'com.blackberry.screenshot' : 'cordova-plugin-screenshot',
'com.blackberry.sensors' : 'cordova-plugin-bb-sensors',
'com.blackberry.system' : 'cordova-plugin-bb-system',
'com.blackberry.ui.contextmenu' : 'cordova-plugin-bb-ctxmenu',
'com.blackberry.ui.cover' : 'cordova-plugin-bb-cover',
'com.blackberry.ui.dialog' : 'cordova-plugin-bb-dialog',
'com.blackberry.ui.input' : 'cordova-plugin-touch-keyboard',
'com.blackberry.ui.toast' : 'cordova-plugin-toast',
'com.blackberry.user.identity' : 'cordova-plugin-bb-idservice',
'com.blackberry.utils' : 'cordova-plugin-bb-utils',
'net.yoik.cordova.plugins.screenorientation' : 'cordova-plugin-screen-orientation',
'com.phonegap.plugins.barcodescanner' : 'phonegap-plugin-barcodescanner',
'com.manifoldjs.hostedwebapp' : 'cordova-plugin-hostedwebapp',
'com.initialxy.cordova.themeablebrowser' : 'cordova-plugin-themeablebrowser',
'gr.denton.photosphere' : 'cordova-plugin-panoramaviewer',
'nl.x-services.plugins.actionsheet' : 'cordova-plugin-actionsheet',
'nl.x-services.plugins.socialsharing' : 'cordova-plugin-x-socialsharing',
'nl.x-services.plugins.googleplus' : 'cordova-plugin-googleplus',
'nl.x-services.plugins.insomnia' : 'cordova-plugin-insomnia',
'nl.x-services.plugins.toast' : 'cordova-plugin-x-toast',
'nl.x-services.plugins.calendar' : 'cordova-plugin-calendar',
'nl.x-services.plugins.launchmyapp' : 'cordova-plugin-customurlscheme',
'nl.x-services.plugins.flashlight' : 'cordova-plugin-flashlight',
'nl.x-services.plugins.sslcertificatechecker' : 'cordova-plugin-sslcertificatechecker',
'com.bridge.open' : 'cordova-open',
'com.bridge.safe' : 'cordova-safe',
'com.disusered.open' : 'cordova-open',
'com.disusered.safe' : 'cordova-safe',
'me.apla.cordova.app-preferences' : 'cordova-plugin-app-preferences',
'com.konotor.cordova' : 'cordova-plugin-konotor',
'io.intercom.cordova' : 'cordova-plugin-intercom',
'com.onesignal.plugins.onesignal' : 'onesignal-cordova-plugin',
'com.danjarvis.document-contract': 'cordova-plugin-document-contract',
'com.eface2face.iosrtc' : 'cordova-plugin-iosrtc',
'com.mobileapptracking.matplugin' : 'cordova-plugin-tune',
'com.marianhello.cordova.background-geolocation' : 'cordova-plugin-mauron85-background-geolocation',
'fr.louisbl.cordova.locationservices' : 'cordova-plugin-locationservices',
'fr.louisbl.cordova.gpslocation' : 'cordova-plugin-gpslocation',
'com.hiliaox.weibo' : 'cordova-plugin-weibo',
'com.uxcam.cordova.plugin' : 'cordova-uxcam',
'de.fastr.phonegap.plugins.downloader' : 'cordova-plugin-fastrde-downloader',
'de.fastr.phonegap.plugins.injectView' : 'cordova-plugin-fastrde-injectview',
'de.fastr.phonegap.plugins.CheckGPS' : 'cordova-plugin-fastrde-checkgps',
'de.fastr.phonegap.plugins.md5chksum' : ' cordova-plugin-fastrde-md5',
'io.repro.cordova' : 'cordova-plugin-repro',
're.notifica.cordova': 'cordova-plugin-notificare-push',
'com.megster.cordova.ble': 'cordova-plugin-ble-central',
'com.megster.cordova.bluetoothserial': 'cordova-plugin-bluetooth-serial',
'com.megster.cordova.rfduino': 'cordova-plugin-rfduino',
'cz.velda.cordova.plugin.devicefeedback': 'cordova-plugin-velda-devicefeedback',
'cz.Velda.cordova.plugin.devicefeedback': 'cordova-plugin-velda-devicefeedback',
'org.scriptotek.appinfo': 'cordova-plugin-appinfo',
'com.yezhiming.cordova.appinfo': 'cordova-plugin-appinfo',
'pl.makingwaves.estimotebeacons': 'cordova-plugin-estimote',
'com.evothings.ble': 'cordova-plugin-ble',
'com.appsee.plugin' : 'cordova-plugin-appsee',
'am.armsoft.plugins.listpicker': 'cordova-plugin-listpicker',
'com.pushbots.push': 'pushbots-cordova-plugin',
'com.admob.google': 'cordova-admob',
'admob.ads.google': 'cordova-admob-ads',
'admob.google.plugin': 'admob-google',
'com.admob.admobads': 'admob-ads',
'com.connectivity.monitor': 'cordova-connectivity-monitor',
'com.ios.libgoogleadmobads': 'cordova-libgoogleadmobads',
'com.google.play.services': 'cordova-google-play-services',
'android.support.v13': 'cordova-android-support-v13',
'android.support.v4': 'cordova-android-support-v4',
'com.analytics.google': 'cordova-plugin-analytics',
'com.analytics.adid.google': 'cordova-plugin-analytics-adid',
'com.chariotsolutions.nfc.plugin': 'phonegap-nfc',
'com.samz.mixpanel': 'cordova-plugin-mixpanel'
}
module.exports.oldToNew = map;
var reverseMap = {};
Object.keys(map).forEach(function(elem){
reverseMap[map[elem]] = elem;
})
module.exports.newToOld = reverseMap;

View File

@@ -0,0 +1 @@
/node_modules

View File

@@ -2,4 +2,3 @@ language: node_js
node_js:
- "0.8"
- "0.10"
- "0.11"

View File

@@ -0,0 +1,18 @@
This software is released under the MIT license:
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.

View File

@@ -0,0 +1,140 @@
var defined = require('defined');
var createDefaultStream = require('./lib/default_stream');
var Test = require('./lib/test');
var createResult = require('./lib/results');
var through = require('through');
var canEmitExit = typeof process !== 'undefined' && process
&& typeof process.on === 'function' && process.browser !== true
;
var canExit = typeof process !== 'undefined' && process
&& typeof process.exit === 'function'
;
var nextTick = typeof setImmediate !== 'undefined'
? setImmediate
: process.nextTick
;
exports = module.exports = (function () {
var harness;
var lazyLoad = function () {
return getHarness().apply(this, arguments);
};
lazyLoad.only = function () {
return getHarness().only.apply(this, arguments);
};
lazyLoad.createStream = function (opts) {
if (!opts) opts = {};
if (!harness) {
var output = through();
getHarness({ stream: output, objectMode: opts.objectMode });
return output;
}
return harness.createStream(opts);
};
return lazyLoad
function getHarness (opts) {
if (!opts) opts = {};
opts.autoclose = !canEmitExit;
if (!harness) harness = createExitHarness(opts);
return harness;
}
})();
function createExitHarness (conf) {
if (!conf) conf = {};
var harness = createHarness({
autoclose: defined(conf.autoclose, false)
});
var stream = harness.createStream({ objectMode: conf.objectMode });
var es = stream.pipe(conf.stream || createDefaultStream());
if (canEmitExit) {
es.on('error', function (err) { harness._exitCode = 1 });
}
var ended = false;
stream.on('end', function () { ended = true });
if (conf.exit === false) return harness;
if (!canEmitExit || !canExit) return harness;
var inErrorState = false;
process.on('exit', function (code) {
// let the process exit cleanly.
if (code !== 0) {
return
}
if (!ended) {
var only = harness._results._only;
for (var i = 0; i < harness._tests.length; i++) {
var t = harness._tests[i];
if (only && t.name !== only) continue;
t._exit();
}
}
harness.close();
process.exit(code || harness._exitCode);
});
return harness;
}
exports.createHarness = createHarness;
exports.Test = Test;
exports.test = exports; // tap compat
exports.test.skip = Test.skip;
var exitInterval;
function createHarness (conf_) {
if (!conf_) conf_ = {};
var results = createResult();
if (conf_.autoclose !== false) {
results.once('done', function () { results.close() });
}
var test = function (name, conf, cb) {
var t = new Test(name, conf, cb);
test._tests.push(t);
(function inspectCode (st) {
st.on('test', function sub (st_) {
inspectCode(st_);
});
st.on('result', function (r) {
if (!r.ok) test._exitCode = 1
});
})(t);
results.push(t);
return t;
};
test._results = results;
test._tests = [];
test.createStream = function (opts) {
return results.createStream(opts);
};
var only = false;
test.only = function (name) {
if (only) throw new Error('there can only be one only test');
results.only(name);
only = true;
return test.apply(null, arguments);
};
test._exitCode = 0;
test.close = function () { results.close() };
return test;
}

View File

@@ -0,0 +1,31 @@
var through = require('through');
var fs = require('fs');
module.exports = function () {
var line = '';
var stream = through(write, flush);
return stream;
function write (buf) {
for (var i = 0; i < buf.length; i++) {
var c = typeof buf === 'string'
? buf.charAt(i)
: String.fromCharCode(buf[i])
;
if (c === '\n') flush();
else line += c;
}
}
function flush () {
if (fs.writeSync && /^win/.test(process.platform)) {
try { fs.writeSync(1, line + '\n'); }
catch (e) { stream.emit('error', e) }
}
else {
try { console.log(line) }
catch (e) { stream.emit('error', e) }
}
line = '';
}
};

View File

@@ -0,0 +1,189 @@
var EventEmitter = require('events').EventEmitter;
var inherits = require('inherits');
var through = require('through');
var resumer = require('resumer');
var inspect = require('object-inspect');
var nextTick = typeof setImmediate !== 'undefined'
? setImmediate
: process.nextTick
;
module.exports = Results;
inherits(Results, EventEmitter);
function Results () {
if (!(this instanceof Results)) return new Results;
this.count = 0;
this.fail = 0;
this.pass = 0;
this._stream = through();
this.tests = [];
}
Results.prototype.createStream = function (opts) {
if (!opts) opts = {};
var self = this;
var output, testId = 0;
if (opts.objectMode) {
output = through();
self.on('_push', function ontest (t, extra) {
if (!extra) extra = {};
var id = testId++;
t.once('prerun', function () {
var row = {
type: 'test',
name: t.name,
id: id
};
if (has(extra, 'parent')) {
row.parent = extra.parent;
}
output.queue(row);
});
t.on('test', function (st) {
ontest(st, { parent: id });
});
t.on('result', function (res) {
res.test = id;
res.type = 'assert';
output.queue(res);
});
t.on('end', function () {
output.queue({ type: 'end', test: id });
});
});
self.on('done', function () { output.queue(null) });
}
else {
output = resumer();
output.queue('TAP version 13\n');
self._stream.pipe(output);
}
nextTick(function next() {
var t;
while (t = getNextTest(self)) {
t.run();
if (!t.ended) return t.once('end', function(){ nextTick(next); });
}
self.emit('done');
});
return output;
};
Results.prototype.push = function (t) {
var self = this;
self.tests.push(t);
self._watch(t);
self.emit('_push', t);
};
Results.prototype.only = function (name) {
if (this._only) {
self.count ++;
self.fail ++;
write('not ok ' + self.count + ' already called .only()\n');
}
this._only = name;
};
Results.prototype._watch = function (t) {
var self = this;
var write = function (s) { self._stream.queue(s) };
t.once('prerun', function () {
write('# ' + t.name + '\n');
});
t.on('result', function (res) {
if (typeof res === 'string') {
write('# ' + res + '\n');
return;
}
write(encodeResult(res, self.count + 1));
self.count ++;
if (res.ok) self.pass ++
else self.fail ++
});
t.on('test', function (st) { self._watch(st) });
};
Results.prototype.close = function () {
var self = this;
if (self.closed) self._stream.emit('error', new Error('ALREADY CLOSED'));
self.closed = true;
var write = function (s) { self._stream.queue(s) };
write('\n1..' + self.count + '\n');
write('# tests ' + self.count + '\n');
write('# pass ' + self.pass + '\n');
if (self.fail) write('# fail ' + self.fail + '\n')
else write('\n# ok\n')
self._stream.queue(null);
};
function encodeResult (res, count) {
var output = '';
output += (res.ok ? 'ok ' : 'not ok ') + count;
output += res.name ? ' ' + res.name.toString().replace(/\s+/g, ' ') : '';
if (res.skip) output += ' # SKIP';
else if (res.todo) output += ' # TODO';
output += '\n';
if (res.ok) return output;
var outer = ' ';
var inner = outer + ' ';
output += outer + '---\n';
output += inner + 'operator: ' + res.operator + '\n';
if (has(res, 'expected') || has(res, 'actual')) {
var ex = inspect(res.expected);
var ac = inspect(res.actual);
if (Math.max(ex.length, ac.length) > 65) {
output += inner + 'expected:\n' + inner + ' ' + ex + '\n';
output += inner + 'actual:\n' + inner + ' ' + ac + '\n';
}
else {
output += inner + 'expected: ' + ex + '\n';
output += inner + 'actual: ' + ac + '\n';
}
}
if (res.at) {
output += inner + 'at: ' + res.at + '\n';
}
if (res.operator === 'error' && res.actual && res.actual.stack) {
var lines = String(res.actual.stack).split('\n');
output += inner + 'stack:\n';
output += inner + ' ' + lines[0] + '\n';
for (var i = 1; i < lines.length; i++) {
output += inner + lines[i] + '\n';
}
}
output += outer + '...\n';
return output;
}
function getNextTest (results) {
if (!results._only) {
return results.tests.shift();
}
do {
var t = results.tests.shift();
if (!t) continue;
if (results._only === t.name) {
return t;
}
} while (results.tests.length !== 0)
}
function has (obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}

View File

@@ -0,0 +1,496 @@
var deepEqual = require('deep-equal');
var defined = require('defined');
var path = require('path');
var inherits = require('inherits');
var EventEmitter = require('events').EventEmitter;
module.exports = Test;
var nextTick = typeof setImmediate !== 'undefined'
? setImmediate
: process.nextTick
;
inherits(Test, EventEmitter);
var getTestArgs = function (name_, opts_, cb_) {
var name = '(anonymous)';
var opts = {};
var cb;
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
var t = typeof arg;
if (t === 'string') {
name = arg;
}
else if (t === 'object') {
opts = arg || opts;
}
else if (t === 'function') {
cb = arg;
}
}
return { name: name, opts: opts, cb: cb };
};
function Test (name_, opts_, cb_) {
if (! (this instanceof Test)) {
return new Test(name_, opts_, cb_);
}
var args = getTestArgs(name_, opts_, cb_);
this.readable = true;
this.name = args.name || '(anonymous)';
this.assertCount = 0;
this.pendingCount = 0;
this._skip = args.opts.skip || false;
this._plan = undefined;
this._cb = args.cb;
this._progeny = [];
this._ok = true;
if (args.opts.timeout !== undefined) {
this.timeoutAfter(args.opts.timeout);
}
for (var prop in this) {
this[prop] = (function bind(self, val) {
if (typeof val === 'function') {
return function bound() {
return val.apply(self, arguments);
};
}
else return val;
})(this, this[prop]);
}
}
Test.prototype.run = function () {
if (!this._cb || this._skip) {
return this._end();
}
this.emit('prerun');
this._cb(this);
this.emit('run');
};
Test.prototype.test = function (name, opts, cb) {
var self = this;
var t = new Test(name, opts, cb);
this._progeny.push(t);
this.pendingCount++;
this.emit('test', t);
t.on('prerun', function () {
self.assertCount++;
})
if (!self._pendingAsserts()) {
nextTick(function () {
self._end();
});
}
nextTick(function() {
if (!self._plan && self.pendingCount == self._progeny.length) {
self._end();
}
});
};
Test.prototype.comment = function (msg) {
this.emit('result', msg.trim().replace(/^#\s*/, ''));
};
Test.prototype.plan = function (n) {
this._plan = n;
this.emit('plan', n);
};
Test.prototype.timeoutAfter = function(ms) {
if (!ms) throw new Error('timeoutAfter requires a timespan');
var self = this;
var timeout = setTimeout(function() {
self.fail('test timed out after ' + ms + 'ms');
self.end();
}, ms);
this.once('end', function() {
clearTimeout(timeout);
});
}
Test.prototype.end = function (err) {
var self = this;
if (arguments.length >= 1) {
this.ifError(err);
}
if (this.calledEnd) {
this.fail('.end() called twice');
}
this.calledEnd = true;
this._end();
};
Test.prototype._end = function (err) {
var self = this;
if (this._progeny.length) {
var t = this._progeny.shift();
t.on('end', function () { self._end() });
t.run();
return;
}
if (!this.ended) this.emit('end');
var pendingAsserts = this._pendingAsserts();
if (!this._planError && this._plan !== undefined && pendingAsserts) {
this._planError = true;
this.fail('plan != count', {
expected : this._plan,
actual : this.assertCount
});
}
this.ended = true;
};
Test.prototype._exit = function () {
if (this._plan !== undefined &&
!this._planError && this.assertCount !== this._plan) {
this._planError = true;
this.fail('plan != count', {
expected : this._plan,
actual : this.assertCount,
exiting : true
});
}
else if (!this.ended) {
this.fail('test exited without ending', {
exiting: true
});
}
};
Test.prototype._pendingAsserts = function () {
if (this._plan === undefined) {
return 1;
}
else {
return this._plan - (this._progeny.length + this.assertCount);
}
};
Test.prototype._assert = function assert (ok, opts) {
var self = this;
var extra = opts.extra || {};
var res = {
id : self.assertCount ++,
ok : Boolean(ok),
skip : defined(extra.skip, opts.skip),
name : defined(extra.message, opts.message, '(unnamed assert)'),
operator : defined(extra.operator, opts.operator)
};
if (has(opts, 'actual') || has(extra, 'actual')) {
res.actual = defined(extra.actual, opts.actual);
}
if (has(opts, 'expected') || has(extra, 'expected')) {
res.expected = defined(extra.expected, opts.expected);
}
this._ok = Boolean(this._ok && ok);
if (!ok) {
res.error = defined(extra.error, opts.error, new Error(res.name));
}
if (!ok) {
var e = new Error('exception');
var err = (e.stack || '').split('\n');
var dir = path.dirname(__dirname) + '/';
for (var i = 0; i < err.length; i++) {
var m = /^[^\s]*\s*\bat\s+(.+)/.exec(err[i]);
if (!m) {
continue;
}
var s = m[1].split(/\s+/);
var filem = /(\/[^:\s]+:(\d+)(?::(\d+))?)/.exec(s[1]);
if (!filem) {
filem = /(\/[^:\s]+:(\d+)(?::(\d+))?)/.exec(s[2]);
if (!filem) {
filem = /(\/[^:\s]+:(\d+)(?::(\d+))?)/.exec(s[3]);
if (!filem) {
continue;
}
}
}
if (filem[1].slice(0, dir.length) === dir) {
continue;
}
res.functionName = s[0];
res.file = filem[1];
res.line = Number(filem[2]);
if (filem[3]) res.column = filem[3];
res.at = m[1];
break;
}
}
self.emit('result', res);
var pendingAsserts = self._pendingAsserts();
if (!pendingAsserts) {
if (extra.exiting) {
self._end();
} else {
nextTick(function () {
self._end();
});
}
}
if (!self._planError && pendingAsserts < 0) {
self._planError = true;
self.fail('plan != count', {
expected : self._plan,
actual : self._plan - pendingAsserts
});
}
};
Test.prototype.fail = function (msg, extra) {
this._assert(false, {
message : msg,
operator : 'fail',
extra : extra
});
};
Test.prototype.pass = function (msg, extra) {
this._assert(true, {
message : msg,
operator : 'pass',
extra : extra
});
};
Test.prototype.skip = function (msg, extra) {
this._assert(true, {
message : msg,
operator : 'skip',
skip : true,
extra : extra
});
};
Test.prototype.ok
= Test.prototype['true']
= Test.prototype.assert
= function (value, msg, extra) {
this._assert(value, {
message : msg,
operator : 'ok',
expected : true,
actual : value,
extra : extra
});
};
Test.prototype.notOk
= Test.prototype['false']
= Test.prototype.notok
= function (value, msg, extra) {
this._assert(!value, {
message : msg,
operator : 'notOk',
expected : false,
actual : value,
extra : extra
});
};
Test.prototype.error
= Test.prototype.ifError
= Test.prototype.ifErr
= Test.prototype.iferror
= function (err, msg, extra) {
this._assert(!err, {
message : defined(msg, String(err)),
operator : 'error',
actual : err,
extra : extra
});
};
Test.prototype.equal
= Test.prototype.equals
= Test.prototype.isEqual
= Test.prototype.is
= Test.prototype.strictEqual
= Test.prototype.strictEquals
= function (a, b, msg, extra) {
this._assert(a === b, {
message : defined(msg, 'should be equal'),
operator : 'equal',
actual : a,
expected : b,
extra : extra
});
};
Test.prototype.notEqual
= Test.prototype.notEquals
= Test.prototype.notStrictEqual
= Test.prototype.notStrictEquals
= Test.prototype.isNotEqual
= Test.prototype.isNot
= Test.prototype.not
= Test.prototype.doesNotEqual
= Test.prototype.isInequal
= function (a, b, msg, extra) {
this._assert(a !== b, {
message : defined(msg, 'should not be equal'),
operator : 'notEqual',
actual : a,
notExpected : b,
extra : extra
});
};
Test.prototype.deepEqual
= Test.prototype.deepEquals
= Test.prototype.isEquivalent
= Test.prototype.same
= function (a, b, msg, extra) {
this._assert(deepEqual(a, b, { strict: true }), {
message : defined(msg, 'should be equivalent'),
operator : 'deepEqual',
actual : a,
expected : b,
extra : extra
});
};
Test.prototype.deepLooseEqual
= Test.prototype.looseEqual
= Test.prototype.looseEquals
= function (a, b, msg, extra) {
this._assert(deepEqual(a, b), {
message : defined(msg, 'should be equivalent'),
operator : 'deepLooseEqual',
actual : a,
expected : b,
extra : extra
});
};
Test.prototype.notDeepEqual
= Test.prototype.notEquivalent
= Test.prototype.notDeeply
= Test.prototype.notSame
= Test.prototype.isNotDeepEqual
= Test.prototype.isNotDeeply
= Test.prototype.isNotEquivalent
= Test.prototype.isInequivalent
= function (a, b, msg, extra) {
this._assert(!deepEqual(a, b, { strict: true }), {
message : defined(msg, 'should not be equivalent'),
operator : 'notDeepEqual',
actual : a,
notExpected : b,
extra : extra
});
};
Test.prototype.notDeepLooseEqual
= Test.prototype.notLooseEqual
= Test.prototype.notLooseEquals
= function (a, b, msg, extra) {
this._assert(!deepEqual(a, b), {
message : defined(msg, 'should be equivalent'),
operator : 'notDeepLooseEqual',
actual : a,
expected : b,
extra : extra
});
};
Test.prototype['throws'] = function (fn, expected, msg, extra) {
if (typeof expected === 'string') {
msg = expected;
expected = undefined;
}
var caught = undefined;
try {
fn();
} catch (err) {
caught = { error : err };
var message = err.message;
delete err.message;
err.message = message;
}
var passed = caught;
if (expected instanceof RegExp) {
passed = expected.test(caught && caught.error);
expected = String(expected);
}
if (typeof expected === 'function') {
passed = caught.error instanceof expected;
caught.error = caught.error.constructor;
}
this._assert(passed, {
message : defined(msg, 'should throw'),
operator : 'throws',
actual : caught && caught.error,
expected : expected,
error: !passed && caught && caught.error,
extra : extra
});
};
Test.prototype.doesNotThrow = function (fn, expected, msg, extra) {
if (typeof expected === 'string') {
msg = expected;
expected = undefined;
}
var caught = undefined;
try {
fn();
}
catch (err) {
caught = { error : err };
}
this._assert(!caught, {
message : defined(msg, 'should not throw'),
operator : 'throws',
actual : caught && caught.error,
expected : expected,
error : caught && caught.error,
extra : extra
});
};
function has (obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
Test.skip = function (name_, _opts, _cb) {
var args = getTestArgs.apply(null, arguments);
args.opts.skip = true;
return Test(args.name, args.opts, args.cb);
};
// vim: set softtabstop=4 shiftwidth=4:

View File

@@ -0,0 +1,4 @@
language: node_js
node_js:
- 0.4
- 0.6

View File

@@ -0,0 +1,18 @@
This software is released under the MIT license:
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.

Some files were not shown because too many files have changed in this diff Show More