Teach sky_tools about prebuilt artifacts
This patch makes `flutter start` work without a clone of the engine git
repository. Making this work pulled a relatively large refactor of how the
commands interact with application packages and devices. Now commands that want
to interact with application packages or devices inherit from a common base
class that holds stores of those objects as members.
In production, the commands download and connect to devices based on the build
configuration stored on the FlutterCommandRunner. In testing, these fields are
used to mock out the real application package and devices.
diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart
index 301e589..1385672 100644
--- a/packages/flutter_tools/lib/executable.dart
+++ b/packages/flutter_tools/lib/executable.dart
@@ -2,14 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'dart:async';
-
-import 'package:args/args.dart';
-import 'package:args/command_runner.dart';
import 'package:logging/logging.dart';
-import 'src/application_package.dart';
-import 'src/artifacts.dart';
+import 'src/commands/flutter_command_runner.dart';
import 'src/commands/build.dart';
import 'src/commands/cache.dart';
import 'src/commands/init.dart';
@@ -22,136 +17,20 @@
import 'src/commands/stop.dart';
import 'src/commands/trace.dart';
-class _FlutterCommandRunner extends CommandRunner {
- _FlutterCommandRunner()
- : super('flutter', 'Manage your Flutter app development.') {
- argParser.addFlag('verbose',
- abbr: 'v',
- negatable: false,
- help: 'Noisy logging, including all shell commands executed.');
- argParser.addFlag('very-verbose',
- negatable: false,
- help: 'Very noisy logging, including the output of all '
- 'shell commands executed.');
-
- argParser.addSeparator('Global build selection options:');
- argParser.addFlag('debug',
- negatable: false,
- help:
- 'Set this if you are building Sky locally and want to use the debug build products. '
- 'When set, attempts to automaticaly determine sky-src-path if sky-src-path is '
- 'not set. Not normally required.');
- argParser.addFlag('release',
- negatable: false,
- help:
- 'Set this if you are building Sky locally and want to use the release build products. '
- 'When set, attempts to automaticaly determine sky-src-path if sky-src-path is '
- 'not set. Note that release is not compatible with the listen command '
- 'on iOS devices and simulators. Not normally required.');
- argParser.addOption('sky-src-path',
- help:
- 'Path to your Sky src directory, if you are building Sky locally. '
- 'Ignored if neither debug nor release is set. Not normally required.');
- argParser.addOption('android-debug-build-path',
- help:
- 'Path to your Android Debug out directory, if you are building Sky locally. '
- 'This path is relative to sky-src-path. Not normally required.',
- defaultsTo: 'out/android_Debug/');
- argParser.addOption('android-release-build-path',
- help:
- 'Path to your Android Release out directory, if you are building Sky locally. '
- 'This path is relative to sky-src-path. Not normally required.',
- defaultsTo: 'out/android_Release/');
- argParser.addOption('ios-debug-build-path',
- help:
- 'Path to your iOS Debug out directory, if you are building Sky locally. '
- 'This path is relative to sky-src-path. Not normally required.',
- defaultsTo: 'out/ios_Debug/');
- argParser.addOption('ios-release-build-path',
- help:
- 'Path to your iOS Release out directory, if you are building Sky locally. '
- 'This path is relative to sky-src-path. Not normally required.',
- defaultsTo: 'out/ios_Release/');
- argParser.addOption('ios-sim-debug-build-path',
- help:
- 'Path to your iOS Simulator Debug out directory, if you are building Sky locally. '
- 'This path is relative to sky-src-path. Not normally required.',
- defaultsTo: 'out/ios_sim_Debug/');
- argParser.addOption('ios-sim-release-build-path',
- help:
- 'Path to your iOS Simulator Release out directory, if you are building Sky locally. '
- 'This path is relative to sky-src-path. Not normally required.',
- defaultsTo: 'out/ios_sim_Release/');
- argParser.addOption('package-root',
- help: 'Path to your packages directory.', defaultsTo: 'packages');
- }
-
- Future<int> runCommand(ArgResults topLevelResults) async {
- if (topLevelResults['verbose']) {
- Logger.root.level = Level.INFO;
- }
-
- if (topLevelResults['very-verbose']) {
- Logger.root.level = Level.FINE;
- }
-
- _setupPaths(topLevelResults);
-
- return super.runCommand(topLevelResults);
- }
-
- void _setupPaths(ArgResults results) {
- ArtifactStore.packageRoot = results['package-root'];
- if (results['debug'] || results['release']) {
- if (results['sky-src-path'] == null) {
- // TODO(iansf): Figure out how to get the default src path
- assert(false);
- }
- ApplicationPackageFactory.srcPath = results['sky-src-path'];
- } else {
- assert(false);
- // TODO(iansf): set paths up for commands using PREBUILT binaries
- // ApplicationPackageFactory.setBuildPath(BuildType.PREBUILT,
- // BuildPlatform.android, results['android-debug-build-path']);
- }
-
- if (results['debug']) {
- ApplicationPackageFactory.defaultBuildType = BuildType.debug;
- ApplicationPackageFactory.setBuildPath(BuildType.debug,
- BuildPlatform.android, results['android-debug-build-path']);
- ApplicationPackageFactory.setBuildPath(
- BuildType.debug, BuildPlatform.iOS, results['ios-debug-build-path']);
- ApplicationPackageFactory.setBuildPath(BuildType.debug,
- BuildPlatform.iOSSimulator, results['ios-sim-debug-build-path']);
- }
- if (results['release']) {
- ApplicationPackageFactory.defaultBuildType = BuildType.release;
- ApplicationPackageFactory.setBuildPath(BuildType.release,
- BuildPlatform.android, results['android-release-build-path']);
- ApplicationPackageFactory.setBuildPath(BuildType.release,
- BuildPlatform.iOS, results['ios-release-build-path']);
- ApplicationPackageFactory.setBuildPath(BuildType.release,
- BuildPlatform.iOSSimulator, results['ios-sim-release-build-path']);
- }
- }
-}
-
/// Main entry point for commands.
///
/// This function is intended to be used from the [flutter] command line tool.
void main(List<String> args) {
Logger.root.level = Level.WARNING;
- Logger.root.onRecord.listen((LogRecord rec) {
- print('${rec.level.name}: ${rec.message}');
- if (rec.error != null) {
- print(rec.error);
- }
- if (rec.stackTrace != null) {
- print(rec.stackTrace);
- }
+ Logger.root.onRecord.listen((LogRecord record) {
+ print('${record.level.name}: ${record.message}');
+ if (record.error != null)
+ print(record.error);
+ if (record.stackTrace != null)
+ print(record.stackTrace);
});
- new _FlutterCommandRunner()
+ new FlutterCommandRunner()
..addCommand(new BuildCommand())
..addCommand(new CacheCommand())
..addCommand(new InitCommand())
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart
index f613db4..09a2061 100644
--- a/packages/flutter_tools/lib/src/application_package.dart
+++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -2,137 +2,119 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-library sky_tools.application_package;
-
-import 'dart:io';
+import 'dart:async';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
+import 'artifacts.dart';
+import 'build_configuration.dart';
+
final Logger _logging = new Logger('sky_tools.application_package');
abstract class ApplicationPackage {
- /// Path to the directory the apk or bundle lives in.
- String appDir;
-
/// Path to the actual apk or bundle.
- String get appPath => path.join(appDir, appFileName);
+ final String localPath;
/// Package ID from the Android Manifest or equivalent.
- String appPackageID;
+ final String id;
/// File name of the apk or bundle.
- String appFileName;
+ final String name;
- ApplicationPackage(this.appDir, this.appPackageID, this.appFileName);
+ ApplicationPackage({
+ String localPath,
+ this.id
+ }) : localPath = localPath, name = path.basename(localPath) {
+ assert(localPath != null);
+ assert(id != null);
+ }
}
class AndroidApk extends ApplicationPackage {
- static const String _apkName = 'SkyShell.apk';
- static const String _packageID = 'org.domokit.sky.shell';
- static const String _componentID = '$_packageID/$_packageID.SkyActivity';
+ static const String _defaultName = 'SkyShell.apk';
+ static const String _defaultId = 'org.domokit.sky.shell';
+ static const String _defaultLaunchActivity = '$_defaultId/$_defaultId.SkyActivity';
/// The path to the activity that should be launched.
/// Defaults to 'org.domokit.sky.shell/org.domokit.sky.shell.SkyActivity'
- String component;
- AndroidApk(String appDir,
- {String appPackageID: _packageID,
- String appFileName: _apkName,
- this.component: _componentID})
- : super(path.join(appDir, 'apks'), appPackageID, appFileName);
+ final String launchActivity;
+
+ AndroidApk({
+ String localPath,
+ String id: _defaultId,
+ this.launchActivity: _defaultLaunchActivity
+ }) : super(localPath: localPath, id: id) {
+ assert(launchActivity != null);
+ }
}
class IOSApp extends ApplicationPackage {
- static const String _appName = 'SkyShell.app';
- static const String _packageID = 'com.google.SkyShell';
+ static const String _defaultName = 'SkyShell.app';
+ static const String _defaultId = 'com.google.SkyShell';
- IOSApp(String appDir,
- {String appPackageID: _packageID, String appFileName: _appName})
- : super(appDir, appPackageID, appFileName);
+ IOSApp({
+ String localPath,
+ String id: _defaultId
+ }) : super(localPath: localPath, id: id);
}
-enum BuildType { prebuilt, release, debug, }
+class ApplicationPackageStore {
+ final AndroidApk android;
+ final IOSApp iOS;
+ final IOSApp iOSSimulator;
-enum BuildPlatform { android, iOS, iOSSimulator, mac, linux, }
+ ApplicationPackageStore({ this.android, this.iOS, this.iOSSimulator });
-class ApplicationPackageFactory {
- static final Map<BuildPlatform, Map<BuildType, String>> _buildPaths =
- _initBuildPaths();
-
- /// Path to your Sky src directory, if you are building Sky locally.
- /// Required if you are requesting release or debug BuildTypes.
- static String _srcPath = null;
- static String get srcPath => _srcPath;
- static void set srcPath(String newPath) {
- _srcPath = path.normalize(newPath);
+ ApplicationPackage getPackageForPlatform(BuildPlatform platform) {
+ switch (platform) {
+ case BuildPlatform.android:
+ return android;
+ case BuildPlatform.iOS:
+ return iOS;
+ case BuildPlatform.iOSSimulator:
+ return iOSSimulator;
+ case BuildPlatform.mac:
+ case BuildPlatform.linux:
+ return null;
+ }
}
- /// Default BuildType chosen if no BuildType is specified.
- static BuildType defaultBuildType = BuildType.prebuilt;
+ static Future<ApplicationPackageStore> forConfigs(List<BuildConfiguration> configs) async {
+ AndroidApk android;
+ IOSApp iOS;
+ IOSApp iOSSimulator;
- /// Default BuildPlatforms chosen if no BuildPlatforms are specified.
- static List<BuildPlatform> defaultBuildPlatforms = [
- BuildPlatform.android,
- BuildPlatform.iOS,
- BuildPlatform.iOSSimulator,
- ];
-
- static Map<BuildPlatform, ApplicationPackage> getAvailableApplicationPackages(
- {BuildType requestedType, List<BuildPlatform> requestedPlatforms}) {
- if (requestedType == null) {
- requestedType = defaultBuildType;
- }
- if (requestedPlatforms == null) {
- requestedPlatforms = defaultBuildPlatforms;
- }
-
- Map<BuildPlatform, ApplicationPackage> packages = {};
- for (BuildPlatform platform in requestedPlatforms) {
- String buildPath = _getBuildPath(requestedType, platform);
- switch (platform) {
+ for (BuildConfiguration config in configs) {
+ switch (config.platform) {
case BuildPlatform.android:
- packages[platform] = new AndroidApk(buildPath);
+ assert(android == null);
+ String localPath = config.type == BuildType.prebuilt ?
+ await ArtifactStore.getPath(Artifact.flutterShell) :
+ path.join(config.buildDir, 'apks', AndroidApk._defaultName);
+ android = new AndroidApk(localPath: localPath);
break;
+
case BuildPlatform.iOS:
- packages[platform] = new IOSApp(buildPath);
+ assert(iOS == null);
+ assert(config.type != BuildType.prebuilt);
+ iOS = new IOSApp(localPath: path.join(config.buildDir, IOSApp._defaultName));
break;
+
case BuildPlatform.iOSSimulator:
- packages[platform] = new IOSApp(buildPath);
+ assert(iOSSimulator == null);
+ assert(config.type != BuildType.prebuilt);
+ iOSSimulator = new IOSApp(localPath: path.join(config.buildDir, IOSApp._defaultName));
break;
- default:
- // TODO(iansf): Add other platforms
+
+ case BuildPlatform.mac:
+ case BuildPlatform.linux:
+ // TODO(abarth): Support mac and linux targets.
assert(false);
+ break;
}
}
- return packages;
- }
- static Map<BuildPlatform, Map<BuildType, String>> _initBuildPaths() {
- Map<BuildPlatform, Map<BuildType, String>> buildPaths = {};
- for (BuildPlatform platform in BuildPlatform.values) {
- buildPaths[platform] = {};
- }
- return buildPaths;
- }
-
- static String _getBuildPath(BuildType type, BuildPlatform platform) {
- String path = _buildPaths[platform][type];
- // You must set paths before getting them
- assert(path != null);
- return path;
- }
-
- static void setBuildPath(
- BuildType type, BuildPlatform platform, String buildPath) {
- // You must set srcPath before attempting to set a BuildPath for
- // non prebuilt ApplicationPackages.
- assert(type != BuildType.prebuilt || srcPath != null);
- if (type != BuildType.prebuilt) {
- buildPath = path.join(srcPath, buildPath);
- }
- if (!FileSystemEntity.isDirectorySync(buildPath)) {
- _logging.warning('$buildPath is not a valid directory');
- }
- _buildPaths[platform][type] = path.normalize(buildPath);
+ return new ApplicationPackageStore(android: android, iOS: iOS, iOSSimulator: iOSSimulator);
}
}
diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart
index 85d51cf..751cbd8 100644
--- a/packages/flutter_tools/lib/src/artifacts.dart
+++ b/packages/flutter_tools/lib/src/artifacts.dart
@@ -12,7 +12,11 @@
final Logger _logging = new Logger('sky_tools.artifacts');
-enum Artifact { FlutterCompiler, SkyViewerMojo, }
+enum Artifact {
+ flutterCompiler,
+ flutterShell,
+ skyViewerMojo,
+}
class ArtifactStore {
static String packageRoot;
@@ -66,20 +70,27 @@
// Whether the artifact needs to be marked as executable on disk.
static bool _needsToBeExecutable(Artifact artifact) {
- return artifact == Artifact.FlutterCompiler;
+ return artifact == Artifact.flutterCompiler;
}
static Future<String> getPath(Artifact artifact) async {
Directory cacheDir = await _engineSpecificCacheDir();
- String category, name;
+ String category;
+ String platform;
+ String name;
switch (artifact) {
- case Artifact.FlutterCompiler:
+ case Artifact.flutterCompiler:
category = 'shell';
name = 'sky_snapshot';
break;
- case Artifact.SkyViewerMojo:
+ case Artifact.flutterShell:
+ category = 'shell';
+ platform = 'android-arm';
+ name = 'SkyShell.apk';
+ break;
+ case Artifact.skyViewerMojo:
category = 'viewer';
name = 'sky_viewer.mojo';
break;
@@ -88,9 +99,12 @@
File cachedFile = new File(path.join(cacheDir.path, name));
if (!await cachedFile.exists()) {
_logging.info('Downloading ${name} from the cloud, one moment please...');
- if (!Platform.isLinux)
- throw new Exception('Platform unsupported.');
- String url = googleStorageUrl(category, 'linux-x64') + name;
+ if (platform == null) {
+ if (!Platform.isLinux)
+ throw new Exception('Platform unsupported.');
+ platform = 'linux-x64';
+ }
+ String url = googleStorageUrl(category, platform) + name;
await _downloadFile(url, cachedFile);
if (_needsToBeExecutable(artifact)) {
ProcessResult result = await Process.run('chmod', ['u+x', cachedFile.path]);
diff --git a/packages/flutter_tools/lib/src/build_configuration.dart b/packages/flutter_tools/lib/src/build_configuration.dart
new file mode 100644
index 0000000..0db0299e
--- /dev/null
+++ b/packages/flutter_tools/lib/src/build_configuration.dart
@@ -0,0 +1,37 @@
+// 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 'package:path/path.dart' as path;
+
+enum BuildType {
+ prebuilt,
+ release,
+ debug,
+}
+
+enum BuildPlatform {
+ android,
+ iOS,
+ iOSSimulator,
+ mac,
+ linux,
+}
+
+class BuildConfiguration {
+ BuildConfiguration.prebuilt({ this.platform })
+ : type = BuildType.prebuilt, buildDir = null;
+
+ BuildConfiguration.local({
+ this.type,
+ this.platform,
+ String enginePath,
+ String buildPath
+ }) : buildDir = path.normalize(path.join(enginePath, buildPath)) {
+ assert(type == BuildType.debug || type == BuildType.release);
+ }
+
+ final BuildType type;
+ final BuildPlatform platform;
+ final String buildDir;
+}
diff --git a/packages/flutter_tools/lib/src/commands/build.dart b/packages/flutter_tools/lib/src/commands/build.dart
index 7874e9d..b547240 100644
--- a/packages/flutter_tools/lib/src/commands/build.dart
+++ b/packages/flutter_tools/lib/src/commands/build.dart
@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-library sky_tools.build;
-
import 'dart:async';
import 'dart:io';
@@ -105,7 +103,7 @@
String snapshotPath
}) async {
if (compilerPath == null) {
- compilerPath = await ArtifactStore.getPath(Artifact.FlutterCompiler);
+ compilerPath = await ArtifactStore.getPath(Artifact.flutterCompiler);
}
ProcessResult result = await Process.run(compilerPath, [
mainPath,
diff --git a/packages/flutter_tools/lib/src/commands/cache.dart b/packages/flutter_tools/lib/src/commands/cache.dart
index 74c2ac6..ac8d185 100644
--- a/packages/flutter_tools/lib/src/commands/cache.dart
+++ b/packages/flutter_tools/lib/src/commands/cache.dart
@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-library sky_tools.cache;
-
import 'dart:async';
import 'package:args/command_runner.dart';
@@ -14,8 +12,8 @@
final Logger _logging = new Logger('sky_tools.cache');
class CacheCommand extends Command {
- final name = 'cache';
- final description = 'Manages sky_tools\' cache of binary artifacts.';
+ final String name = 'cache';
+ final String description = 'Manages sky_tools\' cache of binary artifacts.';
CacheCommand() {
addSubcommand(new _ClearCommand());
addSubcommand(new _PopulateCommand());
@@ -23,8 +21,8 @@
}
class _ClearCommand extends Command {
- final name = 'clear';
- final description = 'Clears all artifacts from the cache.';
+ final String name = 'clear';
+ final String description = 'Clears all artifacts from the cache.';
@override
Future<int> run() async {
@@ -34,8 +32,8 @@
}
class _PopulateCommand extends Command {
- final name = 'populate';
- final description = 'Populates the cache with all known artifacts.';
+ final String name = 'populate';
+ final String description = 'Populates the cache with all known artifacts.';
@override
Future<int> run() async {
diff --git a/packages/flutter_tools/lib/src/commands/flutter_command.dart b/packages/flutter_tools/lib/src/commands/flutter_command.dart
new file mode 100644
index 0000000..55038b9
--- /dev/null
+++ b/packages/flutter_tools/lib/src/commands/flutter_command.dart
@@ -0,0 +1,38 @@
+// 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:async';
+
+import 'package:args/command_runner.dart';
+
+import '../application_package.dart';
+import '../device.dart';
+import 'flutter_command_runner.dart';
+
+abstract class FlutterCommand extends Command {
+ FlutterCommandRunner get runner => super.runner;
+
+ Future downloadApplicationPackages() async {
+ if (applicationPackages == null)
+ applicationPackages = await ApplicationPackageStore.forConfigs(runner.buildConfigurations);
+ }
+
+ void connectToDevices() {
+ if (devices == null)
+ devices = new DeviceStore.forConfigs(runner.buildConfigurations);
+ }
+
+ Future downloadApplicationPackagesAndConnectToDevices() async {
+ await downloadApplicationPackages();
+ connectToDevices();
+ }
+
+ void inheritFromParent(FlutterCommand other) {
+ applicationPackages = other.applicationPackages;
+ devices = other.devices;
+ }
+
+ ApplicationPackageStore applicationPackages;
+ DeviceStore devices;
+}
diff --git a/packages/flutter_tools/lib/src/commands/flutter_command_runner.dart b/packages/flutter_tools/lib/src/commands/flutter_command_runner.dart
new file mode 100644
index 0000000..b346ecf
--- /dev/null
+++ b/packages/flutter_tools/lib/src/commands/flutter_command_runner.dart
@@ -0,0 +1,161 @@
+// 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:async';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:args/command_runner.dart';
+import 'package:logging/logging.dart';
+
+import '../artifacts.dart';
+import '../build_configuration.dart';
+
+final Logger _logging = new Logger('sky_tools.flutter_command_runner');
+
+class FlutterCommandRunner extends CommandRunner {
+ FlutterCommandRunner()
+ : super('flutter', 'Manage your Flutter app development.') {
+ argParser.addFlag('verbose',
+ abbr: 'v',
+ negatable: false,
+ help: 'Noisy logging, including all shell commands executed.');
+ argParser.addFlag('very-verbose',
+ negatable: false,
+ help: 'Very noisy logging, including the output of all '
+ 'shell commands executed.');
+
+ argParser.addSeparator('Global build selection options:');
+ argParser.addFlag('debug',
+ negatable: false,
+ help:
+ 'Set this if you are building Sky locally and want to use the debug build products. '
+ 'When set, attempts to automaticaly determine sky-src-path if sky-src-path is '
+ 'not set. Not normally required.');
+ argParser.addFlag('release',
+ negatable: false,
+ help:
+ 'Set this if you are building Sky locally and want to use the release build products. '
+ 'When set, attempts to automaticaly determine sky-src-path if sky-src-path is '
+ 'not set. Note that release is not compatible with the listen command '
+ 'on iOS devices and simulators. Not normally required.');
+ argParser.addOption('sky-src-path',
+ help:
+ 'Path to your Sky src directory, if you are building Sky locally. '
+ 'Ignored if neither debug nor release is set. Not normally required.');
+ argParser.addOption('android-debug-build-path',
+ help:
+ 'Path to your Android Debug out directory, if you are building Sky locally. '
+ 'This path is relative to sky-src-path. Not normally required.',
+ defaultsTo: 'out/android_Debug/');
+ argParser.addOption('android-release-build-path',
+ help:
+ 'Path to your Android Release out directory, if you are building Sky locally. '
+ 'This path is relative to sky-src-path. Not normally required.',
+ defaultsTo: 'out/android_Release/');
+ argParser.addOption('ios-debug-build-path',
+ help:
+ 'Path to your iOS Debug out directory, if you are building Sky locally. '
+ 'This path is relative to sky-src-path. Not normally required.',
+ defaultsTo: 'out/ios_Debug/');
+ argParser.addOption('ios-release-build-path',
+ help:
+ 'Path to your iOS Release out directory, if you are building Sky locally. '
+ 'This path is relative to sky-src-path. Not normally required.',
+ defaultsTo: 'out/ios_Release/');
+ argParser.addOption('ios-sim-debug-build-path',
+ help:
+ 'Path to your iOS Simulator Debug out directory, if you are building Sky locally. '
+ 'This path is relative to sky-src-path. Not normally required.',
+ defaultsTo: 'out/ios_sim_Debug/');
+ argParser.addOption('ios-sim-release-build-path',
+ help:
+ 'Path to your iOS Simulator Release out directory, if you are building Sky locally. '
+ 'This path is relative to sky-src-path. Not normally required.',
+ defaultsTo: 'out/ios_sim_Release/');
+ argParser.addOption('package-root',
+ help: 'Path to your packages directory.', defaultsTo: 'packages');
+ }
+
+ List<BuildConfiguration> buildConfigurations;
+
+ Future<int> runCommand(ArgResults globalResults) async {
+ if (globalResults['verbose'])
+ Logger.root.level = Level.INFO;
+
+ if (globalResults['very-verbose'])
+ Logger.root.level = Level.FINE;
+
+ ArtifactStore.packageRoot = globalResults['package-root'];
+ buildConfigurations = _createBuildConfigurations(globalResults);
+
+ return super.runCommand(globalResults);
+ }
+
+ List<BuildConfiguration> _createBuildConfigurations(ArgResults globalResults) {
+ // TODO(iansf): Figure out how to get the default src path
+ String enginePath = globalResults['sky-src-path'];
+
+ List<BuildConfiguration> configs = <BuildConfiguration>[];
+
+ if (enginePath == null) {
+ configs.add(new BuildConfiguration.prebuilt(platform: BuildPlatform.android));
+ } else {
+ if (!FileSystemEntity.isDirectorySync(enginePath))
+ _logging.warning('$enginePath is not a valid directory');
+
+ if (globalResults['debug']) {
+ configs.add(new BuildConfiguration.local(
+ type: BuildType.debug,
+ platform: BuildPlatform.android,
+ enginePath: enginePath,
+ buildPath: globalResults['android-debug-build-path']
+ ));
+
+ if (Platform.isMacOS) {
+ configs.add(new BuildConfiguration.local(
+ type: BuildType.debug,
+ platform: BuildPlatform.iOS,
+ enginePath: enginePath,
+ buildPath: globalResults['ios-debug-build-path']
+ ));
+
+ configs.add(new BuildConfiguration.local(
+ type: BuildType.debug,
+ platform: BuildPlatform.iOSSimulator,
+ enginePath: enginePath,
+ buildPath: globalResults['ios-sim-debug-build-path']
+ ));
+ }
+ }
+
+ if (globalResults['release']) {
+ configs.add(new BuildConfiguration.local(
+ type: BuildType.release,
+ platform: BuildPlatform.android,
+ enginePath: enginePath,
+ buildPath: globalResults['android-release-build-path']
+ ));
+
+ if (Platform.isMacOS) {
+ configs.add(new BuildConfiguration.local(
+ type: BuildType.release,
+ platform: BuildPlatform.iOS,
+ enginePath: enginePath,
+ buildPath: globalResults['ios-release-build-path']
+ ));
+
+ configs.add(new BuildConfiguration.local(
+ type: BuildType.release,
+ platform: BuildPlatform.iOSSimulator,
+ enginePath: enginePath,
+ buildPath: globalResults['ios-sim-release-build-path']
+ ));
+ }
+ }
+ }
+
+ return configs;
+ }
+}
diff --git a/packages/flutter_tools/lib/src/commands/init.dart b/packages/flutter_tools/lib/src/commands/init.dart
index 9a1de2f..c72dac2 100644
--- a/packages/flutter_tools/lib/src/commands/init.dart
+++ b/packages/flutter_tools/lib/src/commands/init.dart
@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-library sky_tools.init;
-
import 'dart:async';
import 'dart:io';
@@ -12,8 +10,8 @@
import 'package:path/path.dart' as p;
class InitCommand extends Command {
- final name = 'init';
- final description = 'Create a new Flutter project.';
+ final String name = 'init';
+ final String description = 'Create a new Flutter project.';
InitCommand() {
argParser.addOption('out', abbr: 'o', help: 'The output directory.');
diff --git a/packages/flutter_tools/lib/src/commands/install.dart b/packages/flutter_tools/lib/src/commands/install.dart
index 5f98fd5..9dc7daa 100644
--- a/packages/flutter_tools/lib/src/commands/install.dart
+++ b/packages/flutter_tools/lib/src/commands/install.dart
@@ -2,70 +2,39 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-library sky_tools.install;
-
import 'dart:async';
-import 'package:args/command_runner.dart';
-
import '../application_package.dart';
import '../device.dart';
+import 'flutter_command.dart';
-class InstallCommand extends Command {
- final name = 'install';
- final description = 'Install your Flutter app on attached devices.';
+class InstallCommand extends FlutterCommand {
+ final String name = 'install';
+ final String description = 'Install your Flutter app on attached devices.';
- AndroidDevice android;
- IOSDevice ios;
- IOSSimulator iosSim;
-
- InstallCommand({this.android, this.ios, this.iosSim}) {
+ InstallCommand() {
argParser.addFlag('boot',
help: 'Boot the iOS Simulator if it isn\'t already running.');
}
@override
Future<int> run() async {
- if (install(argResults['boot'])) {
- return 0;
- } else {
- return 2;
- }
+ await downloadApplicationPackagesAndConnectToDevices();
+ return install(boot: argResults['boot']) ? 0 : 2;
}
- bool install([bool boot = false]) {
- if (android == null) {
- android = new AndroidDevice();
- }
- if (ios == null) {
- ios = new IOSDevice();
- }
- if (iosSim == null) {
- iosSim = new IOSSimulator();
- }
-
- if (boot) {
- iosSim.boot();
- }
+ bool install({ bool boot: false }) {
+ if (boot)
+ devices.iOSSimulator?.boot();
bool installedSomewhere = false;
- Map<BuildPlatform, ApplicationPackage> packages =
- ApplicationPackageFactory.getAvailableApplicationPackages();
- ApplicationPackage androidApp = packages[BuildPlatform.android];
- ApplicationPackage iosApp = packages[BuildPlatform.iOS];
- ApplicationPackage iosSimApp = packages[BuildPlatform.iOSSimulator];
-
- if (androidApp != null && android.isConnected()) {
- installedSomewhere = android.installApp(androidApp) || installedSomewhere;
- }
-
- if (iosApp != null && ios.isConnected()) {
- installedSomewhere = ios.installApp(iosApp) || installedSomewhere;
- }
-
- if (iosSimApp != null && iosSim.isConnected()) {
- installedSomewhere = iosSim.installApp(iosSimApp) || installedSomewhere;
+ for (Device device in devices.all) {
+ ApplicationPackage package = applicationPackages.getPackageForPlatform(device.platform);
+ if (package == null || !device.isConnected())
+ continue;
+ if (device.installApp(package))
+ installedSomewhere = true;
}
return installedSomewhere;
diff --git a/packages/flutter_tools/lib/src/commands/list.dart b/packages/flutter_tools/lib/src/commands/list.dart
index 5af6c50..e81ca0b 100644
--- a/packages/flutter_tools/lib/src/commands/list.dart
+++ b/packages/flutter_tools/lib/src/commands/list.dart
@@ -2,25 +2,20 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-library sky_tools.list;
-
import 'dart:async';
-import 'package:args/command_runner.dart';
import 'package:logging/logging.dart';
+import 'flutter_command.dart';
import '../device.dart';
final Logger _logging = new Logger('sky_tools.list');
-class ListCommand extends Command {
- final name = 'list';
- final description = 'List all connected devices.';
- AndroidDevice android;
- IOSDevice ios;
- IOSSimulator iosSim;
+class ListCommand extends FlutterCommand {
+ final String name = 'list';
+ final String description = 'List all connected devices.';
- ListCommand({this.android, this.ios, this.iosSim}) {
+ ListCommand() {
argParser.addFlag('details',
abbr: 'd',
negatable: false,
@@ -29,11 +24,14 @@
@override
Future<int> run() async {
+ connectToDevices();
+
bool details = argResults['details'];
- if (details) {
+
+ if (details)
print('Android Devices:');
- }
- for (AndroidDevice device in AndroidDevice.getAttachedDevices(android)) {
+
+ for (AndroidDevice device in AndroidDevice.getAttachedDevices(devices.android)) {
if (details) {
print('${device.id}\t'
'${device.modelID}\t'
@@ -44,10 +42,10 @@
}
}
- if (details) {
+ if (details)
print('iOS Devices:');
- }
- for (IOSDevice device in IOSDevice.getAttachedDevices(ios)) {
+
+ for (IOSDevice device in IOSDevice.getAttachedDevices(devices.iOS)) {
if (details) {
print('${device.id}\t${device.name}');
} else {
@@ -58,7 +56,7 @@
if (details) {
print('iOS Simulators:');
}
- for (IOSSimulator device in IOSSimulator.getAttachedDevices(iosSim)) {
+ for (IOSSimulator device in IOSSimulator.getAttachedDevices(devices.iOSSimulator)) {
if (details) {
print('${device.id}\t${device.name}');
} else {
diff --git a/packages/flutter_tools/lib/src/commands/listen.dart b/packages/flutter_tools/lib/src/commands/listen.dart
index b08a2a6..9e294c5 100644
--- a/packages/flutter_tools/lib/src/commands/listen.dart
+++ b/packages/flutter_tools/lib/src/commands/listen.dart
@@ -2,45 +2,31 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-library sky_tools.listen;
-
import 'dart:async';
import 'dart:io';
-import 'package:args/command_runner.dart';
import 'package:logging/logging.dart';
+import 'flutter_command.dart';
import '../application_package.dart';
import '../device.dart';
import '../process.dart';
final Logger _logging = new Logger('sky_tools.listen');
-class ListenCommand extends Command {
- final name = 'listen';
- final description = 'Listen for changes to files and reload the running app '
- 'on all connected devices.';
- AndroidDevice android;
- IOSDevice ios;
- IOSSimulator iosSim;
+class ListenCommand extends FlutterCommand {
+ final String name = 'listen';
+ final String description = 'Listen for changes to files and reload the running app on all connected devices.';
List<String> watchCommand;
/// Only run once. Used for testing.
bool singleRun;
- ListenCommand({this.android, this.ios, this.iosSim, this.singleRun: false}) {}
+ ListenCommand({ this.singleRun: false });
@override
Future<int> run() async {
- if (android == null) {
- android = new AndroidDevice();
- }
- if (ios == null) {
- ios = new IOSDevice();
- }
- if (iosSim == null) {
- iosSim = new IOSSimulator();
- }
+ await downloadApplicationPackagesAndConnectToDevices();
if (argResults.rest.length > 0) {
watchCommand = _initWatchCommand(argResults.rest);
@@ -48,14 +34,8 @@
watchCommand = _initWatchCommand(['.']);
}
- Map<BuildPlatform, ApplicationPackage> packages =
- ApplicationPackageFactory.getAvailableApplicationPackages();
- ApplicationPackage androidApp = packages[BuildPlatform.android];
- ApplicationPackage iosApp = packages[BuildPlatform.iOS];
- ApplicationPackage iosSimApp = packages[BuildPlatform.iOSSimulator];
-
while (true) {
- _logging.info('Updating running Sky apps...');
+ _logging.info('Updating running Flutter apps...');
// TODO(iansf): refactor build command so that this doesn't have
// to call out like this.
@@ -76,25 +56,29 @@
} catch (e) {}
runSync(command);
- String localFLXPath = 'app.flx';
- String remoteFLXPath = 'Documents/app.flx';
+ String localFlutterBundle = 'app.flx';
+ String remoteFlutterBundle = 'Documents/app.flx';
- if (ios.isConnected()) {
- await ios.pushFile(iosApp, localFLXPath, remoteFLXPath);
+ 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);
+ }
}
- if (iosSim.isConnected()) {
- await iosSim.pushFile(iosSimApp, localFLXPath, remoteFLXPath);
- }
-
- if (android.isConnected()) {
- await android.startServer(
- argResults['target'], true, argResults['checked'], androidApp);
- }
-
- if (singleRun || !watchDirectory()) {
+ if (singleRun || !watchDirectory())
break;
- }
}
return 0;
@@ -135,9 +119,8 @@
}
bool watchDirectory() {
- if (watchCommand == null) {
+ if (watchCommand == null)
return false;
- }
try {
runCheckedSync(watchCommand);
@@ -145,6 +128,7 @@
_logging.warning('Watching directories failed.', e);
return false;
}
+
return true;
}
}
diff --git a/packages/flutter_tools/lib/src/commands/logs.dart b/packages/flutter_tools/lib/src/commands/logs.dart
index f488a5f..68a4979 100644
--- a/packages/flutter_tools/lib/src/commands/logs.dart
+++ b/packages/flutter_tools/lib/src/commands/logs.dart
@@ -2,25 +2,20 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-library sky_tools.logs;
-
import 'dart:async';
-import 'package:args/command_runner.dart';
import 'package:logging/logging.dart';
+import 'flutter_command.dart';
import '../device.dart';
final Logger _logging = new Logger('sky_tools.logs');
-class LogsCommand extends Command {
+class LogsCommand extends FlutterCommand {
final name = 'logs';
final description = 'Show logs for running Sky apps.';
- AndroidDevice android;
- IOSDevice ios;
- IOSSimulator iosSim;
- LogsCommand({this.android, this.ios, this.iosSim}) {
+ LogsCommand() {
argParser.addFlag('clear',
negatable: false,
help: 'Clear log history before reading from logs (Android only).');
@@ -28,42 +23,15 @@
@override
Future<int> run() async {
- if (android == null) {
- android = new AndroidDevice();
- }
- if (ios == null) {
- ios = new IOSDevice();
- }
- if (iosSim == null) {
- iosSim = new IOSSimulator();
- }
+ connectToDevices();
- Future<int> androidLogProcess = null;
- if (android.isConnected()) {
- androidLogProcess = android.logs(clear: argResults['clear']);
- }
+ bool clear = argResults['clear'];
- Future<int> iosLogProcess = null;
- if (ios.isConnected()) {
- iosLogProcess = ios.logs(clear: argResults['clear']);
- }
+ Iterable<Future<int>> results = devices.all.map(
+ (Device device) => device.logs(clear: clear));
- Future<int> iosSimLogProcess = null;
- if (iosSim.isConnected()) {
- iosSimLogProcess = iosSim.logs(clear: argResults['clear']);
- }
-
- if (androidLogProcess != null) {
- await androidLogProcess;
- }
-
- if (iosLogProcess != null) {
- await iosLogProcess;
- }
-
- if (iosSimLogProcess != null) {
- await iosSimLogProcess;
- }
+ for (Future<int> result in results)
+ await result;
return 0;
}
diff --git a/packages/flutter_tools/lib/src/commands/run_mojo.dart b/packages/flutter_tools/lib/src/commands/run_mojo.dart
index 077fccb..48679f8 100644
--- a/packages/flutter_tools/lib/src/commands/run_mojo.dart
+++ b/packages/flutter_tools/lib/src/commands/run_mojo.dart
@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-library sky_tools.run_mojo;
-
import 'dart:async';
import 'dart:io';
@@ -19,8 +17,9 @@
enum _MojoConfig { Debug, Release }
class RunMojoCommand extends Command {
- final name = 'run_mojo';
- final description = 'Run a Flutter app in mojo.';
+ final String name = 'run_mojo';
+ final String description = 'Run a Flutter app in mojo.';
+
RunMojoCommand() {
argParser.addFlag('android', negatable: false, help: 'Run on an Android device');
argParser.addFlag('checked', negatable: false, help: 'Run Flutter in checked mode');
@@ -31,6 +30,7 @@
argParser.addOption('mojo-path', help: 'Path to directory containing mojo_shell and services');
}
+ // TODO(abarth): Why not use path.absolute?
Future<String> _makePathAbsolute(String relativePath) async {
File file = new File(relativePath);
if (!await file.exists()) {
@@ -65,7 +65,7 @@
}
Future<int> _runLinux(String mojoPath, _MojoConfig mojoConfig, String appPath, List<String> additionalArgs) async {
- String viewerPath = await _makePathAbsolute(await ArtifactStore.getPath(Artifact.SkyViewerMojo));
+ String viewerPath = await _makePathAbsolute(await ArtifactStore.getPath(Artifact.skyViewerMojo));
String mojoBuildType = mojoConfig == _MojoConfig.Debug ? 'Debug' : 'Release';
String mojoShellPath = await _makePathAbsolute(path.join(mojoPath, 'out', mojoBuildType, 'mojo_shell'));
List<String> cmd = [
diff --git a/packages/flutter_tools/lib/src/commands/start.dart b/packages/flutter_tools/lib/src/commands/start.dart
index 67f1773..77e3c69 100644
--- a/packages/flutter_tools/lib/src/commands/start.dart
+++ b/packages/flutter_tools/lib/src/commands/start.dart
@@ -2,29 +2,24 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-library sky_tools.start;
-
import 'dart:async';
-import 'package:args/command_runner.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;
import '../application_package.dart';
import '../device.dart';
+import 'flutter_command.dart';
import 'install.dart';
import 'stop.dart';
final Logger _logging = new Logger('sky_tools.start');
-class StartCommand extends Command {
- final name = 'start';
- final description = 'Start your Flutter app on attached devices.';
- AndroidDevice android;
- IOSDevice ios;
- IOSSimulator iosSim;
+class StartCommand extends FlutterCommand {
+ final String name = 'start';
+ final String description = 'Start your Flutter app on attached devices.';
- StartCommand({this.android, this.ios, this.iosSim}) {
+ StartCommand() {
argParser.addFlag('poke',
negatable: false,
help: 'Restart the connection to the server (Android only).');
@@ -42,53 +37,36 @@
@override
Future<int> run() async {
- if (android == null) {
- android = new AndroidDevice();
- }
- if (ios == null) {
- ios = new IOSDevice();
- }
- if (iosSim == null) {
- iosSim = new IOSSimulator();
- }
+ await downloadApplicationPackagesAndConnectToDevices();
- bool startedSomewhere = false;
bool poke = argResults['poke'];
if (!poke) {
- StopCommand stopper = new StopCommand(android: android, ios: ios);
+ StopCommand stopper = new StopCommand();
+ stopper.inheritFromParent(this);
stopper.stop();
// Only install if the user did not specify a poke
- InstallCommand installer =
- new InstallCommand(android: android, ios: ios, iosSim: iosSim);
- installer.install(argResults['boot']);
+ InstallCommand installer = new InstallCommand();
+ installer.inheritFromParent(this);
+ installer.install(boot: argResults['boot']);
}
- Map<BuildPlatform, ApplicationPackage> packages =
- ApplicationPackageFactory.getAvailableApplicationPackages();
- ApplicationPackage androidApp = packages[BuildPlatform.android];
- ApplicationPackage iosApp = packages[BuildPlatform.iOS];
- ApplicationPackage iosSimApp = packages[BuildPlatform.iOSSimulator];
+ bool startedSomething = false;
- bool startedOnAndroid = false;
- if (androidApp != null && android.isConnected()) {
- String target = path.absolute(argResults['target']);
- startedOnAndroid = await android.startServer(
- target, poke, argResults['checked'], androidApp);
+ for (Device device in devices.all) {
+ ApplicationPackage package = applicationPackages.getPackageForPlatform(device.platform);
+ if (package == null || !device.isConnected())
+ continue;
+ if (device is AndroidDevice) {
+ String target = path.absolute(argResults['target']);
+ if (await device.startServer(target, poke, argResults['checked'], package))
+ startedSomething = true;
+ } else {
+ if (await device.startApp(package))
+ startedSomething = true;
+ }
}
- if (iosApp != null && ios.isConnected()) {
- startedSomewhere = await ios.startApp(iosApp) || startedSomewhere;
- }
-
- if (iosSimApp != null && iosSim.isConnected()) {
- startedSomewhere = await iosSim.startApp(iosSimApp) || startedSomewhere;
- }
-
- if (startedSomewhere || startedOnAndroid) {
- return 0;
- } else {
- return 2;
- }
+ return startedSomething ? 0 : 2;
}
}
diff --git a/packages/flutter_tools/lib/src/commands/stop.dart b/packages/flutter_tools/lib/src/commands/stop.dart
index 3d5128e..6118b9b 100644
--- a/packages/flutter_tools/lib/src/commands/stop.dart
+++ b/packages/flutter_tools/lib/src/commands/stop.dart
@@ -2,64 +2,35 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-library sky_tools.stop;
-
import 'dart:async';
-import 'package:args/command_runner.dart';
import 'package:logging/logging.dart';
import '../application_package.dart';
import '../device.dart';
+import 'flutter_command.dart';
final Logger _logging = new Logger('sky_tools.stop');
-class StopCommand extends Command {
- final name = 'stop';
- final description = 'Stop your Flutter app on all attached devices.';
- AndroidDevice android;
- IOSDevice ios;
- IOSSimulator iosSim;
-
- StopCommand({this.android, this.ios, this.iosSim});
+class StopCommand extends FlutterCommand {
+ final String name = 'stop';
+ final String description = 'Stop your Flutter app on all attached devices.';
@override
Future<int> run() async {
- if (await stop()) {
- return 0;
- } else {
- return 2;
- }
+ await downloadApplicationPackagesAndConnectToDevices();
+ return await stop() ? 0 : 2;
}
Future<bool> stop() async {
- if (android == null) {
- android = new AndroidDevice();
- }
- if (ios == null) {
- ios = new IOSDevice();
- }
- if (iosSim == null) {
- iosSim = new IOSSimulator();
- }
-
bool stoppedSomething = false;
- Map<BuildPlatform, ApplicationPackage> packages =
- ApplicationPackageFactory.getAvailableApplicationPackages();
- if (android.isConnected()) {
- ApplicationPackage androidApp = packages[BuildPlatform.android];
- stoppedSomething = await android.stopApp(androidApp) || stoppedSomething;
- }
-
- if (ios.isConnected()) {
- ApplicationPackage iosApp = packages[BuildPlatform.iOS];
- stoppedSomething = await ios.stopApp(iosApp) || stoppedSomething;
- }
-
- if (iosSim.isConnected()) {
- ApplicationPackage iosApp = packages[BuildPlatform.iOSSimulator];
- stoppedSomething = await iosSim.stopApp(iosApp) || stoppedSomething;
+ for (Device device in devices.all) {
+ ApplicationPackage package = applicationPackages.getPackageForPlatform(device.platform);
+ if (package == null || !device.isConnected())
+ continue;
+ if (await device.stopApp(package))
+ stoppedSomething = true;
}
return stoppedSomething;
diff --git a/packages/flutter_tools/lib/src/commands/trace.dart b/packages/flutter_tools/lib/src/commands/trace.dart
index 6b41635..7ad257f 100644
--- a/packages/flutter_tools/lib/src/commands/trace.dart
+++ b/packages/flutter_tools/lib/src/commands/trace.dart
@@ -2,28 +2,25 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-library sky_tools.trace;
-
import 'dart:async';
-import 'package:args/command_runner.dart';
import 'package:logging/logging.dart';
+import 'flutter_command.dart';
import '../application_package.dart';
import '../device.dart';
final Logger _logging = new Logger('sky_tools.trace');
-class TraceCommand extends Command {
- final name = 'trace';
- final description = 'Start and stop tracing a running Flutter app '
+class TraceCommand extends FlutterCommand {
+ final String name = 'trace';
+ final String description = 'Start and stop tracing a running Flutter app '
'(Android only, requires root).\n'
'To start a trace, wait, and then stop the trace, don\'t set any flags '
'except (optionally) duration.\n'
'Otherwise, specify either start or stop to manually control the trace.';
- AndroidDevice android;
- TraceCommand([this.android]) {
+ TraceCommand() {
argParser.addFlag('start', negatable: false, help: 'Start tracing.');
argParser.addFlag('stop', negatable: false, help: 'Stop tracing.');
argParser.addOption('duration',
@@ -32,35 +29,32 @@
@override
Future<int> run() async {
- if (android == null) {
- android = new AndroidDevice();
- }
+ await downloadApplicationPackagesAndConnectToDevices();
- if (!android.isConnected()) {
+ if (!devices.android.isConnected()) {
_logging.warning('No device connected, so no trace was completed.');
return 1;
}
- Map<BuildPlatform, ApplicationPackage> packages =
- ApplicationPackageFactory.getAvailableApplicationPackages();
- ApplicationPackage androidApp = packages[BuildPlatform.android];
+
+ ApplicationPackage androidApp = applicationPackages.android;
if ((!argResults['start'] && !argResults['stop']) ||
(argResults['start'] && argResults['stop'])) {
// Setting neither flags or both flags means do both commands and wait
// duration seconds in between.
- android.startTracing(androidApp);
+ devices.android.startTracing(androidApp);
await new Future.delayed(
new Duration(seconds: int.parse(argResults['duration'])),
- () => _stopTracing(androidApp));
+ () => _stopTracing(devices.android, androidApp));
} else if (argResults['stop']) {
- _stopTracing(androidApp);
+ _stopTracing(devices.android, androidApp);
} else {
- android.startTracing(androidApp);
+ devices.android.startTracing(androidApp);
}
return 0;
}
- void _stopTracing(AndroidApk androidApp) {
+ void _stopTracing(AndroidDevice android, AndroidApk androidApp) {
String tracePath = android.stopTracing(androidApp);
if (tracePath == null) {
_logging.warning('No trace file saved.');
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index 69ae496..4187858 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -12,15 +12,16 @@
import 'package:path/path.dart' as path;
import 'application_package.dart';
+import 'build_configuration.dart';
import 'process.dart';
final Logger _logging = new Logger('sky_tools.device');
-abstract class _Device {
+abstract class Device {
final String id;
- static Map<String, _Device> _deviceCache = {};
+ static Map<String, Device> _deviceCache = {};
- factory _Device(String className, [String id = null]) {
+ factory Device._unique(String className, [String id = null]) {
if (id == null) {
if (className == AndroidDevice.className) {
id = AndroidDevice.defaultDeviceID;
@@ -52,7 +53,7 @@
});
}
- _Device._(this.id);
+ Device._(this.id);
/// Install an app package on the current device
bool installApp(ApplicationPackage app);
@@ -63,6 +64,10 @@
/// Check if the current version of the given app is already installed
bool isAppInstalled(ApplicationPackage app);
+ BuildPlatform get platform;
+
+ Future<int> logs({bool clear: false});
+
/// Start an app package on the current device
Future<bool> startApp(ApplicationPackage app);
@@ -70,7 +75,7 @@
Future<bool> stopApp(ApplicationPackage app);
}
-class IOSDevice extends _Device {
+class IOSDevice extends Device {
static const String className = 'IOSDevice';
static final String defaultDeviceID = 'default_ios_id';
@@ -105,7 +110,7 @@
String get name => _name;
factory IOSDevice({String id, String name}) {
- IOSDevice device = new _Device(className, id);
+ IOSDevice device = new Device._unique(className, id);
device._name = name;
return device;
}
@@ -174,9 +179,9 @@
bool installApp(ApplicationPackage app) {
try {
if (id == defaultDeviceID) {
- runCheckedSync([installerPath, '-i', app.appPath]);
+ runCheckedSync([installerPath, '-i', app.localPath]);
} else {
- runCheckedSync([installerPath, '-u', id, '-i', app.appPath]);
+ runCheckedSync([installerPath, '-u', id, '-i', app.localPath]);
}
return true;
} catch (e) {
@@ -200,7 +205,7 @@
bool isAppInstalled(ApplicationPackage app) {
try {
String apps = runCheckedSync([installerPath, '-l']);
- if (new RegExp(app.appPackageID, multiLine: true).hasMatch(apps)) {
+ if (new RegExp(app.id, multiLine: true).hasMatch(apps)) {
return true;
}
} catch (e) {
@@ -217,7 +222,7 @@
// idevicedebug hangs forever after launching the app, so kill it after
// giving it plenty of time to send the launch command.
return runAndKill(
- [debuggerPath, 'run', app.appPackageID], new Duration(seconds: 3)).then(
+ [debuggerPath, 'run', app.id], new Duration(seconds: 3)).then(
(_) {
return true;
}, onError: (e) {
@@ -240,7 +245,7 @@
'-t',
'1',
'--bundle_id',
- app.appPackageID,
+ app.id,
'--upload',
localFile,
'--to',
@@ -258,6 +263,9 @@
return false;
}
+ @override
+ BuildPlatform get platform => BuildPlatform.iOS;
+
/// Note that clear is not supported on iOS at this time.
Future<int> logs({bool clear: false}) async {
if (!isConnected()) {
@@ -268,7 +276,7 @@
}
}
-class IOSSimulator extends _Device {
+class IOSSimulator extends Device {
static const String className = 'IOSSimulator';
static final String defaultDeviceID = 'default_ios_sim_id';
@@ -288,7 +296,7 @@
String get name => _name;
factory IOSSimulator({String id, String name, String iOSSimulatorPath}) {
- IOSSimulator device = new _Device(className, id);
+ IOSSimulator device = new Device._unique(className, id);
device._name = name;
if (iOSSimulatorPath == null) {
iOSSimulatorPath = path.join('/Applications', 'iOS Simulator.app',
@@ -401,9 +409,9 @@
}
try {
if (id == defaultDeviceID) {
- runCheckedSync([xcrunPath, 'simctl', 'install', 'booted', app.appPath]);
+ runCheckedSync([xcrunPath, 'simctl', 'install', 'booted', app.localPath]);
} else {
- runCheckedSync([xcrunPath, 'simctl', 'install', id, app.appPath]);
+ runCheckedSync([xcrunPath, 'simctl', 'install', id, app.localPath]);
}
return true;
} catch (e) {
@@ -444,9 +452,9 @@
try {
if (id == defaultDeviceID) {
runCheckedSync(
- [xcrunPath, 'simctl', 'launch', 'booted', app.appPackageID]);
+ [xcrunPath, 'simctl', 'launch', 'booted', app.id]);
} else {
- runCheckedSync([xcrunPath, 'simctl', 'launch', id, app.appPackageID]);
+ runCheckedSync([xcrunPath, 'simctl', 'launch', id, app.id]);
}
return true;
} catch (e) {
@@ -471,6 +479,9 @@
return false;
}
+ @override
+ BuildPlatform get platform => BuildPlatform.iOSSimulator;
+
Future<int> logs({bool clear: false}) async {
if (!isConnected()) {
return 2;
@@ -487,7 +498,7 @@
}
}
-class AndroidDevice extends _Device {
+class AndroidDevice extends Device {
static const String _ADB_PATH = 'adb';
static const String _observatoryPort = '8181';
static const String _serverPort = '9888';
@@ -509,7 +520,7 @@
String productID: null,
String modelID: null,
String deviceCodeName: null}) {
- AndroidDevice device = new _Device(className, id);
+ AndroidDevice device = new Device._unique(className, id);
device.productID = productID;
device.modelID = modelID;
device.deviceCodeName = deviceCodeName;
@@ -553,15 +564,13 @@
_adbPath = _getAdbPath();
_hasAdb = _checkForAdb();
- if (isConnected()) {
- // Checking for lollipop only needs to be done if we are starting an
- // app, but it has an important side effect, which is to discard any
- // progress messages if the adb server is restarted.
- _hasValidAndroid = _checkForLollipopOrLater();
+ // Checking for lollipop only needs to be done if we are starting an
+ // app, but it has an important side effect, which is to discard any
+ // progress messages if the adb server is restarted.
+ _hasValidAndroid = _checkForLollipopOrLater();
- if (!_hasAdb || !_hasValidAndroid) {
- _logging.severe('Unable to run on Android.');
- }
+ if (!_hasAdb || !_hasValidAndroid) {
+ _logging.severe('Unable to run on Android.');
}
}
@@ -663,7 +672,7 @@
}
String _getDeviceSha1Path(ApplicationPackage app) {
- return '/sdcard/${app.appPackageID}/${app.appFileName}.sha1';
+ return '/sdcard/${app.id}/${app.name}.sha1';
}
String _getDeviceApkSha1(ApplicationPackage app) {
@@ -672,7 +681,7 @@
String _getSourceSha1(ApplicationPackage app) {
String sha1 =
- runCheckedSync(['shasum', '-a', '1', '-p', app.appPath]).split(' ')[0];
+ runCheckedSync(['shasum', '-a', '1', '-p', app.localPath]).split(' ')[0];
return sha1;
}
@@ -681,15 +690,15 @@
if (!isConnected()) {
return false;
}
- if (runCheckedSync([adbPath, 'shell', 'pm', 'path', app.appPackageID]) ==
+ if (runCheckedSync([adbPath, 'shell', 'pm', 'path', app.id]) ==
'') {
_logging.info(
- 'TODO(iansf): move this log to the caller. ${app.appFileName} is not on the device. Installing now...');
+ 'TODO(iansf): move this log to the caller. ${app.name} is not on the device. Installing now...');
return false;
}
if (_getDeviceApkSha1(app) != _getSourceSha1(app)) {
_logging.info(
- 'TODO(iansf): move this log to the caller. ${app.appFileName} is out of date. Installing now...');
+ 'TODO(iansf): move this log to the caller. ${app.name} is out of date. Installing now...');
return false;
}
return true;
@@ -701,16 +710,16 @@
_logging.info('Android device not connected. Not installing.');
return false;
}
- if (!FileSystemEntity.isFileSync(app.appPath)) {
- _logging.severe('"${app.appPath}" does not exist.');
+ if (!FileSystemEntity.isFileSync(app.localPath)) {
+ _logging.severe('"${app.localPath}" does not exist.');
return false;
}
- runCheckedSync([adbPath, 'install', '-r', app.appPath]);
+ runCheckedSync([adbPath, 'install', '-r', app.localPath]);
Directory tempDir = Directory.systemTemp;
String sha1Path = path.join(
- tempDir.path, (app.appPath + '.sha1').replaceAll(path.separator, '_'));
+ tempDir.path, (app.localPath + '.sha1').replaceAll(path.separator, '_'));
File sha1TempFile = new File(sha1Path);
sha1TempFile.writeAsStringSync(_getSourceSha1(app), flush: true);
runCheckedSync([adbPath, 'push', sha1Path, _getDeviceSha1Path(app)]);
@@ -774,7 +783,7 @@
if (checked) {
cmd.addAll(['--ez', 'enable-checked-mode', 'true']);
}
- cmd.add(apk.component);
+ cmd.add(apk.launchActivity);
runCheckedSync(cmd);
@@ -792,7 +801,7 @@
// Turn off reverse port forwarding
runSync([adbPath, 'reverse', '--remove', 'tcp:$_serverPort']);
// Stop the app
- runSync([adbPath, 'shell', 'am', 'force-stop', apk.appPackageID]);
+ runSync([adbPath, 'shell', 'am', 'force-stop', apk.id]);
// Kill the server
if (Platform.isMacOS) {
String pid = runSync(['lsof', '-i', ':$_serverPort', '-t']);
@@ -808,6 +817,9 @@
return true;
}
+ @override
+ BuildPlatform get platform => BuildPlatform.android;
+
void clearLogs() {
runSync([adbPath, 'logcat', '-c']);
}
@@ -839,7 +851,7 @@
'am',
'broadcast',
'-a',
- '${apk.appPackageID}.TRACING_START'
+ '${apk.id}.TRACING_START'
]);
}
@@ -851,7 +863,7 @@
'am',
'broadcast',
'-a',
- '${apk.appPackageID}.TRACING_STOP'
+ '${apk.id}.TRACING_STOP'
]);
RegExp traceRegExp = new RegExp(r'Saving trace to (\S+)', multiLine: true);
@@ -891,3 +903,57 @@
@override
bool isConnected() => _hasValidAndroid;
}
+
+class DeviceStore {
+ final AndroidDevice android;
+ final IOSDevice iOS;
+ final IOSSimulator iOSSimulator;
+
+ List<Device> get all {
+ List<Device> result = <Device>[];
+ if (android != null)
+ result.add(android);
+ if (iOS != null)
+ result.add(iOS);
+ if (iOSSimulator != null)
+ result.add(iOSSimulator);
+ return result;
+ }
+
+ DeviceStore({
+ this.android,
+ this.iOS,
+ this.iOSSimulator
+ });
+
+ factory DeviceStore.forConfigs(List<BuildConfiguration> configs) {
+ AndroidDevice android;
+ IOSDevice iOS;
+ IOSSimulator iOSSimulator;
+
+ for (BuildConfiguration config in configs) {
+ switch (config.platform) {
+ case BuildPlatform.android:
+ assert(android == null);
+ android = new AndroidDevice();
+ break;
+ case BuildPlatform.iOS:
+ assert(iOS == null);
+ iOS = new IOSDevice();
+ break;
+ case BuildPlatform.iOSSimulator:
+ assert(iOSSimulator == null);
+ iOSSimulator = new IOSSimulator();
+ break;
+
+ case BuildPlatform.mac:
+ case BuildPlatform.linux:
+ // TODO(abarth): Support mac and linux targets.
+ assert(false);
+ break;
+ }
+ }
+
+ return new DeviceStore(android: android, iOS: iOS, iOSSimulator: iOSSimulator);
+ }
+}