723 lines
26 KiB
Dart
723 lines
26 KiB
Dart
// 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:meta/meta.dart';
|
|
|
|
import '../base/error_handling_io.dart';
|
|
import '../base/file_system.dart';
|
|
import '../base/process.dart';
|
|
import '../base/terminal.dart';
|
|
import '../globals.dart' as globals;
|
|
import '../project.dart';
|
|
import '../reporting/reporting.dart';
|
|
import 'android_sdk.dart';
|
|
import 'android_studio.dart';
|
|
import 'gradle_utils.dart';
|
|
import 'multidex.dart';
|
|
|
|
typedef GradleErrorTest = bool Function(String);
|
|
|
|
/// A Gradle error handled by the tool.
|
|
class GradleHandledError {
|
|
const GradleHandledError({
|
|
required this.test,
|
|
required this.handler,
|
|
this.eventLabel,
|
|
});
|
|
|
|
/// The test function.
|
|
/// Returns [true] if the current error message should be handled.
|
|
final GradleErrorTest test;
|
|
|
|
/// The handler function.
|
|
final Future<GradleBuildStatus> Function({
|
|
required String line,
|
|
required FlutterProject project,
|
|
required bool usesAndroidX,
|
|
required bool multidexEnabled,
|
|
}) handler;
|
|
|
|
/// The [BuildEvent] label is named gradle-[eventLabel].
|
|
/// If not empty, the build event is logged along with
|
|
/// additional metadata such as the attempt number.
|
|
final String? eventLabel;
|
|
}
|
|
|
|
/// The status of the Gradle build.
|
|
enum GradleBuildStatus {
|
|
/// The tool cannot recover from the failure and should exit.
|
|
exit,
|
|
/// The tool can retry the exact same build.
|
|
retry,
|
|
}
|
|
|
|
/// Returns a simple test function that evaluates to `true` if at least one of
|
|
/// `errorMessages` is contained in the error message.
|
|
GradleErrorTest _lineMatcher(List<String> errorMessages) {
|
|
return (String line) {
|
|
return errorMessages.any((String errorMessage) => line.contains(errorMessage));
|
|
};
|
|
}
|
|
|
|
/// The list of Gradle errors that the tool can handle.
|
|
///
|
|
/// The handlers are executed in the order in which they appear in the list.
|
|
///
|
|
/// Only the first error handler for which the [test] function returns [true]
|
|
/// is handled. As a result, sort error handlers based on how strict the [test]
|
|
/// function is to eliminate false positives.
|
|
final List<GradleHandledError> gradleErrors = <GradleHandledError>[
|
|
licenseNotAcceptedHandler,
|
|
networkErrorHandler,
|
|
permissionDeniedErrorHandler,
|
|
flavorUndefinedHandler,
|
|
r8FailureHandler,
|
|
minSdkVersionHandler,
|
|
transformInputIssueHandler,
|
|
lockFileDepMissingHandler,
|
|
multidexErrorHandler,
|
|
incompatibleKotlinVersionHandler,
|
|
minCompileSdkVersionHandler,
|
|
jvm11RequiredHandler,
|
|
outdatedGradleHandler,
|
|
sslExceptionHandler,
|
|
zipExceptionHandler,
|
|
incompatibleJavaAndGradleVersionsHandler,
|
|
remoteTerminatedHandshakeHandler,
|
|
];
|
|
|
|
const String _boxTitle = 'Flutter Fix';
|
|
|
|
// 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. '
|
|
'See https://docs.flutter.dev/deployment/android#enabling-multidex-support for more information. '
|
|
"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.printBox(
|
|
'Flutter multidex handling is disabled. If you wish to let the tool configure multidex, use the --multidex flag.',
|
|
title: _boxTitle,
|
|
);
|
|
}
|
|
return GradleBuildStatus.exit;
|
|
},
|
|
eventLabel: 'multidex-error',
|
|
);
|
|
|
|
// Permission defined error message.
|
|
@visibleForTesting
|
|
final GradleHandledError permissionDeniedErrorHandler = GradleHandledError(
|
|
test: _lineMatcher(const <String>[
|
|
'Permission denied',
|
|
]),
|
|
handler: ({
|
|
required String line,
|
|
required FlutterProject project,
|
|
required bool usesAndroidX,
|
|
required bool multidexEnabled,
|
|
}) async {
|
|
globals.printBox(
|
|
'${globals.logger.terminal.warningMark} Gradle does not have execution permission.\n'
|
|
'You should change the ownership of the project directory to your user, '
|
|
'or move the project to a directory with execute permissions.',
|
|
title: _boxTitle,
|
|
);
|
|
return GradleBuildStatus.exit;
|
|
},
|
|
eventLabel: 'permission-denied',
|
|
);
|
|
|
|
/// Gradle crashes for several known reasons when downloading that are not
|
|
/// actionable by Flutter.
|
|
@visibleForTesting
|
|
final GradleHandledError networkErrorHandler = GradleHandledError(
|
|
test: _lineMatcher(const <String>[
|
|
'java.io.FileNotFoundException: https://downloads.gradle.org',
|
|
'java.io.IOException: Unable to tunnel through proxy',
|
|
'java.lang.RuntimeException: Timeout of',
|
|
'java.util.zip.ZipException: error in opening zip file',
|
|
'javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake',
|
|
'java.net.SocketException: Connection reset',
|
|
'java.io.FileNotFoundException',
|
|
"> Could not get resource 'http",
|
|
]),
|
|
handler: ({
|
|
required String line,
|
|
required FlutterProject project,
|
|
required bool usesAndroidX,
|
|
required bool multidexEnabled,
|
|
}) async {
|
|
globals.printError(
|
|
'${globals.logger.terminal.warningMark} '
|
|
'Gradle threw an error while downloading artifacts from the network.'
|
|
);
|
|
return GradleBuildStatus.retry;
|
|
},
|
|
eventLabel: 'network',
|
|
);
|
|
|
|
/// Handles corrupted jar or other types of zip files.
|
|
///
|
|
/// If a terminal is attached, this handler prompts the user if they would like to
|
|
/// delete the $HOME/.gradle directory prior to retrying the build.
|
|
///
|
|
/// If this handler runs on a bot (e.g. a CI bot), the $HOME/.gradle is automatically deleted.
|
|
///
|
|
/// See also:
|
|
/// * https://github.com/flutter/flutter/issues/51195
|
|
/// * https://github.com/flutter/flutter/issues/89959
|
|
/// * https://docs.gradle.org/current/userguide/directory_layout.html#dir:gradle_user_home
|
|
@visibleForTesting
|
|
final GradleHandledError zipExceptionHandler = GradleHandledError(
|
|
test: _lineMatcher(const <String>[
|
|
'java.util.zip.ZipException: error in opening zip file',
|
|
]),
|
|
handler: ({
|
|
required String line,
|
|
required FlutterProject project,
|
|
required bool usesAndroidX,
|
|
required bool multidexEnabled,
|
|
}) async {
|
|
globals.printError(
|
|
'${globals.logger.terminal.warningMark} '
|
|
'Your .gradle directory under the home directory might be corrupted.'
|
|
);
|
|
bool shouldDeleteUserGradle = await globals.botDetector.isRunningOnBot;
|
|
if (!shouldDeleteUserGradle && globals.terminal.stdinHasTerminal) {
|
|
try {
|
|
final String selection = await globals.terminal.promptForCharInput(
|
|
<String>['y', 'n'],
|
|
logger: globals.logger,
|
|
prompt: 'Do you want to delete the .gradle directory under the home directory?',
|
|
defaultChoiceIndex: 0,
|
|
);
|
|
shouldDeleteUserGradle = selection == 'y';
|
|
} on StateError catch (e) {
|
|
globals.printError(
|
|
e.message,
|
|
indent: 0,
|
|
);
|
|
}
|
|
}
|
|
if (shouldDeleteUserGradle) {
|
|
final String? homeDir = globals.platform.environment['HOME'];
|
|
if (homeDir == null) {
|
|
globals.logger.printStatus("Could not delete .gradle directory because there isn't a HOME env variable");
|
|
return GradleBuildStatus.retry;
|
|
}
|
|
final Directory userGradle = globals.fs.directory(globals.fs.path.join(homeDir, '.gradle'));
|
|
globals.logger.printStatus('Deleting ${userGradle.path}');
|
|
try {
|
|
ErrorHandlingFileSystem.deleteIfExists(userGradle, recursive: true);
|
|
} on FileSystemException catch (err) {
|
|
globals.printTrace('Failed to delete Gradle cache: $err');
|
|
}
|
|
}
|
|
return GradleBuildStatus.retry;
|
|
},
|
|
eventLabel: 'zip-exception',
|
|
);
|
|
|
|
// R8 failure.
|
|
@visibleForTesting
|
|
final GradleHandledError r8FailureHandler = GradleHandledError(
|
|
test: _lineMatcher(const <String>[
|
|
'com.android.tools.r8',
|
|
]),
|
|
handler: ({
|
|
required String line,
|
|
required FlutterProject project,
|
|
required bool usesAndroidX,
|
|
required bool multidexEnabled,
|
|
}) async {
|
|
globals.printBox(
|
|
'${globals.logger.terminal.warningMark} The shrinker may have failed to optimize the Java bytecode.\n'
|
|
'To disable the shrinker, pass the `--no-shrink` flag to this command.\n'
|
|
'To learn more, see: https://developer.android.com/studio/build/shrink-code',
|
|
title: _boxTitle,
|
|
);
|
|
return GradleBuildStatus.exit;
|
|
},
|
|
eventLabel: 'r8',
|
|
);
|
|
|
|
/// Handle Gradle error thrown when Gradle needs to download additional
|
|
/// Android SDK components (e.g. Platform Tools), and the license
|
|
/// for that component has not been accepted.
|
|
@visibleForTesting
|
|
final GradleHandledError licenseNotAcceptedHandler = GradleHandledError(
|
|
test: _lineMatcher(const <String>[
|
|
'You have not accepted the license agreements of the following SDK components',
|
|
]),
|
|
handler: ({
|
|
required String line,
|
|
required FlutterProject project,
|
|
required bool usesAndroidX,
|
|
required bool multidexEnabled,
|
|
}) async {
|
|
const String licenseNotAcceptedMatcher =
|
|
r'You have not accepted the license agreements of the following SDK components:\s*\[(.+)\]';
|
|
|
|
final RegExp licenseFailure = RegExp(licenseNotAcceptedMatcher, multiLine: true);
|
|
final Match? licenseMatch = licenseFailure.firstMatch(line);
|
|
globals.printBox(
|
|
'${globals.logger.terminal.warningMark} Unable to download needed Android SDK components, as the '
|
|
'following licenses have not been accepted: '
|
|
'${licenseMatch?.group(1)}\n\n'
|
|
'To resolve this, please run the following command in a Terminal:\n'
|
|
'flutter doctor --android-licenses',
|
|
title: _boxTitle,
|
|
);
|
|
return GradleBuildStatus.exit;
|
|
},
|
|
eventLabel: 'license-not-accepted',
|
|
);
|
|
|
|
final RegExp _undefinedTaskPattern = RegExp(r'Task .+ not found in root project.');
|
|
|
|
final RegExp _assembleTaskPattern = RegExp(r'assemble(\S+)');
|
|
|
|
/// Handler when a flavor is undefined.
|
|
@visibleForTesting
|
|
final GradleHandledError flavorUndefinedHandler = GradleHandledError(
|
|
test: (String line) {
|
|
return _undefinedTaskPattern.hasMatch(line);
|
|
},
|
|
handler: ({
|
|
required String line,
|
|
required FlutterProject project,
|
|
required bool usesAndroidX,
|
|
required bool multidexEnabled,
|
|
}) async {
|
|
final RunResult tasksRunResult = await globals.processUtils.run(
|
|
<String>[
|
|
globals.gradleUtils!.getExecutable(project),
|
|
'app:tasks' ,
|
|
'--all',
|
|
'--console=auto',
|
|
],
|
|
throwOnError: true,
|
|
workingDirectory: project.android.hostAppGradleRoot.path,
|
|
environment: <String, String>{
|
|
if (javaPath != null)
|
|
AndroidSdk.javaHomeEnvironmentVariable: javaPath!,
|
|
},
|
|
);
|
|
// Extract build types and product flavors.
|
|
final Set<String> variants = <String>{};
|
|
for (final String task in tasksRunResult.stdout.split('\n')) {
|
|
final Match? match = _assembleTaskPattern.matchAsPrefix(task);
|
|
if (match != null) {
|
|
final String variant = match.group(1)!.toLowerCase();
|
|
if (!variant.endsWith('test')) {
|
|
variants.add(variant);
|
|
}
|
|
}
|
|
}
|
|
final Set<String> productFlavors = <String>{};
|
|
for (final String variant1 in variants) {
|
|
for (final String variant2 in variants) {
|
|
if (variant2.startsWith(variant1) && variant2 != variant1) {
|
|
final String buildType = variant2.substring(variant1.length);
|
|
if (variants.contains(buildType)) {
|
|
productFlavors.add(variant1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
final String errorMessage = '${globals.logger.terminal.warningMark} Gradle project does not define a task suitable for the requested build.';
|
|
final File buildGradle = project.directory.childDirectory('android').childDirectory('app').childFile('build.gradle');
|
|
if (productFlavors.isEmpty) {
|
|
globals.printBox(
|
|
'$errorMessage\n\n'
|
|
'The ${buildGradle.absolute.path} file does not define '
|
|
'any custom product flavors. '
|
|
'You cannot use the --flavor option.',
|
|
title: _boxTitle,
|
|
);
|
|
} else {
|
|
globals.printBox(
|
|
'$errorMessage\n\n'
|
|
'The ${buildGradle.absolute.path} file defines product '
|
|
'flavors: ${productFlavors.join(', ')}. '
|
|
'You must specify a --flavor option to select one of them.',
|
|
title: _boxTitle,
|
|
);
|
|
}
|
|
return GradleBuildStatus.exit;
|
|
},
|
|
eventLabel: 'flavor-undefined',
|
|
);
|
|
|
|
|
|
final RegExp _minSdkVersionPattern = RegExp(r'uses-sdk:minSdkVersion ([0-9]+) cannot be smaller than version ([0-9]+) declared in library \[\:(.+)\]');
|
|
|
|
/// Handler when a plugin requires a higher Android API level.
|
|
@visibleForTesting
|
|
final GradleHandledError minSdkVersionHandler = GradleHandledError(
|
|
test: (String line) {
|
|
return _minSdkVersionPattern.hasMatch(line);
|
|
},
|
|
handler: ({
|
|
required String line,
|
|
required FlutterProject project,
|
|
required bool usesAndroidX,
|
|
required bool multidexEnabled,
|
|
}) async {
|
|
final File gradleFile = project.directory
|
|
.childDirectory('android')
|
|
.childDirectory('app')
|
|
.childFile('build.gradle');
|
|
|
|
final Match? minSdkVersionMatch = _minSdkVersionPattern.firstMatch(line);
|
|
assert(minSdkVersionMatch?.groupCount == 3);
|
|
|
|
final String textInBold = globals.logger.terminal.bolden(
|
|
'Fix this issue by adding the following to the file ${gradleFile.path}:\n'
|
|
'android {\n'
|
|
' defaultConfig {\n'
|
|
' minSdkVersion ${minSdkVersionMatch?.group(2)}\n'
|
|
' }\n'
|
|
'}\n'
|
|
);
|
|
globals.printBox(
|
|
'The plugin ${minSdkVersionMatch?.group(3)} requires a higher Android SDK version.\n'
|
|
'$textInBold\n'
|
|
'Following this change, your app will not be available to users running Android SDKs below ${minSdkVersionMatch?.group(2)}.\n'
|
|
'Consider searching for a version of this plugin that supports these lower versions of the Android SDK instead.\n'
|
|
'For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration',
|
|
title: _boxTitle,
|
|
);
|
|
return GradleBuildStatus.exit;
|
|
},
|
|
eventLabel: 'plugin-min-sdk',
|
|
);
|
|
|
|
/// Handler when https://issuetracker.google.com/issues/141126614 or
|
|
/// https://github.com/flutter/flutter/issues/58247 is triggered.
|
|
@visibleForTesting
|
|
final GradleHandledError transformInputIssueHandler = GradleHandledError(
|
|
test: (String line) {
|
|
return line.contains('https://issuetracker.google.com/issues/158753935');
|
|
},
|
|
handler: ({
|
|
required String line,
|
|
required FlutterProject project,
|
|
required bool usesAndroidX,
|
|
required bool multidexEnabled,
|
|
}) async {
|
|
final File gradleFile = project.directory
|
|
.childDirectory('android')
|
|
.childDirectory('app')
|
|
.childFile('build.gradle');
|
|
final String textInBold = globals.logger.terminal.bolden(
|
|
'Fix this issue by adding the following to the file ${gradleFile.path}:\n'
|
|
'android {\n'
|
|
' lintOptions {\n'
|
|
' checkReleaseBuilds false\n'
|
|
' }\n'
|
|
'}'
|
|
);
|
|
globals.printBox(
|
|
'This issue appears to be https://github.com/flutter/flutter/issues/58247.\n'
|
|
'$textInBold',
|
|
title: _boxTitle,
|
|
);
|
|
return GradleBuildStatus.exit;
|
|
},
|
|
eventLabel: 'transform-input-issue',
|
|
);
|
|
|
|
/// Handler when a dependency is missing in the lockfile.
|
|
@visibleForTesting
|
|
final GradleHandledError lockFileDepMissingHandler = GradleHandledError(
|
|
test: (String line) {
|
|
return line.contains('which is not part of the dependency lock state');
|
|
},
|
|
handler: ({
|
|
required String line,
|
|
required FlutterProject project,
|
|
required bool usesAndroidX,
|
|
required bool multidexEnabled,
|
|
}) async {
|
|
final File gradleFile = project.directory
|
|
.childDirectory('android')
|
|
.childFile('build.gradle');
|
|
final String textInBold = globals.logger.terminal.bolden(
|
|
'To regenerate the lockfiles run: `./gradlew :generateLockfiles` in ${gradleFile.path}\n'
|
|
'To remove dependency locking, remove the `dependencyLocking` from ${gradleFile.path}'
|
|
);
|
|
globals.printBox(
|
|
'You need to update the lockfile, or disable Gradle dependency locking.\n'
|
|
'$textInBold',
|
|
title: _boxTitle,
|
|
);
|
|
return GradleBuildStatus.exit;
|
|
},
|
|
eventLabel: 'lock-dep-issue',
|
|
);
|
|
|
|
@visibleForTesting
|
|
final GradleHandledError incompatibleKotlinVersionHandler = GradleHandledError(
|
|
test: _lineMatcher(const <String>[
|
|
'was compiled with an incompatible version of Kotlin',
|
|
]),
|
|
handler: ({
|
|
required String line,
|
|
required FlutterProject project,
|
|
required bool usesAndroidX,
|
|
required bool multidexEnabled,
|
|
}) async {
|
|
final File gradleFile = project.directory
|
|
.childDirectory('android')
|
|
.childFile('build.gradle');
|
|
globals.printBox(
|
|
'${globals.logger.terminal.warningMark} Your project requires a newer version of the Kotlin Gradle plugin.\n'
|
|
'Find the latest version on https://kotlinlang.org/docs/releases.html#release-details, then update ${gradleFile.path}:\n'
|
|
"ext.kotlin_version = '<latest-version>'",
|
|
title: _boxTitle,
|
|
);
|
|
return GradleBuildStatus.exit;
|
|
},
|
|
eventLabel: 'incompatible-kotlin-version',
|
|
);
|
|
|
|
final RegExp _outdatedGradlePattern = RegExp(r'The current Gradle version (.+) is not compatible with the Kotlin Gradle plugin');
|
|
|
|
@visibleForTesting
|
|
final GradleHandledError outdatedGradleHandler = GradleHandledError(
|
|
test: _outdatedGradlePattern.hasMatch,
|
|
handler: ({
|
|
required String line,
|
|
required FlutterProject project,
|
|
required bool usesAndroidX,
|
|
required bool multidexEnabled,
|
|
}) async {
|
|
final File gradleFile = project.directory
|
|
.childDirectory('android')
|
|
.childFile('build.gradle');
|
|
final File gradlePropertiesFile = project.directory
|
|
.childDirectory('android')
|
|
.childDirectory('gradle')
|
|
.childDirectory('wrapper')
|
|
.childFile('gradle-wrapper.properties');
|
|
globals.printBox(
|
|
'${globals.logger.terminal.warningMark} Your project needs to upgrade Gradle and the Android Gradle plugin.\n\n'
|
|
'To fix this issue, replace the following content:\n'
|
|
'${gradleFile.path}:\n'
|
|
' ${globals.terminal.color("- classpath 'com.android.tools.build:gradle:<current-version>'", TerminalColor.red)}\n'
|
|
' ${globals.terminal.color("+ classpath 'com.android.tools.build:gradle:$templateAndroidGradlePluginVersion'", TerminalColor.green)}\n'
|
|
'${gradlePropertiesFile.path}:\n'
|
|
' ${globals.terminal.color('- https://services.gradle.org/distributions/gradle-<current-version>-all.zip', TerminalColor.red)}\n'
|
|
' ${globals.terminal.color('+ https://services.gradle.org/distributions/gradle-$templateDefaultGradleVersion-all.zip', TerminalColor.green)}',
|
|
title: _boxTitle,
|
|
);
|
|
return GradleBuildStatus.exit;
|
|
},
|
|
eventLabel: 'outdated-gradle-version',
|
|
);
|
|
|
|
final RegExp _minCompileSdkVersionPattern = RegExp(r'The minCompileSdk \(([0-9]+)\) specified in a');
|
|
|
|
@visibleForTesting
|
|
final GradleHandledError minCompileSdkVersionHandler = GradleHandledError(
|
|
test: _minCompileSdkVersionPattern.hasMatch,
|
|
handler: ({
|
|
required String line,
|
|
required FlutterProject project,
|
|
required bool usesAndroidX,
|
|
required bool multidexEnabled,
|
|
}) async {
|
|
final Match? minCompileSdkVersionMatch = _minCompileSdkVersionPattern.firstMatch(line);
|
|
assert(minCompileSdkVersionMatch?.groupCount == 1);
|
|
|
|
final File gradleFile = project.directory
|
|
.childDirectory('android')
|
|
.childDirectory('app')
|
|
.childFile('build.gradle');
|
|
globals.printBox(
|
|
'${globals.logger.terminal.warningMark} Your project requires a higher compileSdkVersion.\n'
|
|
'Fix this issue by bumping the compileSdkVersion in ${gradleFile.path}:\n'
|
|
'android {\n'
|
|
' compileSdkVersion ${minCompileSdkVersionMatch?.group(1)}\n'
|
|
'}',
|
|
title: _boxTitle,
|
|
);
|
|
return GradleBuildStatus.exit;
|
|
},
|
|
eventLabel: 'min-compile-sdk-version',
|
|
);
|
|
|
|
@visibleForTesting
|
|
final GradleHandledError jvm11RequiredHandler = GradleHandledError(
|
|
test: (String line) {
|
|
return line.contains('Android Gradle plugin requires Java 11 to run');
|
|
},
|
|
handler: ({
|
|
required String line,
|
|
required FlutterProject project,
|
|
required bool usesAndroidX,
|
|
required bool multidexEnabled,
|
|
}) async {
|
|
globals.printBox(
|
|
'${globals.logger.terminal.warningMark} You need Java 11 or higher to build your app with this version of Gradle.\n\n'
|
|
'To get Java 11, update to the latest version of Android Studio on https://developer.android.com/studio/install.\n\n'
|
|
'To check the Java version used by Flutter, run `flutter doctor -v`.',
|
|
title: _boxTitle,
|
|
);
|
|
return GradleBuildStatus.exit;
|
|
},
|
|
eventLabel: 'java11-required',
|
|
);
|
|
|
|
/// Handles SSL exceptions: https://github.com/flutter/flutter/issues/104628
|
|
@visibleForTesting
|
|
final GradleHandledError sslExceptionHandler = GradleHandledError(
|
|
test: _lineMatcher(const <String>[
|
|
'javax.net.ssl.SSLException: Tag mismatch!',
|
|
'javax.crypto.AEADBadTagException: Tag mismatch!',
|
|
]),
|
|
handler: ({
|
|
required String line,
|
|
required FlutterProject project,
|
|
required bool usesAndroidX,
|
|
required bool multidexEnabled,
|
|
}) async {
|
|
globals.printError(
|
|
'${globals.logger.terminal.warningMark} '
|
|
'Gradle threw an error while downloading artifacts from the network.'
|
|
);
|
|
return GradleBuildStatus.retry;
|
|
},
|
|
eventLabel: 'ssl-exception-tag-mismatch',
|
|
);
|
|
|
|
/// If an incompatible Java and Gradle versions error is caught, we expect an
|
|
/// error specifying that the Java major class file version, one of
|
|
/// https://javaalmanac.io/bytecode/versions/, is unsupported by Gradle.
|
|
final RegExp _unsupportedClassFileMajorVersionPattern = RegExp(r'Unsupported class file major version\s+\d+');
|
|
|
|
@visibleForTesting
|
|
final GradleHandledError incompatibleJavaAndGradleVersionsHandler = GradleHandledError(
|
|
test: (String line) {
|
|
return _unsupportedClassFileMajorVersionPattern.hasMatch(line);
|
|
},
|
|
handler: ({
|
|
required String line,
|
|
required FlutterProject project,
|
|
required bool usesAndroidX,
|
|
required bool multidexEnabled,
|
|
}) async {
|
|
// TODO(reidbaker): Replace URL with constant defined in
|
|
// https://github.com/flutter/flutter/pull/123916.
|
|
globals.printBox(
|
|
"${globals.logger.terminal.warningMark} Your project's Gradle version "
|
|
'is incompatible with the Java version that Flutter is using for Gradle.\n\n'
|
|
'To fix this issue, consult the migration guide at docs.flutter.dev/go/android-java-gradle-error.',
|
|
title: _boxTitle,
|
|
);
|
|
return GradleBuildStatus.exit;
|
|
},
|
|
eventLabel: 'incompatible-java-gradle-version',
|
|
);
|
|
|
|
@visibleForTesting
|
|
final GradleHandledError remoteTerminatedHandshakeHandler = GradleHandledError(
|
|
test: (String line) => line.contains('Remote host terminated the handshake'),
|
|
handler: ({
|
|
required String line,
|
|
required FlutterProject project,
|
|
required bool usesAndroidX,
|
|
required bool multidexEnabled,
|
|
}) async {
|
|
globals.printError(
|
|
'${globals.logger.terminal.warningMark} '
|
|
'Gradle threw an error while downloading artifacts from the network.'
|
|
);
|
|
|
|
return GradleBuildStatus.retry;
|
|
},
|
|
eventLabel: 'remote-terminated-handshake',
|
|
);
|