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

import '../artifacts.dart';
14 15
import '../base/logging.dart';
import '../base/process.dart';
16
import '../build_configuration.dart';
17
import 'version.dart';
18

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

25 26 27 28 29 30 31 32 33 34 35
class FlutterCommandRunner extends CommandRunner {
  FlutterCommandRunner()
      : super('flutter', 'Manage your Flutter app development.') {
    argParser.addFlag('verbose',
        abbr: 'v',
        negatable: false,
        help: 'Noisy logging, including all shell commands executed.');
    argParser.addFlag('very-verbose',
        negatable: false,
        help: 'Very noisy logging, including the output of all '
            'shell commands executed.');
36 37 38
    argParser.addFlag('version',
        negatable: false,
        help: 'Reports the version of this tool.');
Ian Hickson's avatar
Ian Hickson committed
39 40 41 42 43
    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)';
44
    argParser.addOption('package-root',
Ian Hickson's avatar
Ian Hickson committed
45 46 47
        help: 'Path to your packages directory.$packagesHelp');
    argParser.addOption('flutter-root',
        help: 'The root directory of the Flutter repository. Defaults to \$$kFlutterRootEnvironmentVariableName if set,\n'
48
              'otherwise defaults to a value derived from the location of this tool.', defaultsTo: _defaultFlutterRoot);
Ian Hickson's avatar
Ian Hickson committed
49

50 51
    argParser.addOption('android-device-id',
        help: 'Serial number of the target Android device.');
52

Ian Hickson's avatar
Ian Hickson committed
53
    argParser.addSeparator('Local build selection options (not normally required):');
54 55 56
    argParser.addFlag('debug',
        negatable: false,
        help:
Ian Hickson's avatar
Ian Hickson committed
57 58
            '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.');
59 60 61
    argParser.addFlag('release',
        negatable: false,
        help:
Ian Hickson's avatar
Ian Hickson committed
62 63
            '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
64
    argParser.addOption('engine-src-path',
65
        help:
Ian Hickson's avatar
Ian Hickson committed
66 67 68 69 70 71 72 73 74 75 76 77 78 79
            '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.');
    argParser.addOption('host-debug-build-path', hide: true,
        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/');
    argParser.addOption('host-release-build-path', hide: true,
        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/');
80
    argParser.addOption('android-debug-build-path', hide: true,
81
        help:
Ian Hickson's avatar
Ian Hickson committed
82 83
            '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.',
84
        defaultsTo: 'out/android_Debug/');
85
    argParser.addOption('android-release-build-path', hide: true,
86
        help:
Ian Hickson's avatar
Ian Hickson committed
87 88
            '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.',
89
        defaultsTo: 'out/android_Release/');
90
    argParser.addOption('ios-debug-build-path', hide: true,
91
        help:
Ian Hickson's avatar
Ian Hickson committed
92 93
            '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.',
94
        defaultsTo: 'out/ios_Debug/');
95
    argParser.addOption('ios-release-build-path', hide: true,
96
        help:
Ian Hickson's avatar
Ian Hickson committed
97 98
            '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.',
99
        defaultsTo: 'out/ios_Release/');
100
    argParser.addOption('ios-sim-debug-build-path', hide: true,
101
        help:
Ian Hickson's avatar
Ian Hickson committed
102 103
            '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.',
104
        defaultsTo: 'out/ios_sim_Debug/');
105
    argParser.addOption('ios-sim-release-build-path', hide: true,
106
        help:
Ian Hickson's avatar
Ian Hickson committed
107 108
            '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.',
109 110 111
        defaultsTo: 'out/ios_sim_Release/');
  }

Adam Barth's avatar
Adam Barth committed
112 113 114 115 116 117 118
  List<BuildConfiguration> get buildConfigurations {
    if (_buildConfigurations == null)
      _buildConfigurations = _createBuildConfigurations(_globalResults);
    return _buildConfigurations;
  }
  List<BuildConfiguration> _buildConfigurations;

119 120 121 122 123 124 125 126 127 128
  String get enginePath {
    if (!_enginePathSet) {
      _enginePath = _findEnginePath(_globalResults);
      _enginePathSet = true;
    }
    return _enginePath;
  }
  String _enginePath;
  bool _enginePathSet = false;

Adam Barth's avatar
Adam Barth committed
129
  ArgResults _globalResults;
130

131
  String get _defaultFlutterRoot {
Ian Hickson's avatar
Ian Hickson committed
132 133
    if (Platform.environment.containsKey(kFlutterRootEnvironmentVariableName))
      return Platform.environment[kFlutterRootEnvironmentVariableName];
134
    String script = Platform.script.toFilePath();
Ian Hickson's avatar
Ian Hickson committed
135 136 137 138 139 140 141
    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))));
    return '.';
  }

142
  Future<int> runCommand(ArgResults globalResults) {
143 144 145 146 147 148
    if (globalResults['verbose'])
      Logger.root.level = Level.INFO;

    if (globalResults['very-verbose'])
      Logger.root.level = Level.FINE;

Adam Barth's avatar
Adam Barth committed
149
    _globalResults = globalResults;
150
    ArtifactStore.flutterRoot = path.normalize(path.absolute(globalResults['flutter-root']));
Ian Hickson's avatar
Ian Hickson committed
151
    if (globalResults.wasParsed('package-root'))
152
      ArtifactStore.packageRoot = path.normalize(path.absolute(globalResults['package-root']));
153

154 155 156
    if (globalResults['version']) {
      print(getVersion(ArtifactStore.flutterRoot));
      return new Future<int>.value(0);
157 158
    }

159
    return super.runCommand(globalResults);
Adam Barth's avatar
Adam Barth committed
160 161
  }

162 163 164 165 166 167
  String _tryEnginePath(String enginePath) {
    if (FileSystemEntity.isDirectorySync(path.join(enginePath, 'out')))
      return enginePath;
    return null;
  }

168 169
  String _findEnginePath(ArgResults globalResults) {
    String engineSourcePath = globalResults['engine-src-path'] ?? Platform.environment[kFlutterEngineEnvironmentVariableName];
170 171 172
    bool isDebug = globalResults['debug'];
    bool isRelease = globalResults['release'];

173
    if (engineSourcePath == null && (isDebug || isRelease)) {
Ian Hickson's avatar
Ian Hickson committed
174 175 176 177
      if (ArtifactStore.isPackageRootValid) {
        Directory engineDir = new Directory(path.join(ArtifactStore.packageRoot, kFlutterEnginePackageName));
        try {
          String realEnginePath = engineDir.resolveSymbolicLinksSync();
178 179 180 181
          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
182 183
        } on FileSystemException { }
      }
184 185 186 187

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

188
      if (engineSourcePath == null) {
Ian Hickson's avatar
Ian Hickson committed
189 190 191 192
        stderr.writeln('Unable to detect local Flutter engine build directory.\n'
            '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.');
193
        throw new ProcessExit(2);
194
      }
195
    }
196

197 198 199 200 201 202 203
    if (engineSourcePath != null && _tryEnginePath(engineSourcePath) == null) {
      stderr.writeln('Unable to detect a Flutter engine build directory in $engineSourcePath.\n'
          '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);
    }

204 205 206 207 208 209 210 211 212
    return engineSourcePath;
  }

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

213 214 215
    List<BuildConfiguration> configs = <BuildConfiguration>[];

    if (enginePath == null) {
216
      configs.add(new BuildConfiguration.prebuilt(
217 218 219 220
        hostPlatform: hostPlatform,
        targetPlatform: TargetPlatform.android,
        deviceId: globalResults['android-device-id']
      ));
221 222 223 224 225 226 227 228

      if (hostPlatform == HostPlatform.linux) {
        configs.add(new BuildConfiguration.prebuilt(
          hostPlatform: HostPlatform.linux,
          targetPlatform: TargetPlatform.linux,
          testable: true
        ));
      }
229 230 231 232 233 234 235 236 237 238 239 240

      if (hostPlatform == HostPlatform.mac) {
        configs.add(new BuildConfiguration.prebuilt(
          hostPlatform: HostPlatform.mac,
          targetPlatform: TargetPlatform.iOS
        ));

        configs.add(new BuildConfiguration.prebuilt(
          hostPlatform: HostPlatform.mac,
          targetPlatform: TargetPlatform.iOSSimulator
        ));
      }
241 242
    } else {
      if (!FileSystemEntity.isDirectorySync(enginePath))
243
        logging.warning('$enginePath is not a valid directory');
244

245 246 247 248
      if (!isDebug && !isRelease)
        isDebug = true;

      if (isDebug) {
Ian Hickson's avatar
Ian Hickson committed
249 250 251
        configs.add(new BuildConfiguration.local(
          type: BuildType.debug,
          hostPlatform: hostPlatform,
252
          targetPlatform: TargetPlatform.android,
Ian Hickson's avatar
Ian Hickson committed
253
          enginePath: enginePath,
254 255
          buildPath: globalResults['android-debug-build-path'],
          deviceId: globalResults['android-device-id']
Ian Hickson's avatar
Ian Hickson committed
256 257
        ));

258 259
        configs.add(new BuildConfiguration.local(
          type: BuildType.debug,
260
          hostPlatform: hostPlatform,
261
          targetPlatform: hostPlatformAsTarget,
262
          enginePath: enginePath,
263 264
          buildPath: globalResults['host-debug-build-path'],
          testable: true
265 266 267 268 269
        ));

        if (Platform.isMacOS) {
          configs.add(new BuildConfiguration.local(
            type: BuildType.debug,
270 271
            hostPlatform: hostPlatform,
            targetPlatform: TargetPlatform.iOS,
272 273 274 275 276 277
            enginePath: enginePath,
            buildPath: globalResults['ios-debug-build-path']
          ));

          configs.add(new BuildConfiguration.local(
            type: BuildType.debug,
278 279
            hostPlatform: hostPlatform,
            targetPlatform: TargetPlatform.iOSSimulator,
280 281 282 283 284 285
            enginePath: enginePath,
            buildPath: globalResults['ios-sim-debug-build-path']
          ));
        }
      }

286
      if (isRelease) {
Ian Hickson's avatar
Ian Hickson committed
287 288 289
        configs.add(new BuildConfiguration.local(
          type: BuildType.release,
          hostPlatform: hostPlatform,
290
          targetPlatform: TargetPlatform.android,
Ian Hickson's avatar
Ian Hickson committed
291
          enginePath: enginePath,
292 293
          buildPath: globalResults['android-release-build-path'],
          deviceId: globalResults['android-device-id']
Ian Hickson's avatar
Ian Hickson committed
294 295
        ));

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

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

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

    return configs;
  }
}