blob: e38c53cf7d42b244aeba316ac47d728e4851158a [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:meta/meta.dart';
import '../application_package.dart';
import '../base/common.dart';
import '../build_info.dart';
import '../dart/package_map.dart';
import '../dart/pub.dart';
import '../device.dart';
import '../flx.dart' as flx;
import '../globals.dart';
import '../usage.dart';
import 'flutter_command_runner.dart';
typedef void Validator();
abstract class FlutterCommand extends Command<Null> {
FlutterCommand() {
commandValidator = commonCommandValidator;
}
@override
FlutterCommandRunner get runner => super.runner;
/// Whether this command uses the 'target' option.
bool _usesTargetOption = false;
bool _usesPubOption = false;
bool get shouldRunPub => _usesPubOption && argResults['pub'];
BuildMode _defaultBuildMode;
void usesTargetOption() {
argParser.addOption('target',
abbr: 't',
defaultsTo: flx.defaultMainPath,
help: 'Target app path / main entry-point file.');
_usesTargetOption = true;
}
String get targetFile {
if (argResults.wasParsed('target'))
return argResults['target'];
else if (argResults.rest.isNotEmpty)
return argResults.rest.first;
else
return flx.defaultMainPath;
}
void usesPubOption() {
argParser.addFlag('pub',
defaultsTo: true,
help: 'Whether to run "flutter packages get" before executing this command.');
_usesPubOption = true;
}
void addBuildModeFlags({ bool defaultToRelease: true }) {
defaultBuildMode = defaultToRelease ? BuildMode.release : BuildMode.debug;
argParser.addFlag('debug',
negatable: false,
help: 'Build a debug version of your app${defaultToRelease ? '' : ' (default mode)'}.');
argParser.addFlag('profile',
negatable: false,
help: 'Build a version of your app specialized for performance profiling.');
argParser.addFlag('release',
negatable: false,
help: 'Build a release version of your app${defaultToRelease ? ' (default mode)' : ''}.');
}
set defaultBuildMode(BuildMode buildMode) {
_defaultBuildMode = buildMode;
}
BuildMode getBuildMode() {
List<bool> modeFlags = <bool>[argResults['debug'], argResults['profile'], argResults['release']];
if (modeFlags.where((bool flag) => flag).length > 1)
throw new UsageException('Only one of --debug, --profile, or --release can be specified.', null);
if (argResults['debug'])
return BuildMode.debug;
if (argResults['profile'])
return BuildMode.profile;
if (argResults['release'])
return BuildMode.release;
return _defaultBuildMode;
}
void setupApplicationPackages() {
applicationPackages ??= new ApplicationPackageStore();
}
/// The path to send to Google Analytics. Return `null` here to disable
/// tracking of the command.
String get usagePath => name;
/// Runs this command.
///
/// Rather than overriding this method, subclasses should override
/// [verifyThenRunCommand] to perform any verification
/// and [runCommand] to execute the command
/// so that this method can record and report the overall time to analytics.
@override
Future<Null> run() {
Stopwatch stopwatch = new Stopwatch()..start();
UsageTimer analyticsTimer = usagePath == null ? null : flutterUsage.startTimer(name);
if (flutterUsage.isFirstRun)
flutterUsage.printUsage();
return verifyThenRunCommand().whenComplete(() {
int ms = stopwatch.elapsedMilliseconds;
printTrace("'flutter $name' took ${ms}ms.");
analyticsTimer?.finish();
});
}
/// Perform validation then call [runCommand] to execute the command.
/// Return a [Future] that completes with an exit code
/// indicating whether execution was successful.
///
/// Subclasses should override this method to perform verification
/// then call this method to execute the command
/// rather than calling [runCommand] directly.
@mustCallSuper
Future<Null> verifyThenRunCommand() async {
// Populate the cache. We call this before pub get below so that the sky_engine
// package is available in the flutter cache for pub to find.
await cache.updateAll();
if (shouldRunPub)
await pubGet();
setupApplicationPackages();
String commandPath = usagePath;
if (commandPath != null)
flutterUsage.sendCommand(usagePath);
await runCommand();
}
/// Subclasses must implement this to execute the command.
Future<Null> runCommand();
/// Find and return the target [Device] based upon currently connected
/// devices and criteria entered by the user on the command line.
/// If a device cannot be found that meets specified criteria,
/// then print an error message and return `null`.
Future<Device> findTargetDevice({bool androidOnly: false}) async {
if (!doctor.canLaunchAnything) {
printError("Unable to locate a development device; please run 'flutter doctor' "
"for information about installing additional components.");
return null;
}
List<Device> devices = await deviceManager.getDevices();
if (devices.isEmpty && deviceManager.hasSpecifiedDeviceId) {
printStatus("No devices found with name or id "
"matching '${deviceManager.specifiedDeviceId}'");
return null;
} else if (devices.isEmpty) {
printNoConnectedDevices();
return null;
}
devices = devices.where((Device device) => device.isSupported()).toList();
if (androidOnly)
devices = devices.where((Device device) => device.platform == TargetPlatform.android_arm).toList();
if (devices.isEmpty) {
printStatus('No supported devices connected.');
return null;
} else if (devices.length > 1) {
if (deviceManager.hasSpecifiedDeviceId) {
printStatus("Found ${devices.length} devices with name or id matching "
"'${deviceManager.specifiedDeviceId}':");
} else {
printStatus("More than one device connected; please specify a device with "
"the '-d <deviceId>' flag.");
devices = await deviceManager.getAllConnectedDevices();
}
printStatus('');
Device.printDevices(devices);
return null;
}
return devices.single;
}
void printNoConnectedDevices() {
printStatus('No connected devices.');
}
// This is a field so that you can modify the value for testing.
Validator commandValidator;
void commonCommandValidator() {
if (!PackageMap.isUsingCustomPackagesPath) {
// Don't expect a pubspec.yaml file if the user passed in an explicit .packages file path.
if (!FileSystemEntity.isFileSync('pubspec.yaml')) {
throw new ToolExit('Error: No pubspec.yaml file found.\n'
'This command should be run from the root of your Flutter project.\n'
'Do not run this command from the root of your git clone of Flutter.');
}
}
if (_usesTargetOption) {
String targetPath = targetFile;
if (!FileSystemEntity.isFileSync(targetPath))
throw new ToolExit('Target file "$targetPath" not found.');
}
// Validate the current package map only if we will not be running "pub get" later.
if (!(_usesPubOption && argResults['pub'])) {
String error = new PackageMap(PackageMap.globalPackagesPath).checkValid();
if (error != null)
throw new ToolExit(error);
}
}
ApplicationPackageStore applicationPackages;
}