Merge remote-tracking branch 'origin/android_tweaks' into cordova-httpd

Conflicts:
	tools/commands-cordova.js
This commit is contained in:
Slava Kim
2014-09-10 10:43:26 -07:00
9 changed files with 476 additions and 28 deletions

View File

@@ -13,8 +13,8 @@
if (/Android/i.test(navigator.userAgent)) {
// When Android app is emulated, it cannot connect to localhost,
// instead it should connect to 10.0.2.2
__meteor_runtime_config__.ROOT_URL = (__meteor_runtime_config__.ROOT_URL || '').replace(/localhost/i, '10.0.2.2');
__meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL = (__meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL || '').replace(/localhost/i, '10.0.2.2');
//__meteor_runtime_config__.ROOT_URL = (__meteor_runtime_config__.ROOT_URL || '').replace(/localhost/i, '10.0.2.2');
//__meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL = (__meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL || '').replace(/localhost/i, '10.0.2.2');
}
</script>

View File

@@ -46,7 +46,7 @@ else
fi
{
curl -O http://www.motorlogy.com/apache//ant/binaries/apache-ant-1.9.4-bin.tar.gz
curl -O http://apache.osuosl.org/ant/binaries/apache-ant-1.9.4-bin.tar.gz
tar xzf apache-ant-1.9.4-bin.tar.gz
rm apache-ant-1.9.4-bin.tar.gz
@@ -58,9 +58,12 @@ fi
# the platform that cordova likes
echo y | android-sdk/tools/android update sdk -t android-19 -u
# system image for android 19
# system image for android 19 - arm
echo y | android-sdk/tools/android update sdk -t sys-img-armeabi-v7a-android-19 --all -u
# system image for android 19 - x86
echo y | android-sdk/tools/android update sdk -t sys-img-x86-android-19 --all -u
# build tools
echo y | android-sdk/tools/android update sdk -t "build-tools-20.0.0" -u

View File

@@ -57,7 +57,11 @@ var execFileAsyncOrThrow = function (file, args, opts, cb) {
}
// XXX a hack to always tell the scripts where warehouse is
if (opts) opts.env = _.extend({ "WAREHOUSE_DIR": tropo.root, "USE_GLOBAL_ADK": process.env.USE_GLOBAL_ADK || "", HOME: process.env.HOME }, opts.env);
opts = opts || {};
opts.env = _.extend({ "USE_GLOBAL_ADK": "" },
process.env,
opts.env || {},
{ "WAREHOUSE_DIR": tropo.root });
var execFileAsync = require('./utils.js').execFileAsync;
ensureAndroidBundle(file);
@@ -82,7 +86,11 @@ var execFileSyncOrThrow = function (file, args, opts) {
verboseLog('Running synchronously: ', file, args);
// XXX a hack to always tell the scripts where warehouse is
if (opts) opts.env = _.extend({ "WAREHOUSE_DIR": tropo.root, "USE_GLOBAL_ADK": process.env.USE_GLOBAL_ADK || "", HOME: process.env.HOME }, opts.env);
opts = opts || {};
opts.env = _.extend({ "USE_GLOBAL_ADK": "" },
process.env,
opts.env || {},
{ "WAREHOUSE_DIR": tropo.root });
var childProcess = execFileSync(file, args, opts);
if (! childProcess.success)
@@ -616,6 +624,44 @@ cordova.buildPlatforms = function (localPath, platforms, options) {
buildCordova(localPath, 'build', options);
};
cordova.buildPlatformRunners = function (localPath, platforms, options) {
var runners = [];
_.each(platforms, function (platformName) {
runners.push(new CordovaRunner(localPath, platformName, options));
});
return runners;
};
// This is a runner, that we pass to Runner (run-all.js)
var CordovaRunner = function (localPath, platformName, options) {
var self = this;
self.localPath = localPath;
self.platformName = platformName;
self.options = options;
self.title = 'Cordova (' + self.platformName + ')';
};
_.extend(CordovaRunner.prototype, {
start: function () {
var self = this;
execCordovaOnPlatform(self.localPath,
self.platformName,
self.options);
},
stop: function () {
var self = this;
// XXX: A no-op for now (we leave it running because it's slow!)
}
});
// Start the simulator or physical device for a specific platform.
// platformName is of the form ios/ios-device/android/android-device
// options:
@@ -631,9 +677,12 @@ var execCordovaOnPlatform = function (localPath, platformName, options) {
verboseLog('isDevice:', isDevice);
var args = [ 'run',
isDevice ? '--device' : '--emulator',
platform ];
var args = [ 'run' ];
if (options.verbose) {
args.push('--verbose');
}
args.push(isDevice ? '--device' : '--emulator');
args.push(platform);
// XXX assert we have a valid Cordova project
if (platform === 'ios' && isDevice) {
@@ -645,9 +694,16 @@ var execCordovaOnPlatform = function (localPath, platformName, options) {
'platforms', 'ios', '*.xcodeproj')]);
} else {
verboseLog('Running emulator:', localCordova, args);
var emulatorOptions = { verbose: options.verbose, cwd: cordovaPath };
emulatorOptions.env = _.extend({}, process.env);
if (options.httpProxyPort) {
// XXX: Is this Android only?
// This is odd; the IP address is on the host, not inside the emulator
emulatorOptions.env['http_proxy'] = '127.0.0.1:' + options.httpProxyPort;
}
execFileAsyncOrThrow(
localCordova, args,
{ verbose: options.verbose, cwd: cordovaPath });
emulatorOptions);
}
var Log = getLoadedPackages().logging.Log;
@@ -733,14 +789,7 @@ var execCordovaOnPlatform = function (localPath, platformName, options) {
return 0;
};
// Start the simulator or physical device for a list of platforms
// options:
// - verbose: print all build logs
cordova.runPlatforms = function (localPath, platforms, options) {
_.each(platforms, function (platformName) {
execCordovaOnPlatform(localPath, platformName, options);
});
};
// packages - list of strings
cordova.filterPackages = function (packages) {
@@ -843,10 +892,16 @@ main.registerCommand({
name: "configure-android",
options: {
verbose: { type: Boolean, short: "v" }
}
},
minArgs: 0,
maxArgs: Infinity
}, function (options) {
cordova.setVerboseness(options.verbose);
var androidArgs = options.args || [];
try {
execFileSyncOrThrow(localAndroid, [], options);
var execOptions = { pipeOutput: true, verbose: options.verbose };
execFileSyncOrThrow(localAndroid, androidArgs, execOptions);
} catch (err) {
// this tool can crash for whatever reason, ignore its failures
}

View File

@@ -151,6 +151,7 @@ main.registerCommand({
options: {
port: { type: String, short: "p", default: '3000' },
'app-port': { type: String },
'http-proxy-port': { type: String },
production: { type: Boolean },
'raw-logs': { type: Boolean },
settings: { type: String },
@@ -199,6 +200,8 @@ main.registerCommand({
return 1;
}
options.httpProxyPort = options['http-proxy-port'];
// If we are targeting the remote devices
if (_.intersection(options.args, ['ios-device', 'android-device']).length) {
cordova.verboseLog('A run on a device requested');
@@ -213,15 +216,22 @@ main.registerCommand({
// Always bundle for the browser by default.
var webArchs = project.getWebArchs();
var runners = [];
// If additional args were specified, then also start a mobile build.
if (options.args.length) {
// will asynchronously start mobile emulators/devices
try {
// --clean encpasulates the behavior of once
// --clean encapsulates the behavior of once
if (options.clean) {
options.once = true;
}
if (!options.httpProxyPort) {
console.log('Forcing http proxy on port 3002 for mobile');
options.httpProxyPort = '3002';
}
cordova.verboseLog('Will compile mobile builds');
var appName = path.basename(options.appDir);
var localPath = path.join(options.appDir, '.meteor', 'local');
@@ -229,7 +239,7 @@ main.registerCommand({
cordova.buildPlatforms(localPath, options.args,
_.extend({ appName: appName, debug: ! options.production },
options, parsedHostPort));
cordova.runPlatforms(localPath, options.args, options);
runners = runners.concat(cordova.buildPlatformRunners(localPath, options.args, options));
} catch (err) {
if (options.verbose) {
process.stderr.write('Error while running for mobile platforms ' +
@@ -278,6 +288,7 @@ main.registerCommand({
return runAll.run(options.appDir, {
proxyPort: parsedHostPort.port,
proxyHost: parsedHostPort.host,
httpProxyPort: options.httpProxyPort,
appPort: appPort,
appHost: appHost,
settingsFile: options.settings,
@@ -289,7 +300,8 @@ main.registerCommand({
rootUrl: process.env.ROOT_URL,
mongoUrl: process.env.MONGO_URL,
oplogUrl: process.env.MONGO_OPLOG_URL,
once: options.once
once: options.once,
extraRunners: runners
});
});
@@ -1119,6 +1131,7 @@ main.registerCommand({
maxArgs: Infinity,
options: {
port: { type: String, short: "p", default: "localhost:3000" },
'http-proxy-port': { type: String },
deploy: { type: String },
production: { type: Boolean },
settings: { type: String },
@@ -1151,6 +1164,8 @@ main.registerCommand({
return 1;
}
options.httpProxyPort = options['http-proxy-port'];
// XXX not good to change the options this way
_.extend(options, parsedHostPort);
@@ -1187,6 +1202,8 @@ main.registerCommand({
[options['driver-package'] || 'test-in-browser'],
'add');
var runners = [];
var mobileOptions = ['ios', 'ios-device', 'android', 'android-device'];
var mobilePlatforms = [];
@@ -1196,6 +1213,11 @@ main.registerCommand({
});
if (! _.isEmpty(mobilePlatforms)) {
if (!options.httpProxyPort) {
console.log('Forcing http proxy on port 3002 for mobile');
options.httpProxyPort = '3002'
}
var localPath = path.join(testRunnerAppDir, '.meteor', 'local');
var platforms =
@@ -1211,13 +1233,14 @@ main.registerCommand({
appName: path.basename(testRunnerAppDir),
debug: ! options.production
}));
cordova.runPlatforms(localPath, mobilePlatforms, options);
runners = runners.concat(cordova.buildPlatformRunners(localPath, mobilePlatforms, options));
} catch (err) {
process.stderr.write(err.message + '\n');
return 1;
}
}
options.extraRunners = runners;
return runTestAppForPackages(testPackages, testRunnerAppDir, options);
});
@@ -1394,6 +1417,7 @@ var runTestAppForPackages = function (testPackages, testRunnerAppDir, options) {
// a switch to a different release
appDirForVersionCheck: options.appDir,
proxyPort: options.port,
httpProxyPort: options.httpProxyPort,
disableOplog: options['disable-oplog'],
settingsFile: options.settings,
banner: "Tests",
@@ -1402,7 +1426,8 @@ var runTestAppForPackages = function (testPackages, testRunnerAppDir, options) {
mongoUrl: process.env.MONGO_URL,
oplogUrl: process.env.MONGO_OPLOG_URL,
once: options.once,
recordPackageUsage: false
recordPackageUsage: false,
extraRunners: options.extraRunners
});
}

View File

@@ -47,9 +47,8 @@ if [ -z "$USE_GLOBAL_ADK" ] ; then
export HOME="${ANDROID_BUNDLE}"
export ANDROID_SDK_HOME="${ANDROID_BUNDLE}"
else
# to use a global ADK we don't set PATH, ANT_HOME
# to use a global ADK we don't set PATH, ANT_HOME, ANDROID_SDK_HOME
# relying that they are installed and available globally
export ANDROID_SDK_HOME="${HOME}"
true
fi

View File

@@ -1,4 +1,7 @@
#!/bin/bash
set -e
BUNDLE_VERSION=0.1
# OS Check. Put here because here is where we download the precompiled
@@ -92,11 +95,52 @@ command -v javac >/dev/null 2>&1 || {
echo >&2 "To add the android platform, please install a JDK. Here are some directions: http://openjdk.java.net/install/"; exit 1;
}
set_config () {
KEY=$1
VALUE=$2
CONFIG_FILE=${ANDROID_BUNDLE}/meteor_avd/config.ini
TEMP_FILE=`mktemp tmp.XXXXXXXXXX`
grep -v "^${KEY}=" ${CONFIG_FILE} > ${TEMP_FILE}
echo "${KEY}=${VALUE}" >> ${TEMP_FILE}
mv -f ${TEMP_FILE} ${CONFIG_FILE}
}
install_x86 () {
echo "Android x86 System image not found. Found targets:"
android list target
echo "Downloading x86 system image..."
echo y | android update sdk -t sys-img-x86-android-19 --all -u
}
# create avd if necessary
if [[ ! $("${ANDROID_BUNDLE}/android-sdk/tools/android" list avd | grep Name) ]] ; then
#ABI="default/armeabi-v7a"
ABI="default/x86"
(android list target | grep ABIs | grep default/x86 > /dev/null) || install_x86
echo "
" | "${ANDROID_BUNDLE}/android-sdk/tools/android" create avd --target 1 --name meteor --abi default/armeabi-v7a --path ${ANDROID_BUNDLE}/meteor_avd/ 1>&2
" | "${ANDROID_BUNDLE}/android-sdk/tools/android" create avd --target 1 --name meteor --abi ${ABI} --path ${ANDROID_BUNDLE}/meteor_avd/ 1>&2
# Nice keyboard support
set_config "hw.keyboard" "yes"
set_config "hw.mainKeys" "no"
# More RAM than the default
set_config "hw.ramSize" "1024"
set_config "vm.heapSize" "64"
# These are the settings for a Nexus 4, but it's a bit big for some screens
# (and likely a bit slow without GPU & KVM/HAXM acceleration)
#set_config "skin.dynamic" "yes"
#set_config "hw.lcd.density" "320"
#set_config "hw.device.name" "Nexus 4"
#set_config "hw.device.manufacturer" "Google"
# XXX: hw.gpu.enabled=yes ?
fi

View File

@@ -0,0 +1,9 @@
#!/bin/bash
# import all the environment
source $(dirname $0)/common_env.sh
adb shell sqlite3 /data/data/com.android.providers.telephony/databases/telephony.db \
"update carriers set proxy='10.0.2.2', port='3002' where current=1"
adb shell stop
adb shell start

View File

@@ -5,6 +5,7 @@ var release = require('./release.js');
var runLog = require('./run-log.js');
var Proxy = require('./run-proxy.js').Proxy;
var HttpProxy = require('./run-httpproxy.js').HttpProxy;
var AppRunner = require('./run-app.js').AppRunner;
var MongoRunner = require('./run-mongo.js').MongoRunner;
var Updater = require('./run-updater.js').Updater;
@@ -36,6 +37,8 @@ var Runner = function (appDir, options) {
self.rootUrl = 'http://localhost:' + listenPort + '/';
}
self.extraRunners = options.extraRunners;
self.proxy = new Proxy({
listenPort: listenPort,
listenHost: options.proxyHost,
@@ -44,6 +47,13 @@ var Runner = function (appDir, options) {
onFailure: options.onFailure
});
self.httpProxy = null;
if (options.httpProxyPort) {
self.httpProxy = new HttpProxy({
listenPort: options.httpProxyPort
})
}
self.mongoRunner = null;
var mongoUrl, oplogUrl;
if (options.mongoUrl) {
@@ -99,6 +109,14 @@ _.extend(Runner.prototype, {
self.updater.start();
}
// print the banner only once we've successfully bound the port
if (! self.stopped && self.httpProxy) {
self.httpProxy.start();
if (! self.quiet) {
runLog.log("=> Started http proxy.");
}
}
if (! self.stopped && self.mongoRunner) {
var spinner = ['-', '\\', '|', '/'];
// I looked at some Unicode indeterminate progress indicators, such as:
@@ -136,6 +154,17 @@ _.extend(Runner.prototype, {
}
}
_.forEach(self.extraRunners, function (extraRunner) {
if (! self.stopped) {
var title = extraRunner.title;
if (! self.quiet)
runLog.logTemporary("=> Starting " + title + "...");
extraRunner.start();
if (! self.quiet && ! self.stopped)
runLog.log("=> Started " + title + ".");
}
});
if (! self.stopped) {
if (! self.quiet)
runLog.logTemporary("=> Starting your app...");
@@ -161,8 +190,12 @@ _.extend(Runner.prototype, {
self.stopped = true;
self.proxy.stop();
self.httpProxy && self.httpProxy.stop();
self.updater.stop();
self.mongoRunner && self.mongoRunner.stop();
_.forEach(self.extraRunners, function (extraRunner) {
extraRunner.stop();
});
self.appRunner.stop();
// XXX does calling this 'finish' still make sense now that runLog is a
// singleton?

280
tools/run-httpproxy.js Normal file
View File

@@ -0,0 +1,280 @@
// The HTTP proxy is primarily so we can use localhost:3000 with OAuth,
// on devices which don't run a webserver e.g. Android / iOS
// This is a generic HTTP proxy, like a mini-Squid
// (whereas run-proxy.js is just for our app)
var _ = require('underscore');
var Future = require('fibers/future');
var runLog = require('./run-log.js');
var url = require('url');
// options: listenPort, listenHost, onFailure
var HttpProxy = function (options) {
var self = this;
self.listenPort = options.listenPort;
self.listenHost = options.listenHost;
self.onFailure = options.onFailure || function () {};
self.mode = "proxy";
self.httpQueue = []; // keys: req, res
self.websocketQueue = []; // keys: req, socket, head
self.connectQueue = []; // keys: req, socket, head
self.proxy = null;
self.server = null;
};
_.extend(HttpProxy.prototype, {
// Start the proxy server, block (yield) until it is ready to go
// (actively listening on outer and proxying to inner), and then
// return.
start: function () {
var self = this;
if (self.server)
throw new Error("already running?");
self.started = false;
var http = require('http');
var net = require('net');
var httpProxy = require('http-proxy');
self.proxy = httpProxy.createProxyServer({
// agent is required to handle keep-alive, and http-proxy 1.0 is a little
// buggy without it: https://github.com/nodejitsu/node-http-proxy/pull/488
agent: new http.Agent({ maxSockets: 100 }),
xfwd: false //true
});
var server = self.server = http.createServer(function (req, res) {
// Normal HTTP request
self.httpQueue.push({ req: req, res: res });
self._tryHandleConnections();
});
self.server.on('connect', function (req, socket, head) {
self.connectQueue.push({ req: req, socket: socket, head: head });
self._tryHandleConnections();
});
self.server.on('upgrade', function (req, socket, head) {
// Websocket connection
self.websocketQueue.push({ req: req, socket: socket, head: head });
self._tryHandleConnections();
});
var fut = new Future;
self.server.on('error', function (err) {
if (err.code === 'EADDRINUSE') {
var port = self.listenPort;
runLog.log(
"HTTP proxy server can't listen on port " + port + ". \n" +
"If something else is using port " + port + ", you can\n" +
"specify an alternative port with --http-proxy-port <port>.");
} else if (self.listenHost &&
(err.code === 'ENOTFOUND' || err.code === 'EADDRNOTAVAIL')) {
// This handles the case of "entered a DNS name that's unknown"
// (ENOTFOUND from getaddrinfo) and "entered some random IP that we
// can't bind to" (EADDRNOTAVAIL from listen).
runLog.log(
"Can't listen on host " + self.listenHost +
" (" + err.code + " from " + err.syscall + ").");
} else {
runLog.log('' + err);
}
self.onFailure();
// Allow start() to return.
fut.isResolved() || fut['return']();
});
// Don't crash if the app doesn't respond; instead return an error
// immediately.
self.proxy.on('error', function (err, req, resOrSocket) {
if (resOrSocket instanceof http.ServerResponse) {
resOrSocket.writeHead(503, {
'Content-Type': 'text/plain'
});
resOrSocket.end('Unexpected error.');
} else if (resOrSocket instanceof net.Socket) {
resOrSocket.end();
}
});
self.server.listen(self.listenPort, self.listenHost || '0.0.0.0', function () {
if (self.server) {
self.started = true;
} else {
// stop() got called while we were invoking listen! Close the server (we
// still have the var server). The rest of the cleanup shouldn't be
// necessary.
server.close();
}
fut.isResolved() || fut['return']();
});
fut.wait();
},
// Idempotent.
stop: function () {
var self = this;
if (! self.server)
return;
if (! self.started) {
// This probably means that we failed to listen. However, there could be a
// race condition and we could be in the middle of starting to listen! In
// that case, the listen callback will notice that we nulled out server
// here.
self.server = null;
return;
}
// This stops listening but allows existing connections to
// complete gracefully.
self.server.close();
self.server = null;
// It doesn't seem to be necessary to do anything special to
// destroy an httpProxy proxyserver object.
self.proxy = null;
// Drop any held connections.
_.each(self.httpQueue, function (c) {
c.res.statusCode = 500;
c.res.end();
});
self.httpQueue = [];
_.each(self.websocketQueue, function (c) {
c.socket.destroy();
});
self.websocketQueue = [];
_.each(self.connectQueue, function (c) {
c.socket.destroy();
});
self.connectQueue = [];
self.mode = "hold";
},
_tryHandleConnections: function () {
var self = this;
while (self.httpQueue.length) {
if (self.mode !== "proxy")
break;
var c = self.httpQueue.shift();
var req = c.req;
var targetUrl = req.url;
runLog.log("Proxy request: " + req.method + " " +req.url);
var newUrl = req.url
self.proxy.web(c.req, c.res, {
target: targetUrl
});
}
while (self.websocketQueue.length) {
if (self.mode !== "proxy")
break;
var c = self.websocketQueue.shift();
var req = c.req;
var targetUrl = req.url;
runLog.log("Proxy request (websocket): " + req.method + " " +req.url);
self.proxy.ws(c.req, c.socket, c.head, {
target: targetUrl
});
}
while (self.connectQueue.length) {
if (self.mode !== "proxy")
break;
var c = self.connectQueue.shift();
runLog.log("Proxy request (connect): " + c.req.method + " " + c.req.url);
proxyConnectMethod(c.req, c.socket, c.head);
}
},
// The proxy can be in one of three modes:
// - "proxy": connections are proxied
//
// The initial mode is "proxy".
setMode: function (mode) {
var self = this;
self.mode = mode;
self._tryHandleConnections();
}
});
// This is what http-proxy does
// XXX: We should submit connect support upstream
var setupSocket = function(socket) {
socket.setTimeout(0);
socket.setNoDelay(true);
socket.setKeepAlive(true, 0);
return socket;
};
var proxyConnectMethod = function (req, socket, options, head, server, clb) {
if (req.method !== 'CONNECT') {
socket.destroy();
return true;
}
var tokens = req.url.split(':');
if (tokens.length != 2) {
runLog.log("Bad request: " + req.url);
socket.destroy();
return true;
}
var host = tokens[0];
var port = tokens[1];
if (port != 443) {
runLog.log("Blocking request to non-443 port: " + req.url);
socket.destroy();
return true;
}
setupSocket(socket);
// XXX: Needed?
// if (head && head.length) socket.unshift(head);
var net = require('net');
var proxySocket = net.createConnection(port, host);
setupSocket(proxySocket);
socket.on('error', function (err) {
runLog.log("Error on socket: " + err);
proxySocket.end();
});
proxySocket.on('error', function (err) {
runLog.log("Error on proxySocket: " + err);
socket.end();
});
proxySocket.on('connect', function(connect) {
runLog.log("Connection established to " + host + ":" + port);
socket.write("HTTP/1.0 200 Connection established\n\n");
socket.pipe(proxySocket);
proxySocket.pipe(socket);
});
};
exports.HttpProxy = HttpProxy;