Commit e384c0d9 authored by Dan Rubel's avatar Dan Rubel Committed by GitHub

Refactor flutter command exit code - part 2 (#6817)

* convert pubGet to throw ToolExit on non-zero exit code
* convert commandValidator to throw ToolExit for non-zero exit code
* convert flutter commands to throw ToolExit for non-zero exit code
* use convenience method throwToolExit
* only show "if this problem persists" for unusual exceptions
parent 90f7afcb
...@@ -7,6 +7,7 @@ import 'dart:io'; ...@@ -7,6 +7,7 @@ import 'dart:io';
import 'package:args/args.dart'; import 'package:args/args.dart';
import '../lib/src/base/common.dart';
import '../lib/src/base/context.dart'; import '../lib/src/base/context.dart';
import '../lib/src/base/logger.dart'; import '../lib/src/base/logger.dart';
import '../lib/src/cache.dart'; import '../lib/src/cache.dart';
...@@ -42,17 +43,18 @@ Future<Null> main(List<String> args) async { ...@@ -42,17 +43,18 @@ Future<Null> main(List<String> args) async {
} }
Cache.flutterRoot = Platform.environment['FLUTTER_ROOT']; Cache.flutterRoot = Platform.environment['FLUTTER_ROOT'];
String outputPath = argResults[_kOptionOutput]; String outputPath = argResults[_kOptionOutput];
final int buildResult = await assemble( try {
outputPath: outputPath, await assemble(
snapshotFile: new File(argResults[_kOptionSnapshot]), outputPath: outputPath,
workingDirPath: argResults[_kOptionWorking], snapshotFile: new File(argResults[_kOptionSnapshot]),
packagesPath: argResults[_kOptionPackages], workingDirPath: argResults[_kOptionWorking],
manifestPath: defaultManifestPath, packagesPath: argResults[_kOptionPackages],
includeDefaultFonts: false, manifestPath: defaultManifestPath,
); includeDefaultFonts: false,
if (buildResult != 0) { );
printError('Error building $outputPath: $buildResult.'); } on ToolExit catch (e) {
exit(buildResult); printError(e.message);
exit(e.exitCode);
} }
final int headerResult = _addHeader(outputPath, argResults[_kOptionHeader]); final int headerResult = _addHeader(outputPath, argResults[_kOptionHeader]);
if (headerResult != 0) { if (headerResult != 0) {
......
...@@ -114,9 +114,11 @@ Future<Null> main(List<String> args) async { ...@@ -114,9 +114,11 @@ Future<Null> main(List<String> args) async {
stderr.writeln(chain.terse.toString()); stderr.writeln(chain.terse.toString());
stderr.writeln(); stderr.writeln();
} }
stderr.writeln('If this problem persists, please report the problem at'); if (error.isUnusual) {
stderr.writeln('https://github.com/flutter/flutter/issues/new'); stderr.writeln('If this problem persists, please report the problem at');
_exit(error.exitCode ?? 65); stderr.writeln('https://github.com/flutter/flutter/issues/new');
}
_exit(error.exitCode ?? 1);
} else if (error is ProcessExit) { } else if (error is ProcessExit) {
// We've caught an exit code. // We've caught an exit code.
_exit(error.exitCode); _exit(error.exitCode);
......
...@@ -7,6 +7,7 @@ import 'dart:io'; ...@@ -7,6 +7,7 @@ import 'dart:io';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import '../base/common.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/os.dart'; import '../base/os.dart';
import '../base/process.dart'; import '../base/process.dart';
...@@ -90,7 +91,7 @@ String locateProjectGradlew({ bool ensureExecutable: true }) { ...@@ -90,7 +91,7 @@ String locateProjectGradlew({ bool ensureExecutable: true }) {
} }
} }
Future<int> buildGradleProject(BuildMode buildMode) async { Future<Null> buildGradleProject(BuildMode buildMode) async {
// Create android/local.properties. // Create android/local.properties.
File localProperties = new File('android/local.properties'); File localProperties = new File('android/local.properties');
if (!localProperties.existsSync()) { if (!localProperties.existsSync()) {
...@@ -111,10 +112,9 @@ Future<int> buildGradleProject(BuildMode buildMode) async { ...@@ -111,10 +112,9 @@ Future<int> buildGradleProject(BuildMode buildMode) async {
if (gradlew == null) { if (gradlew == null) {
String gradle = locateSystemGradle(); String gradle = locateSystemGradle();
if (gradle == null) { if (gradle == null) {
printError( throwToolExit(
'Unable to locate gradle. Please configure the path to gradle using \'flutter config --gradle-dir\'.' 'Unable to locate gradle. Please configure the path to gradle using \'flutter config --gradle-dir\'.'
); );
return 1;
} else { } else {
printTrace('Using gradle from $gradle.'); printTrace('Using gradle from $gradle.');
} }
...@@ -139,17 +139,14 @@ Future<int> buildGradleProject(BuildMode buildMode) async { ...@@ -139,17 +139,14 @@ Future<int> buildGradleProject(BuildMode buildMode) async {
); );
status.stop(); status.stop();
if (exitcode != 0) if (exitcode != 0)
return exitcode; throwToolExit('Gradle failed: $exitcode', exitCode: exitcode);
} catch (error) { } catch (error) {
printError('$error'); throwToolExit('$error');
return 1;
} }
gradlew = locateProjectGradlew(); gradlew = locateProjectGradlew();
if (gradlew == null) { if (gradlew == null)
printError('Unable to build android/gradlew.'); throwToolExit('Unable to build android/gradlew.');
return 1;
}
} }
// Run 'gradlew build'. // Run 'gradlew build'.
...@@ -161,12 +158,11 @@ Future<int> buildGradleProject(BuildMode buildMode) async { ...@@ -161,12 +158,11 @@ Future<int> buildGradleProject(BuildMode buildMode) async {
); );
status.stop(); status.stop();
if (exitcode == 0) { if (exitcode != 0)
File apkFile = new File(gradleAppOut); throwToolExit('Gradlew failed: $exitcode', exitCode: exitcode);
printStatus('Built $gradleAppOut (${getSizeAsMB(apkFile.lengthSync())}).');
}
return exitcode; File apkFile = new File(gradleAppOut);
printStatus('Built $gradleAppOut (${getSizeAsMB(apkFile.lengthSync())}).');
} }
class _GradleFile { class _GradleFile {
......
...@@ -27,8 +27,8 @@ String _homeDirPath; ...@@ -27,8 +27,8 @@ String _homeDirPath;
/// where the tool should exit with a clear message to the user /// where the tool should exit with a clear message to the user
/// and no stack trace unless the --verbose option is specified. /// and no stack trace unless the --verbose option is specified.
/// For example: network errors /// For example: network errors
void throwToolExit(String message, { int exitCode }) { void throwToolExit(String message, { int exitCode, bool isUnusual: false }) {
throw new ToolExit(message, exitCode: exitCode); throw new ToolExit(message, exitCode: exitCode, isUnusual: isUnusual);
} }
/// Specialized exception for expected situations /// Specialized exception for expected situations
...@@ -36,10 +36,12 @@ void throwToolExit(String message, { int exitCode }) { ...@@ -36,10 +36,12 @@ void throwToolExit(String message, { int exitCode }) {
/// and no stack trace unless the --verbose option is specified. /// and no stack trace unless the --verbose option is specified.
/// For example: network errors /// For example: network errors
class ToolExit implements Exception { class ToolExit implements Exception {
ToolExit(this.message, { this.exitCode });
ToolExit(this.message, { this.exitCode, this.isUnusual: false });
final String message; final String message;
final int exitCode; final int exitCode;
final bool isUnusual;
@override @override
String toString() => "Exception: $message"; String toString() => "Exception: $message";
......
...@@ -24,6 +24,7 @@ Future<List<int>> fetchUrl(Uri url) async { ...@@ -24,6 +24,7 @@ Future<List<int>> fetchUrl(Uri url) async {
'Download failed: $url\n' 'Download failed: $url\n'
' because (${response.statusCode}) ${response.reasonPhrase}', ' because (${response.statusCode}) ${response.reasonPhrase}',
exitCode: kNetworkProblemExitCode, exitCode: kNetworkProblemExitCode,
isUnusual: true,
); );
} }
...@@ -37,6 +38,7 @@ Future<List<int>> fetchUrl(Uri url) async { ...@@ -37,6 +38,7 @@ Future<List<int>> fetchUrl(Uri url) async {
throw new ToolExit( throw new ToolExit(
'Download failed: $url\n $e', 'Download failed: $url\n $e',
exitCode: kNetworkProblemExitCode, exitCode: kNetworkProblemExitCode,
isUnusual: true,
); );
} }
} }
...@@ -10,6 +10,7 @@ import 'package:meta/meta.dart'; ...@@ -10,6 +10,7 @@ import 'package:meta/meta.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../globals.dart'; import '../globals.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import '../base/common.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import 'build_apk.dart'; import 'build_apk.dart';
import 'build_aot.dart'; import 'build_aot.dart';
...@@ -33,8 +34,7 @@ class BuildCommand extends FlutterCommand { ...@@ -33,8 +34,7 @@ class BuildCommand extends FlutterCommand {
@override @override
Future<int> verifyThenRunCommand() async { Future<int> verifyThenRunCommand() async {
if (!commandValidator()) commandValidator();
return 1;
return super.verifyThenRunCommand(); return super.verifyThenRunCommand();
} }
...@@ -46,8 +46,7 @@ abstract class BuildSubCommand extends FlutterCommand { ...@@ -46,8 +46,7 @@ abstract class BuildSubCommand extends FlutterCommand {
@override @override
@mustCallSuper @mustCallSuper
Future<int> verifyThenRunCommand() async { Future<int> verifyThenRunCommand() async {
if (!commandValidator()) commandValidator();
return 1;
return super.verifyThenRunCommand(); return super.verifyThenRunCommand();
} }
...@@ -82,8 +81,7 @@ class BuildCleanCommand extends FlutterCommand { ...@@ -82,8 +81,7 @@ class BuildCleanCommand extends FlutterCommand {
@override @override
Future<int> verifyThenRunCommand() async { Future<int> verifyThenRunCommand() async {
if (!commandValidator()) commandValidator();
return 1;
return super.verifyThenRunCommand(); return super.verifyThenRunCommand();
} }
...@@ -97,10 +95,9 @@ class BuildCleanCommand extends FlutterCommand { ...@@ -97,10 +95,9 @@ class BuildCleanCommand extends FlutterCommand {
try { try {
buildDir.deleteSync(recursive: true); buildDir.deleteSync(recursive: true);
return 0;
} catch (error) { } catch (error) {
printError(error.toString()); throwToolExit(error.toString());
return 1;
} }
return 0;
} }
} }
...@@ -7,6 +7,7 @@ import 'dart:io'; ...@@ -7,6 +7,7 @@ import 'dart:io';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import '../base/common.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../base/utils.dart'; import '../base/utils.dart';
...@@ -46,10 +47,8 @@ class BuildAotCommand extends BuildSubCommand { ...@@ -46,10 +47,8 @@ class BuildAotCommand extends BuildSubCommand {
await super.runCommand(); await super.runCommand();
String targetPlatform = argResults['target-platform']; String targetPlatform = argResults['target-platform'];
TargetPlatform platform = getTargetPlatformForName(targetPlatform); TargetPlatform platform = getTargetPlatformForName(targetPlatform);
if (platform == null) { if (platform == null)
printError('Unknown platform: $targetPlatform'); throwToolExit('Unknown platform: $targetPlatform');
return 1;
}
String typeName = path.basename(tools.getEngineArtifactsDirectory(platform, getBuildMode()).path); String typeName = path.basename(tools.getEngineArtifactsDirectory(platform, getBuildMode()).path);
Status status = logger.startProgress('Building AOT snapshot in ${getModeName(getBuildMode())} mode ($typeName)...'); Status status = logger.startProgress('Building AOT snapshot in ${getModeName(getBuildMode())} mode ($typeName)...');
...@@ -63,7 +62,7 @@ class BuildAotCommand extends BuildSubCommand { ...@@ -63,7 +62,7 @@ class BuildAotCommand extends BuildSubCommand {
status.stop(); status.stop();
if (outputPath == null) if (outputPath == null)
return 1; throwToolExit(null);
printStatus('Built to $outputPath${Platform.pathSeparator}.'); printStatus('Built to $outputPath${Platform.pathSeparator}.');
return 0; return 0;
......
...@@ -10,6 +10,7 @@ import 'package:path/path.dart' as path; ...@@ -10,6 +10,7 @@ import 'package:path/path.dart' as path;
import '../android/android_sdk.dart'; import '../android/android_sdk.dart';
import '../android/gradle.dart'; import '../android/gradle.dart';
import '../base/common.dart';
import '../base/file_system.dart' show ensureDirectoryExists; import '../base/file_system.dart' show ensureDirectoryExists;
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/os.dart'; import '../base/os.dart';
...@@ -227,23 +228,19 @@ class BuildApkCommand extends BuildSubCommand { ...@@ -227,23 +228,19 @@ class BuildApkCommand extends BuildSubCommand {
await super.runCommand(); await super.runCommand();
TargetPlatform targetPlatform = _getTargetPlatform(argResults['target-arch']); TargetPlatform targetPlatform = _getTargetPlatform(argResults['target-arch']);
if (targetPlatform != TargetPlatform.android_arm && getBuildMode() != BuildMode.debug) { if (targetPlatform != TargetPlatform.android_arm && getBuildMode() != BuildMode.debug)
printError('Profile and release builds are only supported on ARM targets.'); throwToolExit('Profile and release builds are only supported on ARM targets.');
return 1;
}
if (isProjectUsingGradle()) { if (isProjectUsingGradle()) {
if (targetPlatform != TargetPlatform.android_arm) { if (targetPlatform != TargetPlatform.android_arm)
printError('Gradle builds only support ARM targets.'); throwToolExit('Gradle builds only support ARM targets.');
return 1; await buildAndroidWithGradle(
}
return await buildAndroidWithGradle(
TargetPlatform.android_arm, TargetPlatform.android_arm,
getBuildMode(), getBuildMode(),
target: targetFile target: targetFile
); );
} else { } else {
return await buildAndroid( await buildAndroid(
targetPlatform, targetPlatform,
getBuildMode(), getBuildMode(),
force: true, force: true,
...@@ -261,6 +258,7 @@ class BuildApkCommand extends BuildSubCommand { ...@@ -261,6 +258,7 @@ class BuildApkCommand extends BuildSubCommand {
) )
); );
} }
return 0;
} }
} }
...@@ -477,7 +475,7 @@ bool _needsRebuild( ...@@ -477,7 +475,7 @@ bool _needsRebuild(
return false; return false;
} }
Future<int> buildAndroid( Future<Null> buildAndroid(
TargetPlatform platform, TargetPlatform platform,
BuildMode buildMode, { BuildMode buildMode, {
bool force: false, bool force: false,
...@@ -492,16 +490,13 @@ Future<int> buildAndroid( ...@@ -492,16 +490,13 @@ Future<int> buildAndroid(
outputFile ??= _defaultOutputPath; outputFile ??= _defaultOutputPath;
// Validate that we can find an android sdk. // Validate that we can find an android sdk.
if (androidSdk == null) { if (androidSdk == null)
printError('No Android SDK found. Try setting the ANDROID_HOME environment variable.'); throwToolExit('No Android SDK found. Try setting the ANDROID_HOME environment variable.');
return 1;
}
List<String> validationResult = androidSdk.validateSdkWellFormed(); List<String> validationResult = androidSdk.validateSdkWellFormed();
if (validationResult.isNotEmpty) { if (validationResult.isNotEmpty) {
validationResult.forEach(printError); validationResult.forEach(printError);
printError('Try re-installing or updating your Android SDK.'); throwToolExit('Try re-installing or updating your Android SDK.');
return 1;
} }
Map<String, File> extraFiles = <String, File>{}; Map<String, File> extraFiles = <String, File>{};
...@@ -522,14 +517,12 @@ Future<int> buildAndroid( ...@@ -522,14 +517,12 @@ Future<int> buildAndroid(
!force && !force &&
!_needsRebuild(outputFile, manifest, platform, buildMode, extraFiles)) { !_needsRebuild(outputFile, manifest, platform, buildMode, extraFiles)) {
printTrace('APK up to date; skipping build step.'); printTrace('APK up to date; skipping build step.');
return 0; return;
} }
if (resources != null) { if (resources != null) {
if (!FileSystemEntity.isDirectorySync(resources)) { if (!FileSystemEntity.isDirectorySync(resources))
printError('Resources directory "$resources" not found.'); throwToolExit('Resources directory "$resources" not found.');
return 1;
}
} else { } else {
if (FileSystemEntity.isDirectorySync(_kDefaultResourcesPath)) if (FileSystemEntity.isDirectorySync(_kDefaultResourcesPath))
resources = _kDefaultResourcesPath; resources = _kDefaultResourcesPath;
...@@ -537,19 +530,16 @@ Future<int> buildAndroid( ...@@ -537,19 +530,16 @@ Future<int> buildAndroid(
_ApkComponents components = await _findApkComponents(platform, buildMode, manifest, resources, extraFiles); _ApkComponents components = await _findApkComponents(platform, buildMode, manifest, resources, extraFiles);
if (components == null) { if (components == null)
printError('Failure building APK: unable to find components.'); throwToolExit('Failure building APK: unable to find components.');
return 1;
}
String typeName = path.basename(tools.getEngineArtifactsDirectory(platform, buildMode).path); String typeName = path.basename(tools.getEngineArtifactsDirectory(platform, buildMode).path);
Status status = logger.startProgress('Building APK in ${getModeName(buildMode)} mode ($typeName)...'); Status status = logger.startProgress('Building APK in ${getModeName(buildMode)} mode ($typeName)...');
if (flxPath != null && flxPath.isNotEmpty) { if (flxPath != null && flxPath.isNotEmpty) {
if (!FileSystemEntity.isFileSync(flxPath)) { if (!FileSystemEntity.isFileSync(flxPath)) {
printError('FLX does not exist: $flxPath'); throwToolExit('FLX does not exist: $flxPath\n'
printError('(Omit the --flx option to build the FLX automatically)'); '(Omit the --flx option to build the FLX automatically)');
return 1;
} }
} else { } else {
// Build the FLX. // Build the FLX.
...@@ -559,33 +549,25 @@ Future<int> buildAndroid( ...@@ -559,33 +549,25 @@ Future<int> buildAndroid(
includeRobotoFonts: false); includeRobotoFonts: false);
if (flxPath == null) if (flxPath == null)
return 1; throwToolExit(null);
} }
// Build an AOT snapshot if needed. // Build an AOT snapshot if needed.
if (isAotBuildMode(buildMode) && aotPath == null) { if (isAotBuildMode(buildMode) && aotPath == null) {
aotPath = await buildAotSnapshot(findMainDartFile(target), platform, buildMode); aotPath = await buildAotSnapshot(findMainDartFile(target), platform, buildMode);
if (aotPath == null) { if (aotPath == null)
printError('Failed to build AOT snapshot'); throwToolExit('Failed to build AOT snapshot');
return 1;
}
} }
if (aotPath != null) { if (aotPath != null) {
if (!isAotBuildMode(buildMode)) { if (!isAotBuildMode(buildMode))
printError('AOT snapshot can not be used in build mode $buildMode'); throwToolExit('AOT snapshot can not be used in build mode $buildMode');
return 1; if (!FileSystemEntity.isDirectorySync(aotPath))
} throwToolExit('AOT snapshot does not exist: $aotPath');
if (!FileSystemEntity.isDirectorySync(aotPath)) {
printError('AOT snapshot does not exist: $aotPath');
return 1;
}
for (String aotFilename in kAotSnapshotFiles) { for (String aotFilename in kAotSnapshotFiles) {
String aotFilePath = path.join(aotPath, aotFilename); String aotFilePath = path.join(aotPath, aotFilename);
if (!FileSystemEntity.isFileSync(aotFilePath)) { if (!FileSystemEntity.isFileSync(aotFilePath))
printError('Missing AOT snapshot file: $aotFilePath'); throwToolExit('Missing AOT snapshot file: $aotFilePath');
return 1;
}
components.extraFiles['assets/$aotFilename'] = new File(aotFilePath); components.extraFiles['assets/$aotFilename'] = new File(aotFilePath);
} }
} }
...@@ -593,43 +575,39 @@ Future<int> buildAndroid( ...@@ -593,43 +575,39 @@ Future<int> buildAndroid(
int result = _buildApk(platform, buildMode, components, flxPath, keystore, outputFile); int result = _buildApk(platform, buildMode, components, flxPath, keystore, outputFile);
status.stop(); status.stop();
if (result == 0) { if (result != 0)
File apkFile = new File(outputFile); throwToolExit('Build APK failed ($result)', exitCode: result);
printTrace('Built $outputFile (${getSizeAsMB(apkFile.lengthSync())}).');
_writeBuildMetaEntry( File apkFile = new File(outputFile);
path.dirname(outputFile), printTrace('Built $outputFile (${getSizeAsMB(apkFile.lengthSync())}).');
'targetBuildType',
_getTargetBuildTypeToken(platform, buildMode, new File(outputFile))
);
}
return result; _writeBuildMetaEntry(
path.dirname(outputFile),
'targetBuildType',
_getTargetBuildTypeToken(platform, buildMode, new File(outputFile))
);
} }
Future<int> buildAndroidWithGradle( Future<Null> buildAndroidWithGradle(
TargetPlatform platform, TargetPlatform platform,
BuildMode buildMode, { BuildMode buildMode, {
bool force: false, bool force: false,
String target String target
}) async { }) async {
// Validate that we can find an android sdk. // Validate that we can find an android sdk.
if (androidSdk == null) { if (androidSdk == null)
printError('No Android SDK found. Try setting the ANDROID_HOME environment variable.'); throwToolExit('No Android SDK found. Try setting the ANDROID_HOME environment variable.');
return 1;
}
List<String> validationResult = androidSdk.validateSdkWellFormed(); List<String> validationResult = androidSdk.validateSdkWellFormed();
if (validationResult.isNotEmpty) { if (validationResult.isNotEmpty) {
validationResult.forEach(printError); validationResult.forEach(printError);
printError('Try re-installing or updating your Android SDK.'); throwToolExit('Try re-installing or updating your Android SDK.');
return 1;
} }
return buildGradleProject(buildMode); return buildGradleProject(buildMode);
} }
Future<int> buildApk( Future<Null> buildApk(
TargetPlatform platform, { TargetPlatform platform, {
String target, String target,
BuildMode buildMode: BuildMode.debug BuildMode buildMode: BuildMode.debug
...@@ -642,10 +620,8 @@ Future<int> buildApk( ...@@ -642,10 +620,8 @@ Future<int> buildApk(
target: target target: target
); );
} else { } else {
if (!FileSystemEntity.isFileSync(_kDefaultAndroidManifestPath)) { if (!FileSystemEntity.isFileSync(_kDefaultAndroidManifestPath))
printError('Cannot build APK: missing $_kDefaultAndroidManifestPath.'); throwToolExit('Cannot build APK: missing $_kDefaultAndroidManifestPath.');
return 1;
}
return await buildAndroid( return await buildAndroid(
platform, platform,
......
...@@ -6,7 +6,6 @@ import 'dart:async'; ...@@ -6,7 +6,6 @@ import 'dart:async';
import '../build_info.dart'; import '../build_info.dart';
import '../flx.dart'; import '../flx.dart';
import '../globals.dart';
import 'build.dart'; import 'build.dart';
class BuildFlxCommand extends BuildSubCommand { class BuildFlxCommand extends BuildSubCommand {
...@@ -42,7 +41,7 @@ class BuildFlxCommand extends BuildSubCommand { ...@@ -42,7 +41,7 @@ class BuildFlxCommand extends BuildSubCommand {
await super.runCommand(); await super.runCommand();
String outputPath = argResults['output-file']; String outputPath = argResults['output-file'];
return await build( await build(
mainPath: targetFile, mainPath: targetFile,
manifestPath: argResults['manifest'], manifestPath: argResults['manifest'],
outputPath: outputPath, outputPath: outputPath,
...@@ -53,12 +52,7 @@ class BuildFlxCommand extends BuildSubCommand { ...@@ -53,12 +52,7 @@ class BuildFlxCommand extends BuildSubCommand {
precompiledSnapshot: argResults['precompiled'], precompiledSnapshot: argResults['precompiled'],
includeRobotoFonts: argResults['include-roboto-fonts'], includeRobotoFonts: argResults['include-roboto-fonts'],
reportLicensedPackages: argResults['report-licensed-packages'] reportLicensedPackages: argResults['report-licensed-packages']
).then((int result) { );
if (result == 0) return 0;
printStatus('Built $outputPath.');
else
printError('Error building $outputPath: $result.');
return result;
});
} }
} }
...@@ -7,6 +7,7 @@ import 'dart:async'; ...@@ -7,6 +7,7 @@ import 'dart:async';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import '../application_package.dart'; import '../application_package.dart';
import '../base/common.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
...@@ -43,17 +44,13 @@ class BuildIOSCommand extends BuildSubCommand { ...@@ -43,17 +44,13 @@ class BuildIOSCommand extends BuildSubCommand {
defaultBuildMode = forSimulator ? BuildMode.debug : BuildMode.release; defaultBuildMode = forSimulator ? BuildMode.debug : BuildMode.release;
await super.runCommand(); await super.runCommand();
if (getCurrentHostPlatform() != HostPlatform.darwin_x64) { if (getCurrentHostPlatform() != HostPlatform.darwin_x64)
printError('Building for iOS is only supported on the Mac.'); throwToolExit('Building for iOS is only supported on the Mac.');
return 1;
}
IOSApp app = applicationPackages.getPackageForPlatform(TargetPlatform.ios); IOSApp app = applicationPackages.getPackageForPlatform(TargetPlatform.ios);
if (app == null) { if (app == null)
printError('Application not configured for iOS'); throwToolExit('Application not configured for iOS');
return 1;
}
bool shouldCodesign = argResults['codesign']; bool shouldCodesign = argResults['codesign'];
...@@ -62,10 +59,8 @@ class BuildIOSCommand extends BuildSubCommand { ...@@ -62,10 +59,8 @@ class BuildIOSCommand extends BuildSubCommand {
'have to manually codesign before deploying to device.'); 'have to manually codesign before deploying to device.');
} }
if (forSimulator && !isEmulatorBuildMode(getBuildMode())) { if (forSimulator && !isEmulatorBuildMode(getBuildMode()))
printError('${toTitleCase(getModeName(getBuildMode()))} mode is not supported for emulators.'); throwToolExit('${toTitleCase(getModeName(getBuildMode()))} mode is not supported for emulators.');
return 1;
}
String logTarget = forSimulator ? 'simulator' : 'device'; String logTarget = forSimulator ? 'simulator' : 'device';
...@@ -83,8 +78,7 @@ class BuildIOSCommand extends BuildSubCommand { ...@@ -83,8 +78,7 @@ class BuildIOSCommand extends BuildSubCommand {
if (!result.success) { if (!result.success) {
printError('Encountered error while building for $logTarget.'); printError('Encountered error while building for $logTarget.');
diagnoseXcodeBuildFailure(result); diagnoseXcodeBuildFailure(result);
printError(''); throwToolExit('');
return 1;
} }
if (result.output != null) if (result.output != null)
......
...@@ -91,47 +91,39 @@ class DriveCommand extends RunCommandBase { ...@@ -91,47 +91,39 @@ class DriveCommand extends RunCommandBase {
@override @override
Future<int> verifyThenRunCommand() async { Future<int> verifyThenRunCommand() async {
if (!commandValidator()) commandValidator();
return 1;
return super.verifyThenRunCommand(); return super.verifyThenRunCommand();
} }
@override @override
Future<int> runCommand() async { Future<int> runCommand() async {
String testFile = _getTestFile(); String testFile = _getTestFile();
if (testFile == null) { if (testFile == null)
return 1; throwToolExit(null);
}
this._device = await targetDeviceFinder(); this._device = await targetDeviceFinder();
if (device == null) { if (device == null)
return 1; throwToolExit(null);
}
if (await fs.type(testFile) != FileSystemEntityType.FILE) { if (await fs.type(testFile) != FileSystemEntityType.FILE)
printError('Test file not found: $testFile'); throwToolExit('Test file not found: $testFile');
return 1;
}
if (!argResults['use-existing-app']) { if (!argResults['use-existing-app']) {
printStatus('Starting application: ${argResults["target"]}'); printStatus('Starting application: ${argResults["target"]}');
if (getBuildMode() == BuildMode.release) { if (getBuildMode() == BuildMode.release) {
// This is because we need VM service to be able to drive the app. // This is because we need VM service to be able to drive the app.
printError( throwToolExit(
'Flutter Driver does not support running in release mode.\n' 'Flutter Driver does not support running in release mode.\n'
'\n' '\n'
'Use --profile mode for testing application performance.\n' 'Use --profile mode for testing application performance.\n'
'Use --debug (default) mode for testing correctness (with assertions).' 'Use --debug (default) mode for testing correctness (with assertions).'
); );
return 1;
} }
int result = await appStarter(this); int result = await appStarter(this);
if (result != 0) { if (result != 0)
printError('Application failed to start. Will not run test. Quitting.'); throwToolExit('Application failed to start ($result). Will not run test. Quitting.', exitCode: result);
return result;
}
} else { } else {
printStatus('Will connect to already running application instance.'); printStatus('Will connect to already running application instance.');
} }
...@@ -139,11 +131,11 @@ class DriveCommand extends RunCommandBase { ...@@ -139,11 +131,11 @@ class DriveCommand extends RunCommandBase {
Cache.releaseLockEarly(); Cache.releaseLockEarly();
try { try {
return await testRunner(<String>[testFile]) await testRunner(<String>[testFile]);
.catchError((dynamic error, dynamic stackTrace) { } catch (error, stackTrace) {
printError('CAUGHT EXCEPTION: $error\n$stackTrace'); if (error is ToolExit)
return 1; rethrow;
}); throwToolExit('CAUGHT EXCEPTION: $error\n$stackTrace');
} finally { } finally {
if (!argResults['keep-app-running'] && !argResults['use-existing-app']) { if (!argResults['keep-app-running'] && !argResults['use-existing-app']) {
printStatus('Stopping application instance.'); printStatus('Stopping application instance.');
...@@ -157,6 +149,7 @@ class DriveCommand extends RunCommandBase { ...@@ -157,6 +149,7 @@ class DriveCommand extends RunCommandBase {
printStatus('Leaving the application running.'); printStatus('Leaving the application running.');
} }
} }
return 0;
} }
String _getTestFile() { String _getTestFile() {
...@@ -285,14 +278,11 @@ Future<int> startApp(DriveCommand command) async { ...@@ -285,14 +278,11 @@ Future<int> startApp(DriveCommand command) async {
// TODO(devoncarew): We should remove the need to special case here. // TODO(devoncarew): We should remove the need to special case here.
if (command.device is AndroidDevice) { if (command.device is AndroidDevice) {
printTrace('Building an APK.'); printTrace('Building an APK.');
int result = await build_apk.buildApk( await build_apk.buildApk(
command.device.platform, command.device.platform,
target: command.targetFile, target: command.targetFile,
buildMode: command.getBuildMode() buildMode: command.getBuildMode()
); );
if (result != 0)
return result;
} }
printTrace('Stopping previously running application, if any.'); printTrace('Stopping previously running application, if any.');
...@@ -335,13 +325,13 @@ Future<int> startApp(DriveCommand command) async { ...@@ -335,13 +325,13 @@ Future<int> startApp(DriveCommand command) async {
} }
/// Runs driver tests. /// Runs driver tests.
typedef Future<int> TestRunner(List<String> testArgs); typedef Future<Null> TestRunner(List<String> testArgs);
TestRunner testRunner = runTests; TestRunner testRunner = runTests;
void restoreTestRunner() { void restoreTestRunner() {
testRunner = runTests; testRunner = runTests;
} }
Future<int> runTests(List<String> testArgs) async { Future<Null> runTests(List<String> testArgs) async {
printTrace('Running driver tests.'); printTrace('Running driver tests.');
PackageMap.globalPackagesPath = path.normalize(path.absolute(PackageMap.globalPackagesPath)); PackageMap.globalPackagesPath = path.normalize(path.absolute(PackageMap.globalPackagesPath));
...@@ -349,7 +339,9 @@ Future<int> runTests(List<String> testArgs) async { ...@@ -349,7 +339,9 @@ Future<int> runTests(List<String> testArgs) async {
..add('--packages=${PackageMap.globalPackagesPath}') ..add('--packages=${PackageMap.globalPackagesPath}')
..add('-rexpanded'); ..add('-rexpanded');
String dartVmPath = path.join(dartSdkPath, 'bin', 'dart'); String dartVmPath = path.join(dartSdkPath, 'bin', 'dart');
return await runCommandAndStreamOutput(<String>[dartVmPath]..addAll(args)); int result = await runCommandAndStreamOutput(<String>[dartVmPath]..addAll(args));
if (result != 0)
throwToolExit('Driver tests failed: $result', exitCode: result);
} }
......
...@@ -6,9 +6,9 @@ import 'dart:async'; ...@@ -6,9 +6,9 @@ import 'dart:async';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import '../base/common.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../cache.dart'; import '../cache.dart';
import '../globals.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
class FormatCommand extends FlutterCommand { class FormatCommand extends FlutterCommand {
...@@ -27,18 +27,22 @@ class FormatCommand extends FlutterCommand { ...@@ -27,18 +27,22 @@ class FormatCommand extends FlutterCommand {
@override @override
Future<int> runCommand() async { Future<int> runCommand() async {
if (argResults.rest.isEmpty) { if (argResults.rest.isEmpty) {
printStatus('No files specified to be formatted.'); throwToolExit(
printStatus(''); 'No files specified to be formatted.\n'
printStatus('To format all files in the current directory tree:'); '\n'
printStatus('${runner.executableName} $name .'); 'To format all files in the current directory tree:\n'
printStatus(''); '${runner.executableName} $name .\n'
printStatus(usage); '\n'
return 1; '$usage'
);
} }
String dartfmt = path.join( String dartfmt = path.join(
Cache.flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', 'dartfmt'); Cache.flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', 'dartfmt');
List<String> cmd = <String>[dartfmt, '-w']..addAll(argResults.rest); List<String> cmd = <String>[dartfmt, '-w']..addAll(argResults.rest);
return runCommandAndStreamOutput(cmd); int result = await runCommandAndStreamOutput(cmd);
if (result != 0)
throwToolExit('Formatting failed: $result', exitCode: result);
return 0;
} }
} }
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'dart:async'; import 'dart:async';
import '../application_package.dart'; import '../application_package.dart';
import '../base/common.dart';
import '../cache.dart'; import '../cache.dart';
import '../device.dart'; import '../device.dart';
import '../globals.dart'; import '../globals.dart';
...@@ -21,11 +22,10 @@ class InstallCommand extends FlutterCommand { ...@@ -21,11 +22,10 @@ class InstallCommand extends FlutterCommand {
@override @override
Future<int> verifyThenRunCommand() async { Future<int> verifyThenRunCommand() async {
if (!commandValidator()) commandValidator();
return 1;
device = await findTargetDevice(); device = await findTargetDevice();
if (device == null) if (device == null)
return 1; throwToolExit('No target device found');
return super.verifyThenRunCommand(); return super.verifyThenRunCommand();
} }
...@@ -37,7 +37,9 @@ class InstallCommand extends FlutterCommand { ...@@ -37,7 +37,9 @@ class InstallCommand extends FlutterCommand {
printStatus('Installing $package to $device...'); printStatus('Installing $package to $device...');
return installApp(device, package) ? 0 : 2; if (!installApp(device, package))
throwToolExit('Install failed');
return 0;
} }
} }
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import '../base/common.dart';
import '../cache.dart'; import '../cache.dart';
import '../device.dart'; import '../device.dart';
import '../globals.dart'; import '../globals.dart';
...@@ -31,7 +32,7 @@ class LogsCommand extends FlutterCommand { ...@@ -31,7 +32,7 @@ class LogsCommand extends FlutterCommand {
Future<int> verifyThenRunCommand() async { Future<int> verifyThenRunCommand() async {
device = await findTargetDevice(); device = await findTargetDevice();
if (device == null) if (device == null)
return 1; throwToolExit(null);
return super.verifyThenRunCommand(); return super.verifyThenRunCommand();
} }
...@@ -74,7 +75,7 @@ class LogsCommand extends FlutterCommand { ...@@ -74,7 +75,7 @@ class LogsCommand extends FlutterCommand {
int result = await exitCompleter.future; int result = await exitCompleter.future;
subscription.cancel(); subscription.cancel();
if (result != 0) if (result != 0)
printError('Error listening to $logReader logs.'); throwToolExit('Error listening to $logReader logs.');
return result; return 0;
} }
} }
...@@ -4,9 +4,9 @@ ...@@ -4,9 +4,9 @@
import 'dart:async'; import 'dart:async';
import '../base/common.dart';
import '../base/os.dart'; import '../base/os.dart';
import '../dart/pub.dart'; import '../dart/pub.dart';
import '../globals.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
class PackagesCommand extends FlutterCommand { class PackagesCommand extends FlutterCommand {
...@@ -26,8 +26,7 @@ class PackagesCommand extends FlutterCommand { ...@@ -26,8 +26,7 @@ class PackagesCommand extends FlutterCommand {
@override @override
Future<int> verifyThenRunCommand() async { Future<int> verifyThenRunCommand() async {
if (!commandValidator()) commandValidator();
return 1;
return super.verifyThenRunCommand(); return super.verifyThenRunCommand();
} }
...@@ -54,25 +53,22 @@ class PackagesGetCommand extends FlutterCommand { ...@@ -54,25 +53,22 @@ class PackagesGetCommand extends FlutterCommand {
@override @override
Future<int> runCommand() async { Future<int> runCommand() async {
if (argResults.rest.length > 1) { if (argResults.rest.length > 1)
printStatus('Too many arguments.'); throwToolExit('Too many arguments.\n$usage');
printStatus(usage);
return 1;
}
String target = findProjectRoot( String target = findProjectRoot(
argResults.rest.length == 1 ? argResults.rest[0] : null); argResults.rest.length == 1 ? argResults.rest[0] : null);
if (target == null) { if (target == null)
printStatus('Expected to find project root starting at ' + throwToolExit(
'Expected to find project root starting at ' +
(argResults.rest.length == 1 (argResults.rest.length == 1
? argResults.rest[0] ? argResults.rest[0]
: 'current working directory')); : 'current working directory') +
printStatus(usage); '$usage');
return 1;
}
// TODO: If the user is using a local build, we should use the packages from their build instead of the cache. // TODO: If the user is using a local build, we should use the packages from their build instead of the cache.
return pubGet(directory: target, upgrade: upgrade, checkLastModified: false); await pubGet(directory: target, upgrade: upgrade, checkLastModified: false);
return 0;
} }
} }
...@@ -95,12 +95,11 @@ class RunCommand extends RunCommandBase { ...@@ -95,12 +95,11 @@ class RunCommand extends RunCommandBase {
argParser.addFlag('benchmark', negatable: false, hide: !verboseHelp); argParser.addFlag('benchmark', negatable: false, hide: !verboseHelp);
commandValidator = () { commandValidator = () {
if (!runningWithPrebuiltApplication) { if (!runningWithPrebuiltApplication)
return commonCommandValidator(); commonCommandValidator();
}
// When running with a prebuilt application, no command validation is // When running with a prebuilt application, no command validation is
// necessary. // necessary.
return true;
}; };
} }
...@@ -150,11 +149,10 @@ class RunCommand extends RunCommandBase { ...@@ -150,11 +149,10 @@ class RunCommand extends RunCommandBase {
@override @override
Future<int> verifyThenRunCommand() async { Future<int> verifyThenRunCommand() async {
if (!commandValidator()) commandValidator();
return 1;
device = await findTargetDevice(); device = await findTargetDevice();
if (device == null) if (device == null)
return 1; throwToolExit(null);
return super.verifyThenRunCommand(); return super.verifyThenRunCommand();
} }
...@@ -173,7 +171,9 @@ class RunCommand extends RunCommandBase { ...@@ -173,7 +171,9 @@ class RunCommand extends RunCommandBase {
AppInstance app = daemon.appDomain.startApp( AppInstance app = daemon.appDomain.startApp(
device, Directory.current.path, targetFile, route, device, Directory.current.path, targetFile, route,
getBuildMode(), argResults['start-paused'], hotMode); getBuildMode(), argResults['start-paused'], hotMode);
return app.runner.waitForAppToFinish(); int result = await app.runner.waitForAppToFinish();
if (result != 0)
throwToolExit(null, exitCode: result);
} }
int debugPort; int debugPort;
...@@ -181,15 +181,12 @@ class RunCommand extends RunCommandBase { ...@@ -181,15 +181,12 @@ class RunCommand extends RunCommandBase {
try { try {
debugPort = int.parse(argResults['debug-port']); debugPort = int.parse(argResults['debug-port']);
} catch (error) { } catch (error) {
printError('Invalid port for `--debug-port`: $error'); throwToolExit('Invalid port for `--debug-port`: $error');
return 1;
} }
} }
if (device.isLocalEmulator && !isEmulatorBuildMode(getBuildMode())) { if (device.isLocalEmulator && !isEmulatorBuildMode(getBuildMode()))
printError('${toTitleCase(getModeName(getBuildMode()))} mode is not supported for emulators.'); throwToolExit('${toTitleCase(getModeName(getBuildMode()))} mode is not supported for emulators.');
return 1;
}
DebuggingOptions options; DebuggingOptions options;
...@@ -204,11 +201,8 @@ class RunCommand extends RunCommandBase { ...@@ -204,11 +201,8 @@ class RunCommand extends RunCommandBase {
} }
if (hotMode) { if (hotMode) {
if (!device.supportsHotMode) { if (!device.supportsHotMode)
printError('Hot mode is not supported by this device. ' throwToolExit('Hot mode is not supported by this device. Run with --no-hot.');
'Run with --no-hot.');
return 1;
}
} }
String pidFile = argResults['pid-file']; String pidFile = argResults['pid-file'];
...@@ -238,6 +232,12 @@ class RunCommand extends RunCommandBase { ...@@ -238,6 +232,12 @@ class RunCommand extends RunCommandBase {
); );
} }
return runner.run(route: route, shouldBuild: !runningWithPrebuiltApplication && argResults['build']); int result = await runner.run(
route: route,
shouldBuild: !runningWithPrebuiltApplication && argResults['build'],
);
if (result != 0)
throwToolExit(null, exitCode: result);
return 0;
} }
} }
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter_tools/src/device.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import '../base/common.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../device.dart';
import '../globals.dart'; import '../globals.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
...@@ -53,24 +54,16 @@ class ScreenshotCommand extends FlutterCommand { ...@@ -53,24 +54,16 @@ class ScreenshotCommand extends FlutterCommand {
@override @override
Future<int> verifyThenRunCommand() async { Future<int> verifyThenRunCommand() async {
if (argResults[_kSkia] != null) { if (argResults[_kSkia] != null) {
if (argResults[_kOut] != null && argResults[_kSkiaServe] != null) { if (argResults[_kOut] != null && argResults[_kSkiaServe] != null)
printError('Cannot specify both --$_kOut and --$_kSkiaServe'); throwToolExit('Cannot specify both --$_kOut and --$_kSkiaServe');
return 1;
}
} else { } else {
if (argResults[_kSkiaServe] != null) { if (argResults[_kSkiaServe] != null)
printError('Must specify --$_kSkia with --$_kSkiaServe'); throwToolExit('Must specify --$_kSkia with --$_kSkiaServe');
return 1;
}
device = await findTargetDevice(); device = await findTargetDevice();
if (device == null) { if (device == null)
printError('Must specify --$_kSkia or have a connected device'); throwToolExit('Must specify --$_kSkia or have a connected device');
return 1; if (!device.supportsScreenshot && argResults[_kSkia] == null)
} throwToolExit('Screenshot not supported for ${device.name}.');
if (!device.supportsScreenshot && argResults[_kSkia] == null) {
printError('Screenshot not supported for ${device.name}.');
return 1;
}
} }
return super.verifyThenRunCommand(); return super.verifyThenRunCommand();
} }
...@@ -91,14 +84,13 @@ class ScreenshotCommand extends FlutterCommand { ...@@ -91,14 +84,13 @@ class ScreenshotCommand extends FlutterCommand {
Future<int> runScreenshot(File outputFile) async { Future<int> runScreenshot(File outputFile) async {
outputFile ??= getUniqueFile(Directory.current, 'flutter', 'png'); outputFile ??= getUniqueFile(Directory.current, 'flutter', 'png');
try { try {
if (await device.takeScreenshot(outputFile)) { if (!await device.takeScreenshot(outputFile))
await showOutputFileInfo(outputFile); throwToolExit('Screenshot failed');
return 0;
}
} catch (error) { } catch (error) {
printError('Error taking screenshot: $error'); throwToolExit('Error taking screenshot: $error');
} }
return 1; await showOutputFileInfo(outputFile);
return 0;
} }
Future<int> runSkia(File outputFile) async { Future<int> runSkia(File outputFile) async {
...@@ -106,26 +98,20 @@ class ScreenshotCommand extends FlutterCommand { ...@@ -106,26 +98,20 @@ class ScreenshotCommand extends FlutterCommand {
port: int.parse(argResults[_kSkia]), port: int.parse(argResults[_kSkia]),
path: '/skp'); path: '/skp');
void printErrorHelpText() { const String errorHelpText =
printError(''); 'Be sure the --$_kSkia= option specifies the diagnostic server port, not the observatory port.\n'
printError('Be sure the --$_kSkia= option specifies the diagnostic server port, not the observatory port.'); 'To find the diagnostic server port number, use "flutter run --verbose"\n'
printError('To find the diagnostic server port number, use "flutter run --verbose"'); 'and look for "Diagnostic server listening on" in the output.';
printError('and look for "Diagnostic server listening on" in the output.');
}
http.StreamedResponse skpResponse; http.StreamedResponse skpResponse;
try { try {
skpResponse = await new http.Request('GET', skpUri).send(); skpResponse = await new http.Request('GET', skpUri).send();
} on SocketException catch (e) { } on SocketException catch (e) {
printError('Skia screenshot failed: $skpUri\n$e'); throwToolExit('Skia screenshot failed: $skpUri\n$e\n\n$errorHelpText');
printErrorHelpText();
return 1;
} }
if (skpResponse.statusCode != HttpStatus.OK) { if (skpResponse.statusCode != HttpStatus.OK) {
String error = await skpResponse.stream.toStringStream().join(); String error = await skpResponse.stream.toStringStream().join();
printError('Error: $error'); throwToolExit('Error: $error\n\n$errorHelpText');
printErrorHelpText();
return 1;
} }
if (argResults[_kSkiaServe] != null) { if (argResults[_kSkiaServe] != null) {
...@@ -136,11 +122,8 @@ class ScreenshotCommand extends FlutterCommand { ...@@ -136,11 +122,8 @@ class ScreenshotCommand extends FlutterCommand {
'file', skpResponse.stream, skpResponse.contentLength)); 'file', skpResponse.stream, skpResponse.contentLength));
http.StreamedResponse postResponse = await postRequest.send(); http.StreamedResponse postResponse = await postRequest.send();
if (postResponse.statusCode != HttpStatus.OK) { if (postResponse.statusCode != HttpStatus.OK)
printError('Failed to post Skia picture to skiaserve.'); throwToolExit('Failed to post Skia picture to skiaserve.\n\n$errorHelpText');
printErrorHelpText();
return 1;
}
} else { } else {
outputFile ??= getUniqueFile(Directory.current, 'flutter', 'skp'); outputFile ??= getUniqueFile(Directory.current, 'flutter', 'skp');
IOSink sink = outputFile.openWrite(); IOSink sink = outputFile.openWrite();
...@@ -149,12 +132,8 @@ class ScreenshotCommand extends FlutterCommand { ...@@ -149,12 +132,8 @@ class ScreenshotCommand extends FlutterCommand {
await showOutputFileInfo(outputFile); await showOutputFileInfo(outputFile);
if (await outputFile.length() < 1000) { if (await outputFile.length() < 1000) {
String content = await outputFile.readAsString(); String content = await outputFile.readAsString();
if (content.startsWith('{"jsonrpc":"2.0", "error"')) { if (content.startsWith('{"jsonrpc":"2.0", "error"'))
printError(''); throwToolExit('\nIt appears the output file contains an error message, not valid skia output.\n\n$errorHelpText');
printError('It appears the output file contains an error message, not valid skia output.');
printErrorHelpText();
return 1;
}
} }
} }
return 0; return 0;
......
...@@ -53,9 +53,7 @@ class UpdatePackagesCommand extends FlutterCommand { ...@@ -53,9 +53,7 @@ class UpdatePackagesCommand extends FlutterCommand {
final bool upgrade = argResults['upgrade']; final bool upgrade = argResults['upgrade'];
for (Directory dir in runner.getRepoPackages()) { for (Directory dir in runner.getRepoPackages()) {
int code = await pubGet(directory: dir.path, upgrade: upgrade, checkLastModified: false); await pubGet(directory: dir.path, upgrade: upgrade, checkLastModified: false);
if (code != 0)
throw code;
count++; count++;
} }
......
...@@ -72,11 +72,7 @@ class UpgradeCommand extends FlutterCommand { ...@@ -72,11 +72,7 @@ class UpgradeCommand extends FlutterCommand {
String projRoot = findProjectRoot(); String projRoot = findProjectRoot();
if (projRoot != null) { if (projRoot != null) {
printStatus(''); printStatus('');
code = await pubGet( await pubGet(directory: projRoot, upgrade: true, checkLastModified: false);
directory: projRoot, upgrade: true, checkLastModified: false);
if (code != 0)
return code;
} }
// Run a doctor check in case system requirements have changed. // Run a doctor check in case system requirements have changed.
......
...@@ -7,6 +7,7 @@ import 'dart:io'; ...@@ -7,6 +7,7 @@ import 'dart:io';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import '../base/common.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/process.dart'; import '../base/process.dart';
import '../cache.dart'; import '../cache.dart';
...@@ -25,7 +26,7 @@ bool _shouldRunPubGet({ File pubSpecYaml, File dotPackages }) { ...@@ -25,7 +26,7 @@ bool _shouldRunPubGet({ File pubSpecYaml, File dotPackages }) {
return false; return false;
} }
Future<int> pubGet({ Future<Null> pubGet({
String directory, String directory,
bool skipIfAbsent: false, bool skipIfAbsent: false,
bool upgrade: false, bool upgrade: false,
...@@ -38,10 +39,9 @@ Future<int> pubGet({ ...@@ -38,10 +39,9 @@ Future<int> pubGet({
File dotPackages = new File(path.join(directory, '.packages')); File dotPackages = new File(path.join(directory, '.packages'));
if (!pubSpecYaml.existsSync()) { if (!pubSpecYaml.existsSync()) {
if (skipIfAbsent) if (!skipIfAbsent)
return 0; throwToolExit('$directory: no pubspec.yaml found');
printError('$directory: no pubspec.yaml found'); return;
return 1;
} }
if (!checkLastModified || _shouldRunPubGet(pubSpecYaml: pubSpecYaml, dotPackages: dotPackages)) { if (!checkLastModified || _shouldRunPubGet(pubSpecYaml: pubSpecYaml, dotPackages: dotPackages)) {
...@@ -55,14 +55,13 @@ Future<int> pubGet({ ...@@ -55,14 +55,13 @@ Future<int> pubGet({
); );
status.stop(); status.stop();
if (code != 0) if (code != 0)
return code; throwToolExit('pub $command failed ($code)', exitCode: code);
} }
if (dotPackages.existsSync() && dotPackages.lastModifiedSync().isAfter(pubSpecYaml.lastModifiedSync())) if (dotPackages.existsSync() && dotPackages.lastModifiedSync().isAfter(pubSpecYaml.lastModifiedSync()))
return 0; return;
printError('$directory: pubspec.yaml and .packages are in an inconsistent state'); throwToolExit('$directory: pubspec.yaml and .packages are in an inconsistent state');
return 1;
} }
String _filterOverrideWarnings(String str) { String _filterOverrideWarnings(String str) {
......
...@@ -8,6 +8,7 @@ import 'dart:io'; ...@@ -8,6 +8,7 @@ import 'dart:io';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'asset.dart'; import 'asset.dart';
import 'base/common.dart';
import 'base/file_system.dart' show ensureDirectoryExists; import 'base/file_system.dart' show ensureDirectoryExists;
import 'base/process.dart'; import 'base/process.dart';
import 'dart/package_map.dart'; import 'dart/package_map.dart';
...@@ -59,18 +60,17 @@ Future<String> buildFlx({ ...@@ -59,18 +60,17 @@ Future<String> buildFlx({
bool precompiledSnapshot: false, bool precompiledSnapshot: false,
bool includeRobotoFonts: true bool includeRobotoFonts: true
}) async { }) async {
int result; await build(
result = await build(
snapshotPath: defaultSnapshotPath, snapshotPath: defaultSnapshotPath,
outputPath: defaultFlxOutputPath, outputPath: defaultFlxOutputPath,
mainPath: mainPath, mainPath: mainPath,
precompiledSnapshot: precompiledSnapshot, precompiledSnapshot: precompiledSnapshot,
includeRobotoFonts: includeRobotoFonts includeRobotoFonts: includeRobotoFonts
); );
return result == 0 ? defaultFlxOutputPath : null; return defaultFlxOutputPath;
} }
Future<int> build({ Future<Null> build({
String snapshotterPath, String snapshotterPath,
String mainPath: defaultMainPath, String mainPath: defaultMainPath,
String manifestPath: defaultManifestPath, String manifestPath: defaultManifestPath,
...@@ -104,10 +104,8 @@ Future<int> build({ ...@@ -104,10 +104,8 @@ Future<int> build({
depfilePath: depfilePath, depfilePath: depfilePath,
packages: packagesPath packages: packagesPath
); );
if (result != 0) { if (result != 0)
printError('Failed to run the Flutter compiler. Exit code: $result'); throwToolExit('Failed to run the Flutter compiler. Exit code: $result', exitCode: result);
return result;
}
snapshotFile = new File(snapshotPath); snapshotFile = new File(snapshotPath);
} }
...@@ -124,7 +122,7 @@ Future<int> build({ ...@@ -124,7 +122,7 @@ Future<int> build({
); );
} }
Future<int> assemble({ Future<Null> assemble({
String manifestPath, String manifestPath,
File snapshotFile, File snapshotFile,
String outputPath, String outputPath,
...@@ -150,9 +148,8 @@ Future<int> assemble({ ...@@ -150,9 +148,8 @@ Future<int> assemble({
includeRobotoFonts: includeRobotoFonts, includeRobotoFonts: includeRobotoFonts,
reportLicensedPackages: reportLicensedPackages reportLicensedPackages: reportLicensedPackages
); );
if (result != 0) { if (result != 0)
return result; throwToolExit('Error building $outputPath: $result', exitCode: result);
}
ZipBuilder zipBuilder = new ZipBuilder(); ZipBuilder zipBuilder = new ZipBuilder();
...@@ -168,6 +165,4 @@ Future<int> assemble({ ...@@ -168,6 +165,4 @@ Future<int> assemble({
zipBuilder.createZip(new File(outputPath), new Directory(workingDirPath)); zipBuilder.createZip(new File(outputPath), new Directory(workingDirPath));
printTrace('Built $outputPath.'); printTrace('Built $outputPath.');
return 0;
} }
...@@ -199,14 +199,11 @@ class HotRunner extends ResidentRunner { ...@@ -199,14 +199,11 @@ class HotRunner extends ResidentRunner {
if (shouldBuild && device is AndroidDevice) { if (shouldBuild && device is AndroidDevice) {
printTrace('Running build command.'); printTrace('Running build command.');
int result = await buildApk( await buildApk(
device.platform, device.platform,
target: target, target: target,
buildMode: debuggingOptions.buildMode buildMode: debuggingOptions.buildMode
); );
if (result != 0)
return result;
} }
// TODO(devoncarew): Move this into the device.startApp() impls. // TODO(devoncarew): Move this into the device.startApp() impls.
......
...@@ -421,8 +421,11 @@ class IOSSimulator extends Device { ...@@ -421,8 +421,11 @@ class IOSSimulator extends Device {
if (!prebuiltApplication) { if (!prebuiltApplication) {
printTrace('Building ${app.name} for $id.'); printTrace('Building ${app.name} for $id.');
if (!(await _setupUpdatedApplicationBundle(app))) try {
await _setupUpdatedApplicationBundle(app);
} on ToolExit {
return new LaunchResult.failed(); return new LaunchResult.failed();
}
} }
ProtocolDiscovery observatoryDiscovery; ProtocolDiscovery observatoryDiscovery;
...@@ -496,45 +499,33 @@ class IOSSimulator extends Device { ...@@ -496,45 +499,33 @@ class IOSSimulator extends Device {
return isInstalled && isRunning; return isInstalled && isRunning;
} }
Future<bool> _setupUpdatedApplicationBundle(ApplicationPackage app) async { Future<Null> _setupUpdatedApplicationBundle(ApplicationPackage app) async {
bool sideloadResult = await _sideloadUpdatedAssetsForInstalledApplicationBundle(app); await _sideloadUpdatedAssetsForInstalledApplicationBundle(app);
if (!sideloadResult)
return false;
if (!_applicationIsInstalledAndRunning(app)) if (!_applicationIsInstalledAndRunning(app))
return _buildAndInstallApplicationBundle(app); return _buildAndInstallApplicationBundle(app);
return true;
} }
Future<bool> _buildAndInstallApplicationBundle(ApplicationPackage app) async { Future<Null> _buildAndInstallApplicationBundle(ApplicationPackage app) async {
// Step 1: Build the Xcode project. // Step 1: Build the Xcode project.
// The build mode for the simulator is always debug. // The build mode for the simulator is always debug.
XcodeBuildResult buildResult = await buildXcodeProject(app: app, mode: BuildMode.debug, buildForDevice: false); XcodeBuildResult buildResult = await buildXcodeProject(app: app, mode: BuildMode.debug, buildForDevice: false);
if (!buildResult.success) { if (!buildResult.success)
printError('Could not build the application for the simulator.'); throwToolExit('Could not build the application for the simulator.');
return false;
}
// Step 2: Assert that the Xcode project was successfully built. // Step 2: Assert that the Xcode project was successfully built.
IOSApp iosApp = app; IOSApp iosApp = app;
Directory bundle = new Directory(iosApp.simulatorBundlePath); Directory bundle = new Directory(iosApp.simulatorBundlePath);
bool bundleExists = await bundle.exists(); bool bundleExists = await bundle.exists();
if (!bundleExists) { if (!bundleExists)
printError('Could not find the built application bundle at ${bundle.path}.'); throwToolExit('Could not find the built application bundle at ${bundle.path}.');
return false;
}
// Step 3: Install the updated bundle to the simulator. // Step 3: Install the updated bundle to the simulator.
SimControl.instance.install(id, path.absolute(bundle.path)); SimControl.instance.install(id, path.absolute(bundle.path));
return true;
} }
Future<bool> _sideloadUpdatedAssetsForInstalledApplicationBundle( Future<Null> _sideloadUpdatedAssetsForInstalledApplicationBundle(ApplicationPackage app) =>
ApplicationPackage app) async { flx.build(precompiledSnapshot: true);
return (await flx.build(precompiledSnapshot: true)) == 0;
}
@override @override
Future<bool> stopApp(ApplicationPackage app) async { Future<bool> stopApp(ApplicationPackage app) async {
......
...@@ -95,14 +95,11 @@ class RunAndStayResident extends ResidentRunner { ...@@ -95,14 +95,11 @@ class RunAndStayResident extends ResidentRunner {
if (shouldBuild && device is AndroidDevice) { if (shouldBuild && device is AndroidDevice) {
printTrace('Running build command.'); printTrace('Running build command.');
int result = await buildApk( await buildApk(
device.platform, device.platform,
target: target, target: target,
buildMode: debuggingOptions.buildMode buildMode: debuggingOptions.buildMode
); );
if (result != 0)
return result;
} }
// TODO(devoncarew): Move this into the device.startApp() impls. // TODO(devoncarew): Move this into the device.startApp() impls.
......
...@@ -135,11 +135,8 @@ abstract class FlutterCommand extends Command { ...@@ -135,11 +135,8 @@ abstract class FlutterCommand extends Command {
// package is available in the flutter cache for pub to find. // package is available in the flutter cache for pub to find.
await cache.updateAll(); await cache.updateAll();
if (shouldRunPub) { if (shouldRunPub)
int exitCode = await pubGet(); await pubGet();
if (exitCode != 0)
return exitCode;
}
setupApplicationPackages(); setupApplicationPackages();
......
...@@ -6,6 +6,7 @@ import 'dart:async'; ...@@ -6,6 +6,7 @@ import 'dart:async';
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:flutter_tools/src/android/android_device.dart'; import 'package:flutter_tools/src/android/android_device.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/base/os.dart';
...@@ -58,19 +59,19 @@ void main() { ...@@ -58,19 +59,19 @@ void main() {
restoreTargetDeviceFinder(); restoreTargetDeviceFinder();
}); });
testUsingContext('returns 1 when test file is not found', () { testUsingContext('returns 1 when test file is not found', () async {
withMockDevice(); withMockDevice();
List<String> args = <String>[ List<String> args = <String>[
'drive', 'drive',
'--target=/some/app/test/e2e.dart', '--target=/some/app/test/e2e.dart',
]; ];
return createTestCommandRunner(command).run(args).then((int code) { try {
expect(code, 1); await createTestCommandRunner(command).run(args);
BufferLogger buffer = logger; fail('Expect exception');
expect(buffer.errorText, contains( } on ToolExit catch (e) {
'Test file not found: /some/app/test_driver/e2e_test.dart' expect(e.exitCode ?? 1, 1);
)); expect(e.message, contains('Test file not found: /some/app/test_driver/e2e_test.dart'));
}); }
}); });
testUsingContext('returns 1 when app fails to run', () async { testUsingContext('returns 1 when app fails to run', () async {
...@@ -88,13 +89,13 @@ void main() { ...@@ -88,13 +89,13 @@ void main() {
'drive', 'drive',
'--target=$testApp', '--target=$testApp',
]; ];
return createTestCommandRunner(command).run(args).then((int code) { try {
expect(code, 1); await createTestCommandRunner(command).run(args);
BufferLogger buffer = logger; fail('Expect exception');
expect(buffer.errorText, contains( } on ToolExit catch (e) {
'Application failed to start. Will not run test. Quitting.' expect(e.exitCode, 1);
)); expect(e.message, contains('Application failed to start (1). Will not run test. Quitting.'));
}); }
}); });
testUsingContext('returns 1 when app file is outside package', () async { testUsingContext('returns 1 when app file is outside package', () async {
...@@ -106,13 +107,15 @@ void main() { ...@@ -106,13 +107,15 @@ void main() {
'drive', 'drive',
'--target=$appFile', '--target=$appFile',
]; ];
return createTestCommandRunner(command).run(args).then((int code) { try {
expect(code, 1); await createTestCommandRunner(command).run(args);
BufferLogger buffer = logger; fail('Expect exception');
expect(buffer.errorText, contains( } on ToolExit catch (e) {
'Application file $appFile is outside the package directory $packageDir' expect(e.exitCode ?? 1, 1);
expect(testLogger.errorText, contains(
'Application file $appFile is outside the package directory $packageDir',
)); ));
}); }
}); });
testUsingContext('returns 1 when app file is in the root dir', () async { testUsingContext('returns 1 when app file is in the root dir', () async {
...@@ -124,14 +127,16 @@ void main() { ...@@ -124,14 +127,16 @@ void main() {
'drive', 'drive',
'--target=$appFile', '--target=$appFile',
]; ];
return createTestCommandRunner(command).run(args).then((int code) { try {
expect(code, 1); await createTestCommandRunner(command).run(args);
BufferLogger buffer = logger; fail('Expect exception');
expect(buffer.errorText, contains( } on ToolExit catch (e) {
expect(e.exitCode ?? 1, 1);
expect(testLogger.errorText, contains(
'Application file main.dart must reside in one of the ' 'Application file main.dart must reside in one of the '
'sub-directories of the package structure, not in the root directory.' 'sub-directories of the package structure, not in the root directory.',
)); ));
}); }
}); });
testUsingContext('returns 0 when test ends successfully', () async { testUsingContext('returns 0 when test ends successfully', () async {
...@@ -175,9 +180,9 @@ void main() { ...@@ -175,9 +180,9 @@ void main() {
appStarter = expectAsync((_) { appStarter = expectAsync((_) {
return new Future<int>.value(0); return new Future<int>.value(0);
}); });
testRunner = expectAsync((_) { testRunner = (_) {
return new Future<int>.value(123); throwToolExit(null, exitCode: 123);
}); };
appStopper = expectAsync((_) { appStopper = expectAsync((_) {
return new Future<int>.value(0); return new Future<int>.value(0);
}); });
...@@ -190,14 +195,15 @@ void main() { ...@@ -190,14 +195,15 @@ void main() {
'drive', 'drive',
'--target=$testApp', '--target=$testApp',
]; ];
return createTestCommandRunner(command).run(args).then((int code) { try {
expect(code, 123); await createTestCommandRunner(command).run(args);
BufferLogger buffer = logger; fail('Expect exception');
expect(buffer.errorText, isEmpty); } on ToolExit catch (e) {
}); expect(e.exitCode ?? 1, 123);
expect(e.message, isNull);
}
}); });
group('findTargetDevice', () { group('findTargetDevice', () {
testUsingContext('uses specified device', () async { testUsingContext('uses specified device', () async {
testDeviceManager.specifiedDeviceId = '123'; testDeviceManager.specifiedDeviceId = '123';
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/commands/logs.dart'; import 'package:flutter_tools/src/commands/logs.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
...@@ -11,12 +12,15 @@ import 'src/mocks.dart'; ...@@ -11,12 +12,15 @@ import 'src/mocks.dart';
void main() { void main() {
group('logs', () { group('logs', () {
testUsingContext('fail with a bad device id', () { testUsingContext('fail with a bad device id', () async {
LogsCommand command = new LogsCommand(); LogsCommand command = new LogsCommand();
applyMocksToCommand(command); applyMocksToCommand(command);
return createTestCommandRunner(command).run(<String>['-d', 'abc123', 'logs']).then((int code) { try {
expect(code, 1); await createTestCommandRunner(command).run(<String>['-d', 'abc123', 'logs']);
}); fail('Expect exception');
} on ToolExit catch (e) {
expect(e.exitCode ?? 1, 1);
}
}); });
}); });
} }
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/commands/run.dart'; import 'package:flutter_tools/src/commands/run.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
...@@ -11,12 +12,15 @@ import 'src/mocks.dart'; ...@@ -11,12 +12,15 @@ import 'src/mocks.dart';
void main() { void main() {
group('run', () { group('run', () {
testUsingContext('fails when target not found', () { testUsingContext('fails when target not found', () async {
RunCommand command = new RunCommand(); RunCommand command = new RunCommand();
applyMocksToCommand(command); applyMocksToCommand(command);
return createTestCommandRunner(command).run(<String>['run', '-t', 'abc123']).then((int code) { try {
expect(code, 1); await createTestCommandRunner(command).run(<String>['run', '-t', 'abc123']);
}); fail('Expect exception');
} on ToolExit catch (e) {
expect(e.exitCode ?? 1, 1);
}
}); });
}); });
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment