Commit 18c8f577 authored by Devon Carew's avatar Devon Carew

Merge pull request #2747 from devoncarew/refactor_build

Refactor the build commands
parents 67b3ce63 f8374cd9
......@@ -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<Null> main(List<String> 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))
......
// 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<int> run() async {
if (argResults['pub']) {
int exitCode = await pubGet();
if (exitCode != 0)
return exitCode;
}
return await super.run();
}
Future<int> runInProject() => new Future<int>.value(0);
}
class BuildCleanCommand extends FlutterCommand {
@override
Future<int> runInProject() async {
String compilerPath = argResults['compiler'];
final String name = 'clean';
if (compilerPath == null)
await downloadToolchain();
else
toolchain = new Toolchain(compiler: new Compiler(compilerPath));
String outputPath = argResults['output-file'];
@override
final String description = 'Delete the build/ directory.';
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;
});
@override
Future<int> runInProject() async {
Directory buildDir = new Directory('build');
printStatus("Deleting '${buildDir.path}${Platform.pathSeparator}'.");
if (!buildDir.existsSync())
return 0;
try {
buildDir.deleteSync(recursive: true);
return 0;
} catch (error) {
printError(error.toString());
return 1;
}
}
}
......@@ -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<int> 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<int> 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<int> 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)');
......
// 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';
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<int> 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)
......
......@@ -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
......
......@@ -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
......
......@@ -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<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 {
toolchain ??= await Toolchain.forConfigs(buildConfigurations);
}
......@@ -56,8 +77,7 @@ abstract class FlutterCommand extends Command {
}
Future<int> _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<int> 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;
}
}
......@@ -101,5 +101,5 @@ void applyMocksToCommand(FlutterCommand command) {
command
..applicationPackages = new MockApplicationPackageStore()
..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