flutter_command_runner.dart 14 KB
Newer Older
1 2 3 4 5 6 7 8 9
// 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/args.dart';
import 'package:args/command_runner.dart';
10
import 'package:path/path.dart' as path;
11 12

import '../artifacts.dart';
13
import '../base/context.dart';
14
import '../base/process.dart';
15
import '../build_configuration.dart';
16
import 'version.dart';
17

Ian Hickson's avatar
Ian Hickson committed
18 19 20
const String kFlutterRootEnvironmentVariableName = 'FLUTTER_ROOT'; // should point to //flutter/ (root of flutter/flutter repo)
const String kFlutterEngineEnvironmentVariableName = 'FLUTTER_ENGINE'; // should point to //engine/src/ (root of flutter/engine repo)
const String kSnapshotFileName = 'flutter_tools.snapshot'; // in //flutter/bin/cache/
21
const String kFlutterToolsScriptFileName = 'flutter_tools.dart'; // in //flutter/packages/flutter_tools/bin/
Ian Hickson's avatar
Ian Hickson committed
22 23
const String kFlutterEnginePackageName = 'sky_engine';

24
class FlutterCommandRunner extends CommandRunner {
Devon Carew's avatar
Devon Carew committed
25 26 27 28
  FlutterCommandRunner({ bool verboseHelp: false }) : super(
    'flutter',
    'Manage your Flutter app development.'
  ) {
29 30 31 32
    argParser.addFlag('verbose',
        abbr: 'v',
        negatable: false,
        help: 'Noisy logging, including all shell commands executed.');
Devon Carew's avatar
Devon Carew committed
33 34 35
    argParser.addOption('device-id',
        abbr: 'd',
        help: 'Target device id.');
36 37 38
    argParser.addFlag('version',
        negatable: false,
        help: 'Reports the version of this tool.');
Devon Carew's avatar
Devon Carew committed
39

Ian Hickson's avatar
Ian Hickson committed
40 41 42 43 44
    String packagesHelp;
    if (ArtifactStore.isPackageRootValid)
      packagesHelp = '\n(defaults to "${ArtifactStore.packageRoot}")';
    else
      packagesHelp = '\n(required, since the current directory does not contain a "packages" subdirectory)';
45
    argParser.addOption('package-root',
Ian Hickson's avatar
Ian Hickson committed
46 47
        help: 'Path to your packages directory.$packagesHelp');
    argParser.addOption('flutter-root',
Devon Carew's avatar
Devon Carew committed
48 49
        help: 'The root directory of the Flutter repository. Defaults to \$$kFlutterRootEnvironmentVariableName if\n'
              'set, otherwise defaults to a value derived from the location of this tool.', defaultsTo: _defaultFlutterRoot);
Ian Hickson's avatar
Ian Hickson committed
50

Devon Carew's avatar
Devon Carew committed
51 52
    if (verboseHelp)
      argParser.addSeparator('Local build selection options (not normally required):');
53 54 55

    argParser.addFlag('debug',
        negatable: false,
Devon Carew's avatar
Devon Carew committed
56
        hide: !verboseHelp,
57
        help:
Ian Hickson's avatar
Ian Hickson committed
58 59
            'Set this if you are building Flutter locally and want to use the debug build products.\n'
            'Defaults to true if --engine-src-path is specified and --release is not, otherwise false.');
60 61
    argParser.addFlag('release',
        negatable: false,
Devon Carew's avatar
Devon Carew committed
62
        hide: !verboseHelp,
63
        help:
Ian Hickson's avatar
Ian Hickson committed
64 65
            'Set this if you are building Flutter locally and want to use the release build products.\n'
            'The --release option is not compatible with the listen command on iOS devices and simulators.');
Adam Barth's avatar
Adam Barth committed
66
    argParser.addOption('engine-src-path',
Devon Carew's avatar
Devon Carew committed
67
        hide: !verboseHelp,
68
        help:
Ian Hickson's avatar
Ian Hickson committed
69 70 71 72
            'Path to your engine src directory, if you are building Flutter locally.\n'
            'Defaults to \$$kFlutterEngineEnvironmentVariableName if set, otherwise defaults to the path given in your pubspec.yaml\n'
            'dependency_overrides for $kFlutterEnginePackageName, if any, or, failing that, tries to guess at the location\n'
            'based on the value of the --flutter-root option.');
Devon Carew's avatar
Devon Carew committed
73 74
    argParser.addOption('host-debug-build-path',
        hide: !verboseHelp,
Ian Hickson's avatar
Ian Hickson committed
75 76 77 78
        help:
            'Path to your host Debug out directory (i.e. the one that runs on your workstation, not a device), if you are building Flutter locally.\n'
            'This path is relative to --engine-src-path. Not normally required.',
        defaultsTo: 'out/Debug/');
Devon Carew's avatar
Devon Carew committed
79 80
    argParser.addOption('host-release-build-path',
        hide: !verboseHelp,
Ian Hickson's avatar
Ian Hickson committed
81 82 83 84
        help:
            'Path to your host Release out directory (i.e. the one that runs on your workstation, not a device), if you are building Flutter locally.\n'
            'This path is relative to --engine-src-path. Not normally required.',
        defaultsTo: 'out/Release/');
Devon Carew's avatar
Devon Carew committed
85 86
    argParser.addOption('android-debug-build-path',
        hide: !verboseHelp,
87
        help:
Ian Hickson's avatar
Ian Hickson committed
88 89
            'Path to your Android Debug out directory, if you are building Flutter locally.\n'
            'This path is relative to --engine-src-path. Not normally required.',
90
        defaultsTo: 'out/android_Debug/');
Devon Carew's avatar
Devon Carew committed
91 92
    argParser.addOption('android-release-build-path',
        hide: !verboseHelp,
93
        help:
Ian Hickson's avatar
Ian Hickson committed
94 95
            'Path to your Android Release out directory, if you are building Flutter locally.\n'
            'This path is relative to --engine-src-path. Not normally required.',
96
        defaultsTo: 'out/android_Release/');
Devon Carew's avatar
Devon Carew committed
97 98
    argParser.addOption('ios-debug-build-path',
        hide: !verboseHelp,
99
        help:
Ian Hickson's avatar
Ian Hickson committed
100 101
            'Path to your iOS Debug out directory, if you are building Flutter locally.\n'
            'This path is relative to --engine-src-path. Not normally required.',
102
        defaultsTo: 'out/ios_Debug/');
Devon Carew's avatar
Devon Carew committed
103 104
    argParser.addOption('ios-release-build-path',
        hide: !verboseHelp,
105
        help:
Ian Hickson's avatar
Ian Hickson committed
106 107
            'Path to your iOS Release out directory, if you are building Flutter locally.\n'
            'This path is relative to --engine-src-path. Not normally required.',
108
        defaultsTo: 'out/ios_Release/');
Devon Carew's avatar
Devon Carew committed
109 110
    argParser.addOption('ios-sim-debug-build-path',
        hide: !verboseHelp,
111
        help:
Ian Hickson's avatar
Ian Hickson committed
112 113
            'Path to your iOS Simulator Debug out directory, if you are building Flutter locally.\n'
            'This path is relative to --engine-src-path. Not normally required.',
114
        defaultsTo: 'out/ios_sim_Debug/');
Devon Carew's avatar
Devon Carew committed
115 116
    argParser.addOption('ios-sim-release-build-path',
        hide: !verboseHelp,
117
        help:
Ian Hickson's avatar
Ian Hickson committed
118 119
            'Path to your iOS Simulator Release out directory, if you are building Flutter locally.\n'
            'This path is relative to --engine-src-path. Not normally required.',
120 121 122
        defaultsTo: 'out/ios_sim_Release/');
  }

Devon Carew's avatar
Devon Carew committed
123 124
  final String usageFooter = 'Run "flutter -h -v" for verbose help output, including less commonly used options.';

Adam Barth's avatar
Adam Barth committed
125 126 127 128 129 130 131
  List<BuildConfiguration> get buildConfigurations {
    if (_buildConfigurations == null)
      _buildConfigurations = _createBuildConfigurations(_globalResults);
    return _buildConfigurations;
  }
  List<BuildConfiguration> _buildConfigurations;

132 133 134 135 136 137 138 139 140 141
  String get enginePath {
    if (!_enginePathSet) {
      _enginePath = _findEnginePath(_globalResults);
      _enginePathSet = true;
    }
    return _enginePath;
  }
  String _enginePath;
  bool _enginePathSet = false;

Adam Barth's avatar
Adam Barth committed
142
  ArgResults _globalResults;
143

144
  String get _defaultFlutterRoot {
Ian Hickson's avatar
Ian Hickson committed
145 146
    if (Platform.environment.containsKey(kFlutterRootEnvironmentVariableName))
      return Platform.environment[kFlutterRootEnvironmentVariableName];
Devon Carew's avatar
Devon Carew committed
147 148 149 150 151 152 153 154 155 156

    try {
      String script = Platform.script.toFilePath();
      if (path.basename(script) == kSnapshotFileName)
        return path.dirname(path.dirname(path.dirname(script)));
      if (path.basename(script) == kFlutterToolsScriptFileName)
        return path.dirname(path.dirname(path.dirname(path.dirname(script))));
    } catch (error) {
      printTrace('Unable to locate fluter root: $error');
    }
Ian Hickson's avatar
Ian Hickson committed
157 158 159
    return '.';
  }

160
  Future<int> runCommand(ArgResults globalResults) {
161
    if (globalResults['verbose'])
162
      context.verbose = true;
163

Adam Barth's avatar
Adam Barth committed
164
    _globalResults = globalResults;
165
    ArtifactStore.flutterRoot = path.normalize(path.absolute(globalResults['flutter-root']));
Ian Hickson's avatar
Ian Hickson committed
166
    if (globalResults.wasParsed('package-root'))
167
      ArtifactStore.packageRoot = path.normalize(path.absolute(globalResults['package-root']));
168

169
    if (globalResults['version']) {
170
      printStatus(getVersion(ArtifactStore.flutterRoot));
171
      return new Future<int>.value(0);
172 173
    }

174
    return super.runCommand(globalResults);
Adam Barth's avatar
Adam Barth committed
175 176
  }

177 178 179 180 181 182
  String _tryEnginePath(String enginePath) {
    if (FileSystemEntity.isDirectorySync(path.join(enginePath, 'out')))
      return enginePath;
    return null;
  }

183 184
  String _findEnginePath(ArgResults globalResults) {
    String engineSourcePath = globalResults['engine-src-path'] ?? Platform.environment[kFlutterEngineEnvironmentVariableName];
185 186 187
    bool isDebug = globalResults['debug'];
    bool isRelease = globalResults['release'];

188
    if (engineSourcePath == null && (isDebug || isRelease)) {
Ian Hickson's avatar
Ian Hickson committed
189 190 191 192
      if (ArtifactStore.isPackageRootValid) {
        Directory engineDir = new Directory(path.join(ArtifactStore.packageRoot, kFlutterEnginePackageName));
        try {
          String realEnginePath = engineDir.resolveSymbolicLinksSync();
193 194 195 196
          engineSourcePath = path.dirname(path.dirname(path.dirname(path.dirname(realEnginePath))));
          bool dirExists = FileSystemEntity.isDirectorySync(path.join(engineSourcePath, 'out'));
          if (engineSourcePath == '/' || engineSourcePath.isEmpty || !dirExists)
            engineSourcePath = null;
Ian Hickson's avatar
Ian Hickson committed
197 198
        } on FileSystemException { }
      }
199 200 201 202

      if (engineSourcePath == null)
        engineSourcePath = _tryEnginePath(path.join(ArtifactStore.flutterRoot, '../engine/src'));

203
      if (engineSourcePath == null) {
204
        printError('Unable to detect local Flutter engine build directory.\n'
Ian Hickson's avatar
Ian Hickson committed
205 206 207
            'Either specify a dependency_override for the $kFlutterEnginePackageName package in your pubspec.yaml and\n'
            'ensure --package-root is set if necessary, or set the \$$kFlutterEngineEnvironmentVariableName environment variable, or\n'
            'use --engine-src-path to specify the path to the root of your flutter/engine repository.');
208
        throw new ProcessExit(2);
209
      }
210
    }
211

212
    if (engineSourcePath != null && _tryEnginePath(engineSourcePath) == null) {
213
      printError('Unable to detect a Flutter engine build directory in $engineSourcePath.\n'
214 215 216 217 218
          'Please ensure that $engineSourcePath is a Flutter engine \'src\' directory and that\n'
          'you have compiled the engine in that directory, which should produce an \'out\' directory');
      throw new ProcessExit(2);
    }

219 220 221 222 223 224 225 226 227
    return engineSourcePath;
  }

  List<BuildConfiguration> _createBuildConfigurations(ArgResults globalResults) {
    bool isDebug = globalResults['debug'];
    bool isRelease = globalResults['release'];
    HostPlatform hostPlatform = getCurrentHostPlatform();
    TargetPlatform hostPlatformAsTarget = getCurrentHostPlatformAsTarget();

228 229 230
    List<BuildConfiguration> configs = <BuildConfiguration>[];

    if (enginePath == null) {
231
      configs.add(new BuildConfiguration.prebuilt(
232 233
        hostPlatform: hostPlatform,
        targetPlatform: TargetPlatform.android,
234
        deviceId: globalResults['device-id']
235
      ));
236 237 238 239 240 241 242 243

      if (hostPlatform == HostPlatform.linux) {
        configs.add(new BuildConfiguration.prebuilt(
          hostPlatform: HostPlatform.linux,
          targetPlatform: TargetPlatform.linux,
          testable: true
        ));
      }
244 245 246 247

      if (hostPlatform == HostPlatform.mac) {
        configs.add(new BuildConfiguration.prebuilt(
          hostPlatform: HostPlatform.mac,
248 249
          targetPlatform: TargetPlatform.iOS,
          deviceId: globalResults['device-id']
250 251 252 253
        ));

        configs.add(new BuildConfiguration.prebuilt(
          hostPlatform: HostPlatform.mac,
254 255
          targetPlatform: TargetPlatform.iOSSimulator,
          deviceId: globalResults['device-id']
256 257
        ));
      }
258 259
    } else {
      if (!FileSystemEntity.isDirectorySync(enginePath))
260
        printError('$enginePath is not a valid directory');
261

262 263 264 265
      if (!isDebug && !isRelease)
        isDebug = true;

      if (isDebug) {
Ian Hickson's avatar
Ian Hickson committed
266 267 268
        configs.add(new BuildConfiguration.local(
          type: BuildType.debug,
          hostPlatform: hostPlatform,
269
          targetPlatform: TargetPlatform.android,
Ian Hickson's avatar
Ian Hickson committed
270
          enginePath: enginePath,
271
          buildPath: globalResults['android-debug-build-path'],
272
          deviceId: globalResults['device-id']
Ian Hickson's avatar
Ian Hickson committed
273 274
        ));

275 276
        configs.add(new BuildConfiguration.local(
          type: BuildType.debug,
277
          hostPlatform: hostPlatform,
278
          targetPlatform: hostPlatformAsTarget,
279
          enginePath: enginePath,
280 281
          buildPath: globalResults['host-debug-build-path'],
          testable: true
282 283 284 285 286
        ));

        if (Platform.isMacOS) {
          configs.add(new BuildConfiguration.local(
            type: BuildType.debug,
287 288
            hostPlatform: hostPlatform,
            targetPlatform: TargetPlatform.iOS,
289
            enginePath: enginePath,
290 291
            buildPath: globalResults['ios-debug-build-path'],
            deviceId: globalResults['device-id']
292 293 294 295
          ));

          configs.add(new BuildConfiguration.local(
            type: BuildType.debug,
296 297
            hostPlatform: hostPlatform,
            targetPlatform: TargetPlatform.iOSSimulator,
298
            enginePath: enginePath,
299 300
            buildPath: globalResults['ios-sim-debug-build-path'],
            deviceId: globalResults['device-id']
301 302 303 304
          ));
        }
      }

305
      if (isRelease) {
Ian Hickson's avatar
Ian Hickson committed
306 307 308
        configs.add(new BuildConfiguration.local(
          type: BuildType.release,
          hostPlatform: hostPlatform,
309
          targetPlatform: TargetPlatform.android,
Ian Hickson's avatar
Ian Hickson committed
310
          enginePath: enginePath,
311
          buildPath: globalResults['android-release-build-path'],
312
          deviceId: globalResults['device-id']
Ian Hickson's avatar
Ian Hickson committed
313 314
        ));

315 316
        configs.add(new BuildConfiguration.local(
          type: BuildType.release,
317
          hostPlatform: hostPlatform,
318
          targetPlatform: hostPlatformAsTarget,
319
          enginePath: enginePath,
320 321
          buildPath: globalResults['host-release-build-path'],
          testable: true
322 323 324 325 326
        ));

        if (Platform.isMacOS) {
          configs.add(new BuildConfiguration.local(
            type: BuildType.release,
327 328
            hostPlatform: hostPlatform,
            targetPlatform: TargetPlatform.iOS,
329
            enginePath: enginePath,
330 331
            buildPath: globalResults['ios-release-build-path'],
            deviceId: globalResults['device-id']
332 333 334 335
          ));

          configs.add(new BuildConfiguration.local(
            type: BuildType.release,
336 337
            hostPlatform: hostPlatform,
            targetPlatform: TargetPlatform.iOSSimulator,
338
            enginePath: enginePath,
339 340
            buildPath: globalResults['ios-sim-release-build-path'],
            deviceId: globalResults['device-id']
341 342 343 344 345 346 347 348
          ));
        }
      }
    }

    return configs;
  }
}