Commit f8374cd9 authored by Devon Carew's avatar Devon Carew

consolidate build commands under the 'build' command

parent 0e743b62
...@@ -12,7 +12,6 @@ import 'src/base/context.dart'; ...@@ -12,7 +12,6 @@ import 'src/base/context.dart';
import 'src/base/logger.dart'; import 'src/base/logger.dart';
import 'src/base/process.dart'; import 'src/base/process.dart';
import 'src/commands/analyze.dart'; import 'src/commands/analyze.dart';
import 'src/commands/apk.dart';
import 'src/commands/build.dart'; import 'src/commands/build.dart';
import 'src/commands/create.dart'; import 'src/commands/create.dart';
import 'src/commands/daemon.dart'; import 'src/commands/daemon.dart';
...@@ -50,7 +49,6 @@ Future<Null> main(List<String> args) async { ...@@ -50,7 +49,6 @@ Future<Null> main(List<String> args) async {
FlutterCommandRunner runner = new FlutterCommandRunner(verboseHelp: verboseHelp) FlutterCommandRunner runner = new FlutterCommandRunner(verboseHelp: verboseHelp)
..addCommand(new AnalyzeCommand()) ..addCommand(new AnalyzeCommand())
..addCommand(new ApkCommand())
..addCommand(new BuildCommand()) ..addCommand(new BuildCommand())
..addCommand(new CreateCommand()) ..addCommand(new CreateCommand())
..addCommand(new DaemonCommand(hidden: !verboseHelp)) ..addCommand(new DaemonCommand(hidden: !verboseHelp))
......
// 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 // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:io';
import '../flx.dart';
import '../dart/pub.dart';
import '../globals.dart'; import '../globals.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import '../toolchain.dart'; import 'build_apk.dart';
import 'build_flx.dart';
class BuildCommand extends FlutterCommand { class BuildCommand extends FlutterCommand {
BuildCommand() {
addSubcommand(new BuildApkCommand());
addSubcommand(new BuildCleanCommand());
addSubcommand(new BuildFlxCommand());
}
@override @override
final String name = 'build'; final String name = 'build';
@override @override
final String description = 'Package your Flutter app into an FLX.'; final String description = 'Flutter build commands.';
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();
}
@override @override
Future<int> run() async { Future<int> runInProject() => new Future<int>.value(0);
if (argResults['pub']) { }
int exitCode = await pubGet();
if (exitCode != 0)
return exitCode;
}
return await super.run();
}
class BuildCleanCommand extends FlutterCommand {
@override @override
Future<int> runInProject() async { final String name = 'clean';
String compilerPath = argResults['compiler'];
if (compilerPath == null) @override
await downloadToolchain(); final String description = 'Delete the build/ directory.';
else
toolchain = new Toolchain(compiler: new Compiler(compilerPath));
String outputPath = argResults['output-file'];
return await build( @override
toolchain, Future<int> runInProject() async {
mainPath: argResults['target'], Directory buildDir = new Directory('build');
manifestPath: argResults['manifest'], printStatus("Deleting '${buildDir.path}${Platform.pathSeparator}'.");
outputPath: outputPath,
snapshotPath: argResults['snapshot'], if (!buildDir.existsSync())
depfilePath: argResults['depfile'], return 0;
privateKeyPath: argResults['private-key'],
workingDirPath: argResults['working-dir'], try {
precompiledSnapshot: argResults['precompiled'] buildDir.deleteSync(recursive: true);
).then((int result) { return 0;
if (result == 0) } catch (error) {
printStatus('Built $outputPath.'); printError(error.toString());
else return 1;
printError('Error building $outputPath: $result.'); }
return result;
});
} }
} }
...@@ -127,22 +127,19 @@ class _ApkComponents { ...@@ -127,22 +127,19 @@ class _ApkComponents {
} }
class ApkKeystoreInfo { 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; final String keystore;
String password; final String password;
String keyAlias; final String keyAlias;
String keyPassword; final String keyPassword;
} }
class ApkCommand extends FlutterCommand { class BuildApkCommand extends FlutterCommand {
@override BuildApkCommand() {
final String name = 'apk'; usesTargetOption();
@override
final String description = 'Build an Android APK package.';
ApkCommand() {
argParser.addOption('manifest', argParser.addOption('manifest',
abbr: 'm', abbr: 'm',
defaultsTo: _kDefaultAndroidManifestPath, defaultsTo: _kDefaultAndroidManifestPath,
...@@ -157,23 +154,24 @@ class ApkCommand extends FlutterCommand { ...@@ -157,23 +154,24 @@ class ApkCommand extends FlutterCommand {
help: 'Output APK file.'); help: 'Output APK file.');
argParser.addOption('flx', argParser.addOption('flx',
abbr: 'f', abbr: 'f',
defaultsTo: '',
help: 'Path to the FLX file. If this is not provided, an FLX will be built.'); help: 'Path to the FLX file. If this is not provided, an FLX will be built.');
argParser.addOption('keystore', argParser.addOption('keystore',
defaultsTo: '',
help: 'Path to the keystore used to sign the app.'); help: 'Path to the keystore used to sign the app.');
argParser.addOption('keystore-password', argParser.addOption('keystore-password',
defaultsTo: '',
help: 'Password used to access the keystore.'); help: 'Password used to access the keystore.');
argParser.addOption('keystore-key-alias', argParser.addOption('keystore-key-alias',
defaultsTo: '',
help: 'Alias of the entry within the keystore.'); help: 'Alias of the entry within the keystore.');
argParser.addOption('keystore-key-password', argParser.addOption('keystore-key-password',
defaultsTo: '',
help: 'Password for the entry within the keystore.'); 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 @override
Future<int> runInProject() async { Future<int> runInProject() async {
// Validate that we can find an android sdk. // Validate that we can find an android sdk.
...@@ -199,7 +197,7 @@ class ApkCommand extends FlutterCommand { ...@@ -199,7 +197,7 @@ class ApkCommand extends FlutterCommand {
outputFile: argResults['output-file'], outputFile: argResults['output-file'],
target: argResults['target'], target: argResults['target'],
flxPath: argResults['flx'], flxPath: argResults['flx'],
keystore: argResults['keystore'].isEmpty ? null : new ApkKeystoreInfo( keystore: (argResults['keystore'] ?? '').isEmpty ? null : new ApkKeystoreInfo(
keystore: argResults['keystore'], keystore: argResults['keystore'],
password: argResults['keystore-password'], password: argResults['keystore-password'],
keyAlias: argResults['keystore-key-alias'], keyAlias: argResults['keystore-key-alias'],
...@@ -324,13 +322,13 @@ int _signApk( ...@@ -324,13 +322,13 @@ int _signApk(
keyPassword = _kDebugKeystorePassword; keyPassword = _kDebugKeystorePassword;
} else { } else {
keystore = new File(keystoreInfo.keystore); keystore = new File(keystoreInfo.keystore);
keystorePassword = keystoreInfo.password; keystorePassword = keystoreInfo.password ?? '';
keyAlias = keystoreInfo.keyAlias; keyAlias = keystoreInfo.keyAlias ?? '';
if (keystorePassword.isEmpty || keyAlias.isEmpty) { if (keystorePassword.isEmpty || keyAlias.isEmpty) {
printError('Must provide a keystore password and a key alias.'); printError('Must provide a keystore password and a key alias.');
return 1; return 1;
} }
keyPassword = keystoreInfo.keyPassword; keyPassword = keystoreInfo.keyPassword ?? '';
if (keyPassword.isEmpty) if (keyPassword.isEmpty)
keyPassword = keystorePassword; keyPassword = keystorePassword;
} }
...@@ -375,7 +373,7 @@ Future<int> buildAndroid({ ...@@ -375,7 +373,7 @@ Future<int> buildAndroid({
String resources: _kDefaultResourcesPath, String resources: _kDefaultResourcesPath,
String outputFile: _kDefaultOutputPath, String outputFile: _kDefaultOutputPath,
String target: '', String target: '',
String flxPath: '', String flxPath,
ApkKeystoreInfo keystore ApkKeystoreInfo keystore
}) async { }) async {
// Validate that we can find an android sdk. // Validate that we can find an android sdk.
...@@ -405,7 +403,7 @@ Future<int> buildAndroid({ ...@@ -405,7 +403,7 @@ Future<int> buildAndroid({
printStatus('Building APK...'); printStatus('Building APK...');
if (flxPath.isNotEmpty) { if (flxPath != null && flxPath.isNotEmpty) {
if (!FileSystemEntity.isFileSync(flxPath)) { if (!FileSystemEntity.isFileSync(flxPath)) {
printError('FLX does not exist: $flxPath'); printError('FLX does not exist: $flxPath');
printError('(Omit the --flx option to build the FLX automatically)'); printError('(Omit the --flx option to build the FLX automatically)');
......
// 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<int> 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;
});
}
}
...@@ -15,7 +15,7 @@ import '../base/os.dart'; ...@@ -15,7 +15,7 @@ import '../base/os.dart';
import '../device.dart'; import '../device.dart';
import '../globals.dart'; import '../globals.dart';
import '../ios/simulators.dart' show SimControl, IOSSimulatorUtils; import '../ios/simulators.dart' show SimControl, IOSSimulatorUtils;
import 'apk.dart' as apk; import 'build_apk.dart' as build_apk;
import 'run.dart'; import 'run.dart';
/// Runs integration (a.k.a. end-to-end) tests. /// Runs integration (a.k.a. end-to-end) tests.
...@@ -244,7 +244,7 @@ Future<int> startApp(DriveCommand command) async { ...@@ -244,7 +244,7 @@ Future<int> startApp(DriveCommand command) async {
if (command.device is AndroidDevice) { if (command.device is AndroidDevice) {
printTrace('Building an APK.'); 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); enginePath: command.runner.enginePath, target: command.target);
if (result != 0) if (result != 0)
......
...@@ -19,7 +19,7 @@ class RefreshCommand extends FlutterCommand { ...@@ -19,7 +19,7 @@ class RefreshCommand extends FlutterCommand {
final String description = 'Build and deploy the Dart code in a Flutter app (Android only).'; final String description = 'Build and deploy the Dart code in a Flutter app (Android only).';
RefreshCommand() { RefreshCommand() {
addTargetOption(); usesTargetOption();
} }
@override @override
......
...@@ -15,7 +15,7 @@ import '../device.dart'; ...@@ -15,7 +15,7 @@ import '../device.dart';
import '../globals.dart'; import '../globals.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import '../toolchain.dart'; import '../toolchain.dart';
import 'apk.dart'; import 'build_apk.dart';
import 'install.dart'; import 'install.dart';
/// Given the value of the --target option, return the path of the Dart file /// Given the value of the --target option, return the path of the Dart file
...@@ -43,7 +43,7 @@ abstract class RunCommandBase extends FlutterCommand { ...@@ -43,7 +43,7 @@ abstract class RunCommandBase extends FlutterCommand {
help: 'Start tracing during startup.'); help: 'Start tracing during startup.');
argParser.addOption('route', argParser.addOption('route',
help: 'Which route to load when starting the app.'); help: 'Which route to load when starting the app.');
addTargetOption(); usesTargetOption();
} }
bool get checked => argResults['checked']; bool get checked => argResults['checked'];
...@@ -73,12 +73,10 @@ class RunCommand extends RunCommandBase { ...@@ -73,12 +73,10 @@ class RunCommand extends RunCommandBase {
defaultsTo: false, defaultsTo: false,
negatable: false, negatable: false,
help: 'Start in a paused mode and wait for a debugger to connect.'); 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', argParser.addOption('debug-port',
defaultsTo: observatoryDefaultPort.toString(), defaultsTo: observatoryDefaultPort.toString(),
help: 'Listen to the given port for a debug connection.'); help: 'Listen to the given port for a debug connection.');
usesPubOption();
} }
@override @override
......
...@@ -7,6 +7,7 @@ import 'dart:io'; ...@@ -7,6 +7,7 @@ import 'dart:io';
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
import '../dart/pub.dart';
import '../application_package.dart'; import '../application_package.dart';
import '../build_configuration.dart'; import '../build_configuration.dart';
import '../device.dart'; import '../device.dart';
...@@ -18,6 +19,10 @@ import 'flutter_command_runner.dart'; ...@@ -18,6 +19,10 @@ import 'flutter_command_runner.dart';
typedef bool Validator(); typedef bool Validator();
abstract class FlutterCommand extends Command { abstract class FlutterCommand extends Command {
FlutterCommand() {
commandValidator = _commandValidator;
}
@override @override
FlutterCommandRunner get runner => super.runner; FlutterCommandRunner get runner => super.runner;
...@@ -30,12 +35,28 @@ abstract class FlutterCommand extends Command { ...@@ -30,12 +35,28 @@ abstract class FlutterCommand extends Command {
/// Whether this command only applies to Android devices. /// Whether this command only applies to Android devices.
bool get androidOnly => false; bool get androidOnly => false;
/// Whether this command allows usage of the 'target' option. /// Whether this command uses the 'target' option.
bool get allowsTarget => _targetOptionSpecified; bool _usesTargetOption = false;
bool _targetOptionSpecified = false;
bool _usesPubOption = false;
List<BuildConfiguration> get buildConfigurations => runner.buildConfigurations; List<BuildConfiguration> 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<Null> downloadToolchain() async { Future<Null> downloadToolchain() async {
toolchain ??= await Toolchain.forConfigs(buildConfigurations); toolchain ??= await Toolchain.forConfigs(buildConfigurations);
} }
...@@ -56,8 +77,7 @@ abstract class FlutterCommand extends Command { ...@@ -56,8 +77,7 @@ abstract class FlutterCommand extends Command {
} }
Future<int> _run() async { Future<int> _run() async {
bool _checkRoot = requiresProjectRoot && allowsTarget && !_targetSpecified; if (requiresProjectRoot && !commandValidator())
if (_checkRoot && !projectRootValidator())
return 1; return 1;
// Ensure at least one toolchain is installed. // Ensure at least one toolchain is installed.
...@@ -99,19 +119,36 @@ abstract class FlutterCommand extends Command { ...@@ -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(); return await runInProject();
} }
// This is a field so that you can modify the value for testing. // This is a field so that you can modify the value for testing.
Validator projectRootValidator = () { Validator commandValidator;
bool _commandValidator() {
if (!FileSystemEntity.isFileSync('pubspec.yaml')) { if (!FileSystemEntity.isFileSync('pubspec.yaml')) {
printError('Error: No pubspec.yaml file found.\n' printError('Error: No pubspec.yaml file found.\n'
'This command should be run from the root of your Flutter project.\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.'); 'Do not run this command from the root of your git clone of Flutter.');
return false; return false;
} }
if (_usesTargetOption) {
String targetPath = argResults['target'];
if (!FileSystemEntity.isFileSync(targetPath)) {
printError('Target file "$targetPath" not found.');
return false;
}
}
return true; return true;
}; }
Future<int> runInProject(); Future<int> runInProject();
...@@ -122,15 +159,4 @@ abstract class FlutterCommand extends Command { ...@@ -122,15 +159,4 @@ abstract class FlutterCommand extends Command {
ApplicationPackageStore applicationPackages; ApplicationPackageStore applicationPackages;
Toolchain toolchain; 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;
}
} }
...@@ -101,5 +101,5 @@ void applyMocksToCommand(FlutterCommand command) { ...@@ -101,5 +101,5 @@ void applyMocksToCommand(FlutterCommand command) {
command command
..applicationPackages = new MockApplicationPackageStore() ..applicationPackages = new MockApplicationPackageStore()
..toolchain = new MockToolchain() ..toolchain = new MockToolchain()
..projectRootValidator = () => true; ..commandValidator = () => true;
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment