Merge pull request #1517 from yjbanov/overflow-box-debugFillDescription-1487
add debugFillDescription to OverflowBox
diff --git a/bin/flutter b/bin/flutter
index 3803576..1ec69b6 100755
--- a/bin/flutter
+++ b/bin/flutter
@@ -7,6 +7,7 @@
export FLUTTER_ROOT=$(dirname $(dirname "${BASH_SOURCE[0]}"))
FLUTTER_TOOLS_DIR="$FLUTTER_ROOT/packages/flutter_tools"
+FLUTTER_DIR="$FLUTTER_ROOT/packages/flutter"
SNAPSHOT_PATH="$FLUTTER_ROOT/bin/cache/flutter_tools.snapshot"
STAMP_PATH="$FLUTTER_ROOT/bin/cache/flutter_tools.stamp"
SCRIPT_PATH="$FLUTTER_TOOLS_DIR/bin/flutter_tools.dart"
@@ -18,6 +19,7 @@
if [ ! -f "$SNAPSHOT_PATH" ] || [ ! -f "$STAMP_PATH" ] || [ `cat "$STAMP_PATH"` != "$REVISION" ] || [ "$FLUTTER_TOOLS_DIR/pubspec.yaml" -nt "$FLUTTER_TOOLS_DIR/pubspec.lock" ]; then
echo Updating flutter tool...
(cd "$FLUTTER_TOOLS_DIR"; pub get > /dev/null)
+ (cd "$FLUTTER_DIR"; pub get > /dev/null) # Allows us to check if sky_engine's REVISION is correct
$DART --snapshot="$SNAPSHOT_PATH" --package-root="$FLUTTER_TOOLS_DIR/packages" "$SCRIPT_PATH"
echo -n $REVISION > "$STAMP_PATH"
fi
diff --git a/bin/flutter.bat b/bin/flutter.bat
index 4d8580e..d047aa6 100644
--- a/bin/flutter.bat
+++ b/bin/flutter.bat
@@ -6,6 +6,7 @@
SETLOCAL ENABLEDELAYEDEXPANSION
FOR %%i IN ("%~dp0..") DO SET "flutter_root=%%~fi" REM Get the parent directory
SET flutter_tools_dir=%flutter_root%\packages\flutter_tools
+SET flutter_dir=%flutter_root%\packages\flutter
SET snapshot_path=%flutter_root%\bin\cache\flutter_tools.snapshot
SET stamp_path=%flutter_root%\bin\cache\flutter_tools.stamp
SET script_path=%flutter_tools_dir%\bin\flutter_tools.dart
@@ -33,6 +34,9 @@
CD "%flutter_tools_dir%"
ECHO Updating flutter tool...
CALL pub.bat get
+CD "%flutter_dir"
+REM Allows us to check if sky_engine's REVISION is correct
+CALL pub.bat get
CD "%flutter_root%"
CALL %dart% --snapshot="%snapshot_path%" --package-root="%flutter_tools_dir%\packages" "%script_path%"
<nul SET /p=%revision%> "%stamp_path%"
diff --git a/packages/flutter_tools/lib/src/android/device_android.dart b/packages/flutter_tools/lib/src/android/device_android.dart
index 6d94c5a..934d4cd 100644
--- a/packages/flutter_tools/lib/src/android/device_android.dart
+++ b/packages/flutter_tools/lib/src/android/device_android.dart
@@ -294,6 +294,7 @@
String mainPath,
String route,
bool checked: true,
+ bool clearLogs: false,
Map<String, dynamic> platformArgs
}) {
return flx.buildInTempDir(
@@ -309,7 +310,7 @@
checked: checked,
traceStartup: platformArgs['trace-startup'],
route: route,
- clearLogs: platformArgs['clear-logs']
+ clearLogs: clearLogs
)) {
return true;
} else {
@@ -334,26 +335,7 @@
runSync(adbCommandForDevice(['logcat', '-c']));
}
- Future<int> logs({bool clear: false}) async {
- if (!isConnected()) {
- return 2;
- }
-
- if (clear) {
- clearLogs();
- }
-
- return await runCommandAndStreamOutput(adbCommandForDevice([
- 'logcat',
- '-v',
- 'tag', // Only log the tag and the message
- '-s',
- 'flutter:V',
- 'ActivityManager:W',
- 'System.err:W',
- '*:F',
- ]), prefix: 'android: ');
- }
+ DeviceLogReader createLogReader() => new _AdbLogReader(this);
void startTracing(AndroidApk apk) {
runCheckedSync(adbCommandForDevice([
@@ -529,3 +511,41 @@
return _defaultAdbPath;
}
}
+
+/// A log reader that logs from `adb logcat`. This will have the same output as
+/// another copy of [_AdbLogReader], and the two instances will be equivalent.
+class _AdbLogReader extends DeviceLogReader {
+ _AdbLogReader(this.device);
+
+ final AndroidDevice device;
+
+ String get name => 'Android';
+
+ Future<int> logs({bool clear: false}) async {
+ if (!device.isConnected())
+ return 2;
+
+ if (clear)
+ device.clearLogs();
+
+ return await runCommandAndStreamOutput(device.adbCommandForDevice(<String>[
+ 'logcat',
+ '-v',
+ 'tag', // Only log the tag and the message
+ '-s',
+ 'flutter:V',
+ 'ActivityManager:W',
+ 'System.err:W',
+ '*:F',
+ ]), prefix: '[Android] ');
+ }
+
+ // Intentionally constant; overridden because we've overridden the `operator ==` method below.
+ int get hashCode => name.hashCode;
+
+ bool operator ==(dynamic other) {
+ if (identical(this, other))
+ return true;
+ return other is _AdbLogReader;
+ }
+}
diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart
index 5af0f3c..123b752 100644
--- a/packages/flutter_tools/lib/src/artifacts.dart
+++ b/packages/flutter_tools/lib/src/artifacts.dart
@@ -192,12 +192,15 @@
}
}
- static void ensureHasSkyEnginePackage() {
- Directory skyEnginePackage = new Directory(path.join(packageRoot, 'sky_engine'));
- if (!skyEnginePackage.existsSync()) {
+ static void validateSkyEnginePackage() {
+ if (engineRevision == null) {
printError("Cannot locate the sky_engine package; did you include 'flutter' in your pubspec.yaml file?");
throw new ProcessExit(2);
}
+ if (engineRevision != expectedEngineRevision) {
+ printError("Error: incompatible sky_engine package; please run 'pub get' to get the correct one.\n");
+ throw new ProcessExit(2);
+ }
}
static String _engineRevision;
@@ -211,6 +214,18 @@
return _engineRevision;
}
+ static String _expectedEngineRevision;
+
+ static String get expectedEngineRevision {
+ if (_expectedEngineRevision == null) {
+ // TODO(jackson): Parse the .packages file and use the path from there instead
+ File revisionFile = new File(path.join(flutterRoot, 'packages', 'flutter', 'packages', 'sky_engine', 'REVISION'));
+ if (revisionFile.existsSync())
+ _expectedEngineRevision = revisionFile.readAsStringSync();
+ }
+ return _expectedEngineRevision;
+ }
+
static String getCloudStorageBaseUrl(String platform) {
return _getCloudStorageBaseUrl(
platform: platform,
@@ -295,7 +310,7 @@
}
static Directory _getCacheDirForPlatform(String platform) {
- ensureHasSkyEnginePackage();
+ validateSkyEnginePackage();
Directory baseDir = _getBaseCacheDir();
// TODO(jamesr): Add support for more configurations.
String config = 'Release';
diff --git a/packages/flutter_tools/lib/src/base/process.dart b/packages/flutter_tools/lib/src/base/process.dart
index bc9b8c8..7da7699 100644
--- a/packages/flutter_tools/lib/src/base/process.dart
+++ b/packages/flutter_tools/lib/src/base/process.dart
@@ -8,12 +8,15 @@
import 'context.dart';
+typedef String StringConverter(String string);
+
/// This runs the command and streams stdout/stderr from the child process to
/// this process' stdout/stderr.
Future<int> runCommandAndStreamOutput(List<String> cmd, {
+ String workingDirectory,
String prefix: '',
RegExp filter,
- String workingDirectory
+ StringConverter mapFunction
}) async {
printTrace(cmd.join(' '));
Process process = await Process.start(
@@ -26,14 +29,20 @@
.transform(const LineSplitter())
.where((String line) => filter == null ? true : filter.hasMatch(line))
.listen((String line) {
- printStatus('$prefix$line');
+ if (mapFunction != null)
+ line = mapFunction(line);
+ if (line != null)
+ printStatus('$prefix$line');
});
process.stderr
.transform(UTF8.decoder)
.transform(const LineSplitter())
.where((String line) => filter == null ? true : filter.hasMatch(line))
.listen((String line) {
- printError('$prefix$line');
+ if (mapFunction != null)
+ line = mapFunction(line);
+ if (line != null)
+ printError('$prefix$line');
});
return await process.exitCode;
}
@@ -57,7 +66,7 @@
/// Run cmd and return stdout.
/// Throws an error if cmd exits with a non-zero value.
String runCheckedSync(List<String> cmd, { String workingDirectory }) {
- return _runWithLoggingSync(cmd, workingDirectory: workingDirectory, checked: true);
+ return _runWithLoggingSync(cmd, workingDirectory: workingDirectory, checked: true, noisyErrors: true);
}
/// Run cmd and return stdout.
@@ -73,6 +82,7 @@
String _runWithLoggingSync(List<String> cmd, {
bool checked: false,
+ bool noisyErrors: false,
String workingDirectory
}) {
printTrace(cmd.join(' '));
@@ -82,8 +92,13 @@
String errorDescription = 'Error code ${results.exitCode} '
'returned when attempting to run command: ${cmd.join(' ')}';
printTrace(errorDescription);
- if (results.stderr.length > 0)
- printTrace('Errors logged: ${results.stderr.trim()}');
+ if (results.stderr.length > 0) {
+ if (noisyErrors) {
+ printError(results.stderr.trim());
+ } else {
+ printTrace('Errors logged: ${results.stderr.trim()}');
+ }
+ }
if (checked)
throw errorDescription;
}
diff --git a/packages/flutter_tools/lib/src/commands/ios.dart b/packages/flutter_tools/lib/src/commands/ios.dart
index 3a9d7e7..b424981 100644
--- a/packages/flutter_tools/lib/src/commands/ios.dart
+++ b/packages/flutter_tools/lib/src/commands/ios.dart
@@ -17,8 +17,6 @@
final String name = "ios";
final String description = "Commands for creating and updating Flutter iOS projects.";
- final bool requiresProjectRoot = true;
-
IOSCommand() {
argParser.addFlag('init', help: 'Initialize the Xcode project for building the iOS application');
}
diff --git a/packages/flutter_tools/lib/src/commands/logs.dart b/packages/flutter_tools/lib/src/commands/logs.dart
index a98e3ab..0b274d3 100644
--- a/packages/flutter_tools/lib/src/commands/logs.dart
+++ b/packages/flutter_tools/lib/src/commands/logs.dart
@@ -4,6 +4,7 @@
import 'dart:async';
+import '../base/context.dart';
import '../device.dart';
import '../runner/flutter_command.dart';
@@ -13,25 +14,52 @@
LogsCommand() {
argParser.addFlag('clear',
- negatable: false,
- abbr: 'c',
- help: 'Clear log history before reading from logs (Android only).');
+ negatable: false,
+ abbr: 'c',
+ help: 'Clear log history before reading from logs.'
+ );
}
bool get requiresProjectRoot => false;
- @override
Future<int> runInProject() async {
- connectToDevices();
+ DeviceManager deviceManager = new DeviceManager();
+ List<Device> devices;
+
+ String deviceId = globalResults['device-id'];
+ if (deviceId != null) {
+ Device device = await deviceManager.getDeviceById(deviceId);
+ if (device == null) {
+ printError("No device found with id '$deviceId'.");
+ return 1;
+ }
+ devices = <Device>[device];
+ } else {
+ devices = await deviceManager.getDevices();
+ }
+
+ if (devices.isEmpty) {
+ printStatus('No connected devices.');
+ return 0;
+ }
bool clear = argResults['clear'];
- Iterable<Future<int>> results = devices.all.map(
- (Device device) => device.logs(clear: clear));
+ Set<DeviceLogReader> readers = new Set<DeviceLogReader>();
+ for (Device device in devices) {
+ readers.add(device.createLogReader());
+ }
- for (Future<int> result in results)
- await result;
+ printStatus('Logging for ${readers.join(', ')}...');
- return 0;
+ List<int> results = await Future.wait(readers.map((DeviceLogReader reader) async {
+ int result = await reader.logs(clear: clear);
+ if (result != 0)
+ printError('Error listening to $reader logs.');
+ return result;
+ }));
+
+ // If all readers failed, return an error.
+ return results.every((int result) => result != 0) ? 1 : 0;
}
}
diff --git a/packages/flutter_tools/lib/src/commands/start.dart b/packages/flutter_tools/lib/src/commands/start.dart
index 121104a..60f3c60 100644
--- a/packages/flutter_tools/lib/src/commands/start.dart
+++ b/packages/flutter_tools/lib/src/commands/start.dart
@@ -134,8 +134,6 @@
if (traceStartup != null)
platformArgs['trace-startup'] = traceStartup;
- if (clearLogs != null)
- platformArgs['clear-logs'] = clearLogs;
printStatus('Starting ${_getDisplayPath(mainPath)} on ${device.name}...');
@@ -145,6 +143,7 @@
mainPath: mainPath,
route: route,
checked: checked,
+ clearLogs: clearLogs,
platformArgs: platformArgs
);
diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart
index 4ca8119..127391b 100644
--- a/packages/flutter_tools/lib/src/commands/test.dart
+++ b/packages/flutter_tools/lib/src/commands/test.dart
@@ -20,13 +20,18 @@
bool get requiresProjectRoot => false;
- String get projectRootValidationErrorMessage {
- return 'Error: No pubspec.yaml file found.\n'
- 'If you wish to run the tests in the Flutter repository\'s \'flutter\' package,\n'
- 'pass --flutter-repo before any test paths. Otherwise, run this command from the\n'
- 'root of your project. Test files must be called *_test.dart and must reside in\n'
- 'the package\'s \'test\' directory (or one of its subdirectories).';
- }
+ @override
+ Validator projectRootValidator = () {
+ if (!FileSystemEntity.isFileSync('pubspec.yaml')) {
+ printError('Error: No pubspec.yaml file found.\n'
+ 'If you wish to run the tests in the Flutter repository\'s \'flutter\' package,\n'
+ 'pass --flutter-repo before any test paths. Otherwise, run this command from the\n'
+ 'root of your project. Test files must be called *_test.dart and must reside in\n'
+ 'the package\'s \'test\' directory (or one of its subdirectories).');
+ return false;
+ }
+ return true;
+ };
Future<String> _getShellPath(BuildConfiguration config) async {
if (config.type == BuildType.prebuilt) {
@@ -80,7 +85,7 @@
List<String> testArgs = argResults.rest.map((String testPath) => path.absolute(testPath)).toList();
final bool runFlutterTests = argResults['flutter-repo'];
- if (!runFlutterTests && !validateProjectRoot())
+ if (!runFlutterTests && !projectRootValidator())
return 1;
// If we're running the flutter tests, we want to use the packages directory
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index b89b786..4de713e 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -34,6 +34,19 @@
Completer _initedCompleter = new Completer();
+ /// Return the device with the matching ID; else, complete the Future with
+ /// `null`.
+ ///
+ /// This does a case insentitive compare with `deviceId`.
+ Future<Device> getDeviceById(String deviceId) async {
+ deviceId = deviceId.toLowerCase();
+ List<Device> devices = await getDevices();
+ return devices.firstWhere(
+ (Device device) => device.id.toLowerCase() == deviceId,
+ orElse: () => null
+ );
+ }
+
Future<List<Device>> getDevices() async {
await _initedCompleter.future;
@@ -78,7 +91,7 @@
TargetPlatform get platform;
- Future<int> logs({bool clear: false});
+ DeviceLogReader createLogReader();
/// Start an app package on the current device.
///
@@ -90,6 +103,7 @@
String mainPath,
String route,
bool checked: true,
+ bool clearLogs: false,
Map<String, dynamic> platformArgs
});
@@ -99,6 +113,21 @@
String toString() => '$runtimeType $id';
}
+/// Read the log for a particular device. Subclasses must implement `hashCode`
+/// and `operator ==` so that log readers that read from the same location can be
+/// de-duped. For example, two Android devices will both try and log using
+/// `adb logcat`; we don't want to display two identical log streams.
+abstract class DeviceLogReader {
+ String get name;
+
+ Future<int> logs({ bool clear: false });
+
+ int get hashCode;
+ bool operator ==(dynamic other);
+
+ String toString() => name;
+}
+
// TODO(devoncarew): Unify this with [DeviceManager].
class DeviceStore {
final AndroidDevice android;
diff --git a/packages/flutter_tools/lib/src/ios/device_ios.dart b/packages/flutter_tools/lib/src/ios/device_ios.dart
index 0b9e549..5a48a9e 100644
--- a/packages/flutter_tools/lib/src/ios/device_ios.dart
+++ b/packages/flutter_tools/lib/src/ios/device_ios.dart
@@ -184,9 +184,10 @@
String mainPath,
String route,
bool checked: true,
+ bool clearLogs: false,
Map<String, dynamic> platformArgs
}) async {
- // TODO: Use checked, mainPath, route
+ // TODO(chinmaygarde): Use checked, mainPath, route, clearLogs.
printTrace('Building ${app.name} for $id');
// Step 1: Install the precompiled application if necessary
@@ -231,8 +232,7 @@
return false;
}
- Future<bool> pushFile(
- ApplicationPackage app, String localFile, String targetFile) async {
+ Future<bool> pushFile(ApplicationPackage app, String localFile, String targetFile) async {
if (Platform.isMacOS) {
runSync([
pusherPath,
@@ -255,14 +255,7 @@
@override
TargetPlatform get platform => TargetPlatform.iOS;
- /// Note that clear is not supported on iOS at this time.
- Future<int> logs({bool clear: false}) async {
- if (!isConnected()) {
- return 2;
- }
- return await runCommandAndStreamOutput([loggerPath],
- prefix: 'iOS: ', filter: new RegExp(r'(FlutterRunner|flutter.runner.Runner)'));
- }
+ DeviceLogReader createLogReader() => new _IOSDeviceLogReader(this);
}
class IOSSimulator extends Device {
@@ -335,10 +328,9 @@
String _getSimulatorPath() {
String deviceID = id == defaultDeviceID ? _getRunningSimulatorInfo()?.id : id;
- String homeDirectory = path.absolute(Platform.environment['HOME']);
if (deviceID == null)
return null;
- return path.join(homeDirectory, 'Library', 'Developer', 'CoreSimulator', 'Devices', deviceID);
+ return path.join(_homeDirectory, 'Library', 'Developer', 'CoreSimulator', 'Devices', deviceID);
}
String _getSimulatorAppHomeDirectory(ApplicationPackage app) {
@@ -438,11 +430,15 @@
String mainPath,
String route,
bool checked: true,
+ bool clearLogs: false,
Map<String, dynamic> platformArgs
}) async {
- // TODO: Use checked, mainPath, route
+ // TODO(chinmaygarde): Use checked, mainPath, route.
printTrace('Building ${app.name} for $id');
+ if (clearLogs)
+ this.clearLogs();
+
// Step 1: Build the Xcode project
bool buildResult = await _buildIOSXcodeProject(app, false);
if (!buildResult) {
@@ -506,35 +502,123 @@
return false;
}
+ String get logFilePath {
+ return path.join(_homeDirectory, 'Library', 'Logs', 'CoreSimulator', id, 'system.log');
+ }
+
@override
TargetPlatform get platform => TargetPlatform.iOSSimulator;
- Future<int> logs({bool clear: false}) async {
- if (!isConnected())
+ DeviceLogReader createLogReader() => new _IOSSimulatorLogReader(this);
+
+ void clearLogs() {
+ File logFile = new File(logFilePath);
+ if (logFile.existsSync())
+ logFile.delete();
+ }
+}
+
+class _IOSDeviceLogReader extends DeviceLogReader {
+ _IOSDeviceLogReader(this.device);
+
+ final IOSDevice device;
+
+ String get name => device.name;
+
+ // TODO(devoncarew): Support [clear].
+ Future<int> logs({ bool clear: false }) async {
+ if (!device.isConnected())
return 2;
- String homeDirectory = path.absolute(Platform.environment['HOME']);
- String simulatorDeviceID = _getRunningSimulatorInfo().id;
- String logFilePath = path.join(
- homeDirectory, 'Library', 'Logs', 'CoreSimulator', simulatorDeviceID, 'system.log'
- );
-
- if (clear)
- runSync(['rm', logFilePath]);
-
- // TODO(devoncarew): The log message prefix could be shortened or removed.
- // Jan 29 01:31:44 devoncarew-macbookpro3 SpringBoard[96648]:
- // TODO(devoncarew): This truncates multi-line messages like:
- // Jan 29 01:31:43 devoncarew-macbookpro3 CoreSimulatorBridge[96656]: Requesting... {
- // environment = {
- // };
- // }
return await runCommandAndStreamOutput(
- ['tail', '-f', logFilePath],
- prefix: 'iOS: ',
+ [device.loggerPath],
+ prefix: '[$name] ',
filter: new RegExp(r'(FlutterRunner|flutter.runner.Runner)')
);
}
+
+ int get hashCode => name.hashCode;
+
+ bool operator ==(dynamic other) {
+ if (identical(this, other))
+ return true;
+ if (other is! _IOSDeviceLogReader)
+ return false;
+ return other.name == name;
+ }
+}
+
+class _IOSSimulatorLogReader extends DeviceLogReader {
+ _IOSSimulatorLogReader(this.device);
+
+ final IOSSimulator device;
+
+ String get name => device.name;
+
+ Future<int> logs({bool clear: false}) async {
+ if (!device.isConnected())
+ return 2;
+
+ if (clear)
+ device.clearLogs();
+
+ // Match the log prefix (in order to shorten it):
+ // 'Jan 29 01:31:44 devoncarew-macbookpro3 SpringBoard[96648]: ...'
+ RegExp mapRegex = new RegExp(r'\S+ +\S+ +\S+ \S+ (.+)\[\d+\]\)?: (.*)$');
+ // Jan 31 19:23:28 --- last message repeated 1 time ---
+ RegExp lastMessageRegex = new RegExp(r'\S+ +\S+ +\S+ (--- .* ---)$');
+
+ // This filter matches many Flutter lines in the log:
+ // new RegExp(r'(FlutterRunner|flutter.runner.Runner|$id)'), but it misses
+ // a fair number, including ones that would be useful in diagnosing crashes.
+ // For now, we're not filtering the log file (but do clear it with each run).
+
+ Future<int> result = runCommandAndStreamOutput(
+ <String>['tail', '-n', '+0', '-F', device.logFilePath],
+ prefix: '[$name] ',
+ mapFunction: (String string) {
+ Match match = mapRegex.matchAsPrefix(string);
+ if (match != null) {
+ // Filter out some messages that clearly aren't related to Flutter.
+ if (string.contains(': could not find icon for representation -> com.apple.'))
+ return null;
+ String category = match.group(1);
+ String content = match.group(2);
+ if (category == 'Game Center' || category == 'itunesstored' || category == 'nanoregistrylaunchd')
+ return null;
+ return '$category: $content';
+ }
+ match = lastMessageRegex.matchAsPrefix(string);
+ if (match != null)
+ return match.group(1);
+ return string;
+ }
+ );
+
+ // Track system.log crashes.
+ // ReportCrash[37965]: Saved crash report for FlutterRunner[37941]...
+ runCommandAndStreamOutput(
+ <String>['tail', '-F', '/private/var/log/system.log'],
+ prefix: '[$name] ',
+ filter: new RegExp(r' FlutterRunner\[\d+\] '),
+ mapFunction: (String string) {
+ Match match = mapRegex.matchAsPrefix(string);
+ return match == null ? string : '${match.group(1)}: ${match.group(2)}';
+ }
+ );
+
+ return result;
+ }
+
+ int get hashCode => device.logFilePath.hashCode;
+
+ bool operator ==(dynamic other) {
+ if (identical(this, other))
+ return true;
+ if (other is! _IOSSimulatorLogReader)
+ return false;
+ return other.device.logFilePath == device.logFilePath;
+ }
}
class _IOSSimulatorInfo {
@@ -547,6 +631,8 @@
final RegExp _xcodeVersionRegExp = new RegExp(r'Xcode (\d+)\..*');
final String _xcodeRequirement = 'Xcode 7.0 or greater is required to develop for iOS.';
+String get _homeDirectory => path.absolute(Platform.environment['HOME']);
+
bool _checkXcodeVersion() {
if (!Platform.isMacOS)
return false;
@@ -573,15 +659,18 @@
if (!_checkXcodeVersion())
return false;
- List<String> command = [
- 'xcrun', 'xcodebuild', '-target', 'Runner', '-configuration', 'Release'
+ List<String> commands = [
+ '/usr/bin/env', 'xcrun', 'xcodebuild', '-target', 'Runner', '-configuration', 'Release'
];
if (!isDevice) {
- command.addAll(['-sdk', 'iphonesimulator']);
+ commands.addAll(['-sdk', 'iphonesimulator']);
}
- ProcessResult result = Process.runSync('/usr/bin/env', command,
- workingDirectory: app.localPath);
- return result.exitCode == 0;
+ try {
+ runCheckedSync(commands, workingDirectory: app.localPath);
+ return true;
+ } catch (error) {
+ return false;
+ }
}
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 5cab91d..4df0131 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -10,22 +10,19 @@
import '../application_package.dart';
import '../base/context.dart';
import '../build_configuration.dart';
+import '../artifacts.dart';
import '../device.dart';
import '../toolchain.dart';
import 'flutter_command_runner.dart';
+typedef bool Validator();
+
abstract class FlutterCommand extends Command {
FlutterCommandRunner get runner => super.runner;
/// Whether this command needs to be run from the root of a project.
bool get requiresProjectRoot => true;
- String get projectRootValidationErrorMessage {
- return 'Error: No pubspec.yaml file found.\n'
- 'This command should be run from the root of your Flutter project. Do not run\n'
- 'this command from the root of your git clone of Flutter.';
- }
-
List<BuildConfiguration> get buildConfigurations => runner.buildConfigurations;
Future downloadApplicationPackages() async {
@@ -49,18 +46,22 @@
}
Future<int> run() async {
- if (requiresProjectRoot && !validateProjectRoot())
+ if (requiresProjectRoot && !projectRootValidator())
return 1;
return await runInProject();
}
- bool validateProjectRoot() {
+ // This is a field so that you can modify the value for testing.
+ Validator projectRootValidator = () {
if (!FileSystemEntity.isFileSync('pubspec.yaml')) {
- printError(projectRootValidationErrorMessage);
+ printError('Error: No pubspec.yaml file found.\n'
+ 'This command should be run from the root of your Flutter project.\n'
+ 'Do not run this command from the root of your git clone of Flutter.');
return false;
}
+ ArtifactStore.validateSkyEnginePackage();
return true;
- }
+ };
Future<int> runInProject();
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
index d2e6d18..079b20c 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
@@ -142,11 +142,16 @@
String get _defaultFlutterRoot {
if (Platform.environment.containsKey(kFlutterRootEnvironmentVariableName))
return Platform.environment[kFlutterRootEnvironmentVariableName];
- String script = Platform.script.toFilePath();
- if (path.basename(script) == kSnapshotFileName)
- return path.dirname(path.dirname(path.dirname(script)));
- if (path.basename(script) == kFlutterToolsScriptFileName)
- return path.dirname(path.dirname(path.dirname(path.dirname(script))));
+
+ try {
+ String script = Platform.script.toFilePath();
+ if (path.basename(script) == kSnapshotFileName)
+ return path.dirname(path.dirname(path.dirname(script)));
+ if (path.basename(script) == kFlutterToolsScriptFileName)
+ return path.dirname(path.dirname(path.dirname(path.dirname(script))));
+ } catch (error) {
+ printTrace('Unable to locate fluter root: $error');
+ }
return '.';
}
diff --git a/packages/flutter_tools/test/logs_test.dart b/packages/flutter_tools/test/logs_test.dart
index a7736a6..1e5590b 100644
--- a/packages/flutter_tools/test/logs_test.dart
+++ b/packages/flutter_tools/test/logs_test.dart
@@ -4,7 +4,7 @@
import 'package:args/command_runner.dart';
import 'package:flutter_tools/src/commands/logs.dart';
-import 'package:mockito/mockito.dart';
+import 'package:flutter_tools/src/runner/flutter_command_runner.dart';
import 'package:test/test.dart';
import 'src/mocks.dart';
@@ -13,18 +13,13 @@
defineTests() {
group('logs', () {
- test('returns 0 when no device is connected', () {
+ test('fail with a bad device id', () {
LogsCommand command = new LogsCommand();
applyMocksToCommand(command);
- MockDeviceStore mockDevices = command.devices;
-
- when(mockDevices.android.isConnected()).thenReturn(false);
- when(mockDevices.iOS.isConnected()).thenReturn(false);
- when(mockDevices.iOSSimulator.isConnected()).thenReturn(false);
-
- CommandRunner runner = new CommandRunner('test_flutter', '')
- ..addCommand(command);
- runner.run(['logs']).then((int code) => expect(code, equals(0)));
+ CommandRunner runner = new FlutterCommandRunner()..addCommand(command);
+ runner.run(<String>['-d', 'abc123', 'logs']).then((int code) {
+ expect(code, equals(1));
+ });
});
});
}
diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart
index 9af0463..99161d6 100644
--- a/packages/flutter_tools/test/src/mocks.dart
+++ b/packages/flutter_tools/test/src/mocks.dart
@@ -48,5 +48,6 @@
command
..applicationPackages = new MockApplicationPackageStore()
..toolchain = new MockToolchain()
- ..devices = new MockDeviceStore();
+ ..devices = new MockDeviceStore()
+ ..projectRootValidator = () => true;
}