Clean up code organization in flutter_tools 1) Moved basic utility code into base/ directory to make it clear which code doesn't depend on Flutter-specific knowldge. 2) Move the CommandRunner subclasses into a runner/ directory because these aren't commands themselves.
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart new file mode 100644 index 0000000..6aa76eb --- /dev/null +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -0,0 +1,75 @@ +// 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/command_runner.dart'; + +import '../application_package.dart'; +import '../build_configuration.dart'; +import '../device.dart'; +import '../toolchain.dart'; +import 'flutter_command_runner.dart'; + +abstract class FlutterCommand extends Command { + FlutterCommandRunner get runner => super.runner; + + /// Whether this command needs to be run from the root of a project. + bool get requiresProjectRoot => true; + + String get projectRootValidationErrorMessage { + return 'Error: No pubspec.yaml file found.\n' + 'This command should be run from the root of your Flutter project. Do not run\n' + 'this command from the root of your git clone of Flutter.'; + } + + List<BuildConfiguration> get buildConfigurations => runner.buildConfigurations; + + Future downloadApplicationPackages() async { + if (applicationPackages == null) + applicationPackages = await ApplicationPackageStore.forConfigs(buildConfigurations); + } + + Future downloadToolchain() async { + if (toolchain == null) + toolchain = await Toolchain.forConfigs(buildConfigurations); + } + + void connectToDevices() { + if (devices == null) + devices = new DeviceStore.forConfigs(buildConfigurations); + } + + Future downloadApplicationPackagesAndConnectToDevices() async { + await downloadApplicationPackages(); + connectToDevices(); + } + + void inheritFromParent(FlutterCommand other) { + applicationPackages = other.applicationPackages; + toolchain = other.toolchain; + devices = other.devices; + } + + Future<int> run() async { + if (requiresProjectRoot && !validateProjectRoot()) + return 1; + return await runInProject(); + } + + bool validateProjectRoot() { + if (!FileSystemEntity.isFileSync('pubspec.yaml')) { + stderr.writeln(projectRootValidationErrorMessage); + return false; + } + return true; + } + + Future<int> runInProject(); + + ApplicationPackageStore applicationPackages; + Toolchain toolchain; + DeviceStore devices; +}
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart new file mode 100644 index 0000000..9bcf10d --- /dev/null +++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
@@ -0,0 +1,307 @@ +// 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'; +import 'package:path/path.dart' as path; + +import '../artifacts.dart'; +import '../base/logging.dart'; +import '../base/process.dart'; +import '../build_configuration.dart'; +import 'version.dart'; + +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/ +const String kFlutterToolsScriptFileName = 'flutter_tools.dart'; // in //flutter/packages/flutter_tools/bin/ +const String kFlutterEnginePackageName = 'sky_engine'; + +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.'); + argParser.addFlag('version', + negatable: false, + help: 'Reports the version of this tool.'); + 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)'; + argParser.addOption('package-root', + 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' + 'otherwise defaults to a value derived from the location of this tool.', defaultsTo: _defaultFlutterRoot); + + argParser.addOption('android-device-id', + help: 'Serial number of the target Android device.'); + + argParser.addSeparator('Local build selection options (not normally required):'); + argParser.addFlag('debug', + negatable: false, + help: + '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.'); + argParser.addFlag('release', + negatable: false, + help: + '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.'); + argParser.addOption('engine-src-path', + help: + '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/'); + argParser.addOption('android-debug-build-path', hide: true, + help: + '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.', + defaultsTo: 'out/android_Debug/'); + argParser.addOption('android-release-build-path', hide: true, + help: + '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.', + defaultsTo: 'out/android_Release/'); + argParser.addOption('ios-debug-build-path', hide: true, + help: + '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.', + defaultsTo: 'out/ios_Debug/'); + argParser.addOption('ios-release-build-path', hide: true, + help: + '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.', + defaultsTo: 'out/ios_Release/'); + argParser.addOption('ios-sim-debug-build-path', hide: true, + help: + '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.', + defaultsTo: 'out/ios_sim_Debug/'); + argParser.addOption('ios-sim-release-build-path', hide: true, + help: + '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.', + defaultsTo: 'out/ios_sim_Release/'); + } + + List<BuildConfiguration> get buildConfigurations { + if (_buildConfigurations == null) + _buildConfigurations = _createBuildConfigurations(_globalResults); + return _buildConfigurations; + } + List<BuildConfiguration> _buildConfigurations; + + String get enginePath { + if (!_enginePathSet) { + _enginePath = _findEnginePath(_globalResults); + _enginePathSet = true; + } + return _enginePath; + } + String _enginePath; + bool _enginePathSet = false; + + ArgResults _globalResults; + + String get _defaultFlutterRoot { + if (Platform.environment.containsKey(kFlutterRootEnvironmentVariableName)) + return Platform.environment[kFlutterRootEnvironmentVariableName]; + 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)))); + return '.'; + } + + Future<int> runCommand(ArgResults globalResults) { + if (globalResults['verbose']) + Logger.root.level = Level.INFO; + + if (globalResults['very-verbose']) + Logger.root.level = Level.FINE; + + _globalResults = globalResults; + ArtifactStore.flutterRoot = globalResults['flutter-root']; + if (globalResults.wasParsed('package-root')) + ArtifactStore.packageRoot = globalResults['package-root']; + + if (globalResults['version']) { + print(getVersion(ArtifactStore.flutterRoot)); + return new Future<int>.value(0); + } + + return super.runCommand(globalResults); + } + + String _tryEnginePath(String enginePath) { + if (FileSystemEntity.isDirectorySync(path.join(enginePath, 'out'))) + return enginePath; + return null; + } + + String _findEnginePath(ArgResults globalResults) { + String engineSourcePath = globalResults['engine-src-path'] ?? Platform.environment[kFlutterEngineEnvironmentVariableName]; + bool isDebug = globalResults['debug']; + bool isRelease = globalResults['release']; + + if (engineSourcePath == null && (isDebug || isRelease)) { + if (ArtifactStore.isPackageRootValid) { + Directory engineDir = new Directory(path.join(ArtifactStore.packageRoot, kFlutterEnginePackageName)); + try { + String realEnginePath = engineDir.resolveSymbolicLinksSync(); + 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; + } on FileSystemException { } + } + + if (engineSourcePath == null) + engineSourcePath = _tryEnginePath(path.join(ArtifactStore.flutterRoot, '../engine/src')); + + if (engineSourcePath == null) { + 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.'); + throw new ProcessExit(2); + } + } + + 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); + } + + return engineSourcePath; + } + + List<BuildConfiguration> _createBuildConfigurations(ArgResults globalResults) { + bool isDebug = globalResults['debug']; + bool isRelease = globalResults['release']; + HostPlatform hostPlatform = getCurrentHostPlatform(); + TargetPlatform hostPlatformAsTarget = getCurrentHostPlatformAsTarget(); + + List<BuildConfiguration> configs = <BuildConfiguration>[]; + + if (enginePath == null) { + configs.add(new BuildConfiguration.prebuilt( + hostPlatform: hostPlatform, + targetPlatform: TargetPlatform.android, + deviceId: globalResults['android-device-id'] + )); + } else { + if (!FileSystemEntity.isDirectorySync(enginePath)) + logging.warning('$enginePath is not a valid directory'); + + if (!isDebug && !isRelease) + isDebug = true; + + if (isDebug) { + configs.add(new BuildConfiguration.local( + type: BuildType.debug, + hostPlatform: hostPlatform, + targetPlatform: TargetPlatform.android, + enginePath: enginePath, + buildPath: globalResults['android-debug-build-path'], + deviceId: globalResults['android-device-id'] + )); + + configs.add(new BuildConfiguration.local( + type: BuildType.debug, + hostPlatform: hostPlatform, + targetPlatform: hostPlatformAsTarget, + enginePath: enginePath, + buildPath: globalResults['host-debug-build-path'], + testable: true + )); + + if (Platform.isMacOS) { + configs.add(new BuildConfiguration.local( + type: BuildType.debug, + hostPlatform: hostPlatform, + targetPlatform: TargetPlatform.iOS, + enginePath: enginePath, + buildPath: globalResults['ios-debug-build-path'] + )); + + configs.add(new BuildConfiguration.local( + type: BuildType.debug, + hostPlatform: hostPlatform, + targetPlatform: TargetPlatform.iOSSimulator, + enginePath: enginePath, + buildPath: globalResults['ios-sim-debug-build-path'] + )); + } + } + + if (isRelease) { + configs.add(new BuildConfiguration.local( + type: BuildType.release, + hostPlatform: hostPlatform, + targetPlatform: TargetPlatform.android, + enginePath: enginePath, + buildPath: globalResults['android-release-build-path'], + deviceId: globalResults['android-device-id'] + )); + + configs.add(new BuildConfiguration.local( + type: BuildType.release, + hostPlatform: hostPlatform, + targetPlatform: hostPlatformAsTarget, + enginePath: enginePath, + buildPath: globalResults['host-release-build-path'], + testable: true + )); + + if (Platform.isMacOS) { + configs.add(new BuildConfiguration.local( + type: BuildType.release, + hostPlatform: hostPlatform, + targetPlatform: TargetPlatform.iOS, + enginePath: enginePath, + buildPath: globalResults['ios-release-build-path'] + )); + + configs.add(new BuildConfiguration.local( + type: BuildType.release, + hostPlatform: hostPlatform, + targetPlatform: TargetPlatform.iOSSimulator, + enginePath: enginePath, + buildPath: globalResults['ios-sim-release-build-path'] + )); + } + } + } + + return configs; + } +}
diff --git a/packages/flutter_tools/lib/src/runner/version.dart b/packages/flutter_tools/lib/src/runner/version.dart new file mode 100644 index 0000000..134108f --- /dev/null +++ b/packages/flutter_tools/lib/src/runner/version.dart
@@ -0,0 +1,24 @@ +// 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 '../base/process.dart'; + +String getVersion(String flutterRoot) { + String upstream = runSync([ + 'git', 'rev-parse', '--abbrev-ref', '--symbolic', '@{u}' + ], workingDirectory: flutterRoot).trim(); + String repository = '<unknown>'; + int slash = upstream.indexOf('/'); + if (slash != -1) { + String remote = upstream.substring(0, slash); + repository = runSync([ + 'git', 'ls-remote', '--get-url', remote + ], workingDirectory: flutterRoot).trim(); + upstream = upstream.substring(slash + 1); + } + String revision = runSync([ + 'git', 'log', '-n', '1', '--pretty=format:%H (%ar)' + ], workingDirectory: flutterRoot).trim(); + return 'Flutter\nRepository: $repository\nBranch: $upstream\nRevision: $revision'; +}