flutter_command_runner.dart 14.4 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 '../android/android_sdk.dart';
13
import '../artifacts.dart';
14
import '../base/context.dart';
Devon Carew's avatar
Devon Carew committed
15
import '../base/logger.dart';
16
import '../base/process.dart';
17
import '../build_configuration.dart';
18
import '../globals.dart';
19
import 'version.dart';
20

Ian Hickson's avatar
Ian Hickson committed
21 22 23
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/
24
const String kFlutterToolsScriptFileName = 'flutter_tools.dart'; // in //flutter/packages/flutter_tools/bin/
Ian Hickson's avatar
Ian Hickson committed
25 26
const String kFlutterEnginePackageName = 'sky_engine';

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

Ian Hickson's avatar
Ian Hickson committed
43 44 45 46 47
    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)';
48
    argParser.addOption('package-root',
49
        hide: !verboseHelp,
Ian Hickson's avatar
Ian Hickson committed
50 51
        help: 'Path to your packages directory.$packagesHelp');
    argParser.addOption('flutter-root',
52 53
        help: 'The root directory of the Flutter repository (uses \$$kFlutterRootEnvironmentVariableName if set).',
              defaultsTo: _defaultFlutterRoot);
Ian Hickson's avatar
Ian Hickson committed
54

Devon Carew's avatar
Devon Carew committed
55 56
    if (verboseHelp)
      argParser.addSeparator('Local build selection options (not normally required):');
57 58 59

    argParser.addFlag('debug',
        negatable: false,
Devon Carew's avatar
Devon Carew committed
60
        hide: !verboseHelp,
61
        help:
Ian Hickson's avatar
Ian Hickson committed
62 63
            '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.');
64 65
    argParser.addFlag('release',
        negatable: false,
Devon Carew's avatar
Devon Carew committed
66
        hide: !verboseHelp,
67
        help:
Ian Hickson's avatar
Ian Hickson committed
68 69
            '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
70
    argParser.addOption('engine-src-path',
Devon Carew's avatar
Devon Carew committed
71
        hide: !verboseHelp,
72
        help:
Ian Hickson's avatar
Ian Hickson committed
73 74 75 76
            '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
77 78
    argParser.addOption('host-debug-build-path',
        hide: !verboseHelp,
Ian Hickson's avatar
Ian Hickson committed
79 80 81 82
        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
83 84
    argParser.addOption('host-release-build-path',
        hide: !verboseHelp,
Ian Hickson's avatar
Ian Hickson committed
85 86 87 88
        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
89 90
    argParser.addOption('android-debug-build-path',
        hide: !verboseHelp,
91
        help:
Ian Hickson's avatar
Ian Hickson committed
92 93
            '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.',
94
        defaultsTo: 'out/android_Debug/');
Devon Carew's avatar
Devon Carew committed
95 96
    argParser.addOption('android-release-build-path',
        hide: !verboseHelp,
97
        help:
Ian Hickson's avatar
Ian Hickson committed
98 99
            '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.',
100
        defaultsTo: 'out/android_Release/');
Devon Carew's avatar
Devon Carew committed
101 102
    argParser.addOption('ios-debug-build-path',
        hide: !verboseHelp,
103
        help:
Ian Hickson's avatar
Ian Hickson committed
104 105
            '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.',
106
        defaultsTo: 'out/ios_Debug/');
Devon Carew's avatar
Devon Carew committed
107 108
    argParser.addOption('ios-release-build-path',
        hide: !verboseHelp,
109
        help:
Ian Hickson's avatar
Ian Hickson committed
110 111
            '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.',
112
        defaultsTo: 'out/ios_Release/');
Devon Carew's avatar
Devon Carew committed
113 114
    argParser.addOption('ios-sim-debug-build-path',
        hide: !verboseHelp,
115
        help:
Ian Hickson's avatar
Ian Hickson committed
116 117
            '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.',
118
        defaultsTo: 'out/ios_sim_Debug/');
Devon Carew's avatar
Devon Carew committed
119 120
    argParser.addOption('ios-sim-release-build-path',
        hide: !verboseHelp,
121
        help:
Ian Hickson's avatar
Ian Hickson committed
122 123
            '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.',
124 125 126
        defaultsTo: 'out/ios_sim_Release/');
  }

127
  @override
Hixie's avatar
Hixie committed
128
  String get usageFooter {
129
    return 'Run "flutter -h -v" for verbose help output, including less commonly used options.';
Hixie's avatar
Hixie committed
130
  }
Devon Carew's avatar
Devon Carew committed
131

Adam Barth's avatar
Adam Barth committed
132 133 134 135 136 137 138
  List<BuildConfiguration> get buildConfigurations {
    if (_buildConfigurations == null)
      _buildConfigurations = _createBuildConfigurations(_globalResults);
    return _buildConfigurations;
  }
  List<BuildConfiguration> _buildConfigurations;

139
  String get enginePath {
Hixie's avatar
Hixie committed
140 141
    assert(ArtifactStore.flutterRoot != null);
    _enginePath ??= _findEnginePath(_globalResults);
142 143 144 145
    return _enginePath;
  }
  String _enginePath;

Adam Barth's avatar
Adam Barth committed
146
  ArgResults _globalResults;
147

148
  String get _defaultFlutterRoot {
Ian Hickson's avatar
Ian Hickson committed
149 150
    if (Platform.environment.containsKey(kFlutterRootEnvironmentVariableName))
      return Platform.environment[kFlutterRootEnvironmentVariableName];
Devon Carew's avatar
Devon Carew committed
151
    try {
Hixie's avatar
Hixie committed
152 153
      if (Platform.script.scheme == 'data')
        return '../..'; // we're running as a test
Devon Carew's avatar
Devon Carew committed
154 155 156 157 158 159
      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) {
Hixie's avatar
Hixie committed
160 161 162
      // we don't have a logger at the time this is run
      // (which is why we don't use printTrace here)
      print('Unable to locate flutter root: $error');
Devon Carew's avatar
Devon Carew committed
163
    }
Ian Hickson's avatar
Ian Hickson committed
164 165 166
    return '.';
  }

167
  @override
Devon Carew's avatar
Devon Carew committed
168 169 170 171 172 173 174
  Future<dynamic> run(Iterable<String> args) {
    return super.run(args).then((dynamic result) {
      logger.flush();
      return result;
    });
  }

175
  @override
176
  Future<int> runCommand(ArgResults globalResults) {
177 178 179
    _globalResults = globalResults;

    // Check for verbose.
180
    if (globalResults['verbose'])
Devon Carew's avatar
Devon Carew committed
181
      context[Logger] = new VerboseLogger();
182

Hixie's avatar
Hixie committed
183 184 185 186 187 188
    // we must set ArtifactStore.flutterRoot early because other features use it
    // (e.g. enginePath's initialiser uses it)
    ArtifactStore.flutterRoot = path.normalize(path.absolute(globalResults['flutter-root']));
    if (globalResults.wasParsed('package-root'))
      ArtifactStore.packageRoot = path.normalize(path.absolute(globalResults['package-root']));

189 190
    // See if the user specified a specific device.
    deviceManager.specifiedDeviceId = globalResults['device-id'];
191

192 193 194 195 196 197 198 199 200
    // The Android SDK could already have been set by tests.
    if (!context.isSet(AndroidSdk)) {
      if (enginePath != null) {
        context[AndroidSdk] = new AndroidSdk('$enginePath/third_party/android_tools/sdk');
      } else {
        context[AndroidSdk] = AndroidSdk.locateAndroidSdk();
      }
    }

201
    if (globalResults['version']) {
202
      printStatus(FlutterVersion.getVersion(ArtifactStore.flutterRoot).toString());
203
      return new Future<int>.value(0);
204 205
    }

206
    return super.runCommand(globalResults);
Adam Barth's avatar
Adam Barth committed
207 208
  }

209 210 211 212 213 214
  String _tryEnginePath(String enginePath) {
    if (FileSystemEntity.isDirectorySync(path.join(enginePath, 'out')))
      return enginePath;
    return null;
  }

215 216
  String _findEnginePath(ArgResults globalResults) {
    String engineSourcePath = globalResults['engine-src-path'] ?? Platform.environment[kFlutterEngineEnvironmentVariableName];
217 218 219
    bool isDebug = globalResults['debug'];
    bool isRelease = globalResults['release'];

220
    if (engineSourcePath == null && (isDebug || isRelease)) {
Ian Hickson's avatar
Ian Hickson committed
221 222 223 224
      if (ArtifactStore.isPackageRootValid) {
        Directory engineDir = new Directory(path.join(ArtifactStore.packageRoot, kFlutterEnginePackageName));
        try {
          String realEnginePath = engineDir.resolveSymbolicLinksSync();
225 226 227 228
          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
229 230
        } on FileSystemException { }
      }
231 232 233 234

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

235
      if (engineSourcePath == null) {
236
        printError('Unable to detect local Flutter engine build directory.\n'
Ian Hickson's avatar
Ian Hickson committed
237 238 239
            '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.');
240
        throw new ProcessExit(2);
241
      }
242
    }
243

244
    if (engineSourcePath != null && _tryEnginePath(engineSourcePath) == null) {
245
      printError('Unable to detect a Flutter engine build directory in $engineSourcePath.\n'
246 247 248 249 250
          '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);
    }

251 252 253 254 255 256 257 258 259
    return engineSourcePath;
  }

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

260 261 262
    List<BuildConfiguration> configs = <BuildConfiguration>[];

    if (enginePath == null) {
263
      configs.add(new BuildConfiguration.prebuilt(
264
        hostPlatform: hostPlatform,
265
        targetPlatform: TargetPlatform.android_arm
266
      ));
267 268 269 270

      if (hostPlatform == HostPlatform.linux) {
        configs.add(new BuildConfiguration.prebuilt(
          hostPlatform: HostPlatform.linux,
271
          targetPlatform: TargetPlatform.linux_x64,
272 273 274
          testable: true
        ));
      }
275 276 277 278

      if (hostPlatform == HostPlatform.mac) {
        configs.add(new BuildConfiguration.prebuilt(
          hostPlatform: HostPlatform.mac,
279
          targetPlatform: TargetPlatform.ios
280 281
        ));
      }
282 283
    } else {
      if (!FileSystemEntity.isDirectorySync(enginePath))
284
        printError('$enginePath is not a valid directory');
285

286 287 288 289
      if (!isDebug && !isRelease)
        isDebug = true;

      if (isDebug) {
Ian Hickson's avatar
Ian Hickson committed
290 291 292
        configs.add(new BuildConfiguration.local(
          type: BuildType.debug,
          hostPlatform: hostPlatform,
293
          targetPlatform: TargetPlatform.android_arm,
Ian Hickson's avatar
Ian Hickson committed
294
          enginePath: enginePath,
295
          buildPath: globalResults['android-debug-build-path']
Ian Hickson's avatar
Ian Hickson committed
296 297
        ));

298 299
        configs.add(new BuildConfiguration.local(
          type: BuildType.debug,
300
          hostPlatform: hostPlatform,
301
          targetPlatform: hostPlatformAsTarget,
302
          enginePath: enginePath,
303 304
          buildPath: globalResults['host-debug-build-path'],
          testable: true
305 306 307 308 309
        ));

        if (Platform.isMacOS) {
          configs.add(new BuildConfiguration.local(
            type: BuildType.debug,
310
            hostPlatform: hostPlatform,
311
            targetPlatform: TargetPlatform.ios,
312
            enginePath: enginePath,
313
            buildPath: globalResults['ios-debug-build-path']
314 315 316 317
          ));

          configs.add(new BuildConfiguration.local(
            type: BuildType.debug,
318
            hostPlatform: hostPlatform,
319
            targetPlatform: TargetPlatform.ios,
320
            enginePath: enginePath,
321
            buildPath: globalResults['ios-sim-debug-build-path']
322 323 324 325
          ));
        }
      }

326
      if (isRelease) {
Ian Hickson's avatar
Ian Hickson committed
327 328 329
        configs.add(new BuildConfiguration.local(
          type: BuildType.release,
          hostPlatform: hostPlatform,
330
          targetPlatform: TargetPlatform.android_arm,
Ian Hickson's avatar
Ian Hickson committed
331
          enginePath: enginePath,
332
          buildPath: globalResults['android-release-build-path']
Ian Hickson's avatar
Ian Hickson committed
333 334
        ));

335 336
        configs.add(new BuildConfiguration.local(
          type: BuildType.release,
337
          hostPlatform: hostPlatform,
338
          targetPlatform: hostPlatformAsTarget,
339
          enginePath: enginePath,
340 341
          buildPath: globalResults['host-release-build-path'],
          testable: true
342 343 344 345 346
        ));

        if (Platform.isMacOS) {
          configs.add(new BuildConfiguration.local(
            type: BuildType.release,
347
            hostPlatform: hostPlatform,
348
            targetPlatform: TargetPlatform.ios,
349
            enginePath: enginePath,
350
            buildPath: globalResults['ios-release-build-path']
351 352 353 354
          ));

          configs.add(new BuildConfiguration.local(
            type: BuildType.release,
355
            hostPlatform: hostPlatform,
356
            targetPlatform: TargetPlatform.ios,
357
            enginePath: enginePath,
358
            buildPath: globalResults['ios-sim-release-build-path']
359 360 361 362 363 364 365 366
          ));
        }
      }
    }

    return configs;
  }
}