Add multidex flag and automatic multidex support (#90944)
This commit is contained in:
parent
abfcc84e58
commit
1d9edde079
@ -215,6 +215,38 @@ class FlutterPlugin implements Plugin<Project> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (project.hasProperty("multidex-enabled") &&
|
||||||
|
project.property("multidex-enabled").toBoolean() &&
|
||||||
|
project.android.defaultConfig.minSdkVersion <= 20) {
|
||||||
|
String flutterMultidexKeepfile = Paths.get(flutterRoot.absolutePath, "packages", "flutter_tools",
|
||||||
|
"gradle", "flutter_multidex_keepfile.txt")
|
||||||
|
project.android {
|
||||||
|
defaultConfig {
|
||||||
|
multiDexEnabled true
|
||||||
|
manifestPlaceholders = [applicationName: "io.flutter.app.FlutterMultiDexApplication"]
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
multiDexKeepFile project.file(flutterMultidexKeepfile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
project.dependencies {
|
||||||
|
implementation "androidx.multidex:multidex:2.0.1"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String baseApplicationName = "android.app.Application"
|
||||||
|
if (project.hasProperty("base-application-name")) {
|
||||||
|
baseApplicationName = project.property("base-application-name")
|
||||||
|
}
|
||||||
|
project.android {
|
||||||
|
defaultConfig {
|
||||||
|
// Setting to android.app.Application is the same as omitting the attribute.
|
||||||
|
manifestPlaceholders = [applicationName: baseApplicationName]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (useLocalEngine()) {
|
if (useLocalEngine()) {
|
||||||
// This is required to pass the local engine to flutter build aot.
|
// This is required to pass the local engine to flutter build aot.
|
||||||
String engineOutPath = project.property('local-engine-out')
|
String engineOutPath = project.property('local-engine-out')
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
io/flutter/app/FlutterApplication.class
|
||||||
|
io/flutter/app/FlutterMultiDexApplication.class
|
||||||
|
io/flutter/embedding/engine/loader/FlutterLoader.class
|
||||||
|
io/flutter/view/FlutterMain.class
|
||||||
|
io/flutter/util/PathUtils.class
|
@ -595,7 +595,8 @@ class AndroidDevice extends Device {
|
|||||||
androidBuildInfo: AndroidBuildInfo(
|
androidBuildInfo: AndroidBuildInfo(
|
||||||
debuggingOptions.buildInfo,
|
debuggingOptions.buildInfo,
|
||||||
targetArchs: <AndroidArch>[androidArch],
|
targetArchs: <AndroidArch>[androidArch],
|
||||||
fastStart: debuggingOptions.fastStart
|
fastStart: debuggingOptions.fastStart,
|
||||||
|
multidexEnabled: platformArgs['multidex'] as bool,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
// Package has been built, so we can get the updated application ID and
|
// Package has been built, so we can get the updated application ID and
|
||||||
|
@ -28,6 +28,7 @@ import 'android_builder.dart';
|
|||||||
import 'android_studio.dart';
|
import 'android_studio.dart';
|
||||||
import 'gradle_errors.dart';
|
import 'gradle_errors.dart';
|
||||||
import 'gradle_utils.dart';
|
import 'gradle_utils.dart';
|
||||||
|
import 'multidex.dart';
|
||||||
|
|
||||||
/// The directory where the APK artifact is generated.
|
/// The directory where the APK artifact is generated.
|
||||||
Directory getApkDirectory(FlutterProject project) {
|
Directory getApkDirectory(FlutterProject project) {
|
||||||
@ -293,6 +294,22 @@ class AndroidGradleBuilder implements AndroidBuilder {
|
|||||||
if (target != null) {
|
if (target != null) {
|
||||||
command.add('-Ptarget=$target');
|
command.add('-Ptarget=$target');
|
||||||
}
|
}
|
||||||
|
// Only attempt adding multidex support if all the flutter generated files exist.
|
||||||
|
// If the files do not exist and it was unintentional, the app will fail to build
|
||||||
|
// and prompt the developer if they wish Flutter to add the files again via gradle_error.dart.
|
||||||
|
if (androidBuildInfo.multidexEnabled &&
|
||||||
|
multiDexApplicationExists(project.directory) &&
|
||||||
|
androidManifestHasNameVariable(project.directory)) {
|
||||||
|
command.add('-Pmultidex-enabled=true');
|
||||||
|
ensureMultiDexApplicationExists(project.directory);
|
||||||
|
_logger.printStatus('Building with Flutter multidex support enabled.');
|
||||||
|
}
|
||||||
|
// If using v1 embedding, we want to use FlutterApplication as the base app.
|
||||||
|
final String baseApplicationName =
|
||||||
|
project.android.getEmbeddingVersion() == AndroidEmbeddingVersion.v2 ?
|
||||||
|
'android.app.Application' :
|
||||||
|
'io.flutter.app.FlutterApplication';
|
||||||
|
command.add('-Pbase-application-name=$baseApplicationName');
|
||||||
final List<DeferredComponent>? deferredComponents = project.manifest.deferredComponents;
|
final List<DeferredComponent>? deferredComponents = project.manifest.deferredComponents;
|
||||||
if (deferredComponents != null) {
|
if (deferredComponents != null) {
|
||||||
if (deferredComponentsEnabled) {
|
if (deferredComponentsEnabled) {
|
||||||
@ -389,6 +406,7 @@ class AndroidGradleBuilder implements AndroidBuilder {
|
|||||||
line: detectedGradleErrorLine!,
|
line: detectedGradleErrorLine!,
|
||||||
project: project,
|
project: project,
|
||||||
usesAndroidX: usesAndroidX,
|
usesAndroidX: usesAndroidX,
|
||||||
|
multidexEnabled: androidBuildInfo.multidexEnabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (retries >= 1) {
|
if (retries >= 1) {
|
||||||
|
@ -7,10 +7,12 @@ import 'package:meta/meta.dart';
|
|||||||
import '../base/error_handling_io.dart';
|
import '../base/error_handling_io.dart';
|
||||||
import '../base/file_system.dart';
|
import '../base/file_system.dart';
|
||||||
import '../base/process.dart';
|
import '../base/process.dart';
|
||||||
|
import '../base/terminal.dart';
|
||||||
import '../globals_null_migrated.dart' as globals;
|
import '../globals_null_migrated.dart' as globals;
|
||||||
import '../project.dart';
|
import '../project.dart';
|
||||||
import '../reporting/reporting.dart';
|
import '../reporting/reporting.dart';
|
||||||
import 'android_studio.dart';
|
import 'android_studio.dart';
|
||||||
|
import 'multidex.dart';
|
||||||
|
|
||||||
typedef GradleErrorTest = bool Function(String);
|
typedef GradleErrorTest = bool Function(String);
|
||||||
|
|
||||||
@ -31,6 +33,7 @@ class GradleHandledError {
|
|||||||
required String line,
|
required String line,
|
||||||
required FlutterProject project,
|
required FlutterProject project,
|
||||||
required bool usesAndroidX,
|
required bool usesAndroidX,
|
||||||
|
required bool multidexEnabled,
|
||||||
}) handler;
|
}) handler;
|
||||||
|
|
||||||
/// The [BuildEvent] label is named gradle-[eventLabel].
|
/// The [BuildEvent] label is named gradle-[eventLabel].
|
||||||
@ -71,8 +74,104 @@ final List<GradleHandledError> gradleErrors = <GradleHandledError>[
|
|||||||
minSdkVersion,
|
minSdkVersion,
|
||||||
transformInputIssue,
|
transformInputIssue,
|
||||||
lockFileDepMissing,
|
lockFileDepMissing,
|
||||||
|
multidexErrorHandler,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Multidex error message.
|
||||||
|
@visibleForTesting
|
||||||
|
final GradleHandledError multidexErrorHandler = GradleHandledError(
|
||||||
|
test: _lineMatcher(const <String>[
|
||||||
|
'com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives:',
|
||||||
|
'The number of method references in a .dex file cannot exceed 64K.',
|
||||||
|
]),
|
||||||
|
handler: ({
|
||||||
|
required String line,
|
||||||
|
required FlutterProject project,
|
||||||
|
required bool usesAndroidX,
|
||||||
|
required bool multidexEnabled,
|
||||||
|
}) async {
|
||||||
|
globals.printStatus('${globals.logger.terminal.warningMark} App requires Multidex support', emphasis: true);
|
||||||
|
if (multidexEnabled) {
|
||||||
|
globals.printStatus(
|
||||||
|
'Multidex support is required for your android app to build since the number of methods has exceeded 64k. '
|
||||||
|
"You may pass the --no-multidex flag to skip Flutter's multidex support to use a manual solution.\n",
|
||||||
|
indent: 4,
|
||||||
|
);
|
||||||
|
if (!androidManifestHasNameVariable(project.directory)) {
|
||||||
|
globals.printStatus(
|
||||||
|
r'Your `android/app/src/main/AndroidManifest.xml` does not contain `android:name="${applicationName}"` '
|
||||||
|
'under the `application` element. This may be due to creating your project with an old version of Flutter. '
|
||||||
|
'Add the `android:name="\${applicationName}"` attribute to your AndroidManifest.xml to enable Flutter\'s multidex support:\n',
|
||||||
|
indent: 4,
|
||||||
|
);
|
||||||
|
globals.printStatus(r'''
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
...
|
||||||
|
<application
|
||||||
|
...
|
||||||
|
android:name=''',
|
||||||
|
indent: 8,
|
||||||
|
newline: false,
|
||||||
|
color: TerminalColor.grey,
|
||||||
|
);
|
||||||
|
globals.printStatus(r'"${applicationName}"', color: TerminalColor.green, newline: true);
|
||||||
|
globals.printStatus(r'''
|
||||||
|
...>
|
||||||
|
''',
|
||||||
|
indent: 8,
|
||||||
|
color: TerminalColor.grey,
|
||||||
|
);
|
||||||
|
|
||||||
|
globals.printStatus(
|
||||||
|
'You may also roll your own multidex support by following the guide at: https://developer.android.com/studio/build/multidex\n',
|
||||||
|
indent: 4,
|
||||||
|
);
|
||||||
|
return GradleBuildStatus.exit;
|
||||||
|
}
|
||||||
|
if (!multiDexApplicationExists(project.directory)) {
|
||||||
|
globals.printStatus(
|
||||||
|
'Flutter tool can add multidex support. The following file will be added by flutter:\n',
|
||||||
|
indent: 4,
|
||||||
|
);
|
||||||
|
globals.printStatus(
|
||||||
|
'android/app/src/main/java/io/flutter/app/FlutterMultiDexApplication.java\n',
|
||||||
|
indent: 8,
|
||||||
|
);
|
||||||
|
String selection = 'n';
|
||||||
|
// Default to 'no' if no interactive terminal.
|
||||||
|
try {
|
||||||
|
selection = await globals.terminal.promptForCharInput(
|
||||||
|
<String>['y', 'n'],
|
||||||
|
logger: globals.logger,
|
||||||
|
prompt: 'Do you want to continue with adding multidex support for Android?',
|
||||||
|
defaultChoiceIndex: 0,
|
||||||
|
);
|
||||||
|
} on StateError catch(e) {
|
||||||
|
globals.printError(
|
||||||
|
e.message,
|
||||||
|
indent: 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (selection == 'y') {
|
||||||
|
ensureMultiDexApplicationExists(project.directory);
|
||||||
|
globals.printStatus(
|
||||||
|
'Multidex enabled. Retrying build.\n',
|
||||||
|
indent: 0,
|
||||||
|
);
|
||||||
|
return GradleBuildStatus.retry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
globals.printStatus(
|
||||||
|
'Flutter multidex handling is disabled. If you wish to let the tool configure multidex, use the --mutidex flag.',
|
||||||
|
indent: 4,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return GradleBuildStatus.exit;
|
||||||
|
},
|
||||||
|
eventLabel: 'multidex-error',
|
||||||
|
);
|
||||||
|
|
||||||
// Permission defined error message.
|
// Permission defined error message.
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
final GradleHandledError permissionDeniedErrorHandler = GradleHandledError(
|
final GradleHandledError permissionDeniedErrorHandler = GradleHandledError(
|
||||||
@ -83,12 +182,13 @@ final GradleHandledError permissionDeniedErrorHandler = GradleHandledError(
|
|||||||
required String line,
|
required String line,
|
||||||
required FlutterProject project,
|
required FlutterProject project,
|
||||||
required bool usesAndroidX,
|
required bool usesAndroidX,
|
||||||
|
required bool multidexEnabled,
|
||||||
}) async {
|
}) async {
|
||||||
globals.printStatus('${globals.logger.terminal.warningMark} Gradle does not have execution permission.', emphasis: true);
|
globals.printStatus('${globals.logger.terminal.warningMark} Gradle does not have execution permission.', emphasis: true);
|
||||||
globals.printStatus(
|
globals.printStatus(
|
||||||
'You should change the ownership of the project directory to your user, '
|
'You should change the ownership of the project directory to your user, '
|
||||||
'or move the project to a directory with execute permissions.',
|
'or move the project to a directory with execute permissions.',
|
||||||
indent: 4
|
indent: 4,
|
||||||
);
|
);
|
||||||
return GradleBuildStatus.exit;
|
return GradleBuildStatus.exit;
|
||||||
},
|
},
|
||||||
@ -119,6 +219,7 @@ final GradleHandledError networkErrorHandler = GradleHandledError(
|
|||||||
required String line,
|
required String line,
|
||||||
required FlutterProject project,
|
required FlutterProject project,
|
||||||
required bool usesAndroidX,
|
required bool usesAndroidX,
|
||||||
|
required bool multidexEnabled,
|
||||||
}) async {
|
}) async {
|
||||||
globals.printError(
|
globals.printError(
|
||||||
'${globals.logger.terminal.warningMark} Gradle threw an error while downloading artifacts from the network. '
|
'${globals.logger.terminal.warningMark} Gradle threw an error while downloading artifacts from the network. '
|
||||||
@ -148,6 +249,7 @@ final GradleHandledError r8FailureHandler = GradleHandledError(
|
|||||||
required String line,
|
required String line,
|
||||||
required FlutterProject project,
|
required FlutterProject project,
|
||||||
required bool usesAndroidX,
|
required bool usesAndroidX,
|
||||||
|
required bool multidexEnabled,
|
||||||
}) async {
|
}) async {
|
||||||
globals.printStatus('${globals.logger.terminal.warningMark} The shrinker may have failed to optimize the Java bytecode.', emphasis: true);
|
globals.printStatus('${globals.logger.terminal.warningMark} The shrinker may have failed to optimize the Java bytecode.', emphasis: true);
|
||||||
globals.printStatus('To disable the shrinker, pass the `--no-shrink` flag to this command.', indent: 4);
|
globals.printStatus('To disable the shrinker, pass the `--no-shrink` flag to this command.', indent: 4);
|
||||||
@ -169,6 +271,7 @@ final GradleHandledError licenseNotAcceptedHandler = GradleHandledError(
|
|||||||
required String line,
|
required String line,
|
||||||
required FlutterProject project,
|
required FlutterProject project,
|
||||||
required bool usesAndroidX,
|
required bool usesAndroidX,
|
||||||
|
required bool multidexEnabled,
|
||||||
}) async {
|
}) async {
|
||||||
const String licenseNotAcceptedMatcher =
|
const String licenseNotAcceptedMatcher =
|
||||||
r'You have not accepted the license agreements of the following SDK components:\s*\[(.+)\]';
|
r'You have not accepted the license agreements of the following SDK components:\s*\[(.+)\]';
|
||||||
@ -202,6 +305,7 @@ final GradleHandledError flavorUndefinedHandler = GradleHandledError(
|
|||||||
required String line,
|
required String line,
|
||||||
required FlutterProject project,
|
required FlutterProject project,
|
||||||
required bool usesAndroidX,
|
required bool usesAndroidX,
|
||||||
|
required bool multidexEnabled,
|
||||||
}) async {
|
}) async {
|
||||||
final RunResult tasksRunResult = await globals.processUtils.run(
|
final RunResult tasksRunResult = await globals.processUtils.run(
|
||||||
<String>[
|
<String>[
|
||||||
@ -274,6 +378,7 @@ final GradleHandledError minSdkVersion = GradleHandledError(
|
|||||||
required String line,
|
required String line,
|
||||||
required FlutterProject project,
|
required FlutterProject project,
|
||||||
required bool usesAndroidX,
|
required bool usesAndroidX,
|
||||||
|
required bool multidexEnabled,
|
||||||
}) async {
|
}) async {
|
||||||
final File gradleFile = project.directory
|
final File gradleFile = project.directory
|
||||||
.childDirectory('android')
|
.childDirectory('android')
|
||||||
@ -314,6 +419,7 @@ final GradleHandledError transformInputIssue = GradleHandledError(
|
|||||||
required String line,
|
required String line,
|
||||||
required FlutterProject project,
|
required FlutterProject project,
|
||||||
required bool usesAndroidX,
|
required bool usesAndroidX,
|
||||||
|
required bool multidexEnabled,
|
||||||
}) async {
|
}) async {
|
||||||
final File gradleFile = project.directory
|
final File gradleFile = project.directory
|
||||||
.childDirectory('android')
|
.childDirectory('android')
|
||||||
@ -347,6 +453,7 @@ final GradleHandledError lockFileDepMissing = GradleHandledError(
|
|||||||
required String line,
|
required String line,
|
||||||
required FlutterProject project,
|
required FlutterProject project,
|
||||||
required bool usesAndroidX,
|
required bool usesAndroidX,
|
||||||
|
required bool multidexEnabled,
|
||||||
}) async {
|
}) async {
|
||||||
final File gradleFile = project.directory
|
final File gradleFile = project.directory
|
||||||
.childDirectory('android')
|
.childDirectory('android')
|
||||||
|
99
packages/flutter_tools/lib/src/android/multidex.dart
Normal file
99
packages/flutter_tools/lib/src/android/multidex.dart
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// Copyright 2014 The Flutter 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:xml/xml.dart';
|
||||||
|
|
||||||
|
import '../base/file_system.dart';
|
||||||
|
|
||||||
|
// These utility methods are used to generate the code for multidex support as
|
||||||
|
// well as verifying the project is properly set up.
|
||||||
|
|
||||||
|
File _getMultiDexApplicationFile(Directory projectDir) {
|
||||||
|
return projectDir.childDirectory('android')
|
||||||
|
.childDirectory('app')
|
||||||
|
.childDirectory('src')
|
||||||
|
.childDirectory('main')
|
||||||
|
.childDirectory('java')
|
||||||
|
.childDirectory('io')
|
||||||
|
.childDirectory('flutter')
|
||||||
|
.childDirectory('app')
|
||||||
|
.childFile('FlutterMultiDexApplication.java');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates the FlutterMultiDexApplication.java if it does not exist.
|
||||||
|
void ensureMultiDexApplicationExists(final Directory projectDir) {
|
||||||
|
final File applicationFile = _getMultiDexApplicationFile(projectDir);
|
||||||
|
if (applicationFile.existsSync()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
applicationFile.createSync(recursive: true);
|
||||||
|
|
||||||
|
final StringBuffer buffer = StringBuffer();
|
||||||
|
buffer.write('''
|
||||||
|
// Generated file.
|
||||||
|
// If you wish to remove Flutter's multidex support, delete this entire file.
|
||||||
|
|
||||||
|
package io.flutter.app;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import androidx.annotation.CallSuper;
|
||||||
|
import androidx.multidex.MultiDex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension of {@link io.flutter.app.FlutterApplication}, adding multidex support.
|
||||||
|
*/
|
||||||
|
public class FlutterMultiDexApplication extends FlutterApplication {
|
||||||
|
@Override
|
||||||
|
@CallSuper
|
||||||
|
protected void attachBaseContext(Context base) {
|
||||||
|
super.attachBaseContext(base);
|
||||||
|
MultiDex.install(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''');
|
||||||
|
applicationFile.writeAsStringSync(buffer.toString(), flush: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if FlutterMultiDexApplication.java exists.
|
||||||
|
///
|
||||||
|
/// This function does not verify the contents of the file.
|
||||||
|
bool multiDexApplicationExists(final Directory projectDir) {
|
||||||
|
if (_getMultiDexApplicationFile(projectDir).existsSync()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
File _getManifestFile(Directory projectDir) {
|
||||||
|
return projectDir.childDirectory('android')
|
||||||
|
.childDirectory('app')
|
||||||
|
.childDirectory('src')
|
||||||
|
.childDirectory('main')
|
||||||
|
.childFile('AndroidManifest.xml');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the `app` module AndroidManifest.xml includes the
|
||||||
|
/// <application android:name="${applicationName}"> attribute.
|
||||||
|
bool androidManifestHasNameVariable(final Directory projectDir) {
|
||||||
|
final File manifestFile = _getManifestFile(projectDir);
|
||||||
|
if (!manifestFile.existsSync()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
XmlDocument document;
|
||||||
|
try {
|
||||||
|
document = XmlDocument.parse(manifestFile.readAsStringSync());
|
||||||
|
} on XmlParserException {
|
||||||
|
return false;
|
||||||
|
} on FileSystemException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Check for the ${androidName} application attribute.
|
||||||
|
for (final XmlElement application in document.findAllElements('application')) {
|
||||||
|
final String? applicationName = application.getAttribute('android:name');
|
||||||
|
if (applicationName == r'${applicationName}') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
@ -304,6 +304,7 @@ class AndroidBuildInfo {
|
|||||||
],
|
],
|
||||||
this.splitPerAbi = false,
|
this.splitPerAbi = false,
|
||||||
this.fastStart = false,
|
this.fastStart = false,
|
||||||
|
this.multidexEnabled = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// The build info containing the mode and flavor.
|
// The build info containing the mode and flavor.
|
||||||
@ -321,6 +322,9 @@ class AndroidBuildInfo {
|
|||||||
|
|
||||||
/// Whether to bootstrap an empty application.
|
/// Whether to bootstrap an empty application.
|
||||||
final bool fastStart;
|
final bool fastStart;
|
||||||
|
|
||||||
|
/// Whether to enable multidex support for apps with more than 64k methods.
|
||||||
|
final bool multidexEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A summary of the compilation strategy used for Dart.
|
/// A summary of the compilation strategy used for Dart.
|
||||||
|
@ -35,6 +35,7 @@ class BuildApkCommand extends BuildSubCommand {
|
|||||||
addNullSafetyModeOptions(hide: !verboseHelp);
|
addNullSafetyModeOptions(hide: !verboseHelp);
|
||||||
usesAnalyzeSizeFlag();
|
usesAnalyzeSizeFlag();
|
||||||
addAndroidSpecificBuildOptions(hide: !verboseHelp);
|
addAndroidSpecificBuildOptions(hide: !verboseHelp);
|
||||||
|
addMultidexOption();
|
||||||
argParser
|
argParser
|
||||||
..addFlag('split-per-abi',
|
..addFlag('split-per-abi',
|
||||||
negatable: false,
|
negatable: false,
|
||||||
@ -99,9 +100,11 @@ class BuildApkCommand extends BuildSubCommand {
|
|||||||
buildInfo,
|
buildInfo,
|
||||||
splitPerAbi: boolArg('split-per-abi'),
|
splitPerAbi: boolArg('split-per-abi'),
|
||||||
targetArchs: stringsArg('target-platform').map<AndroidArch>(getAndroidArchForName),
|
targetArchs: stringsArg('target-platform').map<AndroidArch>(getAndroidArchForName),
|
||||||
|
multidexEnabled: boolArg('multidex'),
|
||||||
);
|
);
|
||||||
validateBuild(androidBuildInfo);
|
validateBuild(androidBuildInfo);
|
||||||
displayNullSafetyMode(androidBuildInfo.buildInfo);
|
displayNullSafetyMode(androidBuildInfo.buildInfo);
|
||||||
|
globals.terminal.usesTerminalUi = true;
|
||||||
await androidBuilder.buildApk(
|
await androidBuilder.buildApk(
|
||||||
project: FlutterProject.current(),
|
project: FlutterProject.current(),
|
||||||
target: targetFile,
|
target: targetFile,
|
||||||
|
@ -39,6 +39,7 @@ class BuildAppBundleCommand extends BuildSubCommand {
|
|||||||
addEnableExperimentation(hide: !verboseHelp);
|
addEnableExperimentation(hide: !verboseHelp);
|
||||||
usesAnalyzeSizeFlag();
|
usesAnalyzeSizeFlag();
|
||||||
addAndroidSpecificBuildOptions(hide: !verboseHelp);
|
addAndroidSpecificBuildOptions(hide: !verboseHelp);
|
||||||
|
addMultidexOption();
|
||||||
argParser.addMultiOption('target-platform',
|
argParser.addMultiOption('target-platform',
|
||||||
splitCommas: true,
|
splitCommas: true,
|
||||||
defaultsTo: <String>['android-arm', 'android-arm64', 'android-x64'],
|
defaultsTo: <String>['android-arm', 'android-arm64', 'android-x64'],
|
||||||
@ -110,6 +111,7 @@ class BuildAppBundleCommand extends BuildSubCommand {
|
|||||||
|
|
||||||
final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(await getBuildInfo(),
|
final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(await getBuildInfo(),
|
||||||
targetArchs: stringsArg('target-platform').map<AndroidArch>(getAndroidArchForName),
|
targetArchs: stringsArg('target-platform').map<AndroidArch>(getAndroidArchForName),
|
||||||
|
multidexEnabled: boolArg('multidex'),
|
||||||
);
|
);
|
||||||
// Do all setup verification that doesn't involve loading units. Checks that
|
// Do all setup verification that doesn't involve loading units. Checks that
|
||||||
// require generated loading units are done after gen_snapshot in assemble.
|
// require generated loading units are done after gen_snapshot in assemble.
|
||||||
@ -144,6 +146,7 @@ class BuildAppBundleCommand extends BuildSubCommand {
|
|||||||
|
|
||||||
validateBuild(androidBuildInfo);
|
validateBuild(androidBuildInfo);
|
||||||
displayNullSafetyMode(androidBuildInfo.buildInfo);
|
displayNullSafetyMode(androidBuildInfo.buildInfo);
|
||||||
|
globals.terminal.usesTerminalUi = true;
|
||||||
await androidBuilder.buildAab(
|
await androidBuilder.buildAab(
|
||||||
project: FlutterProject.current(),
|
project: FlutterProject.current(),
|
||||||
target: targetFile,
|
target: targetFile,
|
||||||
|
@ -65,6 +65,7 @@ class DriveCommand extends RunCommandBase {
|
|||||||
// to prevent a local network permission dialog on iOS 14+,
|
// to prevent a local network permission dialog on iOS 14+,
|
||||||
// which cannot be accepted or dismissed in a CI environment.
|
// which cannot be accepted or dismissed in a CI environment.
|
||||||
addPublishPort(enabledByDefault: false, verboseHelp: verboseHelp);
|
addPublishPort(enabledByDefault: false, verboseHelp: verboseHelp);
|
||||||
|
addMultidexOption();
|
||||||
argParser
|
argParser
|
||||||
..addFlag('keep-app-running',
|
..addFlag('keep-app-running',
|
||||||
defaultsTo: null,
|
defaultsTo: null,
|
||||||
@ -251,6 +252,8 @@ class DriveCommand extends RunCommandBase {
|
|||||||
'trace-startup': traceStartup,
|
'trace-startup': traceStartup,
|
||||||
if (web)
|
if (web)
|
||||||
'--no-launch-chrome': true,
|
'--no-launch-chrome': true,
|
||||||
|
if (boolArg('multidex'))
|
||||||
|
'multidex': true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -249,6 +249,7 @@ class RunCommand extends RunCommandBase {
|
|||||||
// This will allow subsequent "flutter attach" commands to connect to the VM
|
// This will allow subsequent "flutter attach" commands to connect to the VM
|
||||||
// without needing to know the port.
|
// without needing to know the port.
|
||||||
addPublishPort(verboseHelp: verboseHelp);
|
addPublishPort(verboseHelp: verboseHelp);
|
||||||
|
addMultidexOption();
|
||||||
argParser
|
argParser
|
||||||
..addFlag('enable-software-rendering',
|
..addFlag('enable-software-rendering',
|
||||||
negatable: false,
|
negatable: false,
|
||||||
@ -500,6 +501,7 @@ class RunCommand extends RunCommandBase {
|
|||||||
dillOutputPath: stringArg('output-dill'),
|
dillOutputPath: stringArg('output-dill'),
|
||||||
stayResident: stayResident,
|
stayResident: stayResident,
|
||||||
ipv6: ipv6,
|
ipv6: ipv6,
|
||||||
|
multidexEnabled: boolArg('multidex'),
|
||||||
);
|
);
|
||||||
} else if (webMode) {
|
} else if (webMode) {
|
||||||
return webRunnerFactory.createWebRunner(
|
return webRunnerFactory.createWebRunner(
|
||||||
|
@ -416,7 +416,9 @@ class FlutterDevice {
|
|||||||
}
|
}
|
||||||
devFSWriter = device.createDevFSWriter(package, userIdentifier);
|
devFSWriter = device.createDevFSWriter(package, userIdentifier);
|
||||||
|
|
||||||
final Map<String, dynamic> platformArgs = <String, dynamic>{};
|
final Map<String, dynamic> platformArgs = <String, dynamic>{
|
||||||
|
'multidex': hotRunner.multidexEnabled,
|
||||||
|
};
|
||||||
|
|
||||||
await startEchoingDeviceLog();
|
await startEchoingDeviceLog();
|
||||||
|
|
||||||
|
@ -93,6 +93,7 @@ class HotRunner extends ResidentRunner {
|
|||||||
bool stayResident = true,
|
bool stayResident = true,
|
||||||
bool ipv6 = false,
|
bool ipv6 = false,
|
||||||
bool machine = false,
|
bool machine = false,
|
||||||
|
this.multidexEnabled = false,
|
||||||
ResidentDevtoolsHandlerFactory devtoolsHandler = createDefaultHandler,
|
ResidentDevtoolsHandlerFactory devtoolsHandler = createDefaultHandler,
|
||||||
StopwatchFactory stopwatchFactory = const StopwatchFactory(),
|
StopwatchFactory stopwatchFactory = const StopwatchFactory(),
|
||||||
ReloadSourcesHelper reloadSourcesHelper = _defaultReloadSourcesHelper,
|
ReloadSourcesHelper reloadSourcesHelper = _defaultReloadSourcesHelper,
|
||||||
@ -120,6 +121,7 @@ class HotRunner extends ResidentRunner {
|
|||||||
final bool benchmarkMode;
|
final bool benchmarkMode;
|
||||||
final File applicationBinary;
|
final File applicationBinary;
|
||||||
final bool hostIsIde;
|
final bool hostIsIde;
|
||||||
|
final bool multidexEnabled;
|
||||||
|
|
||||||
/// When performing a hot restart, the tool needs to upload a new main.dart.dill to
|
/// When performing a hot restart, the tool needs to upload a new main.dart.dill to
|
||||||
/// each attached device's devfs. Replacing the existing file is not safe and does
|
/// each attached device's devfs. Replacing the existing file is not safe and does
|
||||||
|
@ -818,6 +818,16 @@ abstract class FlutterCommand extends Command<void> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addMultidexOption({ bool hide = false }) {
|
||||||
|
argParser.addFlag('multidex',
|
||||||
|
negatable: true,
|
||||||
|
defaultsTo: true,
|
||||||
|
help: 'When enabled, indicates that the app should be built with multidex support. This '
|
||||||
|
'flag adds the dependencies for multidex when the minimum android sdk is 20 or '
|
||||||
|
'below. For android sdk versions 21 and above, multidex support is native.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds build options common to all of the desktop build commands.
|
/// Adds build options common to all of the desktop build commands.
|
||||||
void addCommonDesktopBuildOptions({ @required bool verboseHelp }) {
|
void addCommonDesktopBuildOptions({ @required bool verboseHelp }) {
|
||||||
addBuildModeFlags(verboseHelp: verboseHelp);
|
addBuildModeFlags(verboseHelp: verboseHelp);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
package="{{androidIdentifier}}">
|
package="{{androidIdentifier}}">
|
||||||
<application
|
<application
|
||||||
android:label="{{projectName}}"
|
android:label="{{projectName}}"
|
||||||
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
@ -157,6 +157,7 @@ void main() {
|
|||||||
'-q',
|
'-q',
|
||||||
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
||||||
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
|
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
|
||||||
|
'-Pbase-application-name=android.app.Application',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Ptrack-widget-creation=true',
|
'-Ptrack-widget-creation=true',
|
||||||
'-Ptree-shake-icons=true',
|
'-Ptree-shake-icons=true',
|
||||||
@ -186,6 +187,7 @@ void main() {
|
|||||||
'-q',
|
'-q',
|
||||||
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
||||||
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
|
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
|
||||||
|
'-Pbase-application-name=android.app.Application',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Psplit-debug-info=${tempDir.path}',
|
'-Psplit-debug-info=${tempDir.path}',
|
||||||
'-Ptrack-widget-creation=true',
|
'-Ptrack-widget-creation=true',
|
||||||
@ -216,6 +218,7 @@ void main() {
|
|||||||
'-q',
|
'-q',
|
||||||
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
||||||
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
|
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
|
||||||
|
'-Pbase-application-name=android.app.Application',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Pextra-front-end-options=foo,bar',
|
'-Pextra-front-end-options=foo,bar',
|
||||||
'-Ptrack-widget-creation=true',
|
'-Ptrack-widget-creation=true',
|
||||||
@ -246,6 +249,7 @@ void main() {
|
|||||||
'-q',
|
'-q',
|
||||||
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
||||||
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
|
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
|
||||||
|
'-Pbase-application-name=android.app.Application',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Ptrack-widget-creation=true',
|
'-Ptrack-widget-creation=true',
|
||||||
'-Ptree-shake-icons=true',
|
'-Ptree-shake-icons=true',
|
||||||
@ -281,6 +285,7 @@ void main() {
|
|||||||
'-q',
|
'-q',
|
||||||
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
||||||
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
|
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
|
||||||
|
'-Pbase-application-name=android.app.Application',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Ptrack-widget-creation=true',
|
'-Ptrack-widget-creation=true',
|
||||||
'-Ptree-shake-icons=true',
|
'-Ptree-shake-icons=true',
|
||||||
@ -335,6 +340,7 @@ void main() {
|
|||||||
'-q',
|
'-q',
|
||||||
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
||||||
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
|
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
|
||||||
|
'-Pbase-application-name=android.app.Application',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Ptrack-widget-creation=true',
|
'-Ptrack-widget-creation=true',
|
||||||
'-Ptree-shake-icons=true',
|
'-Ptree-shake-icons=true',
|
||||||
@ -381,6 +387,7 @@ void main() {
|
|||||||
'-q',
|
'-q',
|
||||||
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
||||||
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
|
'-Ptarget=${globals.fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
|
||||||
|
'-Pbase-application-name=android.app.Application',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Ptrack-widget-creation=true',
|
'-Ptrack-widget-creation=true',
|
||||||
'-Ptree-shake-icons=true',
|
'-Ptree-shake-icons=true',
|
||||||
|
@ -56,6 +56,7 @@ void main() {
|
|||||||
'-q',
|
'-q',
|
||||||
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
||||||
'-Ptarget=lib/main.dart',
|
'-Ptarget=lib/main.dart',
|
||||||
|
'-Pbase-application-name=io.flutter.app.FlutterApplication',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Ptrack-widget-creation=false',
|
'-Ptrack-widget-creation=false',
|
||||||
'-Ptree-shake-icons=false',
|
'-Ptree-shake-icons=false',
|
||||||
@ -101,6 +102,7 @@ void main() {
|
|||||||
String line,
|
String line,
|
||||||
FlutterProject project,
|
FlutterProject project,
|
||||||
bool usesAndroidX,
|
bool usesAndroidX,
|
||||||
|
bool multidexEnabled
|
||||||
}) async {
|
}) async {
|
||||||
handlerCalled = true;
|
handlerCalled = true;
|
||||||
return GradleBuildStatus.exit;
|
return GradleBuildStatus.exit;
|
||||||
@ -142,6 +144,7 @@ void main() {
|
|||||||
'-q',
|
'-q',
|
||||||
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
||||||
'-Ptarget=lib/main.dart',
|
'-Ptarget=lib/main.dart',
|
||||||
|
'-Pbase-application-name=io.flutter.app.FlutterApplication',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Ptrack-widget-creation=false',
|
'-Ptrack-widget-creation=false',
|
||||||
'-Ptree-shake-icons=false',
|
'-Ptree-shake-icons=false',
|
||||||
@ -156,6 +159,7 @@ void main() {
|
|||||||
'-q',
|
'-q',
|
||||||
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
||||||
'-Ptarget=lib/main.dart',
|
'-Ptarget=lib/main.dart',
|
||||||
|
'-Pbase-application-name=io.flutter.app.FlutterApplication',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Ptrack-widget-creation=false',
|
'-Ptrack-widget-creation=false',
|
||||||
'-Ptree-shake-icons=false',
|
'-Ptree-shake-icons=false',
|
||||||
@ -205,6 +209,7 @@ void main() {
|
|||||||
String line,
|
String line,
|
||||||
FlutterProject project,
|
FlutterProject project,
|
||||||
bool usesAndroidX,
|
bool usesAndroidX,
|
||||||
|
bool multidexEnabled
|
||||||
}) async {
|
}) async {
|
||||||
return GradleBuildStatus.retry;
|
return GradleBuildStatus.retry;
|
||||||
},
|
},
|
||||||
@ -243,6 +248,7 @@ void main() {
|
|||||||
'-q',
|
'-q',
|
||||||
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
||||||
'-Ptarget=lib/main.dart',
|
'-Ptarget=lib/main.dart',
|
||||||
|
'-Pbase-application-name=io.flutter.app.FlutterApplication',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Ptrack-widget-creation=false',
|
'-Ptrack-widget-creation=false',
|
||||||
'-Ptree-shake-icons=false',
|
'-Ptree-shake-icons=false',
|
||||||
@ -288,6 +294,7 @@ void main() {
|
|||||||
String line,
|
String line,
|
||||||
FlutterProject project,
|
FlutterProject project,
|
||||||
bool usesAndroidX,
|
bool usesAndroidX,
|
||||||
|
bool multidexEnabled
|
||||||
}) async {
|
}) async {
|
||||||
handlerCalled = true;
|
handlerCalled = true;
|
||||||
return GradleBuildStatus.exit;
|
return GradleBuildStatus.exit;
|
||||||
@ -329,6 +336,7 @@ void main() {
|
|||||||
'-q',
|
'-q',
|
||||||
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
||||||
'-Ptarget=lib/main.dart',
|
'-Ptarget=lib/main.dart',
|
||||||
|
'-Pbase-application-name=io.flutter.app.FlutterApplication',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Ptrack-widget-creation=false',
|
'-Ptrack-widget-creation=false',
|
||||||
'-Ptree-shake-icons=false',
|
'-Ptree-shake-icons=false',
|
||||||
@ -388,6 +396,7 @@ void main() {
|
|||||||
'-q',
|
'-q',
|
||||||
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
||||||
'-Ptarget=lib/main.dart',
|
'-Ptarget=lib/main.dart',
|
||||||
|
'-Pbase-application-name=io.flutter.app.FlutterApplication',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Ptrack-widget-creation=false',
|
'-Ptrack-widget-creation=false',
|
||||||
'-Ptree-shake-icons=false',
|
'-Ptree-shake-icons=false',
|
||||||
@ -402,6 +411,7 @@ void main() {
|
|||||||
'-q',
|
'-q',
|
||||||
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
||||||
'-Ptarget=lib/main.dart',
|
'-Ptarget=lib/main.dart',
|
||||||
|
'-Pbase-application-name=io.flutter.app.FlutterApplication',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Ptrack-widget-creation=false',
|
'-Ptrack-widget-creation=false',
|
||||||
'-Ptree-shake-icons=false',
|
'-Ptree-shake-icons=false',
|
||||||
@ -450,6 +460,7 @@ void main() {
|
|||||||
String line,
|
String line,
|
||||||
FlutterProject project,
|
FlutterProject project,
|
||||||
bool usesAndroidX,
|
bool usesAndroidX,
|
||||||
|
bool multidexEnabled
|
||||||
}) async {
|
}) async {
|
||||||
return GradleBuildStatus.retry;
|
return GradleBuildStatus.retry;
|
||||||
},
|
},
|
||||||
@ -488,6 +499,7 @@ void main() {
|
|||||||
'-q',
|
'-q',
|
||||||
'-Ptarget-platform=android-arm64',
|
'-Ptarget-platform=android-arm64',
|
||||||
'-Ptarget=lib/main.dart',
|
'-Ptarget=lib/main.dart',
|
||||||
|
'-Pbase-application-name=io.flutter.app.FlutterApplication',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Ptrack-widget-creation=false',
|
'-Ptrack-widget-creation=false',
|
||||||
'-Ptree-shake-icons=false',
|
'-Ptree-shake-icons=false',
|
||||||
@ -580,6 +592,7 @@ void main() {
|
|||||||
'-q',
|
'-q',
|
||||||
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
||||||
'-Ptarget=lib/main.dart',
|
'-Ptarget=lib/main.dart',
|
||||||
|
'-Pbase-application-name=io.flutter.app.FlutterApplication',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Ptrack-widget-creation=false',
|
'-Ptrack-widget-creation=false',
|
||||||
'-Ptree-shake-icons=false',
|
'-Ptree-shake-icons=false',
|
||||||
@ -765,6 +778,7 @@ void main() {
|
|||||||
'-Plocal-engine-out=out/android_arm',
|
'-Plocal-engine-out=out/android_arm',
|
||||||
'-Ptarget-platform=android-arm',
|
'-Ptarget-platform=android-arm',
|
||||||
'-Ptarget=lib/main.dart',
|
'-Ptarget=lib/main.dart',
|
||||||
|
'-Pbase-application-name=io.flutter.app.FlutterApplication',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Ptrack-widget-creation=false',
|
'-Ptrack-widget-creation=false',
|
||||||
'-Ptree-shake-icons=false',
|
'-Ptree-shake-icons=false',
|
||||||
@ -838,6 +852,7 @@ void main() {
|
|||||||
'-Plocal-engine-out=out/android_arm64',
|
'-Plocal-engine-out=out/android_arm64',
|
||||||
'-Ptarget-platform=android-arm64',
|
'-Ptarget-platform=android-arm64',
|
||||||
'-Ptarget=lib/main.dart',
|
'-Ptarget=lib/main.dart',
|
||||||
|
'-Pbase-application-name=io.flutter.app.FlutterApplication',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Ptrack-widget-creation=false',
|
'-Ptrack-widget-creation=false',
|
||||||
'-Ptree-shake-icons=false',
|
'-Ptree-shake-icons=false',
|
||||||
@ -911,6 +926,7 @@ void main() {
|
|||||||
'-Plocal-engine-out=out/android_x86',
|
'-Plocal-engine-out=out/android_x86',
|
||||||
'-Ptarget-platform=android-x86',
|
'-Ptarget-platform=android-x86',
|
||||||
'-Ptarget=lib/main.dart',
|
'-Ptarget=lib/main.dart',
|
||||||
|
'-Pbase-application-name=io.flutter.app.FlutterApplication',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Ptrack-widget-creation=false',
|
'-Ptrack-widget-creation=false',
|
||||||
'-Ptree-shake-icons=false',
|
'-Ptree-shake-icons=false',
|
||||||
@ -984,6 +1000,7 @@ void main() {
|
|||||||
'-Plocal-engine-out=out/android_x64',
|
'-Plocal-engine-out=out/android_x64',
|
||||||
'-Ptarget-platform=android-x64',
|
'-Ptarget-platform=android-x64',
|
||||||
'-Ptarget=lib/main.dart',
|
'-Ptarget=lib/main.dart',
|
||||||
|
'-Pbase-application-name=io.flutter.app.FlutterApplication',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Ptrack-widget-creation=false',
|
'-Ptrack-widget-creation=false',
|
||||||
'-Ptree-shake-icons=false',
|
'-Ptree-shake-icons=false',
|
||||||
@ -1056,6 +1073,7 @@ void main() {
|
|||||||
'--no-daemon',
|
'--no-daemon',
|
||||||
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
'-Ptarget-platform=android-arm,android-arm64,android-x64',
|
||||||
'-Ptarget=lib/main.dart',
|
'-Ptarget=lib/main.dart',
|
||||||
|
'-Pbase-application-name=io.flutter.app.FlutterApplication',
|
||||||
'-Pdart-obfuscation=false',
|
'-Pdart-obfuscation=false',
|
||||||
'-Ptrack-widget-creation=false',
|
'-Ptrack-widget-creation=false',
|
||||||
'-Ptree-shake-icons=false',
|
'-Ptree-shake-icons=false',
|
||||||
|
@ -9,7 +9,9 @@ import 'package:file_testing/file_testing.dart';
|
|||||||
import 'package:flutter_tools/src/android/gradle_errors.dart';
|
import 'package:flutter_tools/src/android/gradle_errors.dart';
|
||||||
import 'package:flutter_tools/src/android/gradle_utils.dart';
|
import 'package:flutter_tools/src/android/gradle_utils.dart';
|
||||||
import 'package:flutter_tools/src/base/file_system.dart';
|
import 'package:flutter_tools/src/base/file_system.dart';
|
||||||
|
import 'package:flutter_tools/src/base/logger.dart';
|
||||||
import 'package:flutter_tools/src/base/platform.dart';
|
import 'package:flutter_tools/src/base/platform.dart';
|
||||||
|
import 'package:flutter_tools/src/base/terminal.dart';
|
||||||
import 'package:flutter_tools/src/globals_null_migrated.dart' as globals;
|
import 'package:flutter_tools/src/globals_null_migrated.dart' as globals;
|
||||||
import 'package:flutter_tools/src/project.dart';
|
import 'package:flutter_tools/src/project.dart';
|
||||||
|
|
||||||
@ -30,6 +32,7 @@ void main() {
|
|||||||
minSdkVersion,
|
minSdkVersion,
|
||||||
transformInputIssue,
|
transformInputIssue,
|
||||||
lockFileDepMissing,
|
lockFileDepMissing,
|
||||||
|
multidexErrorHandler,
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -329,6 +332,213 @@ A problem occurred configuring root project 'android'.
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('multidex errors', () {
|
||||||
|
testUsingContext('exits if multidex AndroidManifest not detected', () async {
|
||||||
|
const String errorMessage = r'''
|
||||||
|
Caused by: com.android.tools.r8.utils.b: Cannot fit requested classes in a single dex file (# methods: 85091 > 65536)
|
||||||
|
at com.android.tools.r8.utils.T0.error(SourceFile:1)
|
||||||
|
at com.android.tools.r8.utils.T0.a(SourceFile:2)
|
||||||
|
at com.android.tools.r8.dex.P.a(SourceFile:740)
|
||||||
|
at com.android.tools.r8.dex.P$h.a(SourceFile:7)
|
||||||
|
at com.android.tools.r8.dex.b.a(SourceFile:14)
|
||||||
|
at com.android.tools.r8.dex.b.b(SourceFile:25)
|
||||||
|
at com.android.tools.r8.D8.d(D8.java:133)
|
||||||
|
at com.android.tools.r8.D8.b(D8.java:1)
|
||||||
|
at com.android.tools.r8.utils.Y.a(SourceFile:36)
|
||||||
|
... 38 more
|
||||||
|
|
||||||
|
|
||||||
|
FAILURE: Build failed with an exception.
|
||||||
|
|
||||||
|
* What went wrong:
|
||||||
|
Execution failed for task ':app:mergeDexDebug'.
|
||||||
|
> A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade
|
||||||
|
> com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives:
|
||||||
|
The number of method references in a .dex file cannot exceed 64K.
|
||||||
|
Learn how to resolve this issue at https://developer.android.com/tools/building/multidex.html''';
|
||||||
|
|
||||||
|
expect(formatTestErrorMessage(errorMessage, multidexErrorHandler), isTrue);
|
||||||
|
expect(await multidexErrorHandler.handler(project: FlutterProject.fromDirectory(globals.fs.currentDirectory), multidexEnabled: true), equals(GradleBuildStatus.exit));
|
||||||
|
|
||||||
|
expect(testLogger.statusText,
|
||||||
|
contains(
|
||||||
|
'Multidex support is required for your android app to build since the number of methods has exceeded 64k.'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
expect(testLogger.statusText,
|
||||||
|
contains(
|
||||||
|
'Your `android/app/src/main/AndroidManifest.xml` does not contain'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => MemoryFileSystem.test(),
|
||||||
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
|
});
|
||||||
|
testUsingContext('retries if multidex support enabled', () async {
|
||||||
|
const String errorMessage = r'''
|
||||||
|
Caused by: com.android.tools.r8.utils.b: Cannot fit requested classes in a single dex file (# methods: 85091 > 65536)
|
||||||
|
at com.android.tools.r8.utils.T0.error(SourceFile:1)
|
||||||
|
at com.android.tools.r8.utils.T0.a(SourceFile:2)
|
||||||
|
at com.android.tools.r8.dex.P.a(SourceFile:740)
|
||||||
|
at com.android.tools.r8.dex.P$h.a(SourceFile:7)
|
||||||
|
at com.android.tools.r8.dex.b.a(SourceFile:14)
|
||||||
|
at com.android.tools.r8.dex.b.b(SourceFile:25)
|
||||||
|
at com.android.tools.r8.D8.d(D8.java:133)
|
||||||
|
at com.android.tools.r8.D8.b(D8.java:1)
|
||||||
|
at com.android.tools.r8.utils.Y.a(SourceFile:36)
|
||||||
|
... 38 more
|
||||||
|
|
||||||
|
|
||||||
|
FAILURE: Build failed with an exception.
|
||||||
|
|
||||||
|
* What went wrong:
|
||||||
|
Execution failed for task ':app:mergeDexDebug'.
|
||||||
|
> A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade
|
||||||
|
> com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives:
|
||||||
|
The number of method references in a .dex file cannot exceed 64K.
|
||||||
|
Learn how to resolve this issue at https://developer.android.com/tools/building/multidex.html''';
|
||||||
|
|
||||||
|
final File manifest = globals.fs.currentDirectory
|
||||||
|
.childDirectory('android')
|
||||||
|
.childDirectory('app')
|
||||||
|
.childDirectory('src')
|
||||||
|
.childDirectory('main')
|
||||||
|
.childFile('AndroidManifest.xml');
|
||||||
|
manifest.createSync(recursive: true);
|
||||||
|
manifest.writeAsStringSync(r'''
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.multidexapp">
|
||||||
|
<application
|
||||||
|
android:label="multidextest2"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
|
''', flush: true);
|
||||||
|
|
||||||
|
expect(formatTestErrorMessage(errorMessage, multidexErrorHandler), isTrue);
|
||||||
|
expect(await multidexErrorHandler.handler(project: FlutterProject.fromDirectory(globals.fs.currentDirectory), multidexEnabled: true), equals(GradleBuildStatus.retry));
|
||||||
|
|
||||||
|
expect(testLogger.statusText,
|
||||||
|
contains(
|
||||||
|
'Multidex support is required for your android app to build since the number of methods has exceeded 64k.'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
expect(testLogger.statusText,
|
||||||
|
contains(
|
||||||
|
'android/app/src/main/java/io/flutter/app/FlutterMultiDexApplication.java'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => MemoryFileSystem.test(),
|
||||||
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
|
AnsiTerminal: () => _TestPromptTerminal('y')
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('exits if multidex support skipped', () async {
|
||||||
|
const String errorMessage = r'''
|
||||||
|
Caused by: com.android.tools.r8.utils.b: Cannot fit requested classes in a single dex file (# methods: 85091 > 65536)
|
||||||
|
at com.android.tools.r8.utils.T0.error(SourceFile:1)
|
||||||
|
at com.android.tools.r8.utils.T0.a(SourceFile:2)
|
||||||
|
at com.android.tools.r8.dex.P.a(SourceFile:740)
|
||||||
|
at com.android.tools.r8.dex.P$h.a(SourceFile:7)
|
||||||
|
at com.android.tools.r8.dex.b.a(SourceFile:14)
|
||||||
|
at com.android.tools.r8.dex.b.b(SourceFile:25)
|
||||||
|
at com.android.tools.r8.D8.d(D8.java:133)
|
||||||
|
at com.android.tools.r8.D8.b(D8.java:1)
|
||||||
|
at com.android.tools.r8.utils.Y.a(SourceFile:36)
|
||||||
|
... 38 more
|
||||||
|
|
||||||
|
|
||||||
|
FAILURE: Build failed with an exception.
|
||||||
|
|
||||||
|
* What went wrong:
|
||||||
|
Execution failed for task ':app:mergeDexDebug'.
|
||||||
|
> A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade
|
||||||
|
> com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives:
|
||||||
|
The number of method references in a .dex file cannot exceed 64K.
|
||||||
|
Learn how to resolve this issue at https://developer.android.com/tools/building/multidex.html''';
|
||||||
|
|
||||||
|
final File manifest = globals.fs.currentDirectory
|
||||||
|
.childDirectory('android')
|
||||||
|
.childDirectory('app')
|
||||||
|
.childDirectory('src')
|
||||||
|
.childDirectory('main')
|
||||||
|
.childFile('AndroidManifest.xml');
|
||||||
|
manifest.createSync(recursive: true);
|
||||||
|
manifest.writeAsStringSync(r'''
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.multidexapp">
|
||||||
|
<application
|
||||||
|
android:label="multidextest2"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
|
''', flush: true);
|
||||||
|
|
||||||
|
expect(formatTestErrorMessage(errorMessage, multidexErrorHandler), isTrue);
|
||||||
|
expect(await multidexErrorHandler.handler(project: FlutterProject.fromDirectory(globals.fs.currentDirectory), multidexEnabled: true), equals(GradleBuildStatus.exit));
|
||||||
|
|
||||||
|
expect(testLogger.statusText,
|
||||||
|
contains(
|
||||||
|
'Multidex support is required for your android app to build since the number of methods has exceeded 64k.'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
expect(testLogger.statusText,
|
||||||
|
contains(
|
||||||
|
'Flutter tool can add multidex support. The following file will be added by flutter:'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
expect(testLogger.statusText,
|
||||||
|
contains(
|
||||||
|
'android/app/src/main/java/io/flutter/app/FlutterMultiDexApplication.java'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => MemoryFileSystem.test(),
|
||||||
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
|
AnsiTerminal: () => _TestPromptTerminal('n')
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('exits if multidex support disabled', () async {
|
||||||
|
const String errorMessage = r'''
|
||||||
|
Caused by: com.android.tools.r8.utils.b: Cannot fit requested classes in a single dex file (# methods: 85091 > 65536)
|
||||||
|
at com.android.tools.r8.utils.T0.error(SourceFile:1)
|
||||||
|
at com.android.tools.r8.utils.T0.a(SourceFile:2)
|
||||||
|
at com.android.tools.r8.dex.P.a(SourceFile:740)
|
||||||
|
at com.android.tools.r8.dex.P$h.a(SourceFile:7)
|
||||||
|
at com.android.tools.r8.dex.b.a(SourceFile:14)
|
||||||
|
at com.android.tools.r8.dex.b.b(SourceFile:25)
|
||||||
|
at com.android.tools.r8.D8.d(D8.java:133)
|
||||||
|
at com.android.tools.r8.D8.b(D8.java:1)
|
||||||
|
at com.android.tools.r8.utils.Y.a(SourceFile:36)
|
||||||
|
... 38 more
|
||||||
|
|
||||||
|
|
||||||
|
FAILURE: Build failed with an exception.
|
||||||
|
|
||||||
|
* What went wrong:
|
||||||
|
Execution failed for task ':app:mergeDexDebug'.
|
||||||
|
> A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade
|
||||||
|
> com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives:
|
||||||
|
The number of method references in a .dex file cannot exceed 64K.
|
||||||
|
Learn how to resolve this issue at https://developer.android.com/tools/building/multidex.html''';
|
||||||
|
|
||||||
|
expect(formatTestErrorMessage(errorMessage, multidexErrorHandler), isTrue);
|
||||||
|
expect(await multidexErrorHandler.handler(project: FlutterProject.fromDirectory(globals.fs.currentDirectory), multidexEnabled: false), equals(GradleBuildStatus.exit));
|
||||||
|
|
||||||
|
expect(testLogger.statusText,
|
||||||
|
contains(
|
||||||
|
'Flutter multidex handling is disabled.'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => MemoryFileSystem.test(),
|
||||||
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
group('permission errors', () {
|
group('permission errors', () {
|
||||||
testUsingContext('throws toolExit if gradle is missing execute permissions', () async {
|
testUsingContext('throws toolExit if gradle is missing execute permissions', () async {
|
||||||
const String errorMessage = '''
|
const String errorMessage = '''
|
||||||
@ -667,3 +877,21 @@ class FakeGradleUtils extends GradleUtils {
|
|||||||
return 'gradlew';
|
return 'gradlew';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Simple terminal that returns the specified string when
|
||||||
|
/// promptForCharInput is called.
|
||||||
|
class _TestPromptTerminal extends AnsiTerminal {
|
||||||
|
_TestPromptTerminal(this.promptResult);
|
||||||
|
|
||||||
|
final String promptResult;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> promptForCharInput(List<String> acceptedCharacters, {
|
||||||
|
Logger logger,
|
||||||
|
String prompt,
|
||||||
|
int defaultChoiceIndex,
|
||||||
|
bool displayAcceptedCharacters = true,
|
||||||
|
}) {
|
||||||
|
return Future<String>.value(promptResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,189 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
// @dart = 2.8
|
||||||
|
|
||||||
|
import 'package:file/file.dart';
|
||||||
|
import 'package:file/memory.dart';
|
||||||
|
import 'package:flutter_tools/src/android/multidex.dart';
|
||||||
|
import 'package:flutter_tools/src/base/file_system.dart';
|
||||||
|
import 'package:flutter_tools/src/globals_null_migrated.dart' as globals;
|
||||||
|
|
||||||
|
import '../../src/common.dart';
|
||||||
|
import '../../src/context.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testUsingContext('ensureMultidexUtilsExists returns when exists', () async {
|
||||||
|
final Directory directory = globals.fs.currentDirectory;
|
||||||
|
final File applicationFile = directory.childDirectory('android')
|
||||||
|
.childDirectory('app')
|
||||||
|
.childDirectory('src')
|
||||||
|
.childDirectory('main')
|
||||||
|
.childDirectory('java')
|
||||||
|
.childDirectory('io')
|
||||||
|
.childDirectory('flutter')
|
||||||
|
.childDirectory('app')
|
||||||
|
.childFile('FlutterMultiDexApplication.java');
|
||||||
|
applicationFile.createSync(recursive: true);
|
||||||
|
applicationFile.writeAsStringSync('hello', flush: true);
|
||||||
|
expect(applicationFile.readAsStringSync(), 'hello');
|
||||||
|
|
||||||
|
ensureMultiDexApplicationExists(directory);
|
||||||
|
|
||||||
|
// File should remain untouched
|
||||||
|
expect(applicationFile.readAsStringSync(), 'hello');
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => MemoryFileSystem.test(),
|
||||||
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('ensureMultiDexApplicationExists generates when does not exist', () async {
|
||||||
|
final Directory directory = globals.fs.currentDirectory;
|
||||||
|
final File applicationFile = directory.childDirectory('android')
|
||||||
|
.childDirectory('app')
|
||||||
|
.childDirectory('src')
|
||||||
|
.childDirectory('main')
|
||||||
|
.childDirectory('java')
|
||||||
|
.childDirectory('io')
|
||||||
|
.childDirectory('flutter')
|
||||||
|
.childDirectory('app')
|
||||||
|
.childFile('FlutterMultiDexApplication.java');
|
||||||
|
|
||||||
|
ensureMultiDexApplicationExists(directory);
|
||||||
|
|
||||||
|
final String contents = applicationFile.readAsStringSync();
|
||||||
|
expect(contents.contains('FlutterMultiDexApplication'), true);
|
||||||
|
expect(contents.contains('MultiDex.install(this);'), true);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => MemoryFileSystem.test(),
|
||||||
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('multiDexApplicationExists false when does not exist', () async {
|
||||||
|
final Directory directory = globals.fs.currentDirectory;
|
||||||
|
expect(multiDexApplicationExists(directory), false);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => MemoryFileSystem.test(),
|
||||||
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('multiDexApplicationExists true when does exist', () async {
|
||||||
|
final Directory directory = globals.fs.currentDirectory;
|
||||||
|
final File utilsFile = directory.childDirectory('android')
|
||||||
|
.childDirectory('app')
|
||||||
|
.childDirectory('src')
|
||||||
|
.childDirectory('main')
|
||||||
|
.childDirectory('java')
|
||||||
|
.childDirectory('io')
|
||||||
|
.childDirectory('flutter')
|
||||||
|
.childDirectory('app')
|
||||||
|
.childFile('FlutterMultiDexApplication.java');
|
||||||
|
utilsFile.createSync(recursive: true);
|
||||||
|
|
||||||
|
expect(multiDexApplicationExists(directory), true);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => MemoryFileSystem.test(),
|
||||||
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('androidManifestHasNameVariable true with valid manifest', () async {
|
||||||
|
final Directory directory = globals.fs.currentDirectory;
|
||||||
|
final File applicationFile = directory.childDirectory('android')
|
||||||
|
.childDirectory('app')
|
||||||
|
.childDirectory('src')
|
||||||
|
.childDirectory('main')
|
||||||
|
.childFile('AndroidManifest.xml');
|
||||||
|
applicationFile.createSync(recursive: true);
|
||||||
|
applicationFile.writeAsStringSync(r'''
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.multidexapp">
|
||||||
|
<application
|
||||||
|
android:label="multidextest2"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
|
''', flush: true);
|
||||||
|
expect(androidManifestHasNameVariable(directory), true);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => MemoryFileSystem.test(),
|
||||||
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('androidManifestHasNameVariable false with no android:name attribute', () async {
|
||||||
|
final Directory directory = globals.fs.currentDirectory;
|
||||||
|
final File applicationFile = directory.childDirectory('android')
|
||||||
|
.childDirectory('app')
|
||||||
|
.childDirectory('src')
|
||||||
|
.childDirectory('main')
|
||||||
|
.childFile('AndroidManifest.xml');
|
||||||
|
applicationFile.createSync(recursive: true);
|
||||||
|
applicationFile.writeAsStringSync(r'''
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.multidexapp">
|
||||||
|
<application
|
||||||
|
android:label="multidextest2"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
</application>
|
||||||
|
''', flush: true);
|
||||||
|
expect(androidManifestHasNameVariable(directory), false);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => MemoryFileSystem.test(),
|
||||||
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('androidManifestHasNameVariable false with incorrect android:name attribute', () async {
|
||||||
|
final Directory directory = globals.fs.currentDirectory;
|
||||||
|
final File applicationFile = directory.childDirectory('android')
|
||||||
|
.childDirectory('app')
|
||||||
|
.childDirectory('src')
|
||||||
|
.childDirectory('main')
|
||||||
|
.childFile('AndroidManifest.xml');
|
||||||
|
applicationFile.createSync(recursive: true);
|
||||||
|
applicationFile.writeAsStringSync(r'''
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.multidexapp">
|
||||||
|
<application
|
||||||
|
android:label="multidextest2"
|
||||||
|
android:name="io.flutter.app.FlutterApplication"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
</application>
|
||||||
|
''', flush: true);
|
||||||
|
expect(androidManifestHasNameVariable(directory), false);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => MemoryFileSystem.test(),
|
||||||
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('androidManifestHasNameVariable false with invalid xml manifest', () async {
|
||||||
|
final Directory directory = globals.fs.currentDirectory;
|
||||||
|
final File applicationFile = directory.childDirectory('android')
|
||||||
|
.childDirectory('app')
|
||||||
|
.childDirectory('src')
|
||||||
|
.childDirectory('main')
|
||||||
|
.childFile('AndroidManifest.xml');
|
||||||
|
applicationFile.createSync(recursive: true);
|
||||||
|
applicationFile.writeAsStringSync(r'''
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.multidexapp">
|
||||||
|
<application
|
||||||
|
android:label="multidextest2"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
</application>
|
||||||
|
''', flush: true);
|
||||||
|
expect(androidManifestHasNameVariable(directory), false);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => MemoryFileSystem.test(),
|
||||||
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
|
});
|
||||||
|
|
||||||
|
testUsingContext('androidManifestHasNameVariable false with no manifest file', () async {
|
||||||
|
final Directory directory = globals.fs.currentDirectory;
|
||||||
|
expect(androidManifestHasNameVariable(directory), false);
|
||||||
|
}, overrides: <Type, Generator>{
|
||||||
|
FileSystem: () => MemoryFileSystem.test(),
|
||||||
|
ProcessManager: () => FakeProcessManager.any(),
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
// @dart = 2.8
|
||||||
|
|
||||||
|
import 'package:file/file.dart';
|
||||||
|
import 'package:flutter_tools/src/base/io.dart';
|
||||||
|
|
||||||
|
import '../src/common.dart';
|
||||||
|
import 'test_data/multidex_project.dart';
|
||||||
|
import 'test_driver.dart';
|
||||||
|
import 'test_utils.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
Directory tempDir;
|
||||||
|
FlutterRunTestDriver _flutter;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
tempDir = createResolvedTempDirectorySync('run_test.');
|
||||||
|
_flutter = FlutterRunTestDriver(tempDir);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
await _flutter.stop();
|
||||||
|
tryToDelete(tempDir);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('simple build apk succeeds', () async {
|
||||||
|
final MultidexProject project = MultidexProject(true);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
|
||||||
|
final ProcessResult result = await processManager.run(<String>[
|
||||||
|
flutterBin,
|
||||||
|
...getLocalEngineArguments(),
|
||||||
|
'build',
|
||||||
|
'apk',
|
||||||
|
'--debug',
|
||||||
|
], workingDirectory: tempDir.path);
|
||||||
|
|
||||||
|
expect(result.exitCode, 0);
|
||||||
|
expect(result.stdout.toString(), contains('app-debug.apk'));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('simple build apk without FlutterMultiDexApplication fails', () async {
|
||||||
|
final MultidexProject project = MultidexProject(false);
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
|
||||||
|
final ProcessResult result = await processManager.run(<String>[
|
||||||
|
flutterBin,
|
||||||
|
...getLocalEngineArguments(),
|
||||||
|
'build',
|
||||||
|
'apk',
|
||||||
|
'--debug',
|
||||||
|
], workingDirectory: tempDir.path);
|
||||||
|
|
||||||
|
expect(result.stderr.toString(), contains('Cannot fit requested classes in a single dex file'));
|
||||||
|
expect(result.stderr.toString(), contains('The number of method references in a .dex file cannot exceed 64K.'));
|
||||||
|
expect(result.exitCode, 1);
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,322 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
// @dart = 2.8
|
||||||
|
|
||||||
|
import 'package:file/file.dart';
|
||||||
|
|
||||||
|
import '../../src/common.dart';
|
||||||
|
import '../test_utils.dart';
|
||||||
|
import 'project.dart';
|
||||||
|
|
||||||
|
class MultidexProject extends Project {
|
||||||
|
MultidexProject(this.includeFlutterMultiDexApplication);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setUpIn(Directory dir, {
|
||||||
|
bool useDeferredLoading = false,
|
||||||
|
bool useSyntheticPackage = false,
|
||||||
|
}) {
|
||||||
|
this.dir = dir;
|
||||||
|
if (androidSettings != null) {
|
||||||
|
writeFile(fileSystem.path.join(dir.path, 'android', 'settings.gradle'), androidSettings);
|
||||||
|
}
|
||||||
|
if (androidBuild != null) {
|
||||||
|
writeFile(fileSystem.path.join(dir.path, 'android', 'build.gradle'), androidBuild);
|
||||||
|
}
|
||||||
|
if (androidLocalProperties != null) {
|
||||||
|
writeFile(fileSystem.path.join(dir.path, 'android', 'local.properties'), androidLocalProperties);
|
||||||
|
}
|
||||||
|
if (androidGradleProperties != null) {
|
||||||
|
writeFile(fileSystem.path.join(dir.path, 'android', 'gradle.properties'), androidGradleProperties);
|
||||||
|
}
|
||||||
|
if (appBuild != null) {
|
||||||
|
writeFile(fileSystem.path.join(dir.path, 'android', 'app', 'build.gradle'), appBuild);
|
||||||
|
}
|
||||||
|
if (appManifest != null) {
|
||||||
|
writeFile(fileSystem.path.join(dir.path, 'android', 'app', 'src', 'main', 'AndroidManifest.xml'), appManifest);
|
||||||
|
}
|
||||||
|
if (appStrings != null) {
|
||||||
|
writeFile(fileSystem.path.join(dir.path, 'android', 'app', 'src', 'main', 'res', 'values', 'strings.xml'), appStrings);
|
||||||
|
}
|
||||||
|
if (appStyles != null) {
|
||||||
|
writeFile(fileSystem.path.join(dir.path, 'android', 'app', 'src', 'main', 'res', 'values', 'styles.xml'), appStyles);
|
||||||
|
}
|
||||||
|
if (appLaunchBackground != null) {
|
||||||
|
writeFile(fileSystem.path.join(dir.path, 'android', 'app', 'src', 'main', 'res', 'drawable', 'launch_background.xml'), appLaunchBackground);
|
||||||
|
}
|
||||||
|
if (includeFlutterMultiDexApplication && appMultidexApplication != null) {
|
||||||
|
writeFile(fileSystem.path.join(dir.path, 'android', 'app', 'src', 'main', 'java', fileSystem.path.join('io', 'flutter', 'app', 'FlutterMultiDexApplication.java')), appMultidexApplication);
|
||||||
|
}
|
||||||
|
return super.setUpIn(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
final bool includeFlutterMultiDexApplication;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String pubspec = '''
|
||||||
|
name: test
|
||||||
|
environment:
|
||||||
|
sdk: ">=2.12.0-0 <3.0.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
cloud_firestore: ^2.5.3
|
||||||
|
firebase_core: ^1.6.0
|
||||||
|
''';
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String main = r'''
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
runApp(MyApp());
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyApp extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new MaterialApp(
|
||||||
|
title: 'Flutter Demo',
|
||||||
|
home: new Container(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
String get androidSettings => r'''
|
||||||
|
include ':app'
|
||||||
|
|
||||||
|
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
||||||
|
def properties = new Properties()
|
||||||
|
|
||||||
|
assert localPropertiesFile.exists()
|
||||||
|
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
||||||
|
|
||||||
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
|
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
||||||
|
''';
|
||||||
|
|
||||||
|
String get androidBuild => r'''
|
||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.3.50'
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.buildDir = '../build'
|
||||||
|
subprojects {
|
||||||
|
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
|
project.evaluationDependsOn(':app')
|
||||||
|
}
|
||||||
|
|
||||||
|
task clean(type: Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
String get appBuild => r'''
|
||||||
|
def localProperties = new Properties()
|
||||||
|
def localPropertiesFile = rootProject.file('local.properties')
|
||||||
|
if (localPropertiesFile.exists()) {
|
||||||
|
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||||
|
localProperties.load(reader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||||
|
if (flutterRoot == null) {
|
||||||
|
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
|
if (flutterVersionCode == null) {
|
||||||
|
flutterVersionCode = '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||||
|
if (flutterVersionName == null) {
|
||||||
|
flutterVersionName = '1.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 30
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
|
applicationId "com.example.multidextest2"
|
||||||
|
minSdkVersion 16
|
||||||
|
targetSdkVersion 30
|
||||||
|
versionCode flutterVersionCode.toInteger()
|
||||||
|
versionName flutterVersionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
// TODO: Add your own signing config for the release build.
|
||||||
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source '../..'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
String get androidLocalProperties => '''
|
||||||
|
flutter.sdk=${getFlutterRoot()}
|
||||||
|
flutter.buildMode=debug
|
||||||
|
flutter.versionName=1.0.0
|
||||||
|
flutter.versionCode=22
|
||||||
|
''';
|
||||||
|
|
||||||
|
String get androidGradleProperties => '''
|
||||||
|
org.gradle.jvmargs=-Xmx1536M
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
||||||
|
android.enableR8=true
|
||||||
|
android.experimental.enableNewResourceShrinker=true
|
||||||
|
''';
|
||||||
|
|
||||||
|
String get appManifest => r'''
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.multidextest">
|
||||||
|
<application
|
||||||
|
android:label="multidextest"
|
||||||
|
android:name="${applicationName}">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
|
the Android process has started. This theme is visible to the user
|
||||||
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
to determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
|
''';
|
||||||
|
|
||||||
|
String get appStrings => r'''
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
</resources>
|
||||||
|
''';
|
||||||
|
|
||||||
|
String get appStyles => r'''
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
Flutter draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">@android:color/white</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
|
''';
|
||||||
|
|
||||||
|
String get appLaunchBackground => r'''
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
||||||
|
''';
|
||||||
|
|
||||||
|
String get appMultidexApplication => r'''
|
||||||
|
// Generated file.
|
||||||
|
// If you wish to remove Flutter's multidex support, delete this entire file.
|
||||||
|
|
||||||
|
package io.flutter.app;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import androidx.annotation.CallSuper;
|
||||||
|
import androidx.multidex.MultiDex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension of {@link io.flutter.app.FlutterApplication}, adding multidex support.
|
||||||
|
*/
|
||||||
|
public class FlutterMultiDexApplication extends FlutterApplication {
|
||||||
|
@Override
|
||||||
|
@CallSuper
|
||||||
|
protected void attachBaseContext(Context base) {
|
||||||
|
super.attachBaseContext(base);
|
||||||
|
MultiDex.install(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user