flutter_command_runner.dart 13.2 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;
Ian Hickson's avatar
Ian Hickson committed
150 151 152
    ArtifactStore.flutterRoot = globalResults['flutter-root'];
    if (globalResults.wasParsed('package-root'))
      ArtifactStore.packageRoot = 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
    } else {
      if (!FileSystemEntity.isDirectorySync(enginePath))
231
        logging.warning('$enginePath is not a valid directory');
232

233 234 235 236
      if (!isDebug && !isRelease)
        isDebug = true;

      if (isDebug) {
Ian Hickson's avatar
Ian Hickson committed
237 238 239
        configs.add(new BuildConfiguration.local(
          type: BuildType.debug,
          hostPlatform: hostPlatform,
240
          targetPlatform: TargetPlatform.android,
Ian Hickson's avatar
Ian Hickson committed
241
          enginePath: enginePath,
242 243
          buildPath: globalResults['android-debug-build-path'],
          deviceId: globalResults['android-device-id']
Ian Hickson's avatar
Ian Hickson committed
244 245
        ));

246 247
        configs.add(new BuildConfiguration.local(
          type: BuildType.debug,
248
          hostPlatform: hostPlatform,
249
          targetPlatform: hostPlatformAsTarget,
250
          enginePath: enginePath,
251 252
          buildPath: globalResults['host-debug-build-path'],
          testable: true
253 254 255 256 257
        ));

        if (Platform.isMacOS) {
          configs.add(new BuildConfiguration.local(
            type: BuildType.debug,
258 259
            hostPlatform: hostPlatform,
            targetPlatform: TargetPlatform.iOS,
260 261 262 263 264 265
            enginePath: enginePath,
            buildPath: globalResults['ios-debug-build-path']
          ));

          configs.add(new BuildConfiguration.local(
            type: BuildType.debug,
266 267
            hostPlatform: hostPlatform,
            targetPlatform: TargetPlatform.iOSSimulator,
268 269 270 271 272 273
            enginePath: enginePath,
            buildPath: globalResults['ios-sim-debug-build-path']
          ));
        }
      }

274
      if (isRelease) {
Ian Hickson's avatar
Ian Hickson committed
275 276 277
        configs.add(new BuildConfiguration.local(
          type: BuildType.release,
          hostPlatform: hostPlatform,
278
          targetPlatform: TargetPlatform.android,
Ian Hickson's avatar
Ian Hickson committed
279
          enginePath: enginePath,
280 281
          buildPath: globalResults['android-release-build-path'],
          deviceId: globalResults['android-device-id']
Ian Hickson's avatar
Ian Hickson committed
282 283
        ));

284 285
        configs.add(new BuildConfiguration.local(
          type: BuildType.release,
286
          hostPlatform: hostPlatform,
287
          targetPlatform: hostPlatformAsTarget,
288
          enginePath: enginePath,
289 290
          buildPath: globalResults['host-release-build-path'],
          testable: true
291 292 293 294 295
        ));

        if (Platform.isMacOS) {
          configs.add(new BuildConfiguration.local(
            type: BuildType.release,
296 297
            hostPlatform: hostPlatform,
            targetPlatform: TargetPlatform.iOS,
298 299 300 301 302 303
            enginePath: enginePath,
            buildPath: globalResults['ios-release-build-path']
          ));

          configs.add(new BuildConfiguration.local(
            type: BuildType.release,
304 305
            hostPlatform: hostPlatform,
            targetPlatform: TargetPlatform.iOSSimulator,
306 307 308 309 310 311 312 313 314 315
            enginePath: enginePath,
            buildPath: globalResults['ios-sim-release-build-path']
          ));
        }
      }
    }

    return configs;
  }
}