mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
commit
d86fb66e9b
@ -14,6 +14,9 @@ DART=dart
|
||||
|
||||
if [ "$FLUTTER_TOOLS_DIR/pubspec.yaml" -nt "$FLUTTER_TOOLS_DIR/pubspec.lock" ]; then
|
||||
(cd "$FLUTTER_TOOLS_DIR"; pub get)
|
||||
if [ -f "$SNAPSHOT_PATH" ]; then
|
||||
rm "$SNAPSHOT_PATH"
|
||||
fi
|
||||
fi
|
||||
|
||||
REVISION=`(cd "$FLUTTER_ROOT"; git rev-parse HEAD)`
|
||||
|
||||
@ -1,85 +0,0 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:args/args.dart';
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:shelf/shelf_io.dart' as io;
|
||||
import 'package:shelf_route/shelf_route.dart' as shelf_route;
|
||||
import 'package:shelf_static/shelf_static.dart';
|
||||
|
||||
void printUsage(parser) {
|
||||
print('Usage: sky_server [-v] PORT');
|
||||
print(parser.usage);
|
||||
}
|
||||
|
||||
void addRoute(var router, String route, String path) {
|
||||
router.add(
|
||||
route,
|
||||
['GET', 'HEAD'],
|
||||
createStaticHandler(
|
||||
path,
|
||||
serveFilesOutsidePath: true,
|
||||
listDirectories: true
|
||||
), exactMatch: false
|
||||
);
|
||||
}
|
||||
|
||||
main(List<String> argv) async {
|
||||
ArgParser parser = new ArgParser();
|
||||
parser.addFlag('help', abbr: 'h', negatable: false,
|
||||
help: 'Display this help message.');
|
||||
parser.addFlag('verbose', abbr: 'v', negatable: false,
|
||||
help: 'Log requests to stdout.');
|
||||
parser.addOption('route', allowMultiple: true, splitCommas: false,
|
||||
help: 'Adds a virtual directory to the root.');
|
||||
|
||||
ArgResults args = parser.parse(argv);
|
||||
|
||||
if (args['help'] || args.rest.length != 1) {
|
||||
printUsage(parser);
|
||||
return;
|
||||
}
|
||||
|
||||
int port;
|
||||
try {
|
||||
port = int.parse(args.rest[0]);
|
||||
} catch(e) {
|
||||
printUsage(parser);
|
||||
return;
|
||||
}
|
||||
|
||||
var router = shelf_route.router();
|
||||
|
||||
if (args['route'] != null) {
|
||||
for (String arg in args['route']) {
|
||||
List<String> parsedArgs = arg.split(',');
|
||||
addRoute(router, parsedArgs[0], parsedArgs[1]);
|
||||
}
|
||||
}
|
||||
|
||||
addRoute(router, '/', Directory.current.path);
|
||||
|
||||
var handler = router.handler;
|
||||
|
||||
if (args['verbose'])
|
||||
handler = const Pipeline().addMiddleware(logRequests()).addHandler(handler);
|
||||
|
||||
HttpServer server;
|
||||
try {
|
||||
server = await io.serve(handler, InternetAddress.LOOPBACK_IP_V4, port);
|
||||
print('Serving ${Directory.current.absolute.path} from '
|
||||
'http://${server.address.address}:${server.port}.');
|
||||
} catch(e) {
|
||||
print(e);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
server.defaultResponseHeaders
|
||||
..removeAll('x-content-type-options')
|
||||
..removeAll('x-frame-options')
|
||||
..removeAll('x-xss-protection')
|
||||
..add('cache-control', 'no-store');
|
||||
}
|
||||
@ -9,6 +9,7 @@ import 'dart:typed_data';
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:flx/bundle.dart';
|
||||
import 'package:flx/signing.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
import '../toolchain.dart';
|
||||
@ -147,6 +148,27 @@ class BuildCommand extends FlutterCommand {
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> buildInTempDir({
|
||||
String mainPath: _kDefaultMainPath,
|
||||
void onBundleAvailable(String bundlePath)
|
||||
}) async {
|
||||
int result;
|
||||
Directory tempDir = await Directory.systemTemp.createTemp('flutter_tools');
|
||||
try {
|
||||
String localBundlePath = path.join(tempDir.path, 'app.flx');
|
||||
String localSnapshotPath = path.join(tempDir.path, 'snapshot_blob.bin');
|
||||
result = await build(
|
||||
snapshotPath: localSnapshotPath,
|
||||
outputPath: localBundlePath,
|
||||
mainPath: mainPath
|
||||
);
|
||||
onBundleAvailable(localBundlePath);
|
||||
} finally {
|
||||
tempDir.deleteSync(recursive: true);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<int> build({
|
||||
String assetBase: _kDefaultAssetBase,
|
||||
String mainPath: _kDefaultMainPath,
|
||||
|
||||
@ -34,7 +34,6 @@ class ListenCommand extends FlutterCommand {
|
||||
help: 'Target app path or filename to start.');
|
||||
}
|
||||
|
||||
static const String _localFlutterBundle = 'app.flx';
|
||||
static const String _remoteFlutterBundle = 'Documents/app.flx';
|
||||
|
||||
@override
|
||||
@ -53,25 +52,26 @@ class ListenCommand extends FlutterCommand {
|
||||
|
||||
BuildCommand builder = new BuildCommand();
|
||||
builder.inheritFromParent(this);
|
||||
builder.build(outputPath: _localFlutterBundle);
|
||||
|
||||
for (Device device in devices.all) {
|
||||
ApplicationPackage package = applicationPackages.getPackageForPlatform(device.platform);
|
||||
if (package == null || !device.isConnected())
|
||||
continue;
|
||||
if (device is AndroidDevice) {
|
||||
await devices.android.startServer(
|
||||
argResults['target'], true, argResults['checked'], package);
|
||||
} else if (device is IOSDevice) {
|
||||
device.pushFile(package, _localFlutterBundle, _remoteFlutterBundle);
|
||||
} else if (device is IOSSimulator) {
|
||||
// TODO(abarth): Move pushFile up to Device once Android supports
|
||||
// pushing new bundles.
|
||||
device.pushFile(package, _localFlutterBundle, _remoteFlutterBundle);
|
||||
} else {
|
||||
assert(false);
|
||||
await builder.buildInTempDir(
|
||||
onBundleAvailable: (String localBundlePath) {
|
||||
for (Device device in devices.all) {
|
||||
ApplicationPackage package = applicationPackages.getPackageForPlatform(device.platform);
|
||||
if (package == null || !device.isConnected())
|
||||
continue;
|
||||
if (device is AndroidDevice) {
|
||||
device.startBundle(package, localBundlePath, true, argResults['checked']);
|
||||
} else if (device is IOSDevice) {
|
||||
device.pushFile(package, localBundlePath, _remoteFlutterBundle);
|
||||
} else if (device is IOSSimulator) {
|
||||
// TODO(abarth): Move pushFile up to Device once Android supports
|
||||
// pushing new bundles.
|
||||
device.pushFile(package, localBundlePath, _remoteFlutterBundle);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (singleRun || !watchDirectory())
|
||||
break;
|
||||
|
||||
@ -16,8 +16,6 @@ import 'install.dart';
|
||||
import 'stop.dart';
|
||||
|
||||
final Logger _logging = new Logger('sky_tools.start');
|
||||
const String _localBundleName = 'app.flx';
|
||||
const String _localSnapshotName = 'snapshot_blob.bin';
|
||||
|
||||
class StartCommand extends FlutterCommand {
|
||||
final String name = 'start';
|
||||
@ -35,9 +33,6 @@ class StartCommand extends FlutterCommand {
|
||||
defaultsTo: '.',
|
||||
abbr: 't',
|
||||
help: 'Target app path or filename to start.');
|
||||
argParser.addFlag('http',
|
||||
negatable: true,
|
||||
help: 'Use a local HTTP server to serve your app to your device.');
|
||||
argParser.addFlag('boot',
|
||||
help: 'Boot the iOS Simulator if it isn\'t already running.');
|
||||
}
|
||||
@ -69,30 +64,18 @@ class StartCommand extends FlutterCommand {
|
||||
continue;
|
||||
if (device is AndroidDevice) {
|
||||
String target = path.absolute(argResults['target']);
|
||||
if (argResults['http']) {
|
||||
if (await device.startServer(target, poke, argResults['checked'], package))
|
||||
startedSomething = true;
|
||||
} else {
|
||||
String mainPath = target;
|
||||
if (FileSystemEntity.isDirectorySync(target))
|
||||
mainPath = path.join(target, 'lib', 'main.dart');
|
||||
BuildCommand builder = new BuildCommand();
|
||||
builder.inheritFromParent(this);
|
||||
|
||||
Directory tempDir = await Directory.systemTemp.createTemp('flutter_tools');
|
||||
try {
|
||||
String localBundlePath = path.join(tempDir.path, _localBundleName);
|
||||
String localSnapshotPath = path.join(tempDir.path, _localSnapshotName);
|
||||
await builder.build(
|
||||
snapshotPath: localSnapshotPath,
|
||||
outputPath: localBundlePath,
|
||||
mainPath: mainPath);
|
||||
String mainPath = target;
|
||||
if (FileSystemEntity.isDirectorySync(target))
|
||||
mainPath = path.join(target, 'lib', 'main.dart');
|
||||
BuildCommand builder = new BuildCommand();
|
||||
builder.inheritFromParent(this);
|
||||
await builder.buildInTempDir(
|
||||
mainPath: mainPath,
|
||||
onBundleAvailable: (String localBundlePath) {
|
||||
if (device.startBundle(package, localBundlePath, poke, argResults['checked']))
|
||||
startedSomething = true;
|
||||
} finally {
|
||||
tempDir.deleteSync(recursive: true);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
if (await device.startApp(package))
|
||||
startedSomething = true;
|
||||
|
||||
@ -3,9 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@ -13,7 +11,6 @@ import 'package:path/path.dart' as path;
|
||||
|
||||
import 'application_package.dart';
|
||||
import 'build_configuration.dart';
|
||||
import 'os_utils.dart';
|
||||
import 'process.dart';
|
||||
|
||||
final Logger _logging = new Logger('sky_tools.device');
|
||||
@ -507,14 +504,10 @@ class IOSSimulator extends Device {
|
||||
class AndroidDevice extends Device {
|
||||
static const String _ADB_PATH = 'adb';
|
||||
static const int _observatoryPort = 8181;
|
||||
static const int _serverPort = 9888;
|
||||
|
||||
static const String className = 'AndroidDevice';
|
||||
static final String defaultDeviceID = 'default_android_device';
|
||||
|
||||
static const String _kFlutterServerStartMessage = 'Serving';
|
||||
static const Duration _kFlutterServerTimeout = const Duration(seconds: 3);
|
||||
|
||||
String productID;
|
||||
String modelID;
|
||||
String deviceCodeName;
|
||||
@ -718,13 +711,6 @@ class AndroidDevice extends Device {
|
||||
return CryptoUtils.bytesToHex(sha1.close());
|
||||
}
|
||||
|
||||
/**
|
||||
* Since Window's paths have backslashes, we need to convert those to forward slashes to make a valid URL
|
||||
*/
|
||||
String _convertToURL(String path) {
|
||||
return path.replaceAll('\\', '/');
|
||||
}
|
||||
|
||||
@override
|
||||
bool isAppInstalled(ApplicationPackage app) {
|
||||
if (!isConnected()) {
|
||||
@ -793,81 +779,16 @@ class AndroidDevice extends Device {
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<bool> startServer(
|
||||
String target, bool poke, bool checked, AndroidApk apk) async {
|
||||
String serverRoot = '';
|
||||
String mainDart = '';
|
||||
String missingMessage = '';
|
||||
if (FileSystemEntity.isDirectorySync(target)) {
|
||||
serverRoot = target;
|
||||
mainDart = path.join(serverRoot, 'lib', 'main.dart');
|
||||
missingMessage = 'Missing lib/main.dart in project: $serverRoot';
|
||||
} else {
|
||||
serverRoot = Directory.current.path;
|
||||
mainDart = target;
|
||||
missingMessage = '$mainDart does not exist.';
|
||||
}
|
||||
|
||||
if (!FileSystemEntity.isFileSync(mainDart)) {
|
||||
_logging.severe(missingMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!poke) {
|
||||
_forwardObservatoryPort();
|
||||
|
||||
// Actually start the server.
|
||||
Process server = await Process.start(
|
||||
sdkBinaryName('pub'), ['run', 'sky_tools:sky_server', _serverPort.toString()],
|
||||
workingDirectory: serverRoot,
|
||||
mode: ProcessStartMode.DETACHED_WITH_STDIO
|
||||
);
|
||||
await server.stdout.transform(UTF8.decoder)
|
||||
.firstWhere((String value) => value.startsWith(_kFlutterServerStartMessage))
|
||||
.timeout(_kFlutterServerTimeout);
|
||||
|
||||
// Set up reverse port-forwarding so that the Android app can reach the
|
||||
// server running on localhost.
|
||||
String serverPortString = 'tcp:$_serverPort';
|
||||
runCheckedSync(adbCommandForDevice(['reverse', serverPortString, serverPortString]));
|
||||
}
|
||||
|
||||
String relativeDartMain = _convertToURL(path.relative(mainDart, from: serverRoot));
|
||||
String url = 'http://localhost:$_serverPort/$relativeDartMain';
|
||||
if (poke)
|
||||
url += '?rand=${new Random().nextDouble()}';
|
||||
|
||||
// Actually launch the app on Android.
|
||||
List<String> cmd = adbCommandForDevice([
|
||||
'shell', 'am', 'start',
|
||||
'-a', 'android.intent.action.VIEW',
|
||||
'-d', url,
|
||||
]);
|
||||
if (checked)
|
||||
cmd.addAll(['--ez', 'enable-checked-mode', 'true']);
|
||||
cmd.add(apk.launchActivity);
|
||||
runCheckedSync(cmd);
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> startApp(ApplicationPackage app) async {
|
||||
// Android currently has to be started with startServer(...).
|
||||
// Android currently has to be started with startBundle(...).
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool> stopApp(ApplicationPackage app) async {
|
||||
final AndroidApk apk = app;
|
||||
|
||||
// Turn off reverse port forwarding
|
||||
runSync(adbCommandForDevice(['reverse', '--remove', 'tcp:$_serverPort']));
|
||||
// Stop the app
|
||||
runSync(adbCommandForDevice(['shell', 'am', 'force-stop', apk.id]));
|
||||
|
||||
// Kill the server
|
||||
osUtils.killTcpPortListeners(_serverPort);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -6,8 +6,6 @@ import 'dart:io';
|
||||
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import 'process.dart';
|
||||
|
||||
final OperatingSystemUtils osUtils = new OperatingSystemUtils._();
|
||||
|
||||
final Logger _logging = new Logger('sky_tools.os');
|
||||
@ -16,21 +14,16 @@ abstract class OperatingSystemUtils {
|
||||
factory OperatingSystemUtils._() {
|
||||
if (Platform.isWindows) {
|
||||
return new _WindowsUtils();
|
||||
} else if (Platform.isMacOS) {
|
||||
return new _MacUtils();
|
||||
} else {
|
||||
return new _LinuxUtils();
|
||||
return new _PosixUtils();
|
||||
}
|
||||
}
|
||||
|
||||
/// Make the given file executable. This may be a no-op on some platforms.
|
||||
ProcessResult makeExecutable(File file);
|
||||
|
||||
/// A best-effort attempt to kill all listeners on the given TCP port.
|
||||
void killTcpPortListeners(int tcpPort);
|
||||
}
|
||||
|
||||
abstract class _PosixUtils implements OperatingSystemUtils {
|
||||
class _PosixUtils implements OperatingSystemUtils {
|
||||
ProcessResult makeExecutable(File file) {
|
||||
return Process.runSync('chmod', ['u+x', file.path]);
|
||||
}
|
||||
@ -41,51 +34,4 @@ class _WindowsUtils implements OperatingSystemUtils {
|
||||
ProcessResult makeExecutable(File file) {
|
||||
return new ProcessResult(0, 0, null, null);
|
||||
}
|
||||
|
||||
void killTcpPortListeners(int tcpPort) {
|
||||
// Get list of network processes and split on newline
|
||||
List<String> processes = runSync(['netstat.exe','-ano']).split("\r");
|
||||
|
||||
// List entries from netstat is formatted like so:
|
||||
// TCP 192.168.2.11:50945 192.30.252.90:443 LISTENING 1304
|
||||
// This regexp is to find process where the the port exactly matches
|
||||
RegExp pattern = new RegExp(':$tcpPort[ ]+');
|
||||
|
||||
// Split the columns by 1 or more spaces
|
||||
RegExp columnPattern = new RegExp('[ ]+');
|
||||
processes.forEach((String process) {
|
||||
if (process.contains(pattern)) {
|
||||
// The last column is the Process ID
|
||||
String processId = process.split(columnPattern).last;
|
||||
// Force and Tree kill the process
|
||||
_logging.info('kill $processId');
|
||||
runSync(['TaskKill.exe', '/F', '/T', '/PID', processId]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _MacUtils extends _PosixUtils {
|
||||
void killTcpPortListeners(int tcpPort) {
|
||||
String pids = runSync(['lsof', '-i', ':$tcpPort', '-t']).trim();
|
||||
if (pids.isNotEmpty) {
|
||||
// Handle multiple returned pids.
|
||||
for (String pidString in pids.split('\n')) {
|
||||
// Killing a pid with a shell command from within dart is hard, so use a
|
||||
// library command, but it's still nice to give the equivalent command
|
||||
// when doing verbose logging.
|
||||
_logging.info('kill $pidString');
|
||||
|
||||
int pid = int.parse(pidString, onError: (_) => null);
|
||||
if (pid != null)
|
||||
Process.killPid(pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _LinuxUtils extends _PosixUtils {
|
||||
void killTcpPortListeners(int tcpPort) {
|
||||
runSync(['fuser', '-k', '$tcpPort/tcp']);
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,9 +15,6 @@ dependencies:
|
||||
crypto: ^0.9.1
|
||||
mustache4dart: ^1.0.0
|
||||
path: ^1.3.0
|
||||
shelf_route: ^0.13.4
|
||||
shelf_static: ^0.2.3
|
||||
shelf: ^0.6.2
|
||||
stack_trace: ^1.4.0
|
||||
test: ^0.12.5
|
||||
yaml: ^2.1.3
|
||||
|
||||
@ -34,33 +34,5 @@ defineTests() {
|
||||
expect(mode.substring(0, 3), endsWith('x'));
|
||||
}
|
||||
});
|
||||
|
||||
/// Start a script listening on a port, try and kill that process.
|
||||
test('killTcpPortListeners', () async {
|
||||
final int port = 40170;
|
||||
|
||||
File file = new File(p.join(temp.path, 'script.dart'));
|
||||
file.writeAsStringSync('''
|
||||
import 'dart:io';
|
||||
|
||||
void main() async {
|
||||
ServerSocket serverSocket = await ServerSocket.bind(
|
||||
InternetAddress.LOOPBACK_IP_V4, ${port});
|
||||
// wait...
|
||||
print('listening on port ${port}...');
|
||||
}
|
||||
''');
|
||||
Process process = await Process.start('dart', [file.path]);
|
||||
await process.stdout.first;
|
||||
|
||||
osUtils.killTcpPortListeners(40170);
|
||||
int exitCode = await process.exitCode;
|
||||
expect(exitCode, isNot(equals(0)));
|
||||
});
|
||||
|
||||
/// Try and kill with a port that no process is listening to.
|
||||
test('killTcpPortListeners none', () {
|
||||
osUtils.killTcpPortListeners(40171);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user