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