diff --git a/dev/integration_tests/android_custom_host_app/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/android_custom_host_app/gradle/wrapper/gradle-wrapper.properties index 3c9d0852bf..ceccc3a854 100644 --- a/dev/integration_tests/android_custom_host_app/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/android_custom_host_app/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip diff --git a/dev/integration_tests/android_embedding_v2_smoke_test/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/android_embedding_v2_smoke_test/android/gradle/wrapper/gradle-wrapper.properties index 3c9d0852bf..ceccc3a854 100644 --- a/dev/integration_tests/android_embedding_v2_smoke_test/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/android_embedding_v2_smoke_test/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip diff --git a/dev/integration_tests/android_host_app_v2_embedding/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/android_host_app_v2_embedding/gradle/wrapper/gradle-wrapper.properties index 3c9d0852bf..ceccc3a854 100644 --- a/dev/integration_tests/android_host_app_v2_embedding/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/android_host_app_v2_embedding/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip diff --git a/dev/integration_tests/android_semantics_testing/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/android_semantics_testing/android/gradle/wrapper/gradle-wrapper.properties index 3c9d0852bf..ceccc3a854 100644 --- a/dev/integration_tests/android_semantics_testing/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/android_semantics_testing/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip diff --git a/dev/integration_tests/android_views/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/android_views/android/gradle/wrapper/gradle-wrapper.properties index 3c9d0852bf..ceccc3a854 100644 --- a/dev/integration_tests/android_views/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/android_views/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip diff --git a/dev/integration_tests/external_ui/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/external_ui/android/gradle/wrapper/gradle-wrapper.properties index 3c9d0852bf..ceccc3a854 100644 --- a/dev/integration_tests/external_ui/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/external_ui/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip diff --git a/dev/integration_tests/gradle_deprecated_settings/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/gradle_deprecated_settings/android/gradle/wrapper/gradle-wrapper.properties index 3c9d0852bf..ceccc3a854 100644 --- a/dev/integration_tests/gradle_deprecated_settings/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/gradle_deprecated_settings/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip diff --git a/dev/integration_tests/hybrid_android_views/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/hybrid_android_views/android/gradle/wrapper/gradle-wrapper.properties index 3c9d0852bf..ceccc3a854 100644 --- a/dev/integration_tests/hybrid_android_views/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/hybrid_android_views/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip diff --git a/dev/integration_tests/module_host_with_custom_build_v2_embedding/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/module_host_with_custom_build_v2_embedding/gradle/wrapper/gradle-wrapper.properties index 3c9d0852bf..ceccc3a854 100644 --- a/dev/integration_tests/module_host_with_custom_build_v2_embedding/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/module_host_with_custom_build_v2_embedding/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip diff --git a/dev/integration_tests/non_nullable/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/non_nullable/android/gradle/wrapper/gradle-wrapper.properties index 3c9d0852bf..ceccc3a854 100644 --- a/dev/integration_tests/non_nullable/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/non_nullable/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip diff --git a/dev/integration_tests/platform_interaction/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/platform_interaction/android/gradle/wrapper/gradle-wrapper.properties index 3c9d0852bf..ceccc3a854 100644 --- a/dev/integration_tests/platform_interaction/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/platform_interaction/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip diff --git a/dev/integration_tests/release_smoke_test/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/release_smoke_test/android/gradle/wrapper/gradle-wrapper.properties index 3c9d0852bf..ceccc3a854 100644 --- a/dev/integration_tests/release_smoke_test/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/release_smoke_test/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip diff --git a/dev/manual_tests/android/gradle/wrapper/gradle-wrapper.properties b/dev/manual_tests/android/gradle/wrapper/gradle-wrapper.properties index 3c9d0852bf..ceccc3a854 100644 --- a/dev/manual_tests/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/manual_tests/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip diff --git a/dev/tracing_tests/android/gradle/wrapper/gradle-wrapper.properties b/dev/tracing_tests/android/gradle/wrapper/gradle-wrapper.properties index 3c9d0852bf..ceccc3a854 100644 --- a/dev/tracing_tests/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/tracing_tests/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 698585cd1f..60cf9b99f0 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -31,8 +31,10 @@ import '../project.dart'; import '../reporting/reporting.dart'; import 'android_builder.dart'; import 'android_sdk.dart'; +import 'android_studio.dart'; import 'gradle_errors.dart'; import 'gradle_utils.dart'; +import 'migrations/android_studio_java_gradle_conflict_migration.dart'; import 'migrations/top_level_gradle_build_file_migration.dart'; import 'multidex.dart'; @@ -137,13 +139,16 @@ class AndroidGradleBuilder implements AndroidBuilder { required Usage usage, required GradleUtils gradleUtils, required Platform platform, + required AndroidStudio? androidStudio, }) : _logger = logger, _fileSystem = fileSystem, _artifacts = artifacts, _usage = usage, _gradleUtils = gradleUtils, + _androidStudio = androidStudio, _fileSystemUtils = FileSystemUtils(fileSystem: fileSystem, platform: platform), - _processUtils = ProcessUtils(logger: logger, processManager: processManager); + _processUtils = ProcessUtils(logger: logger, processManager: processManager), + _platform = platform; final Logger _logger; final ProcessUtils _processUtils; @@ -152,6 +157,8 @@ class AndroidGradleBuilder implements AndroidBuilder { final Usage _usage; final GradleUtils _gradleUtils; final FileSystemUtils _fileSystemUtils; + final AndroidStudio? _androidStudio; + final Platform _platform; /// Builds the AAR and POM files for the current Flutter module or plugin. @override @@ -258,6 +265,15 @@ class AndroidGradleBuilder implements AndroidBuilder { final List migrators = [ TopLevelGradleBuildFileMigration(project.android, _logger), + AndroidStudioJavaGradleConflictMigration(_logger, + project: project.android, + androidStudio: _androidStudio, + fileSystem: _fileSystem, + processUtils: _processUtils, + platform: _platform, + os: globals.os, + androidSdk: globals.androidSdk) + , ]; final ProjectMigration migration = ProjectMigration(migrators); diff --git a/packages/flutter_tools/lib/src/android/gradle_utils.dart b/packages/flutter_tools/lib/src/android/gradle_utils.dart index 86aff6bc44..12d83a7ded 100644 --- a/packages/flutter_tools/lib/src/android/gradle_utils.dart +++ b/packages/flutter_tools/lib/src/android/gradle_utils.dart @@ -59,6 +59,22 @@ const String maxKnownAgpVersion = '8.1'; final RegExp _androidGradlePluginRegExp = RegExp(r'com\.android\.tools\.build:gradle:(\d+\.\d+\.\d+)'); +// Expected content format (with lines above and below). +// Version can have 2 or 3 numbers. +// 'distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip' +// '^\s*' protects against commented out lines. +final RegExp distributionUrlRegex = + RegExp(r'^\s*distributionUrl\s*=\s*.*\.zip', multiLine: true); + +// Modified version of the gradle distribution url match designed to only match +// gradle.org urls so that we can guarantee any modifications to the url +// still points to a hosted zip. +final RegExp gradleOrgVersionMatch = +RegExp( + r'^\s*distributionUrl\s*=\s*https\\://services\.gradle\.org/distributions/gradle-((?:\d|\.)+)-(.*)\.zip', + multiLine: true +); + // From https://docs.gradle.org/current/userguide/command_line_interface.html#command_line_interface const String gradleVersionFlag = r'--version'; @@ -169,42 +185,49 @@ String getGradleVersionForAndroidPlugin(Directory directory, Logger logger) { return getGradleVersionFor(androidPluginVersion ?? 'unknown'); } +/// Returns the gradle file from the top level directory. +/// The returned file is not guaranteed to be present. +File getGradleWrapperFile(Directory directory) { + return directory.childDirectory(gradleDirectoryName) + .childDirectory(gradleWrapperDirectoryName) + .childFile(gradleWrapperPropertiesFilename); +} + +/// Parses the gradle wrapper distribution url to return a string containing +/// the version number. +/// +/// Expected input is of the form '...gradle-7.4.2-all.zip', and the output +/// would be of the form '7.4.2'. +String? parseGradleVersionFromDistributionUrl(String? distributionUrl) { + if (distributionUrl == null) { + return null; + } + final List zipParts = distributionUrl.split('-'); + if (zipParts.length < 2) { + return null; + } + return zipParts[1]; +} + /// Returns either the gradle-wrapper.properties value from the passed in /// [directory] or if not present the version available in local path. /// /// If gradle version is not found null is returned. -/// [directory] should be and android directory with a build.gradle file. +/// [directory] should be an android directory with a build.gradle file. Future getGradleVersion( Directory directory, Logger logger, ProcessManager processManager) async { - final File propertiesFile = directory - .childDirectory(gradleDirectoryName) - .childDirectory(gradleWrapperDirectoryName) - .childFile(gradleWrapperPropertiesFilename); + final File propertiesFile = getGradleWrapperFile(directory); if (propertiesFile.existsSync()) { final String wrapperFileContent = propertiesFile.readAsStringSync(); - // Expected content format (with lines above and below). - // Version can have 2 or 3 numbers. - // 'distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip' - // '^\s*' protects against commented out lines. - final RegExp distributionUrlRegex = - RegExp(r'^\s*distributionUrl\s?=\s?.*\.zip', multiLine: true); - final RegExpMatch? distributionUrl = distributionUrlRegex.firstMatch(wrapperFileContent); if (distributionUrl != null) { - // Expected content: 'gradle-7.4.2-all.zip' - final String? gradleZip = distributionUrl.group(0); - if (gradleZip != null) { - final List zipParts = gradleZip.split('-'); - if (zipParts.length > 2) { - final String gradleVersion = zipParts[1]; - return gradleVersion; - } else { - // Did not find gradle zip url. Likely this is a bug in our parsing. - logger.printWarning(_formatParseWarning(wrapperFileContent)); - } + final String? gradleVersion = + parseGradleVersionFromDistributionUrl(distributionUrl.group(0)); + if (gradleVersion != null) { + return gradleVersion; } else { // Did not find gradle zip url. Likely this is a bug in our parsing. logger.printWarning(_formatParseWarning(wrapperFileContent)); @@ -248,7 +271,7 @@ OS: Mac OS X 13.2.1 aarch64 final RegExpMatch? version = gradleVersionRegex.firstMatch(gradleVersionVerbose); if (version == null) { - // Most likley a bug in our parse implementation/regex. + // Most likely a bug in our parse implementation/regex. logger.printWarning(_formatParseWarning(gradleVersionVerbose)); return null; } @@ -300,7 +323,7 @@ String _formatParseWarning(String content) { // // Source of truth found here: // https://developer.android.com/studio/releases/gradle-plugin#updating-gradle -// AGP has a minimim version of gradle required but no max starting at +// AGP has a minimum version of gradle required but no max starting at // AGP version 2.3.0+. bool validateGradleAndAgp(Logger logger, {required String? gradleV, required String? agpV}) { diff --git a/packages/flutter_tools/lib/src/android/migrations/android_studio_java_gradle_conflict_migration.dart b/packages/flutter_tools/lib/src/android/migrations/android_studio_java_gradle_conflict_migration.dart new file mode 100644 index 0000000000..5788a49aff --- /dev/null +++ b/packages/flutter_tools/lib/src/android/migrations/android_studio_java_gradle_conflict_migration.dart @@ -0,0 +1,170 @@ +// 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/file_system.dart'; +import '../../base/os.dart'; +import '../../base/platform.dart'; +import '../../base/process.dart'; +import '../../base/project_migrator.dart'; +import '../../base/version.dart'; +import '../../project.dart'; +import '../android_sdk.dart'; +import '../android_studio.dart'; +import '../gradle_utils.dart'; + +// Android Studio 2022.2 "Flamingo" is the first to bundle a Java 17 JDK. +// Previous versions bundled a Java 11 JDK. +@visibleForTesting +final Version androidStudioFlamingo = Version(2022, 2, 0); +@visibleForTesting +const String gradleVersion7_6_1 = r'7.6.1'; + +// String that can be placed in the gradle-wrapper.properties to opt out of this +// migrator. +@visibleForTesting +const String optOutFlag = 'NoFlutterGradleWrapperUpgrade'; +// Only the major version matters. +final Version flamingoBundledJava = Version(17, 0, 0); + +// These gradle versions were chosen because they +// 1. Were output by 'flutter create' at some point in flutter's history and +// 2. Are less than 7.3, the lowest supported gradle version for JDK 17 +const List gradleVersionsToUpgradeFrom = + ['5.6.2', '6.7']; + +// Define log messages as constants to re-use in testing. +@visibleForTesting +const String gradleWrapperNotFound = + 'gradle-wrapper.properties not found, skipping Gradle-Java version compatibility check.'; +@visibleForTesting +const String androidStudioNotFound = + 'Android Studio version could not be detected, ' + 'skipping Gradle-Java version compatibility check.'; +@visibleForTesting +const String androidStudioVersionBelowFlamingo = + 'Version of Android Studio is less than Flamingo (the first impacted version),' + ' no migration attempted.'; +@visibleForTesting +const String javaVersionNot17 = + 'Version of Java is different than impacted version, no migration attempted.'; +@visibleForTesting +const String javaVersionNotFound = + 'Version of Java not found, no migration attempted.'; +@visibleForTesting +const String conflictDetected = 'Conflict detected between versions of Android Studio ' + 'and Gradle, upgrading Gradle version from current to 7.4'; +@visibleForTesting +const String gradleVersionNotFound = 'Failed to parse Gradle version from distribution url, ' + 'skipping Gradle-Java version compatibility check.'; +@visibleForTesting +const String optOutFlagEnabled = 'Skipping Android Studio Java-Gradle compatibility ' + "because opt out flag: '$optOutFlag' is enabled in gradle-wrapper.properties file."; +@visibleForTesting +const String errorWhileMigrating = 'Encountered an error while attempting Gradle-Java ' + 'version compatibility check, skipping migration attempt. Error was: '; + + +/// Migrate to a newer version of Gradle when the existing one does not support +/// the version of Java provided by the detected Android Studio version. +/// +/// For more info see the Gradle-Java compatibility matrix: +/// https://docs.gradle.org/current/userguide/compatibility.html +class AndroidStudioJavaGradleConflictMigration extends ProjectMigrator { + AndroidStudioJavaGradleConflictMigration( + super.logger, + {required AndroidProject project, + AndroidStudio? androidStudio, + required FileSystem fileSystem, + required ProcessUtils processUtils, + required Platform platform, + required OperatingSystemUtils os, + AndroidSdk? androidSdk, + }) : _gradleWrapperPropertiesFile = getGradleWrapperFile(project.hostAppGradleRoot), + _androidStudio = androidStudio, + _fileSystem = fileSystem, + _processUtils = processUtils, + _platform = platform, + _os = os, + _androidSdk = androidSdk; + final File _gradleWrapperPropertiesFile; + final AndroidStudio? _androidStudio; + final FileSystem _fileSystem; + final ProcessUtils _processUtils; + final Platform _platform; + final OperatingSystemUtils _os; + final AndroidSdk? _androidSdk; + + @override + void migrate() { + try { + if (!_gradleWrapperPropertiesFile.existsSync()) { + logger.printTrace(gradleWrapperNotFound); + return; + } + + if (_androidStudio == null || _androidStudio!.version == null) { + logger.printTrace(androidStudioNotFound); + return; + } else if (_androidStudio!.version!.major < androidStudioFlamingo.major) { + logger.printTrace(androidStudioVersionBelowFlamingo); + return; + } + + final String? javaVersionString = _androidSdk?.getJavaVersion( + androidStudio: _androidStudio, + fileSystem: _fileSystem, + operatingSystemUtils: _os, + platform: _platform, + processUtils: _processUtils, + ); + final Version? javaVersion = Version.parse(javaVersionString); + if (javaVersion == null) { + logger.printTrace(javaVersionNotFound); + return; + } + + if (javaVersion.major != flamingoBundledJava.major) { + logger.printTrace(javaVersionNot17); + return; + } + + processFileLines(_gradleWrapperPropertiesFile); + } on Exception catch (e) { + logger.printTrace(errorWhileMigrating + e.toString()); + } on Error catch (e) { + logger.printTrace(errorWhileMigrating + e.toString()); + } + } + + @override + String migrateFileContents(String fileContents) { + if (fileContents.contains(optOutFlag)) { + logger.printTrace(optOutFlagEnabled); + return fileContents; + } + final RegExpMatch? gradleDistributionUrl = gradleOrgVersionMatch.firstMatch(fileContents); + if (gradleDistributionUrl == null + || gradleDistributionUrl.groupCount < 1 + || gradleDistributionUrl[1] == null) { + logger.printTrace(gradleVersionNotFound); + return fileContents; + } + final String existingVersionString = gradleDistributionUrl[1]!; + if (gradleVersionsToUpgradeFrom.contains(existingVersionString)) { + logger.printStatus('Conflict detected between Android Studio Java version and Gradle version, ' + 'upgrading Gradle version from $existingVersionString to $gradleVersion7_6_1.'); + final String? gradleDistributionUrlString = gradleDistributionUrl.group(0); + if (gradleDistributionUrlString != null) { + final String upgradedDistributionUrl = + gradleDistributionUrlString.replaceAll(existingVersionString, gradleVersion7_6_1); + fileContents = fileContents.replaceFirst(gradleOrgVersionMatch, upgradedDistributionUrl); + } else { + logger.printTrace(gradleVersionNotFound); + } + } + return fileContents; + } +} diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index 1f7c6fc24b..3136024b54 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart @@ -94,6 +94,7 @@ Future runInContext( usage: globals.flutterUsage, gradleUtils: globals.gradleUtils!, platform: globals.platform, + androidStudio: globals.androidStudio, ), AndroidLicenseValidator: () => AndroidLicenseValidator( operatingSystemUtils: globals.os, diff --git a/packages/flutter_tools/test/commands.shard/permeable/build_apk_test.dart b/packages/flutter_tools/test/commands.shard/permeable/build_apk_test.dart index 6fa1786305..3963abd709 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/build_apk_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/build_apk_test.dart @@ -8,6 +8,7 @@ import 'package:flutter_tools/src/android/android_sdk.dart'; import 'package:flutter_tools/src/android/android_studio.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/base/version.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/build_apk.dart'; import 'package:flutter_tools/src/globals.dart' as globals; @@ -454,4 +455,7 @@ class FakeAndroidSdk extends Fake implements AndroidSdk { class FakeAndroidStudio extends Fake implements AndroidStudio { @override String get javaPath => 'java'; + + @override + Version get version => Version(2021, 3, 1); } diff --git a/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart b/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart index a6aa27b03a..38b19f74f6 100644 --- a/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart @@ -53,6 +53,7 @@ void main() { usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand(const FakeCommand( command: [ @@ -144,6 +145,7 @@ void main() { usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand(const FakeCommand( command: [ @@ -210,6 +212,7 @@ void main() { usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); const FakeCommand fakeCmd = FakeCommand( @@ -314,6 +317,7 @@ void main() { usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand(const FakeCommand( command: [ @@ -405,6 +409,7 @@ void main() { usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand(FakeCommand( command: const [ @@ -468,6 +473,7 @@ void main() { usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand(const FakeCommand( command: [ @@ -574,6 +580,7 @@ void main() { 'HOME': '/home', }, ), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand(const FakeCommand( command: [ @@ -670,6 +677,7 @@ void main() { usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand(const FakeCommand( command: [ @@ -796,6 +804,7 @@ android { usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand(const FakeCommand( command: [ @@ -829,6 +838,7 @@ BuildVariant: paidProfile usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand(const FakeCommand( command: [ @@ -858,6 +868,7 @@ Gradle Crashed usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand(const FakeCommand( command: [ @@ -922,6 +933,7 @@ Gradle Crashed usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand(const FakeCommand( command: [ @@ -979,6 +991,7 @@ Gradle Crashed usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand(const FakeCommand( command: [ @@ -1037,6 +1050,7 @@ Gradle Crashed usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand(const FakeCommand( command: [ @@ -1114,6 +1128,7 @@ Gradle Crashed usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand(const FakeCommand( command: [ @@ -1191,6 +1206,7 @@ Gradle Crashed usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand(const FakeCommand( command: [ @@ -1268,6 +1284,7 @@ Gradle Crashed usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand(const FakeCommand( command: [ @@ -1346,6 +1363,7 @@ Gradle Crashed usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand( const FakeCommand(command: [ @@ -1405,6 +1423,7 @@ Gradle Crashed usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand(const FakeCommand( command: [ @@ -1491,6 +1510,7 @@ Gradle Crashed usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand(const FakeCommand( command: [ @@ -1577,6 +1597,7 @@ Gradle Crashed usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand(const FakeCommand( command: [ @@ -1663,6 +1684,7 @@ Gradle Crashed usage: testUsage, gradleUtils: FakeGradleUtils(), platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), ); processManager.addCommand(const FakeCommand( command: [ diff --git a/packages/flutter_tools/test/general.shard/android/android_project_migration_test.dart b/packages/flutter_tools/test/general.shard/android/android_project_migration_test.dart index 4385e66be1..cd4de5875a 100644 --- a/packages/flutter_tools/test/general.shard/android/android_project_migration_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_project_migration_test.dart @@ -4,14 +4,48 @@ import 'package:file/file.dart'; import 'package:file/memory.dart'; +import 'package:flutter_tools/src/android/android_sdk.dart'; +import 'package:flutter_tools/src/android/android_studio.dart'; +import 'package:flutter_tools/src/android/gradle_utils.dart'; +import 'package:flutter_tools/src/android/migrations/android_studio_java_gradle_conflict_migration.dart'; import 'package:flutter_tools/src/android/migrations/top_level_gradle_build_file_migration.dart'; import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/base/os.dart'; +import 'package:flutter_tools/src/base/platform.dart'; +import 'package:flutter_tools/src/base/process.dart'; +import 'package:flutter_tools/src/base/version.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:test/fake.dart'; import '../../src/common.dart'; import '../../src/context.dart'; +const String otherGradleVersionWrapper = r''' +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +'''; + +const String gradleWrapperToMigrate = r''' +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +'''; + +const String gradleWrapperToMigrateTo = r''' +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +'''; + +final Version androidStudioDolphin = Version(2021, 3, 1); + void main() { group('Android migration', () { group('migrate the Gradle "clean" task to lazy declaration', () { @@ -77,6 +111,188 @@ tasks.register("clean", Delete) { ''')); }); }); + + group('migrate the gradle version to one that does not conflict with the ' + 'Android Studio-provided java version', () { + late MemoryFileSystem memoryFileSystem; + late BufferLogger bufferLogger; + late FakeAndroidProject project; + late File gradleWrapperPropertiesFile; + + setUp(() { + memoryFileSystem = MemoryFileSystem.test(); + bufferLogger = BufferLogger.test(); + project = FakeAndroidProject( + root: memoryFileSystem.currentDirectory.childDirectory('android')..createSync(), + ); + project.hostAppGradleRoot.childDirectory(gradleDirectoryName) + .childDirectory(gradleWrapperDirectoryName) + .createSync(recursive: true); + gradleWrapperPropertiesFile = project.hostAppGradleRoot + .childDirectory(gradleDirectoryName) + .childDirectory(gradleWrapperDirectoryName) + .childFile(gradleWrapperPropertiesFilename); + }); + + testWithoutContext('skipped if files are missing', () { + final AndroidStudioJavaGradleConflictMigration migration = AndroidStudioJavaGradleConflictMigration( + bufferLogger, + project: project, + androidStudio: FakeAndroidStudio(version: androidStudioDolphin), + fileSystem: FakeFileSystem(), + processUtils: FakeProcessUtils(), + platform: FakePlatform(), + os: FakeOperatingSystemUtils(), + androidSdk: FakeAndroidSdk(javaVersion: '17'), + ); + migration.migrate(); + expect(gradleWrapperPropertiesFile.existsSync(), isFalse); + expect(bufferLogger.traceText, contains(gradleWrapperNotFound)); + }); + + + testWithoutContext('skipped if android studio is null', () { + final AndroidStudioJavaGradleConflictMigration migration = AndroidStudioJavaGradleConflictMigration( + bufferLogger, + project: project, + fileSystem: FakeFileSystem(), + processUtils: FakeProcessUtils(), + platform: FakePlatform(), + os: FakeOperatingSystemUtils(), + androidSdk: FakeAndroidSdk(javaVersion: '17'), + ); + gradleWrapperPropertiesFile.writeAsStringSync(gradleWrapperToMigrate); + migration.migrate(); + expect(bufferLogger.traceText, contains(androidStudioNotFound)); + expect(gradleWrapperPropertiesFile.readAsStringSync(), + gradleWrapperToMigrate); + }); + + testWithoutContext('skipped if android studio version is null', () { + final AndroidStudioJavaGradleConflictMigration migration = AndroidStudioJavaGradleConflictMigration( + bufferLogger, + project: project, + androidStudio: FakeAndroidStudio(version: null), + fileSystem: FakeFileSystem(), + processUtils: FakeProcessUtils(), + platform: FakePlatform(), + os: FakeOperatingSystemUtils(), + androidSdk: FakeAndroidSdk(javaVersion: '17'), + ); + gradleWrapperPropertiesFile.writeAsStringSync(gradleWrapperToMigrate); + migration.migrate(); + expect(bufferLogger.traceText, contains(androidStudioNotFound)); + expect(gradleWrapperPropertiesFile.readAsStringSync(), + gradleWrapperToMigrate); + }); + + testWithoutContext('skipped if error is encountered in migrate()', () { + final AndroidStudioJavaGradleConflictMigration migration = AndroidStudioJavaGradleConflictMigration( + bufferLogger, + project: project, + androidStudio: FakeAndroidStudio(version: androidStudioFlamingo), + fileSystem: FakeFileSystem(), + processUtils: FakeProcessUtils(), + platform: FakePlatform(), + os: FakeOperatingSystemUtils(), + androidSdk: FakeErroringAndroidSdk(), + ); + gradleWrapperPropertiesFile.writeAsStringSync(gradleWrapperToMigrate); + migration.migrate(); + expect(bufferLogger.traceText, contains(errorWhileMigrating)); + expect(gradleWrapperPropertiesFile.readAsStringSync(), + gradleWrapperToMigrate); + }); + + testWithoutContext('skipped if android studio version is less than flamingo', () { + final AndroidStudioJavaGradleConflictMigration migration = AndroidStudioJavaGradleConflictMigration( + bufferLogger, + project: project, + androidStudio: FakeAndroidStudio(version: androidStudioDolphin), + fileSystem: FakeFileSystem(), + processUtils: FakeProcessUtils(), + platform: FakePlatform(), + os: FakeOperatingSystemUtils(), + androidSdk: FakeAndroidSdk(javaVersion: '17'), + ); + gradleWrapperPropertiesFile.writeAsStringSync(gradleWrapperToMigrate); + migration.migrate(); + expect(gradleWrapperPropertiesFile.readAsStringSync(), gradleWrapperToMigrate); + expect(bufferLogger.traceText, contains(androidStudioVersionBelowFlamingo)); + }); + + testWithoutContext('skipped if bundled java version is less than 17', () { + final AndroidStudioJavaGradleConflictMigration migration = AndroidStudioJavaGradleConflictMigration( + bufferLogger, + project: project, + androidStudio: FakeAndroidStudio(version: androidStudioFlamingo), + fileSystem: FakeFileSystem(), + processUtils: FakeProcessUtils(), + platform: FakePlatform(), + os: FakeOperatingSystemUtils(), + androidSdk: FakeAndroidSdk(javaVersion: '16'), + ); + gradleWrapperPropertiesFile.writeAsStringSync(gradleWrapperToMigrate); + migration.migrate(); + expect(gradleWrapperPropertiesFile.readAsStringSync(), gradleWrapperToMigrate); + expect(bufferLogger.traceText, contains(javaVersionNot17)); + }); + + testWithoutContext('nothing is changed if gradle version not one that was ' + 'used by flutter create', () { + final AndroidStudioJavaGradleConflictMigration migration = AndroidStudioJavaGradleConflictMigration( + bufferLogger, + project: project, + androidStudio: FakeAndroidStudio(version: androidStudioFlamingo), + fileSystem: FakeFileSystem(), + processUtils: FakeProcessUtils(), + platform: FakePlatform(), + os: FakeOperatingSystemUtils(), + androidSdk: FakeAndroidSdk(javaVersion: '17'), + ); + gradleWrapperPropertiesFile.writeAsStringSync(otherGradleVersionWrapper); + migration.migrate(); + expect(gradleWrapperPropertiesFile.readAsStringSync(), otherGradleVersionWrapper); + expect(bufferLogger.traceText, isEmpty); + }); + + testWithoutContext('change is made with one of the specific gradle versions' + ' we migrate for', () { + final AndroidStudioJavaGradleConflictMigration migration = AndroidStudioJavaGradleConflictMigration( + bufferLogger, + project: project, + androidStudio: FakeAndroidStudio(version: androidStudioFlamingo), + fileSystem: FakeFileSystem(), + processUtils: FakeProcessUtils(), + platform: FakePlatform(), + os: FakeOperatingSystemUtils(), + androidSdk: FakeAndroidSdk(javaVersion: '17'), + ); + gradleWrapperPropertiesFile.writeAsStringSync(gradleWrapperToMigrate); + migration.migrate(); + expect(gradleWrapperPropertiesFile.readAsStringSync(), gradleWrapperToMigrateTo); + expect(bufferLogger.statusText, contains('Conflict detected between ' + 'Android Studio Java version and Gradle version, upgrading Gradle ' + 'version from 6.7 to $gradleVersion7_6_1.')); + }); + + testWithoutContext('change is not made when opt out flag is set', () { + final AndroidStudioJavaGradleConflictMigration migration = AndroidStudioJavaGradleConflictMigration( + bufferLogger, + project: project, + androidStudio: FakeAndroidStudio(version: androidStudioFlamingo), + fileSystem: FakeFileSystem(), + processUtils: FakeProcessUtils(), + platform: FakePlatform(), + os: FakeOperatingSystemUtils(), + androidSdk: FakeAndroidSdk(javaVersion: '17'), + ); + gradleWrapperPropertiesFile.writeAsStringSync(gradleWrapperToMigrate + optOutFlag); + migration.migrate(); + expect(gradleWrapperPropertiesFile.readAsStringSync(), gradleWrapperToMigrate + optOutFlag); + expect(bufferLogger.traceText, contains(optOutFlagEnabled)); + }); + }); }); } @@ -86,3 +302,53 @@ class FakeAndroidProject extends Fake implements AndroidProject { @override Directory hostAppGradleRoot; } + +class FakeAndroidStudio extends Fake implements AndroidStudio { + FakeAndroidStudio({required Version? version}) { + _version = version; + } + + late Version? _version; + + @override + Version? get version => _version; +} + +class FakeAndroidSdk extends Fake implements AndroidSdk { + FakeAndroidSdk({required String javaVersion}) { + _javaVersion = javaVersion; + } + + late String _javaVersion; + + @override + String? getJavaVersion({ + required AndroidStudio? androidStudio, + required FileSystem fileSystem, + required OperatingSystemUtils operatingSystemUtils, + required Platform platform, + required ProcessUtils processUtils, + }) { + return _javaVersion; + } +} + +class FakeErroringAndroidSdk extends Fake implements AndroidSdk { + FakeErroringAndroidSdk(); + + @override + String? getJavaVersion({ + required AndroidStudio? androidStudio, + required FileSystem fileSystem, + required OperatingSystemUtils operatingSystemUtils, + required Platform platform, + required ProcessUtils processUtils, + }) { + throw const FileSystemException(); + } +} + +class FakeFileSystem extends Fake implements FileSystem {} +class FakeProcessUtils extends Fake implements ProcessUtils {} +class FakePlatform extends Fake implements Platform {} +class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils {} diff --git a/packages/flutter_tools/test/general.shard/android/gradle_utils_test.dart b/packages/flutter_tools/test/general.shard/android/gradle_utils_test.dart index 054bec557c..9aa482e5f3 100644 --- a/packages/flutter_tools/test/general.shard/android/gradle_utils_test.dart +++ b/packages/flutter_tools/test/general.shard/android/gradle_utils_test.dart @@ -216,6 +216,20 @@ void main() { expect(getGradlewFileName(windowsPlatform), 'gradlew.bat'); }); + testWithoutContext('returns the gradle properties file', () async { + final Directory androidDirectory = fileSystem.directory('/android') + ..createSync(); + final Directory wrapperDirectory = androidDirectory + .childDirectory(gradleDirectoryName) + .childDirectory(gradleWrapperDirectoryName) + ..createSync(recursive: true); + final File expectedFile = await wrapperDirectory + .childFile(gradleWrapperPropertiesFilename) + .create(); + final File gradleWrapperFile = getGradleWrapperFile(androidDirectory); + expect(gradleWrapperFile.path, expectedFile.path); + }); + testWithoutContext('returns the gradle wrapper version', () async { const String expectedVersion = '7.4.2'; final Directory androidDirectory = fileSystem.directory('/android') @@ -535,6 +549,32 @@ allprojects { } }); + group('Parse gradle version from distribution url', () { + testWithoutContext('null distribution url returns null version', () { + expect(parseGradleVersionFromDistributionUrl(null), null); + }); + + testWithoutContext('unparseable format returns null', () { + const String distributionUrl = 'aString'; + expect(parseGradleVersionFromDistributionUrl(distributionUrl), null); + }); + + testWithoutContext("recognizable 'all' format returns correct version", () { + const String distributionUrl = r'distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip'; + expect(parseGradleVersionFromDistributionUrl(distributionUrl), '6.7'); + }); + + testWithoutContext("recognizable 'bin' format returns correct version", () { + const String distributionUrl = r'distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip'; + expect(parseGradleVersionFromDistributionUrl(distributionUrl), '6.7'); + }); + + testWithoutContext("recognizable 'rc' format returns correct version", () { + const String distributionUrl = r'distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-rc-3-all.zip'; + expect(parseGradleVersionFromDistributionUrl(distributionUrl), '8.1'); + }); + }); + group('validates java/gradle versions', () { final List testData = [ // Values too new *these need to update* when diff --git a/packages/integration_test/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/integration_test/example/android/gradle/wrapper/gradle-wrapper.properties index 3c9d0852bf..ceccc3a854 100644 --- a/packages/integration_test/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/integration_test/example/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip