flutter_command.dart 4.98 KB
Newer Older
1 2 3 4 5
// 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';
6
import 'dart:io';
7 8 9 10

import 'package:args/command_runner.dart';

import '../application_package.dart';
11
import '../build_configuration.dart';
12
import '../device.dart';
13
import '../globals.dart';
14
import '../toolchain.dart';
15
import '../flx.dart' as flx;
16 17
import 'flutter_command_runner.dart';

18 19
typedef bool Validator();

20 21 22
abstract class FlutterCommand extends Command {
  FlutterCommandRunner get runner => super.runner;

23
  /// Whether this command needs to be run from the root of a project.
24
  bool get requiresProjectRoot => true;
25

26 27 28 29 30 31
  /// Whether this command requires a (single) Flutter target device to be connected.
  bool get requiresDevice => false;

  /// Whether this command only applies to Android devices.
  bool get androidOnly => false;

32 33 34 35
  /// Whether this command allows usage of the 'target' option.
  bool get allowsTarget => _targetOptionSpecified;
  bool _targetOptionSpecified = false;

Ian Hickson's avatar
Ian Hickson committed
36 37
  List<BuildConfiguration> get buildConfigurations => runner.buildConfigurations;

38 39
  Future downloadToolchain() async {
    toolchain ??= await Toolchain.forConfigs(buildConfigurations);
40 41
  }

42
  Future downloadApplicationPackagesAndConnectToDevices() async {
43
    await downloadApplicationPackages();
44
    _connectToDevices();
45 46
  }

47
  Future downloadApplicationPackages() async {
48
    applicationPackages ??= await ApplicationPackageStore.forConfigs(buildConfigurations);
49 50
  }

51 52
  void _connectToDevices() {
    devices ??= new DeviceStore.forConfigs(buildConfigurations);
53 54
  }

Devon Carew's avatar
Devon Carew committed
55 56 57 58 59 60 61 62 63 64 65
  Future<int> run() {
    Stopwatch stopwatch = new Stopwatch()..start();

    return _run().then((int exitCode) {
      printTrace("'flutter $name' exiting with code $exitCode; "
        "elasped time ${stopwatch.elapsedMilliseconds}ms.");
      return exitCode;
    });
  }

  Future<int> _run() async {
66 67
    bool _checkRoot = requiresProjectRoot && allowsTarget && !_targetSpecified;
    if (_checkRoot && !projectRootValidator())
68
      return 1;
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102

    // Ensure at least one toolchain is installed.
    if (requiresDevice && !doctor.canLaunchAnything) {
      printError("Unable to locate a development device; please run 'flutter doctor' "
        "for information about installing additional components.");
      return 1;
    }

    // Validate devices.
    if (requiresDevice) {
      List<Device> devices = await deviceManager.getDevices();

      if (devices.isEmpty && deviceManager.hasSpecifiedDeviceId) {
        printError("No device found with id '${deviceManager.specifiedDeviceId}'.");
        return 1;
      } else if (devices.isEmpty) {
        printStatus('No connected devices.');
        return 1;
      }

      devices = devices.where((Device device) => device.isSupported()).toList();

      if (androidOnly)
        devices = devices.where((Device device) => device.platform == TargetPlatform.android).toList();

      // TODO(devoncarew): Switch this to just supporting one connected device?
      if (devices.isEmpty) {
        printStatus('No supported devices connected.');
        return 1;
      }

      _devicesForCommand = await _getDevicesForCommand();
    }

Hixie's avatar
Hixie committed
103
    return await runInProject();
104 105
  }

106 107
  // This is a field so that you can modify the value for testing.
  Validator projectRootValidator = () {
108
    if (!FileSystemEntity.isFileSync('pubspec.yaml')) {
109 110 111
      printError('Error: No pubspec.yaml file found.\n'
        'This command should be run from the root of your Flutter project.\n'
        'Do not run this command from the root of your git clone of Flutter.');
112 113 114
      return false;
    }
    return true;
115
  };
116

117 118
  Future<int> runInProject();

119 120 121 122 123 124 125 126 127 128
  List<Device> get devicesForCommand => _devicesForCommand;

  Device get deviceForCommand {
    // TODO(devoncarew): Switch this to just supporting one connected device?
    return devicesForCommand.isNotEmpty ? devicesForCommand.first : null;
  }

  // This is caculated in run() if the command has [requiresDevice] specified.
  List<Device> _devicesForCommand;

129
  ApplicationPackageStore applicationPackages;
130
  Toolchain toolchain;
131
  DeviceStore devices;
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150

  Future<List<Device>> _getDevicesForCommand() async {
    List<Device> devices = await deviceManager.getDevices();

    if (devices.isEmpty)
      return null;

    if (deviceManager.hasSpecifiedDeviceId) {
      Device device = await deviceManager.getDeviceById(deviceManager.specifiedDeviceId);
      return device == null ? <Device>[] : <Device>[device];
    }

    devices = devices.where((Device device) => device.isSupported()).toList();

    if (androidOnly)
      devices = devices.where((Device device) => device.platform == TargetPlatform.android).toList();

    return devices;
  }
151 152 153 154 155 156 157 158 159

  bool _targetSpecified = false;

  void addTargetOption() {
    argParser.addOption('target',
      abbr: 't',
      callback: (val) => _targetSpecified = true,
      defaultsTo: flx.defaultMainPath,
      help: 'Target app path / main entry-point file.');
160
    _targetOptionSpecified = true;
161
  }
162
}