Unverified Commit e7a9adee authored by Emmanuel Garcia's avatar Emmanuel Garcia Committed by GitHub

Remove AndroidX workarounds (#86911)

parent aa89a6e5
// 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 'dart:io';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/task_result.dart';
import 'package:flutter_devicelab/framework/utils.dart';
import 'package:path/path.dart' as path;
final String gradlew = Platform.isWindows ? 'gradlew.bat' : 'gradlew';
final String gradlewExecutable = Platform.isWindows ? '.\\$gradlew' : './$gradlew';
/// Tests that AARs can be built on plugin projects.
Future<void> main() async {
await task(() async {
section('Find Java');
final String? javaHome = await findJavaHome();
if (javaHome == null)
return TaskResult.failure('Could not find Java');
print('\nUsing JAVA_HOME=$javaHome');
section('Create plugin project');
final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.');
final Directory projectDir = Directory(path.join(tempDir.path, 'hello'));
try {
await inDirectory(tempDir, () async {
await flutter(
'create',
options: <String>[
'--org', 'io.flutter.devicelab',
'--template', 'plugin',
'hello',
],
);
});
section('Build release AAR');
await inDirectory(projectDir, () async {
await flutter(
'build',
options: <String>['aar', '--verbose', '--release'],
);
});
final String repoPath = path.join(
projectDir.path,
'build',
'outputs',
'repo',
);
final File releaseAar = File(path.join(
repoPath,
'io',
'flutter',
'devicelab',
'hello',
'hello_release',
'1.0',
'hello_release-1.0.aar',
));
if (!exists(releaseAar)) {
return TaskResult.failure('Failed to build the release AAR file.');
}
final File releasePom = File(path.join(
repoPath,
'io',
'flutter',
'devicelab',
'hello',
'hello_release',
'1.0',
'hello_release-1.0.pom',
));
if (!exists(releasePom)) {
return TaskResult.failure('Failed to build the release POM file.');
}
section('Build debug AAR');
await inDirectory(projectDir, () async {
await flutter(
'build',
options: <String>[
'aar',
'--verbose',
'--debug',
],
);
});
final File debugAar = File(path.join(
repoPath,
'io',
'flutter',
'devicelab',
'hello',
'hello_debug',
'1.0',
'hello_debug-1.0.aar',
));
if (!exists(debugAar)) {
return TaskResult.failure('Failed to build the debug AAR file.');
}
final File debugPom = File(path.join(
repoPath,
'io',
'flutter',
'devicelab',
'hello',
'hello_debug',
'1.0',
'hello_debug-1.0.pom',
));
if (!exists(debugPom)) {
return TaskResult.failure('Failed to build the debug POM file.');
}
return TaskResult.success(null);
} catch (e) {
return TaskResult.failure(e.toString());
} finally {
rmTree(tempDir);
}
});
}
// 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 'dart:io';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/task_result.dart';
import 'package:flutter_devicelab/framework/utils.dart';
import 'package:path/path.dart' as path;
final String gradlew = Platform.isWindows ? 'gradlew.bat' : 'gradlew';
final String gradlewExecutable = Platform.isWindows ? '.\\$gradlew' : './$gradlew';
/// Tests that [settings_aar.gradle] is created when possible.
Future<void> main() async {
await task(() async {
section('Find Java');
final String? javaHome = await findJavaHome();
if (javaHome == null)
return TaskResult.failure('Could not find Java');
print('\nUsing JAVA_HOME=$javaHome');
section('Create app project');
final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.');
final Directory projectDir = Directory(path.join(tempDir.path, 'hello'));
try {
await inDirectory(tempDir, () async {
await flutter(
'create',
options: <String>['hello'],
);
});
section('Override settings.gradle V1');
final String relativeNewSettingsGradle = path.join('android', 'settings_aar.gradle');
section('Build APK');
late String stdout;
await inDirectory(projectDir, () async {
stdout = await evalFlutter(
'build',
options: <String>[
'apk',
'--flavor', 'does-not-exist',
],
canFail: true, // The flavor doesn't exist.
);
});
const String newFileContent = "include ':app'";
final File settingsGradle = File(path.join(projectDir.path, 'android', 'settings.gradle'));
final File newSettingsGradle = File(path.join(projectDir.path, 'android', 'settings_aar.gradle'));
if (!newSettingsGradle.existsSync()) {
return TaskResult.failure('Expected file: `${newSettingsGradle.path}`.');
}
if (newSettingsGradle.readAsStringSync().trim() != newFileContent) {
return TaskResult.failure('Expected to create `${newSettingsGradle.path}` V1.');
}
if (!stdout.contains('Creating `$relativeNewSettingsGradle`') ||
!stdout.contains('`$relativeNewSettingsGradle` created successfully')) {
return TaskResult.failure('Expected update message in stdout.');
}
section('Override settings.gradle V2');
const String deprecatedFileContentV2 = r'''
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withInputStream { stream -> plugins.load(stream) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}
''';
settingsGradle.writeAsStringSync(deprecatedFileContentV2, flush: true);
newSettingsGradle.deleteSync();
section('Build APK');
await inDirectory(projectDir, () async {
stdout = await evalFlutter(
'build',
options: <String>[
'apk',
'--flavor', 'does-not-exist',
],
canFail: true, // The flavor doesn't exist.
);
});
if (newSettingsGradle.readAsStringSync().trim() != newFileContent) {
return TaskResult.failure('Expected to create `${newSettingsGradle.path}` V2.');
}
if (!stdout.contains('Creating `$relativeNewSettingsGradle`') ||
!stdout.contains('`$relativeNewSettingsGradle` created successfully')) {
return TaskResult.failure('Expected update message in stdout.');
}
section('Override settings.gradle with custom logic');
const String customDeprecatedFileContent = r'''
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withInputStream { stream -> plugins.load(stream) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}
// some custom logic
''';
settingsGradle.writeAsStringSync(customDeprecatedFileContent, flush: true);
newSettingsGradle.deleteSync();
section('Build APK');
final StringBuffer stderr = StringBuffer();
await inDirectory(projectDir, () async {
stdout = await evalFlutter(
'build',
options: <String>[
'apk',
'--flavor', 'does-not-exist',
],
canFail: true, // The flavor doesn't exist.
stderr: stderr,
);
});
if (newSettingsGradle.existsSync()) {
return TaskResult.failure('Unexpected file: `${newSettingsGradle.path}`.');
}
if (!stdout.contains('Creating `$relativeNewSettingsGradle`')) {
return TaskResult.failure('Expected update message in stdout.');
}
if (stdout.contains('`$relativeNewSettingsGradle` created successfully')) {
return TaskResult.failure('Unexpected message in stdout.');
}
if (!stderr.toString().contains('Flutter tried to create the file '
'`$relativeNewSettingsGradle`, but failed.')) {
return TaskResult.failure('Expected failure message in stdout.');
}
return TaskResult.success(null);
} catch (e) {
return TaskResult.failure(e.toString());
} finally {
rmTree(tempDir);
}
});
}
......@@ -289,51 +289,8 @@ class FlutterPlugin implements Plugin<Project> {
* Finally, the project's `settings.gradle` loads each plugin's android directory as a subproject.
*/
private void configurePlugins() {
if (!buildPluginAsAar()) {
getPluginList().each this.&configurePluginProject
getPluginDependencies().each this.&configurePluginDependencies
return
}
project.repositories {
maven {
url "${getPluginBuildDir()}/outputs/repo"
}
}
getPluginList().each { pluginName, pluginPath ->
configurePluginAar(pluginName, pluginPath, project)
}
}
private static final Pattern GROUP_PATTERN = ~/group\s+\'(.+)\'/
private static final Pattern PROJECT_NAME_PATTERN = ~/rootProject\.name\s+=\s+\'(.+)\'/
// Adds the plugin AAR dependency to the app project.
private void configurePluginAar(String pluginName, String pluginPath, Project project) {
// Extract the group id from the plugin's build.gradle.
// This is `group '<group-id>'`
File pluginBuildFile = project.file(Paths.get(pluginPath, "android", "build.gradle"));
if (!pluginBuildFile.exists()) {
throw new GradleException("Plugin $pluginName doesn't have the required file $pluginBuildFile.")
}
Matcher groupParts = GROUP_PATTERN.matcher(pluginBuildFile.text)
assert groupParts.count == 1
assert groupParts.hasGroup()
String groupId = groupParts[0][1]
// Extract the artifact name from the plugin's settings.gradle.
// This is `rootProject.name = '<artifact-name>'`
File pluginSettings = project.file(Paths.get(pluginPath, "android", "settings.gradle"));
if (!pluginSettings.exists()) {
throw new GradleException("Plugin $pluginName doesn't have the required file $pluginSettings.")
}
Matcher projectNameParts = PROJECT_NAME_PATTERN.matcher(pluginSettings.text)
assert projectNameParts.count == 1
assert projectNameParts.hasGroup()
String artifactId = "${projectNameParts[0][1]}_release"
assert !groupId.empty
project.dependencies.add("api", "$groupId:$artifactId:+")
getPluginList().each this.&configurePluginProject
getPluginDependencies().each this.&configurePluginDependencies
}
// Adds the plugin project dependency to the app project .
......@@ -544,10 +501,6 @@ class FlutterPlugin implements Plugin<Project> {
return project.plugins.hasPlugin("com.android.application");
}
private static Boolean buildPluginAsAar() {
return System.getProperty('build-plugins-as-aars') == 'true'
}
// Returns true if the build mode is supported by the current call to Gradle.
// This only relevant when using a local engine. Because the engine
// is built for a specific mode, the call to Gradle must match that mode.
......
......@@ -105,57 +105,6 @@ Iterable<String> _apkFilesFor(AndroidBuildInfo androidBuildInfo) {
return <String>['app$flavorString-$buildType.apk'];
}
/// Tries to create `settings_aar.gradle` in an app project by removing the subprojects
/// from the existing `settings.gradle` file. This operation will fail if the existing
/// `settings.gradle` file has local edits.
@visibleForTesting
void createSettingsAarGradle(Directory androidDirectory, Logger logger) {
final FileSystem fileSystem = androidDirectory.fileSystem;
final File newSettingsFile = androidDirectory.childFile('settings_aar.gradle');
if (newSettingsFile.existsSync()) {
return;
}
final File currentSettingsFile = androidDirectory.childFile('settings.gradle');
if (!currentSettingsFile.existsSync()) {
return;
}
final String currentFileContent = currentSettingsFile.readAsStringSync();
final String newSettingsRelativeFile = fileSystem.path.relative(newSettingsFile.path);
final Status status = logger.startProgress('✏️ Creating `$newSettingsRelativeFile`...');
final String flutterRoot = fileSystem.path.absolute(Cache.flutterRoot!);
final File legacySettingsDotGradleFiles = fileSystem.file(fileSystem.path.join(flutterRoot, 'packages','flutter_tools',
'gradle', 'settings.gradle.legacy_versions'));
assert(legacySettingsDotGradleFiles.existsSync());
final String settingsAarContent = fileSystem.file(fileSystem.path.join(flutterRoot, 'packages','flutter_tools',
'gradle', 'settings_aar.gradle.tmpl')).readAsStringSync();
// Get the `settings.gradle` content variants that should be patched.
final List<String> existingVariants = legacySettingsDotGradleFiles.readAsStringSync().split(';EOF');
existingVariants.add(settingsAarContent);
bool exactMatch = false;
for (final String fileContentVariant in existingVariants) {
if (currentFileContent.trim() == fileContentVariant.trim()) {
exactMatch = true;
break;
}
}
if (!exactMatch) {
status.cancel();
logger.printStatus('${logger.terminal.warningMark} Flutter tried to create the file `$newSettingsRelativeFile`, but failed.');
// Print how to manually update the file.
logger.printStatus(fileSystem.file(fileSystem.path.join(flutterRoot, 'packages','flutter_tools',
'gradle', 'manual_migration_settings.gradle.md')).readAsStringSync());
throwToolExit('Please create the file and run this command again.');
}
// Copy the new file.
newSettingsFile.writeAsStringSync(settingsAarContent);
status.stop();
logger.printStatus('${logger.terminal.successMark} `$newSettingsRelativeFile` created successfully.');
}
/// An implementation of the [AndroidBuilder] that delegates to gradle.
class AndroidGradleBuilder implements AndroidBuilder {
AndroidGradleBuilder({
......@@ -262,17 +211,13 @@ class AndroidGradleBuilder implements AndroidBuilder {
/// * [target] is the target dart entry point. Typically, `lib/main.dart`.
/// * If [isBuildingBundle] is `true`, then the output artifact is an `*.aab`,
/// otherwise the output artifact is an `*.apk`.
/// * The plugins are built as AARs if [shouldBuildPluginAsAar] is `true`. This isn't set by default
/// because it makes the build slower proportional to the number of plugins.
/// * [retries] is the max number of build retries in case one of the [GradleHandledError] handler
/// returns [GradleBuildStatus.retry] or [GradleBuildStatus.retryWithAarPlugins].
Future<void> buildGradleApp({
required FlutterProject project,
required AndroidBuildInfo androidBuildInfo,
required String target,
required bool isBuildingBundle,
required List<GradleHandledError> localGradleErrors,
bool shouldBuildPluginAsAar = false,
bool validateDeferredComponents = true,
bool deferredComponentsEnabled = false,
int retries = 1,
......@@ -286,7 +231,6 @@ class AndroidGradleBuilder implements AndroidBuilder {
if (!project.android.isSupportedVersion) {
_exitWithUnsupportedProjectMessage(_usage, _logger.terminal);
}
final Directory buildDirectory = project.android.buildDirectory;
final bool usesAndroidX = isAppUsingAndroidX(project.android.hostAppGradleRoot);
if (usesAndroidX) {
......@@ -304,16 +248,6 @@ class AndroidGradleBuilder implements AndroidBuilder {
// from the local.properties file.
updateLocalProperties(project: project, buildInfo: androidBuildInfo.buildInfo);
if (shouldBuildPluginAsAar) {
// Create a settings.gradle that doesn't import the plugins as subprojects.
createSettingsAarGradle(project.android.hostAppGradleRoot, _logger);
await buildPluginsAsAar(
project,
androidBuildInfo,
buildDirectory: buildDirectory.childDirectory('app'),
);
}
final BuildInfo buildInfo = androidBuildInfo.buildInfo;
final String assembleTask = isBuildingBundle
? getBundleTaskFor(buildInfo)
......@@ -392,13 +326,6 @@ class AndroidGradleBuilder implements AndroidBuilder {
if (androidBuildInfo.splitPerAbi) {
command.add('-Psplit-per-abi=true');
}
if (shouldBuildPluginAsAar) {
// Pass a system flag instead of a project flag, so this flag can be
// read from include_flutter.groovy.
command.add('-Dbuild-plugins-as-aars=true');
// Don't use settings.gradle from the current project since it includes the plugins as subprojects.
command.add('--settings-file=settings_aar.gradle');
}
if (androidBuildInfo.fastStart) {
command.add('-Pfast-start=true');
}
......@@ -407,12 +334,6 @@ class AndroidGradleBuilder implements AndroidBuilder {
GradleHandledError? detectedGradleError;
String? detectedGradleErrorLine;
String? consumeLog(String line) {
// This message was removed from first-party plugins,
// but older plugin versions still display this message.
if (androidXPluginWarningRegex.hasMatch(line)) {
// Don't pipe.
return null;
}
if (detectedGradleError != null) {
// Pipe stdout/stderr from Gradle.
return line;
......@@ -468,7 +389,6 @@ class AndroidGradleBuilder implements AndroidBuilder {
line: detectedGradleErrorLine!,
project: project,
usesAndroidX: usesAndroidX,
shouldBuildPluginAsAar: shouldBuildPluginAsAar,
);
if (retries >= 1) {
......@@ -481,19 +401,6 @@ class AndroidGradleBuilder implements AndroidBuilder {
target: target,
isBuildingBundle: isBuildingBundle,
localGradleErrors: localGradleErrors,
shouldBuildPluginAsAar: shouldBuildPluginAsAar,
retries: retries - 1,
);
BuildEvent(successEventLabel, type: 'gradle', flutterUsage: _usage).send();
return;
case GradleBuildStatus.retryWithAarPlugins:
await buildGradleApp(
project: project,
androidBuildInfo: androidBuildInfo,
target: target,
isBuildingBundle: isBuildingBundle,
localGradleErrors: localGradleErrors,
shouldBuildPluginAsAar: true,
retries: retries - 1,
);
BuildEvent(successEventLabel, type: 'gradle', flutterUsage: _usage).send();
......@@ -749,57 +656,6 @@ class AndroidGradleBuilder implements AndroidBuilder {
color: TerminalColor.green,
);
}
/// Builds the plugins as AARs.
@visibleForTesting
Future<void> buildPluginsAsAar(
FlutterProject flutterProject,
AndroidBuildInfo androidBuildInfo, {
required Directory buildDirectory,
}) async {
final File flutterPluginFile = flutterProject.flutterPluginsFile;
if (!flutterPluginFile.existsSync()) {
return;
}
final List<String> plugins = flutterPluginFile.readAsStringSync().split('\n');
for (final String plugin in plugins) {
final List<String> pluginParts = plugin.split('=');
if (pluginParts.length != 2) {
continue;
}
final Directory pluginDirectory = _fileSystem.directory(pluginParts.last);
assert(pluginDirectory.existsSync());
final String pluginName = pluginParts.first;
final File buildGradleFile = pluginDirectory.childDirectory('android').childFile('build.gradle');
if (!buildGradleFile.existsSync()) {
_logger.printTrace("Skipping plugin $pluginName since it doesn't have a android/build.gradle file");
continue;
}
_logger.printStatus('Building plugin $pluginName...');
try {
await buildGradleAar(
project: FlutterProject.fromDirectory(pluginDirectory),
androidBuildInfo: AndroidBuildInfo(
BuildInfo(
BuildMode.release, // Plugins are built as release.
null, // Plugins don't define flavors.
treeShakeIcons: androidBuildInfo.buildInfo.treeShakeIcons,
packagesPath: androidBuildInfo.buildInfo.packagesPath,
),
),
target: '',
outputDirectory: buildDirectory,
buildNumber: '1.0'
);
} on ToolExit {
// Log the entire plugin entry in `.flutter-plugins` since it
// includes the plugin name and the version.
BuildEvent('gradle-plugin-aar-failure', type: 'gradle', eventError: plugin, flutterUsage: _usage).send();
throwToolExit('The plugin $pluginName could not be built due to the issue above.');
}
}
}
}
/// Prints how to consume the AAR from a host app.
......
......@@ -31,7 +31,6 @@ class GradleHandledError {
required String line,
required FlutterProject project,
required bool usesAndroidX,
required bool shouldBuildPluginAsAar,
}) handler;
/// The [BuildEvent] label is named gradle-[eventLabel].
......@@ -46,8 +45,6 @@ enum GradleBuildStatus {
exit,
/// The tool can retry the exact same build.
retry,
/// The tool can build the plugins as AAR and retry the build.
retryWithAarPlugins,
}
/// Returns a simple test function that evaluates to `true` if at least one of
......@@ -74,7 +71,6 @@ final List<GradleHandledError> gradleErrors = <GradleHandledError>[
minSdkVersion,
transformInputIssue,
lockFileDepMissing,
androidXFailureHandler, // Keep last since the pattern is broader.
];
// Permission defined error message.
......@@ -87,7 +83,6 @@ final GradleHandledError permissionDeniedErrorHandler = GradleHandledError(
required String line,
required FlutterProject project,
required bool usesAndroidX,
required bool shouldBuildPluginAsAar,
}) async {
globals.printStatus('${globals.logger.terminal.warningMark} Gradle does not have execution permission.', emphasis: true);
globals.printStatus(
......@@ -124,7 +119,6 @@ final GradleHandledError networkErrorHandler = GradleHandledError(
required String line,
required FlutterProject project,
required bool usesAndroidX,
required bool shouldBuildPluginAsAar,
}) async {
globals.printError(
'${globals.logger.terminal.warningMark} Gradle threw an error while downloading artifacts from the network. '
......@@ -154,7 +148,6 @@ final GradleHandledError r8FailureHandler = GradleHandledError(
required String line,
required FlutterProject project,
required bool usesAndroidX,
required bool shouldBuildPluginAsAar,
}) async {
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);
......@@ -164,91 +157,6 @@ final GradleHandledError r8FailureHandler = GradleHandledError(
eventLabel: 'r8',
);
// AndroidX failure.
//
// This regex is intentionally broad. AndroidX errors can manifest in multiple
// different ways and each one depends on the specific code config and
// filesystem paths of the project. Throwing the broadest net possible here to
// catch all known and likely cases.
//
// Example stack traces:
// https://github.com/flutter/flutter/issues/27226 "AAPT: error: resource android:attr/fontVariationSettings not found."
// https://github.com/flutter/flutter/issues/27106 "Android resource linking failed|Daemon: AAPT2|error: failed linking references"
// https://github.com/flutter/flutter/issues/27493 "error: cannot find symbol import androidx.annotation.NonNull;"
// https://github.com/flutter/flutter/issues/23995 "error: package android.support.annotation does not exist import android.support.annotation.NonNull;"
final RegExp _androidXFailureRegex = RegExp(r'(AAPT|androidx|android\.support)');
final RegExp androidXPluginWarningRegex = RegExp(r'\*{57}'
r"|WARNING: This version of (\w+) will break your Android build if it or its dependencies aren't compatible with AndroidX."
r'|See https://goo.gl/CP92wY for more information on the problem and how to fix it.'
r'|This warning prints for all Android build failures. The real root cause of the error may be unrelated.');
@visibleForTesting
final GradleHandledError androidXFailureHandler = GradleHandledError(
test: (String line) {
return !androidXPluginWarningRegex.hasMatch(line) &&
_androidXFailureRegex.hasMatch(line);
},
handler: ({
required String line,
required FlutterProject project,
required bool usesAndroidX,
required bool shouldBuildPluginAsAar,
}) async {
final bool hasPlugins = project.flutterPluginsFile.existsSync();
if (!hasPlugins) {
// If the app doesn't use any plugin, then it's unclear where
// the incompatibility is coming from.
BuildEvent(
'gradle-android-x-failure',
type: 'gradle',
eventError: 'app-not-using-plugins',
flutterUsage: globals.flutterUsage,
).send();
}
if (hasPlugins && !usesAndroidX) {
// If the app isn't using AndroidX, then the app is likely using
// a plugin already migrated to AndroidX.
globals.printStatus(
'AndroidX incompatibilities may have caused this build to fail. '
'Please migrate your app to AndroidX. See https://goo.gl/CP92wY .'
);
BuildEvent(
'gradle-android-x-failure',
type: 'gradle',
eventError: 'app-not-using-androidx',
flutterUsage: globals.flutterUsage,
).send();
}
if (hasPlugins && usesAndroidX && shouldBuildPluginAsAar) {
// This is a dependency conflict instead of an AndroidX failure since
// by this point the app is using AndroidX, the plugins are built as
// AARs, Jetifier translated Support libraries for AndroidX equivalents.
BuildEvent(
'gradle-android-x-failure',
type: 'gradle',
eventError: 'using-jetifier',
flutterUsage: globals.flutterUsage,
).send();
}
if (hasPlugins && usesAndroidX && !shouldBuildPluginAsAar) {
globals.printStatus(
'The build failed likely due to AndroidX incompatibilities in a plugin. '
'The tool is about to try using Jetifier to solve the incompatibility.'
);
BuildEvent(
'gradle-android-x-failure',
type: 'gradle',
eventError: 'not-using-jetifier',
flutterUsage: globals.flutterUsage,
).send();
return GradleBuildStatus.retryWithAarPlugins;
}
return GradleBuildStatus.exit;
},
eventLabel: 'android-x',
);
/// 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.
......@@ -261,7 +169,6 @@ final GradleHandledError licenseNotAcceptedHandler = GradleHandledError(
required String line,
required FlutterProject project,
required bool usesAndroidX,
required bool shouldBuildPluginAsAar,
}) async {
const String licenseNotAcceptedMatcher =
r'You have not accepted the license agreements of the following SDK components:\s*\[(.+)\]';
......@@ -295,7 +202,6 @@ final GradleHandledError flavorUndefinedHandler = GradleHandledError(
required String line,
required FlutterProject project,
required bool usesAndroidX,
required bool shouldBuildPluginAsAar,
}) async {
final RunResult tasksRunResult = await globals.processUtils.run(
<String>[
......@@ -368,7 +274,6 @@ final GradleHandledError minSdkVersion = GradleHandledError(
required String line,
required FlutterProject project,
required bool usesAndroidX,
required bool shouldBuildPluginAsAar,
}) async {
final File gradleFile = project.directory
.childDirectory('android')
......@@ -409,7 +314,6 @@ final GradleHandledError transformInputIssue = GradleHandledError(
required String line,
required FlutterProject project,
required bool usesAndroidX,
required bool shouldBuildPluginAsAar,
}) async {
final File gradleFile = project.directory
.childDirectory('android')
......@@ -443,7 +347,6 @@ final GradleHandledError lockFileDepMissing = GradleHandledError(
required String line,
required FlutterProject project,
required bool usesAndroidX,
required bool shouldBuildPluginAsAar,
}) async {
final File gradleFile = project.directory
.childDirectory('android')
......
......@@ -101,7 +101,6 @@ void main() {
String line,
FlutterProject project,
bool usesAndroidX,
bool shouldBuildPluginAsAar,
}) async {
handlerCalled = true;
return GradleBuildStatus.exit;
......@@ -206,7 +205,6 @@ void main() {
String line,
FlutterProject project,
bool usesAndroidX,
bool shouldBuildPluginAsAar,
}) async {
return GradleBuildStatus.retry;
},
......@@ -290,7 +288,6 @@ void main() {
String line,
FlutterProject project,
bool usesAndroidX,
bool shouldBuildPluginAsAar,
}) async {
handlerCalled = true;
return GradleBuildStatus.exit;
......@@ -454,7 +451,6 @@ void main() {
String line,
FlutterProject project,
bool usesAndroidX,
bool shouldBuildPluginAsAar,
}) async {
return GradleBuildStatus.retry;
},
......@@ -570,117 +566,6 @@ void main() {
));
});
testUsingContext('Can retry gradle build with plugins built as AARs', () async {
final AndroidGradleBuilder builder = AndroidGradleBuilder(
logger: logger,
processManager: processManager,
fileSystem: fileSystem,
artifacts: Artifacts.test(),
usage: testUsage,
gradleUtils: FakeGradleUtils(),
platform: FakePlatform(),
);
processManager.addCommand(const FakeCommand(
command: <String>[
'gradlew',
'-q',
'-Ptarget-platform=android-arm,android-arm64,android-x64',
'-Ptarget=lib/main.dart',
'-Pdart-obfuscation=false',
'-Ptrack-widget-creation=false',
'-Ptree-shake-icons=false',
'assembleRelease',
],
exitCode: 1,
stderr: '\nSome gradle message\n',
));
processManager.addCommand(const FakeCommand(
command: <String>[
'gradlew',
'-q',
'-Ptarget-platform=android-arm,android-arm64,android-x64',
'-Ptarget=lib/main.dart',
'-Pdart-obfuscation=false',
'-Ptrack-widget-creation=false',
'-Ptree-shake-icons=false',
'-Dbuild-plugins-as-aars=true',
'--settings-file=settings_aar.gradle',
'assembleRelease'
],
exitCode: 1,
stderr: '\nSome gradle message\n',
));
fileSystem.directory('android')
.childFile('build.gradle')
.createSync(recursive: true);
fileSystem.directory('android')
.childFile('gradle.properties')
.createSync(recursive: true);
fileSystem.directory('android')
.childDirectory('app')
.childFile('build.gradle')
..createSync(recursive: true)
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
int testFnCalled = 0;
bool builtPluginAsAar = false;
await expectLater(() async {
await builder.buildGradleApp(
project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
androidBuildInfo: const AndroidBuildInfo(
BuildInfo(
BuildMode.release,
null,
treeShakeIcons: false,
),
),
target: 'lib/main.dart',
isBuildingBundle: false,
localGradleErrors: <GradleHandledError>[
GradleHandledError(
test: (String line) {
if (line.contains('Some gradle message')) {
testFnCalled++;
return true;
}
return false;
},
handler: ({
String line,
FlutterProject project,
bool usesAndroidX,
bool shouldBuildPluginAsAar,
}) async {
if (testFnCalled == 2) {
builtPluginAsAar = shouldBuildPluginAsAar;
}
return GradleBuildStatus.retryWithAarPlugins;
},
eventLabel: 'random-event-label',
),
],
);
}, throwsToolExit(
message: 'Gradle task assembleRelease failed with exit code 1'
));
expect(testFnCalled, equals(2));
expect(builtPluginAsAar, isTrue);
expect(testUsage.events, contains(
const TestUsageEvent(
'build',
'gradle',
label: 'gradle-random-event-label-failure',
parameters: CustomDimensions(),
),
));
expect(processManager, hasNoRemainingExpectations);
});
testUsingContext('indicates that an APK has been built successfully', () async {
final AndroidGradleBuilder builder = AndroidGradleBuilder(
logger: logger,
......
......@@ -12,7 +12,6 @@ import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/globals_null_migrated.dart' as globals;
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import '../../src/common.dart';
import '../../src/context.dart';
......@@ -31,10 +30,8 @@ void main() {
minSdkVersion,
transformInputIssue,
lockFileDepMissing,
androidXFailureHandler,
])
);
expect(gradleErrors.last, equals(androidXFailureHandler));
});
});
......@@ -355,155 +352,6 @@ Command: /home/android/gradlew assembleRelease
});
});
group('AndroidX', () {
final TestUsage testUsage = TestUsage();
testWithoutContext('pattern', () {
expect(androidXFailureHandler.test(
'AAPT: error: resource android:attr/fontVariationSettings not found.'
), isTrue);
expect(androidXFailureHandler.test(
'AAPT: error: resource android:attr/ttcIndex not found.'
), isTrue);
expect(androidXFailureHandler.test(
'error: package android.support.annotation does not exist'
), isTrue);
expect(androidXFailureHandler.test(
'import android.support.annotation.NonNull;'
), isTrue);
expect(androidXFailureHandler.test(
'import androidx.annotation.NonNull;'
), isTrue);
expect(androidXFailureHandler.test(
'Daemon: AAPT2 aapt2-3.2.1-4818971-linux Daemon #0'
), isTrue);
});
testUsingContext('handler - no plugins', () async {
final GradleBuildStatus status = await androidXFailureHandler
.handler(line: '', project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory));
expect(testUsage.events, contains(
TestUsageEvent(
'build',
'gradle',
label: 'gradle-android-x-failure',
parameters: CustomDimensions.fromMap(<String, String>{
'cd43': 'app-not-using-plugins',
}),
),
));
expect(status, equals(GradleBuildStatus.exit));
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
Usage: () => testUsage,
});
testUsingContext('handler - plugins and no AndroidX', () async {
globals.fs.file('.flutter-plugins').createSync(recursive: true);
final GradleBuildStatus status = await androidXFailureHandler
.handler(
line: '',
project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory),
usesAndroidX: false,
);
expect(testLogger.statusText,
contains(
'AndroidX incompatibilities may have caused this build to fail. '
'Please migrate your app to AndroidX. See https://goo.gl/CP92wY .'
)
);
expect(testUsage.events, contains(
TestUsageEvent(
'build',
'gradle',
label: 'gradle-android-x-failure',
parameters: CustomDimensions.fromMap(<String, String>{
'cd43': 'app-not-using-androidx',
}),
),
));
expect(status, equals(GradleBuildStatus.exit));
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
Usage: () => testUsage,
});
testUsingContext('handler - plugins, AndroidX, and AAR', () async {
globals.fs.file('.flutter-plugins').createSync(recursive: true);
final GradleBuildStatus status = await androidXFailureHandler.handler(
line: '',
project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory),
usesAndroidX: true,
shouldBuildPluginAsAar: true,
);
expect(testUsage.events, contains(
TestUsageEvent(
'build',
'gradle',
label: 'gradle-android-x-failure',
parameters: CustomDimensions.fromMap(<String, String>{
'cd43': 'using-jetifier',
}),
),
));
expect(status, equals(GradleBuildStatus.exit));
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
Usage: () => testUsage,
});
testUsingContext('handler - plugins, AndroidX, and no AAR', () async {
globals.fs.file('.flutter-plugins').createSync(recursive: true);
final GradleBuildStatus status = await androidXFailureHandler.handler(
line: '',
project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory),
usesAndroidX: true,
shouldBuildPluginAsAar: false,
);
expect(testLogger.statusText,
contains(
'The build failed likely due to AndroidX incompatibilities in a plugin. '
'The tool is about to try using Jetifier to solve the incompatibility.'
)
);
expect(testUsage.events, contains(
TestUsageEvent(
'build',
'gradle',
label: 'gradle-android-x-failure',
parameters: CustomDimensions.fromMap(<String, String>{
'cd43': 'not-using-jetifier',
}),
),
));
expect(status, equals(GradleBuildStatus.retryWithAarPlugins));
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
Usage: () => testUsage,
});
});
group('permission errors', () {
testUsingContext('pattern', () async {
const String errorMessage = '''
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment