Start abstracting platform logic builds behind a shared interface (#31889)
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index b1f5ef7..360eaf2 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -8,7 +8,6 @@
import '../android/android_sdk.dart';
import '../android/android_workflow.dart';
-import '../android/apk.dart';
import '../application_package.dart';
import '../base/common.dart' show throwToolExit;
import '../base/file_system.dart';
@@ -20,6 +19,7 @@
import '../convert.dart';
import '../device.dart';
import '../globals.dart';
+import '../platform_step.dart';
import '../project.dart';
import '../protocol_discovery.dart';
@@ -374,10 +374,11 @@
if (!prebuiltApplication || androidSdk.licensesAvailable && androidSdk.latestVersion == null) {
printTrace('Building APK');
final FlutterProject project = FlutterProject.current();
- await buildApk(
- project: project,
- target: mainPath,
- buildInfo: buildInfo,
+ final PlatformBuildStep platformStep = platformBuilders.selectPlatform(buildInfo: buildInfo);
+ await platformStep.build(
+ project: project,
+ target: mainPath,
+ buildInfo: buildInfo,
);
// Package has been built, so we can get the updated application ID and
// activity name from the .apk.
diff --git a/packages/flutter_tools/lib/src/android/apk.dart b/packages/flutter_tools/lib/src/android/apk.dart
index 8b5b8d9..5158177 100644
--- a/packages/flutter_tools/lib/src/android/apk.dart
+++ b/packages/flutter_tools/lib/src/android/apk.dart
@@ -4,38 +4,67 @@
import 'dart:async';
-import 'package:meta/meta.dart';
-
import '../base/common.dart';
import '../build_info.dart';
+import '../cache.dart';
+import '../platform_step.dart';
import '../project.dart';
import 'android_sdk.dart';
import 'gradle.dart';
-Future<void> buildApk({
- @required FlutterProject project,
- @required String target,
- BuildInfo buildInfo = BuildInfo.debug,
-}) async {
- if (!project.android.isUsingGradle) {
- throwToolExit(
+/// The Android Gradle build step.
+class AndroidPlatformBuildStep extends PlatformBuildStep {
+ const AndroidPlatformBuildStep();
+
+ @override
+ Future<void> build({
+ FlutterProject project,
+ BuildInfo buildInfo,
+ String target,
+ }) async {
+ if (!project.android.isUsingGradle) {
+ throwToolExit(
'The build process for Android has changed, and the current project configuration\n'
- 'is no longer valid. Please consult\n\n'
- ' https://github.com/flutter/flutter/wiki/Upgrading-Flutter-projects-to-build-with-gradle\n\n'
- 'for details on how to upgrade the project.'
+ 'is no longer valid. Please consult\n\n'
+ ' https://github.com/flutter/flutter/wiki/Upgrading-Flutter-projects-to-build-with-gradle\n\n'
+ 'for details on how to upgrade the project.'
+ );
+ }
+
+ // Validate that we can find an android sdk.
+ if (androidSdk == null) {
+ throwToolExit('No Android SDK found. Try setting the ANDROID_SDK_ROOT environment variable.');
+ }
+ await buildGradleProject(
+ project: project,
+ buildInfo: buildInfo,
+ target: target,
+ isBuildingBundle: false,
);
+ androidSdk.reinitialize();
}
- // Validate that we can find an android sdk.
- if (androidSdk == null)
- throwToolExit('No Android SDK found. Try setting the ANDROID_SDK_ROOT environment variable.');
+ @override
+ Set<TargetPlatform> get targetPlatforms => const <TargetPlatform>{
+ TargetPlatform.android_arm,
+ TargetPlatform.android_arm64,
+ TargetPlatform.android_x64,
+ TargetPlatform.android_x86,
+ };
- await buildGradleProject(
- project: project,
- buildInfo: buildInfo,
- target: target,
- isBuildingBundle: false,
- );
- androidSdk.reinitialize();
+ @override
+ Set<BuildMode> get buildModes => const <BuildMode>{
+ BuildMode.debug,
+ BuildMode.dynamicProfile,
+ BuildMode.dynamicRelease,
+ BuildMode.profile,
+ BuildMode.release,
+ };
+
+ @override
+ Set<DevelopmentArtifact> get developmentArtifacts => const <DevelopmentArtifact>{
+ DevelopmentArtifact.universal,
+ DevelopmentArtifact.android,
+ };
}
diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart
index 92c7ef0..1fdb3a8 100644
--- a/packages/flutter_tools/lib/src/commands/build_apk.dart
+++ b/packages/flutter_tools/lib/src/commands/build_apk.dart
@@ -4,7 +4,8 @@
import 'dart:async';
-import '../android/apk.dart';
+import '../build_info.dart';
+import '../platform_step.dart';
import '../project.dart';
import '../runner/flutter_command.dart' show DevelopmentArtifact, FlutterCommandResult;
import 'build.dart';
@@ -47,10 +48,12 @@
@override
Future<FlutterCommandResult> runCommand() async {
- await buildApk(
+ final BuildInfo buildInfo = getBuildInfo();
+ final PlatformBuildStep platformStep = platformBuilders.selectPlatform(buildInfo: buildInfo);
+ await platformStep.build(
project: FlutterProject.current(),
target: targetFile,
- buildInfo: getBuildInfo(),
+ buildInfo: buildInfo,
);
return null;
}
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index eb7dea5..f5f6730 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -36,6 +36,7 @@
import 'ios/xcodeproj.dart';
import 'linux/linux_workflow.dart';
import 'macos/macos_workflow.dart';
+import 'platform_step.dart';
import 'run_hot.dart';
import 'usage.dart';
import 'version.dart';
@@ -87,6 +88,7 @@
Logger: () => platform.isWindows ? WindowsStdoutLogger() : StdoutLogger(),
MacOSWorkflow: () => const MacOSWorkflow(),
OperatingSystemUtils: () => OperatingSystemUtils(),
+ PlatformBuilders: () => const PlatformBuilders(),
PlistBuddy: () => const PlistBuddy(),
SimControl: () => SimControl(),
SystemClock: () => const SystemClock(),
diff --git a/packages/flutter_tools/lib/src/platform_step.dart b/packages/flutter_tools/lib/src/platform_step.dart
new file mode 100644
index 0000000..4203340
--- /dev/null
+++ b/packages/flutter_tools/lib/src/platform_step.dart
@@ -0,0 +1,81 @@
+// Copyright 2019 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 'android/apk.dart';
+import 'base/common.dart';
+import 'base/context.dart';
+import 'build_info.dart';
+import 'cache.dart';
+import 'project.dart';
+
+/// The [PlatformBuilders] instance.
+PlatformBuilders get platformBuilders => context.get<PlatformBuilders>();
+
+/// A registry that selects the correct [PlatformBuilder] based on the provided
+/// build info.
+class PlatformBuilders {
+ /// Create a new [PlatformBuilder] with `_platformSteps` as platform specific
+ /// implementations.
+ const PlatformBuilders([this._platformSteps = const <PlatformBuildStep>[
+ AndroidPlatformBuildStep(),
+ ]]);
+
+ final List<PlatformBuildStep> _platformSteps;
+
+ /// Build the platform specific bundle for [flutterProject].
+ ///
+ /// Selects the [PlatformBuildStep] that supports the required [TargetPlatform]
+ /// and [BuildMode] requested.
+ ///
+ /// Throws a [ToolExit] if zero steps or more than one step match.
+ PlatformBuildStep selectPlatform({
+ BuildInfo buildInfo,
+ }) {
+ PlatformBuildStep selected;
+ for (PlatformBuildStep platformStep in _platformSteps) {
+ if (!platformStep.targetPlatforms.contains(buildInfo.targetPlatform)) {
+ continue;
+ }
+ if (!platformStep.buildModes.contains(buildInfo.mode)) {
+ continue;
+ }
+ if (selected != null) {
+ throwToolExit(
+ 'Multiple platform steps registered for targetPlatform: ${buildInfo.targetPlatform} '
+ 'and build mode: ${buildInfo.mode}.'
+ );
+ }
+ selected = platformStep;
+ }
+ if (selected == null) {
+ throwToolExit(
+ 'No platform steps registered for targetPlatform: ${buildInfo.targetPlatform} '
+ 'and build mode: ${buildInfo.mode}.'
+ );
+ }
+ return selected;
+ }
+}
+
+/// The workflow by which a flutter application in packaged into a platform
+/// specific application.
+abstract class PlatformBuildStep {
+ const PlatformBuildStep();
+
+ /// Build the platform specific bundle for [flutterProject].
+ Future<void> build({
+ FlutterProject project,
+ BuildInfo buildInfo,
+ String target,
+ });
+
+ /// The targets this platform step supports.
+ Set<TargetPlatform> get targetPlatforms;
+
+ /// The build modes this platform step supports.
+ Set<BuildMode> get buildModes;
+
+ /// Development artifacts required by this platform step.
+ Set<DevelopmentArtifact> get developmentArtifacts;
+}
diff --git a/packages/flutter_tools/test/platform_step_test.dart b/packages/flutter_tools/test/platform_step_test.dart
new file mode 100644
index 0000000..adaa433
--- /dev/null
+++ b/packages/flutter_tools/test/platform_step_test.dart
@@ -0,0 +1,101 @@
+// Copyright 2019 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 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/build_info.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/platform_step.dart';
+import 'package:flutter_tools/src/project.dart';
+
+import 'src/common.dart';
+import 'src/testbed.dart';
+
+void main() {
+ group(PlatformBuilders, () {
+ Testbed testbed;
+
+ setUp(() {
+ testbed = Testbed();
+ });
+
+ test('Fails if more than one builder matches', () => testbed.run(() {
+ const PlatformBuilders platformBuilders = PlatformBuilders(<PlatformBuildStep>[
+ FakePlatformStep(
+ buildModes: <BuildMode>{ BuildMode.debug, },
+ targetPlatforms: <TargetPlatform>{ TargetPlatform.android_arm }
+ ),
+ FakePlatformStep(
+ buildModes: <BuildMode>{ BuildMode.debug, },
+ targetPlatforms: <TargetPlatform>{ TargetPlatform.android_arm }
+ ),
+ ]);
+ const BuildInfo buildInfo = BuildInfo(
+ BuildMode.debug,
+ '',
+ targetPlatform: TargetPlatform.android_arm,
+ );
+
+ expect(() => platformBuilders.selectPlatform(buildInfo: buildInfo), throwsA(isA<ToolExit>()));
+ }));
+
+ test('Fails if no builders match', () => testbed.run(() {
+ const PlatformBuilders platformBuilders = PlatformBuilders(<PlatformBuildStep>[
+ FakePlatformStep(
+ buildModes: <BuildMode>{ BuildMode.debug, },
+ targetPlatforms: <TargetPlatform>{ TargetPlatform.android_arm }
+ ),
+ ]);
+ const BuildInfo buildInfo = BuildInfo(
+ BuildMode.debug,
+ '',
+ targetPlatform: TargetPlatform.ios,
+ );
+
+ expect(() => platformBuilders.selectPlatform(buildInfo: buildInfo), throwsA(isA<ToolExit>()));
+ }));
+
+ test('Finds a matching step', () => testbed.run(() {
+ const PlatformBuildStep iosStep = FakePlatformStep(
+ buildModes: <BuildMode>{ BuildMode.debug, },
+ targetPlatforms: <TargetPlatform>{ TargetPlatform.ios }
+ );
+ const PlatformBuilders platformBuilders = PlatformBuilders(<PlatformBuildStep>[
+ FakePlatformStep(
+ buildModes: <BuildMode>{ BuildMode.debug, },
+ targetPlatforms: <TargetPlatform>{ TargetPlatform.android_arm }
+ ),
+ iosStep,
+ ]);
+ const BuildInfo buildInfo = BuildInfo(
+ BuildMode.debug,
+ '',
+ targetPlatform: TargetPlatform.ios,
+ );
+
+ expect(platformBuilders.selectPlatform(buildInfo: buildInfo), iosStep);
+ }));
+ });
+}
+
+class FakePlatformStep extends PlatformBuildStep {
+ const FakePlatformStep({
+ this.buildModes,
+ this.developmentArtifacts,
+ this.targetPlatforms,
+ });
+
+ @override
+ Future<void> build({FlutterProject project, BuildInfo buildInfo, String target}) {
+ return null;
+ }
+
+ @override
+ final Set<BuildMode> buildModes;
+
+ @override
+ final Set<DevelopmentArtifact> developmentArtifacts;
+
+ @override
+ final Set<TargetPlatform> targetPlatforms;
+}