diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart index a540b02aad..2db94d8b48 100644 --- a/packages/flutter_tools/lib/executable.dart +++ b/packages/flutter_tools/lib/executable.dart @@ -12,7 +12,6 @@ import 'src/base/context.dart'; import 'src/base/logger.dart'; import 'src/base/process.dart'; import 'src/commands/analyze.dart'; -import 'src/commands/apk.dart'; import 'src/commands/build.dart'; import 'src/commands/create.dart'; import 'src/commands/daemon.dart'; @@ -50,7 +49,6 @@ Future main(List args) async { FlutterCommandRunner runner = new FlutterCommandRunner(verboseHelp: verboseHelp) ..addCommand(new AnalyzeCommand()) - ..addCommand(new ApkCommand()) ..addCommand(new BuildCommand()) ..addCommand(new CreateCommand()) ..addCommand(new DaemonCommand(hidden: !verboseHelp)) diff --git a/packages/flutter_tools/lib/src/commands/build.dart b/packages/flutter_tools/lib/src/commands/build.dart index 54a54478c9..c3c78bc6a2 100644 --- a/packages/flutter_tools/lib/src/commands/build.dart +++ b/packages/flutter_tools/lib/src/commands/build.dart @@ -1,77 +1,53 @@ -// Copyright 2015 The Chromium Authors. All rights reserved. +// Copyright 2016 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 '../flx.dart'; -import '../dart/pub.dart'; import '../globals.dart'; import '../runner/flutter_command.dart'; -import '../toolchain.dart'; +import 'build_apk.dart'; +import 'build_flx.dart'; class BuildCommand extends FlutterCommand { + BuildCommand() { + addSubcommand(new BuildApkCommand()); + addSubcommand(new BuildCleanCommand()); + addSubcommand(new BuildFlxCommand()); + } + @override final String name = 'build'; @override - final String description = 'Package your Flutter app into an FLX.'; - - BuildCommand() { - argParser.addFlag('precompiled', negatable: false); - // This option is still referenced by the iOS build scripts. We should - // remove it once we've updated those build scripts. - argParser.addOption('asset-base', help: 'Ignored. Will be removed.', hide: true); - argParser.addOption('compiler'); - argParser.addOption('manifest', defaultsTo: defaultManifestPath); - argParser.addOption('private-key', defaultsTo: defaultPrivateKeyPath); - argParser.addOption('output-file', abbr: 'o', defaultsTo: defaultFlxOutputPath); - argParser.addOption('snapshot', defaultsTo: defaultSnapshotPath); - argParser.addOption('depfile', defaultsTo: defaultDepfilePath); - argParser.addOption('working-dir', defaultsTo: defaultWorkingDirPath); - argParser.addFlag('pub', - defaultsTo: true, - help: 'Whether to run "pub get" before building the app.'); - addTargetOption(); - } + final String description = 'Flutter build commands.'; @override - Future run() async { - if (argResults['pub']) { - int exitCode = await pubGet(); - if (exitCode != 0) - return exitCode; - } - return await super.run(); - } + Future runInProject() => new Future.value(0); +} + +class BuildCleanCommand extends FlutterCommand { + @override + final String name = 'clean'; + + @override + final String description = 'Delete the build/ directory.'; @override Future runInProject() async { - String compilerPath = argResults['compiler']; + Directory buildDir = new Directory('build'); + printStatus("Deleting '${buildDir.path}${Platform.pathSeparator}'."); - if (compilerPath == null) - await downloadToolchain(); - else - toolchain = new Toolchain(compiler: new Compiler(compilerPath)); + if (!buildDir.existsSync()) + return 0; - String outputPath = argResults['output-file']; - - return await build( - toolchain, - mainPath: argResults['target'], - manifestPath: argResults['manifest'], - outputPath: outputPath, - snapshotPath: argResults['snapshot'], - depfilePath: argResults['depfile'], - privateKeyPath: argResults['private-key'], - workingDirPath: argResults['working-dir'], - precompiledSnapshot: argResults['precompiled'] - ).then((int result) { - if (result == 0) - printStatus('Built $outputPath.'); - else - printError('Error building $outputPath: $result.'); - return result; - }); + try { + buildDir.deleteSync(recursive: true); + return 0; + } catch (error) { + printError(error.toString()); + return 1; + } } } diff --git a/packages/flutter_tools/lib/src/commands/apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart similarity index 95% rename from packages/flutter_tools/lib/src/commands/apk.dart rename to packages/flutter_tools/lib/src/commands/build_apk.dart index 6b13c7ec15..2619888620 100644 --- a/packages/flutter_tools/lib/src/commands/apk.dart +++ b/packages/flutter_tools/lib/src/commands/build_apk.dart @@ -127,22 +127,19 @@ class _ApkComponents { } class ApkKeystoreInfo { - ApkKeystoreInfo({ this.keystore, this.password, this.keyAlias, this.keyPassword }); + ApkKeystoreInfo({ this.keystore, this.password, this.keyAlias, this.keyPassword }) { + assert(keystore != null); + } - String keystore; - String password; - String keyAlias; - String keyPassword; + final String keystore; + final String password; + final String keyAlias; + final String keyPassword; } -class ApkCommand extends FlutterCommand { - @override - final String name = 'apk'; - - @override - final String description = 'Build an Android APK package.'; - - ApkCommand() { +class BuildApkCommand extends FlutterCommand { + BuildApkCommand() { + usesTargetOption(); argParser.addOption('manifest', abbr: 'm', defaultsTo: _kDefaultAndroidManifestPath, @@ -157,23 +154,24 @@ class ApkCommand extends FlutterCommand { help: 'Output APK file.'); argParser.addOption('flx', abbr: 'f', - defaultsTo: '', help: 'Path to the FLX file. If this is not provided, an FLX will be built.'); argParser.addOption('keystore', - defaultsTo: '', help: 'Path to the keystore used to sign the app.'); argParser.addOption('keystore-password', - defaultsTo: '', help: 'Password used to access the keystore.'); argParser.addOption('keystore-key-alias', - defaultsTo: '', help: 'Alias of the entry within the keystore.'); argParser.addOption('keystore-key-password', - defaultsTo: '', help: 'Password for the entry within the keystore.'); - addTargetOption(); + usesPubOption(); } + @override + final String name = 'apk'; + + @override + final String description = 'Build an Android APK file from your app.'; + @override Future runInProject() async { // Validate that we can find an android sdk. @@ -199,7 +197,7 @@ class ApkCommand extends FlutterCommand { outputFile: argResults['output-file'], target: argResults['target'], flxPath: argResults['flx'], - keystore: argResults['keystore'].isEmpty ? null : new ApkKeystoreInfo( + keystore: (argResults['keystore'] ?? '').isEmpty ? null : new ApkKeystoreInfo( keystore: argResults['keystore'], password: argResults['keystore-password'], keyAlias: argResults['keystore-key-alias'], @@ -324,13 +322,13 @@ int _signApk( keyPassword = _kDebugKeystorePassword; } else { keystore = new File(keystoreInfo.keystore); - keystorePassword = keystoreInfo.password; - keyAlias = keystoreInfo.keyAlias; + keystorePassword = keystoreInfo.password ?? ''; + keyAlias = keystoreInfo.keyAlias ?? ''; if (keystorePassword.isEmpty || keyAlias.isEmpty) { printError('Must provide a keystore password and a key alias.'); return 1; } - keyPassword = keystoreInfo.keyPassword; + keyPassword = keystoreInfo.keyPassword ?? ''; if (keyPassword.isEmpty) keyPassword = keystorePassword; } @@ -375,7 +373,7 @@ Future buildAndroid({ String resources: _kDefaultResourcesPath, String outputFile: _kDefaultOutputPath, String target: '', - String flxPath: '', + String flxPath, ApkKeystoreInfo keystore }) async { // Validate that we can find an android sdk. @@ -405,7 +403,7 @@ Future buildAndroid({ printStatus('Building APK...'); - if (flxPath.isNotEmpty) { + if (flxPath != null && flxPath.isNotEmpty) { if (!FileSystemEntity.isFileSync(flxPath)) { printError('FLX does not exist: $flxPath'); printError('(Omit the --flx option to build the FLX automatically)'); diff --git a/packages/flutter_tools/lib/src/commands/build_flx.dart b/packages/flutter_tools/lib/src/commands/build_flx.dart new file mode 100644 index 0000000000..2cfb50ec4a --- /dev/null +++ b/packages/flutter_tools/lib/src/commands/build_flx.dart @@ -0,0 +1,68 @@ +// 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 '../flx.dart'; +import '../globals.dart'; +import '../runner/flutter_command.dart'; +import '../toolchain.dart'; + +class BuildFlxCommand extends FlutterCommand { + BuildFlxCommand() { + usesTargetOption(); + argParser.addFlag('precompiled', negatable: false); + // This option is still referenced by the iOS build scripts. We should + // remove it once we've updated those build scripts. + argParser.addOption('asset-base', help: 'Ignored. Will be removed.', hide: true); + argParser.addOption('compiler'); + argParser.addOption('manifest', defaultsTo: defaultManifestPath); + argParser.addOption('private-key', defaultsTo: defaultPrivateKeyPath); + argParser.addOption('output-file', abbr: 'o', defaultsTo: defaultFlxOutputPath); + argParser.addOption('snapshot', defaultsTo: defaultSnapshotPath); + argParser.addOption('depfile', defaultsTo: defaultDepfilePath); + argParser.addOption('working-dir', defaultsTo: defaultWorkingDirPath); + usesPubOption(); + } + + @override + final String name = 'flx'; + + @override + final String description = 'Build a Flutter FLX file from your app.'; + + @override + final String usageFooter = 'FLX files are archives of your application code and resources; ' + 'they are used by some Flutter Android and iOS runtimes.'; + + @override + Future runInProject() async { + String compilerPath = argResults['compiler']; + + if (compilerPath == null) + await downloadToolchain(); + else + toolchain = new Toolchain(compiler: new Compiler(compilerPath)); + + String outputPath = argResults['output-file']; + + return await build( + toolchain, + mainPath: argResults['target'], + manifestPath: argResults['manifest'], + outputPath: outputPath, + snapshotPath: argResults['snapshot'], + depfilePath: argResults['depfile'], + privateKeyPath: argResults['private-key'], + workingDirPath: argResults['working-dir'], + precompiledSnapshot: argResults['precompiled'] + ).then((int result) { + if (result == 0) + printStatus('Built $outputPath.'); + else + printError('Error building $outputPath: $result.'); + return result; + }); + } +} diff --git a/packages/flutter_tools/lib/src/commands/drive.dart b/packages/flutter_tools/lib/src/commands/drive.dart index 72e4e700b5..7e94ca518b 100644 --- a/packages/flutter_tools/lib/src/commands/drive.dart +++ b/packages/flutter_tools/lib/src/commands/drive.dart @@ -15,7 +15,7 @@ import '../base/os.dart'; import '../device.dart'; import '../globals.dart'; import '../ios/simulators.dart' show SimControl, IOSSimulatorUtils; -import 'apk.dart' as apk; +import 'build_apk.dart' as build_apk; import 'run.dart'; /// Runs integration (a.k.a. end-to-end) tests. @@ -244,7 +244,7 @@ Future startApp(DriveCommand command) async { if (command.device is AndroidDevice) { printTrace('Building an APK.'); - int result = await apk.build(command.toolchain, command.buildConfigurations, + int result = await build_apk.build(command.toolchain, command.buildConfigurations, enginePath: command.runner.enginePath, target: command.target); if (result != 0) diff --git a/packages/flutter_tools/lib/src/commands/refresh.dart b/packages/flutter_tools/lib/src/commands/refresh.dart index 1c4b54bb28..1849900f14 100644 --- a/packages/flutter_tools/lib/src/commands/refresh.dart +++ b/packages/flutter_tools/lib/src/commands/refresh.dart @@ -19,7 +19,7 @@ class RefreshCommand extends FlutterCommand { final String description = 'Build and deploy the Dart code in a Flutter app (Android only).'; RefreshCommand() { - addTargetOption(); + usesTargetOption(); } @override diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index eefe53566a..f7c53959dc 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -15,7 +15,7 @@ import '../device.dart'; import '../globals.dart'; import '../runner/flutter_command.dart'; import '../toolchain.dart'; -import 'apk.dart'; +import 'build_apk.dart'; import 'install.dart'; /// Given the value of the --target option, return the path of the Dart file @@ -43,7 +43,7 @@ abstract class RunCommandBase extends FlutterCommand { help: 'Start tracing during startup.'); argParser.addOption('route', help: 'Which route to load when starting the app.'); - addTargetOption(); + usesTargetOption(); } bool get checked => argResults['checked']; @@ -73,12 +73,10 @@ class RunCommand extends RunCommandBase { defaultsTo: false, negatable: false, help: 'Start in a paused mode and wait for a debugger to connect.'); - argParser.addFlag('pub', - defaultsTo: true, - help: 'Whether to run "pub get" before running the app.'); argParser.addOption('debug-port', defaultsTo: observatoryDefaultPort.toString(), help: 'Listen to the given port for a debug connection.'); + usesPubOption(); } @override diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index d20c7b2d0b..522563c0f3 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -7,6 +7,7 @@ import 'dart:io'; import 'package:args/command_runner.dart'; +import '../dart/pub.dart'; import '../application_package.dart'; import '../build_configuration.dart'; import '../device.dart'; @@ -18,6 +19,10 @@ import 'flutter_command_runner.dart'; typedef bool Validator(); abstract class FlutterCommand extends Command { + FlutterCommand() { + commandValidator = _commandValidator; + } + @override FlutterCommandRunner get runner => super.runner; @@ -30,12 +35,28 @@ abstract class FlutterCommand extends Command { /// Whether this command only applies to Android devices. bool get androidOnly => false; - /// Whether this command allows usage of the 'target' option. - bool get allowsTarget => _targetOptionSpecified; - bool _targetOptionSpecified = false; + /// Whether this command uses the 'target' option. + bool _usesTargetOption = false; + + bool _usesPubOption = false; List get buildConfigurations => runner.buildConfigurations; + void usesTargetOption() { + argParser.addOption('target', + abbr: 't', + defaultsTo: flx.defaultMainPath, + help: 'Target app path / main entry-point file.'); + _usesTargetOption = true; + } + + void usesPubOption() { + argParser.addFlag('pub', + defaultsTo: true, + help: 'Whether to run "pub get" before executing this command.'); + _usesPubOption = true; + } + Future downloadToolchain() async { toolchain ??= await Toolchain.forConfigs(buildConfigurations); } @@ -56,8 +77,7 @@ abstract class FlutterCommand extends Command { } Future _run() async { - bool _checkRoot = requiresProjectRoot && allowsTarget && !_targetSpecified; - if (_checkRoot && !projectRootValidator()) + if (requiresProjectRoot && !commandValidator()) return 1; // Ensure at least one toolchain is installed. @@ -99,19 +119,36 @@ abstract class FlutterCommand extends Command { } } + if (_usesPubOption && argResults['pub']) { + int exitCode = await pubGet(); + if (exitCode != 0) + return exitCode; + } + return await runInProject(); } // This is a field so that you can modify the value for testing. - Validator projectRootValidator = () { + Validator commandValidator; + + bool _commandValidator() { if (!FileSystemEntity.isFileSync('pubspec.yaml')) { printError('Error: No pubspec.yaml file found.\n' 'This command should be run from the root of your Flutter project.\n' 'Do not run this command from the root of your git clone of Flutter.'); return false; } + + if (_usesTargetOption) { + String targetPath = argResults['target']; + if (!FileSystemEntity.isFileSync(targetPath)) { + printError('Target file "$targetPath" not found.'); + return false; + } + } + return true; - }; + } Future runInProject(); @@ -122,15 +159,4 @@ abstract class FlutterCommand extends Command { ApplicationPackageStore applicationPackages; Toolchain toolchain; - - bool _targetSpecified = false; - - void addTargetOption() { - argParser.addOption('target', - abbr: 't', - callback: (dynamic val) => _targetSpecified = true, - defaultsTo: flx.defaultMainPath, - help: 'Target app path / main entry-point file.'); - _targetOptionSpecified = true; - } } diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart index dba59e90e0..c30764e743 100644 --- a/packages/flutter_tools/test/src/mocks.dart +++ b/packages/flutter_tools/test/src/mocks.dart @@ -101,5 +101,5 @@ void applyMocksToCommand(FlutterCommand command) { command ..applicationPackages = new MockApplicationPackageStore() ..toolchain = new MockToolchain() - ..projectRootValidator = () => true; + ..commandValidator = () => true; }