mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Refactor gradle.dart (#43479)
This commit is contained in:
parent
0028887a69
commit
175b37247d
@ -6,19 +6,23 @@ import 'dart:async';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../base/common.dart';
|
||||
import '../android/gradle_errors.dart';
|
||||
import '../base/context.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../build_info.dart';
|
||||
import '../project.dart';
|
||||
|
||||
import 'android_sdk.dart';
|
||||
import 'gradle.dart';
|
||||
|
||||
/// The builder in the current context.
|
||||
AndroidBuilder get androidBuilder => context.get<AndroidBuilder>() ?? _AndroidBuilderImpl();
|
||||
AndroidBuilder get androidBuilder {
|
||||
return context.get<AndroidBuilder>() ?? const _AndroidBuilderImpl();
|
||||
}
|
||||
|
||||
/// Provides the methods to build Android artifacts.
|
||||
// TODO(egarciad): https://github.com/flutter/flutter/issues/43863
|
||||
abstract class AndroidBuilder {
|
||||
const AndroidBuilder();
|
||||
/// Builds an AAR artifact.
|
||||
Future<void> buildAar({
|
||||
@required FlutterProject project,
|
||||
@ -44,7 +48,7 @@ abstract class AndroidBuilder {
|
||||
|
||||
/// Default implementation of [AarBuilder].
|
||||
class _AndroidBuilderImpl extends AndroidBuilder {
|
||||
_AndroidBuilderImpl();
|
||||
const _AndroidBuilderImpl();
|
||||
|
||||
/// Builds the AAR and POM files for the current Flutter module or plugin.
|
||||
@override
|
||||
@ -54,27 +58,18 @@ class _AndroidBuilderImpl extends AndroidBuilder {
|
||||
@required String target,
|
||||
@required String outputDir,
|
||||
}) async {
|
||||
if (!project.android.isUsingGradle) {
|
||||
throwToolExit(
|
||||
'The build process for Android has changed, and the current project configuration '
|
||||
'is no longer valid. Please consult\n\n'
|
||||
' https://github.com/flutter/flutter/wiki/Upgrading-Flutter-projects-to-build-with-gradle\n\n'
|
||||
'for details on how to upgrade the project.'
|
||||
);
|
||||
}
|
||||
if (!project.manifest.isModule && !project.manifest.isPlugin) {
|
||||
throwToolExit('AARs can only be built for plugin or module projects.');
|
||||
}
|
||||
// Validate that we can find an Android SDK.
|
||||
if (androidSdk == null) {
|
||||
throwToolExit('No Android SDK found. Try setting the `ANDROID_SDK_ROOT` environment variable.');
|
||||
}
|
||||
try {
|
||||
Directory outputDirectory =
|
||||
fs.directory(outputDir ?? project.android.buildDirectory);
|
||||
if (project.isModule) {
|
||||
// Module projects artifacts are located in `build/host`.
|
||||
outputDirectory = outputDirectory.childDirectory('host');
|
||||
}
|
||||
await buildGradleAar(
|
||||
project: project,
|
||||
androidBuildInfo: androidBuildInfo,
|
||||
target: target,
|
||||
outputDir: outputDir,
|
||||
outputDir: outputDirectory,
|
||||
);
|
||||
} finally {
|
||||
androidSdk.reinitialize();
|
||||
@ -88,24 +83,13 @@ class _AndroidBuilderImpl extends AndroidBuilder {
|
||||
@required AndroidBuildInfo androidBuildInfo,
|
||||
@required String target,
|
||||
}) async {
|
||||
if (!project.android.isUsingGradle) {
|
||||
throwToolExit(
|
||||
'The build process for Android has changed, and the current project configuration '
|
||||
'is no longer valid. Please consult\n\n'
|
||||
' https://github.com/flutter/flutter/wiki/Upgrading-Flutter-projects-to-build-with-gradle\n\n'
|
||||
'for details on how to upgrade the project.'
|
||||
);
|
||||
}
|
||||
// Validate that we can find an android sdk.
|
||||
if (androidSdk == null) {
|
||||
throwToolExit('No Android SDK found. Try setting the ANDROID_SDK_ROOT environment variable.');
|
||||
}
|
||||
try {
|
||||
await buildGradleProject(
|
||||
await buildGradleApp(
|
||||
project: project,
|
||||
androidBuildInfo: androidBuildInfo,
|
||||
target: target,
|
||||
isBuildingBundle: false,
|
||||
localGradleErrors: gradleErrors,
|
||||
);
|
||||
} finally {
|
||||
androidSdk.reinitialize();
|
||||
@ -119,54 +103,16 @@ class _AndroidBuilderImpl extends AndroidBuilder {
|
||||
@required AndroidBuildInfo androidBuildInfo,
|
||||
@required String target,
|
||||
}) async {
|
||||
if (!project.android.isUsingGradle) {
|
||||
throwToolExit(
|
||||
'The build process for Android has changed, and the current project configuration '
|
||||
'is no longer valid. Please consult\n\n'
|
||||
'https://github.com/flutter/flutter/wiki/Upgrading-Flutter-projects-to-build-with-gradle\n\n'
|
||||
'for details on how to upgrade the project.'
|
||||
);
|
||||
}
|
||||
// Validate that we can find an android sdk.
|
||||
if (androidSdk == null) {
|
||||
throwToolExit('No Android SDK found. Try setting the ANDROID_HOME environment variable.');
|
||||
}
|
||||
|
||||
try {
|
||||
await buildGradleProject(
|
||||
await buildGradleApp(
|
||||
project: project,
|
||||
androidBuildInfo: androidBuildInfo,
|
||||
target: target,
|
||||
isBuildingBundle: true,
|
||||
localGradleErrors: gradleErrors,
|
||||
);
|
||||
} finally {
|
||||
androidSdk.reinitialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A fake implementation of [AndroidBuilder].
|
||||
@visibleForTesting
|
||||
class FakeAndroidBuilder implements AndroidBuilder {
|
||||
@override
|
||||
Future<void> buildAar({
|
||||
@required FlutterProject project,
|
||||
@required AndroidBuildInfo androidBuildInfo,
|
||||
@required String target,
|
||||
@required String outputDir,
|
||||
}) async {}
|
||||
|
||||
@override
|
||||
Future<void> buildApk({
|
||||
@required FlutterProject project,
|
||||
@required AndroidBuildInfo androidBuildInfo,
|
||||
@required String target,
|
||||
}) async {}
|
||||
|
||||
@override
|
||||
Future<void> buildAab({
|
||||
@required FlutterProject project,
|
||||
@required AndroidBuildInfo androidBuildInfo,
|
||||
@required String target,
|
||||
}) async {}
|
||||
}
|
||||
|
@ -236,7 +236,7 @@ class AndroidStudio implements Comparable<AndroidStudio> {
|
||||
|
||||
// Read all $HOME/.AndroidStudio*/system/.home files. There may be several
|
||||
// pointing to the same installation, so we grab only the latest one.
|
||||
if (fs.directory(homeDirPath).existsSync()) {
|
||||
if (homeDirPath != null && fs.directory(homeDirPath).existsSync()) {
|
||||
for (FileSystemEntity entity in fs.directory(homeDirPath).listSync(followLinks: false)) {
|
||||
if (entity is Directory && entity.basename.startsWith('.AndroidStudio')) {
|
||||
final AndroidStudio studio = AndroidStudio.fromHomeDot(entity);
|
||||
|
File diff suppressed because it is too large
Load Diff
326
packages/flutter_tools/lib/src/android/gradle_errors.dart
Normal file
326
packages/flutter_tools/lib/src/android/gradle_errors.dart
Normal file
@ -0,0 +1,326 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../base/process.dart';
|
||||
import '../base/terminal.dart';
|
||||
import '../globals.dart';
|
||||
import '../project.dart';
|
||||
import '../reporting/reporting.dart';
|
||||
import 'gradle_utils.dart';
|
||||
|
||||
typedef GradleErrorTest = bool Function(String);
|
||||
|
||||
/// A Gradle error handled by the tool.
|
||||
class GradleHandledError{
|
||||
const GradleHandledError({
|
||||
this.test,
|
||||
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({
|
||||
String line,
|
||||
FlutterProject project,
|
||||
bool usesAndroidX,
|
||||
bool shouldBuildPluginAsAar,
|
||||
}) 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,
|
||||
/// The tool can build the plugins as AAR and retry the build.
|
||||
retryWithAarPlugins,
|
||||
}
|
||||
|
||||
/// Returns a simple test function that evaluates to [true] if
|
||||
/// [errorMessage] 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,
|
||||
androidXFailureHandler,
|
||||
];
|
||||
|
||||
// Permission defined error message.
|
||||
@visibleForTesting
|
||||
final GradleHandledError permissionDeniedErrorHandler = GradleHandledError(
|
||||
test: _lineMatcher(const <String>[
|
||||
'Permission denied',
|
||||
]),
|
||||
handler: ({
|
||||
String line,
|
||||
FlutterProject project,
|
||||
bool usesAndroidX,
|
||||
bool shouldBuildPluginAsAar,
|
||||
}) async {
|
||||
printStatus('$warningMark Gradle does not have permission to execute by your user.', emphasis: true);
|
||||
printStatus(
|
||||
'You should change the ownership of the project directory to your user, '
|
||||
'or move the project to a directory with execute permissions.',
|
||||
indent: 4
|
||||
);
|
||||
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',
|
||||
]),
|
||||
handler: ({
|
||||
String line,
|
||||
FlutterProject project,
|
||||
bool usesAndroidX,
|
||||
bool shouldBuildPluginAsAar,
|
||||
}) async {
|
||||
printError(
|
||||
'$warningMark Gradle threw an error while trying to update itself. '
|
||||
'Retrying the update...'
|
||||
);
|
||||
return GradleBuildStatus.retry;
|
||||
},
|
||||
eventLabel: 'network',
|
||||
);
|
||||
|
||||
// R8 failure.
|
||||
@visibleForTesting
|
||||
final GradleHandledError r8FailureHandler = GradleHandledError(
|
||||
test: _lineMatcher(const <String>[
|
||||
'com.android.tools.r8',
|
||||
]),
|
||||
handler: ({
|
||||
String line,
|
||||
FlutterProject project,
|
||||
bool usesAndroidX,
|
||||
bool shouldBuildPluginAsAar,
|
||||
}) async {
|
||||
printStatus('$warningMark The shrinker may have failed to optimize the Java bytecode.', emphasis: true);
|
||||
printStatus('To disable the shrinker, pass the `--no-shrink` flag to this command.', indent: 4);
|
||||
printStatus('To learn more, see: https://developer.android.com/studio/build/shrink-code', indent: 4);
|
||||
return GradleBuildStatus.exit;
|
||||
},
|
||||
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: ({
|
||||
String line,
|
||||
FlutterProject project,
|
||||
bool usesAndroidX,
|
||||
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',
|
||||
eventError: 'app-not-using-plugins',
|
||||
).send();
|
||||
}
|
||||
if (hasPlugins && !usesAndroidX) {
|
||||
// If the app isn't using AndroidX, then the app is likely using
|
||||
// a plugin already migrated to AndroidX.
|
||||
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',
|
||||
eventError: 'app-not-using-androidx',
|
||||
).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',
|
||||
eventError: 'using-jetifier',
|
||||
).send();
|
||||
}
|
||||
if (hasPlugins && usesAndroidX && !shouldBuildPluginAsAar) {
|
||||
printStatus(
|
||||
'The built failed likely due to AndroidX incompatibilities in a plugin. '
|
||||
'The tool is about to try using Jetfier to solve the incompatibility.'
|
||||
);
|
||||
BuildEvent(
|
||||
'gradle--android-x-failure',
|
||||
eventError: 'not-using-jetifier',
|
||||
).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.
|
||||
@visibleForTesting
|
||||
final GradleHandledError licenseNotAcceptedHandler = GradleHandledError(
|
||||
test: _lineMatcher(const <String>[
|
||||
'You have not accepted the license agreements of the following SDK components',
|
||||
]),
|
||||
handler: ({
|
||||
String line,
|
||||
FlutterProject project,
|
||||
bool usesAndroidX,
|
||||
bool shouldBuildPluginAsAar,
|
||||
}) async {
|
||||
const String licenseNotAcceptedMatcher =
|
||||
r'You have not accepted the license agreements of the following SDK components:'
|
||||
r'\s*\[(.+)\]';
|
||||
|
||||
final RegExp licenseFailure = RegExp(licenseNotAcceptedMatcher, multiLine: true);
|
||||
assert(licenseFailure != null);
|
||||
final Match licenseMatch = licenseFailure.firstMatch(line);
|
||||
printStatus(
|
||||
'$warningMark Unable to download needed Android SDK components, as the '
|
||||
'following licenses have not been accepted:\n'
|
||||
'${licenseMatch.group(1)}\n\n'
|
||||
'To resolve this, please run the following command in a Terminal:\n'
|
||||
'flutter doctor --android-licenses'
|
||||
);
|
||||
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: ({
|
||||
String line,
|
||||
FlutterProject project,
|
||||
bool usesAndroidX,
|
||||
bool shouldBuildPluginAsAar,
|
||||
}) async {
|
||||
final RunResult tasksRunResult = await processUtils.run(
|
||||
<String>[
|
||||
gradleUtils.getExecutable(project),
|
||||
'app:tasks' ,
|
||||
'--all',
|
||||
'--console=auto',
|
||||
],
|
||||
throwOnError: true,
|
||||
workingDirectory: project.android.hostAppGradleRoot.path,
|
||||
environment: gradleEnvironment,
|
||||
);
|
||||
// Extract build types and product flavors.
|
||||
final Set<String> variants = <String>{};
|
||||
for (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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
printStatus(
|
||||
'\n$warningMark Gradle project does not define a task suitable '
|
||||
'for the requested build.'
|
||||
);
|
||||
if (productFlavors.isEmpty) {
|
||||
printStatus(
|
||||
'The android/app/build.gradle file does not define '
|
||||
'any custom product flavors. '
|
||||
'You cannot use the --flavor option.'
|
||||
);
|
||||
} else {
|
||||
printStatus(
|
||||
'The android/app/build.gradle file defines product '
|
||||
'flavors: ${productFlavors.join(', ')} '
|
||||
'You must specify a --flavor option to select one of them.'
|
||||
);
|
||||
}
|
||||
return GradleBuildStatus.exit;
|
||||
},
|
||||
eventLabel: 'flavor-undefined',
|
||||
);
|
284
packages/flutter_tools/lib/src/android/gradle_utils.dart
Normal file
284
packages/flutter_tools/lib/src/android/gradle_utils.dart
Normal file
@ -0,0 +1,284 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../android/android_sdk.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/context.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/os.dart';
|
||||
import '../base/platform.dart';
|
||||
import '../base/terminal.dart';
|
||||
import '../base/utils.dart';
|
||||
import '../base/version.dart';
|
||||
import '../build_info.dart';
|
||||
import '../cache.dart';
|
||||
import '../globals.dart';
|
||||
import '../project.dart';
|
||||
import '../reporting/reporting.dart';
|
||||
import 'android_sdk.dart';
|
||||
import 'android_studio.dart';
|
||||
|
||||
/// The environment variables needed to run Gradle.
|
||||
Map<String, String> get gradleEnvironment {
|
||||
final Map<String, String> environment = Map<String, String>.from(platform.environment);
|
||||
if (javaPath != null) {
|
||||
// Use java bundled with Android Studio.
|
||||
environment['JAVA_HOME'] = javaPath;
|
||||
}
|
||||
// Don't log analytics for downstream Flutter commands.
|
||||
// e.g. `flutter build bundle`.
|
||||
environment['FLUTTER_SUPPRESS_ANALYTICS'] = 'true';
|
||||
return environment;
|
||||
}
|
||||
|
||||
/// Gradle utils in the current [AppContext].
|
||||
GradleUtils get gradleUtils => context.get<GradleUtils>();
|
||||
|
||||
/// Provides utilities to run a Gradle task,
|
||||
/// such as finding the Gradle executable or constructing a Gradle project.
|
||||
class GradleUtils {
|
||||
/// Gets the Gradle executable path and prepares the Gradle project.
|
||||
/// This is the `gradlew` or `gradlew.bat` script in the `android/` directory.
|
||||
String getExecutable(FlutterProject project) {
|
||||
final Directory androidDir = project.android.hostAppGradleRoot;
|
||||
// Update the project if needed.
|
||||
// TODO(egarciad): https://github.com/flutter/flutter/issues/40460
|
||||
migrateToR8(androidDir);
|
||||
injectGradleWrapperIfNeeded(androidDir);
|
||||
|
||||
final File gradle = androidDir.childFile(
|
||||
platform.isWindows ? 'gradlew.bat' : 'gradlew',
|
||||
);
|
||||
if (gradle.existsSync()) {
|
||||
printTrace('Using gradle from ${gradle.absolute.path}.');
|
||||
return gradle.absolute.path;
|
||||
}
|
||||
throwToolExit(
|
||||
'Unable to locate gradlew script. Please check that ${gradle.path} '
|
||||
'exists or that ${gradle.dirname} can be read.'
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Migrates the Android's [directory] to R8.
|
||||
/// https://developer.android.com/studio/build/shrink-code
|
||||
@visibleForTesting
|
||||
void migrateToR8(Directory directory) {
|
||||
final File gradleProperties = directory.childFile('gradle.properties');
|
||||
if (!gradleProperties.existsSync()) {
|
||||
throwToolExit(
|
||||
'Expected file ${gradleProperties.path}. '
|
||||
'Please ensure that this file exists or that ${gradleProperties.dirname} can be read.'
|
||||
);
|
||||
}
|
||||
final String propertiesContent = gradleProperties.readAsStringSync();
|
||||
if (propertiesContent.contains('android.enableR8')) {
|
||||
printTrace('gradle.properties already sets `android.enableR8`');
|
||||
return;
|
||||
}
|
||||
printTrace('set `android.enableR8=true` in gradle.properties');
|
||||
try {
|
||||
if (propertiesContent.isNotEmpty && !propertiesContent.endsWith('\n')) {
|
||||
// Add a new line if the file doesn't end with a new line.
|
||||
gradleProperties.writeAsStringSync('\n', mode: FileMode.append);
|
||||
}
|
||||
gradleProperties.writeAsStringSync('android.enableR8=true\n', mode: FileMode.append);
|
||||
} on FileSystemException {
|
||||
throwToolExit(
|
||||
'The tool failed to add `android.enableR8=true` to ${gradleProperties.path}. '
|
||||
'Please update the file manually and try this command again.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Injects the Gradle wrapper files if any of these files don't exist in [directory].
|
||||
void injectGradleWrapperIfNeeded(Directory directory) {
|
||||
copyDirectorySync(
|
||||
cache.getArtifactDirectory('gradle_wrapper'),
|
||||
directory,
|
||||
shouldCopyFile: (File sourceFile, File destinationFile) {
|
||||
// Don't override the existing files in the project.
|
||||
return !destinationFile.existsSync();
|
||||
},
|
||||
onFileCopied: (File sourceFile, File destinationFile) {
|
||||
final String modes = sourceFile.statSync().modeString();
|
||||
if (modes != null && modes.contains('x')) {
|
||||
os.makeExecutable(destinationFile);
|
||||
}
|
||||
},
|
||||
);
|
||||
// Add the `gradle-wrapper.properties` file if it doesn't exist.
|
||||
final File propertiesFile = directory.childFile(
|
||||
fs.path.join('gradle', 'wrapper', 'gradle-wrapper.properties'));
|
||||
if (!propertiesFile.existsSync()) {
|
||||
final String gradleVersion = getGradleVersionForAndroidPlugin(directory);
|
||||
propertiesFile.writeAsStringSync('''
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\\://services.gradle.org/distributions/gradle-$gradleVersion-all.zip
|
||||
''', flush: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const String _defaultGradleVersion = '5.6.2';
|
||||
|
||||
final RegExp _androidPluginRegExp = RegExp('com\.android\.tools\.build\:gradle\:(\\d+\.\\d+\.\\d+\)');
|
||||
|
||||
/// Returns the Gradle version that the current Android plugin depends on when found,
|
||||
/// otherwise it returns a default version.
|
||||
///
|
||||
/// The Android plugin version is specified in the [build.gradle] file within
|
||||
/// the project's Android directory.
|
||||
String getGradleVersionForAndroidPlugin(Directory directory) {
|
||||
final File buildFile = directory.childFile('build.gradle');
|
||||
if (!buildFile.existsSync()) {
|
||||
return _defaultGradleVersion;
|
||||
}
|
||||
final String buildFileContent = buildFile.readAsStringSync();
|
||||
final Iterable<Match> pluginMatches = _androidPluginRegExp.allMatches(buildFileContent);
|
||||
if (pluginMatches.isEmpty) {
|
||||
return _defaultGradleVersion;
|
||||
}
|
||||
final String androidPluginVersion = pluginMatches.first.group(1);
|
||||
return getGradleVersionFor(androidPluginVersion);
|
||||
}
|
||||
|
||||
/// Returns true if [targetVersion] is within the range [min] and [max] inclusive.
|
||||
bool _isWithinVersionRange(
|
||||
String targetVersion, {
|
||||
@required String min,
|
||||
@required String max,
|
||||
}) {
|
||||
assert(min != null);
|
||||
assert(max != null);
|
||||
final Version parsedTargetVersion = Version.parse(targetVersion);
|
||||
return parsedTargetVersion >= Version.parse(min) &&
|
||||
parsedTargetVersion <= Version.parse(max);
|
||||
}
|
||||
|
||||
/// Returns the Gradle version that is required by the given Android Gradle plugin version
|
||||
/// by picking the largest compatible version from
|
||||
/// https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
|
||||
String getGradleVersionFor(String androidPluginVersion) {
|
||||
if (_isWithinVersionRange(androidPluginVersion, min: '1.0.0', max: '1.1.3')) {
|
||||
return '2.3';
|
||||
}
|
||||
if (_isWithinVersionRange(androidPluginVersion, min: '1.2.0', max: '1.3.1')) {
|
||||
return '2.9';
|
||||
}
|
||||
if (_isWithinVersionRange(androidPluginVersion, min: '1.5.0', max: '1.5.0')) {
|
||||
return '2.2.1';
|
||||
}
|
||||
if (_isWithinVersionRange(androidPluginVersion, min: '2.0.0', max: '2.1.2')) {
|
||||
return '2.13';
|
||||
}
|
||||
if (_isWithinVersionRange(androidPluginVersion, min: '2.1.3', max: '2.2.3')) {
|
||||
return '2.14.1';
|
||||
}
|
||||
if (_isWithinVersionRange(androidPluginVersion, min: '2.3.0', max: '2.9.9')) {
|
||||
return '3.3';
|
||||
}
|
||||
if (_isWithinVersionRange(androidPluginVersion, min: '3.0.0', max: '3.0.9')) {
|
||||
return '4.1';
|
||||
}
|
||||
if (_isWithinVersionRange(androidPluginVersion, min: '3.1.0', max: '3.1.9')) {
|
||||
return '4.4';
|
||||
}
|
||||
if (_isWithinVersionRange(androidPluginVersion, min: '3.2.0', max: '3.2.1')) {
|
||||
return '4.6';
|
||||
}
|
||||
if (_isWithinVersionRange(androidPluginVersion, min: '3.3.0', max: '3.3.2')) {
|
||||
return '4.10.2';
|
||||
}
|
||||
if (_isWithinVersionRange(androidPluginVersion, min: '3.4.0', max: '3.5.0')) {
|
||||
return '5.6.2';
|
||||
}
|
||||
throwToolExit('Unsuported Android Plugin version: $androidPluginVersion.');
|
||||
return '';
|
||||
}
|
||||
|
||||
/// Overwrite local.properties in the specified Flutter project's Android
|
||||
/// sub-project, if needed.
|
||||
///
|
||||
/// If [requireAndroidSdk] is true (the default) and no Android SDK is found,
|
||||
/// this will fail with a [ToolExit].
|
||||
void updateLocalProperties({
|
||||
@required FlutterProject project,
|
||||
BuildInfo buildInfo,
|
||||
bool requireAndroidSdk = true,
|
||||
}) {
|
||||
if (requireAndroidSdk && androidSdk == null) {
|
||||
exitWithNoSdkMessage();
|
||||
}
|
||||
final File localProperties = project.android.localPropertiesFile;
|
||||
bool changed = false;
|
||||
|
||||
SettingsFile settings;
|
||||
if (localProperties.existsSync()) {
|
||||
settings = SettingsFile.parseFromFile(localProperties);
|
||||
} else {
|
||||
settings = SettingsFile();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
void changeIfNecessary(String key, String value) {
|
||||
if (settings.values[key] == value) {
|
||||
return;
|
||||
}
|
||||
if (value == null) {
|
||||
settings.values.remove(key);
|
||||
} else {
|
||||
settings.values[key] = value;
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (androidSdk != null) {
|
||||
changeIfNecessary('sdk.dir', escapePath(androidSdk.directory));
|
||||
}
|
||||
|
||||
changeIfNecessary('flutter.sdk', escapePath(Cache.flutterRoot));
|
||||
if (buildInfo != null) {
|
||||
changeIfNecessary('flutter.buildMode', buildInfo.modeName);
|
||||
final String buildName = validatedBuildNameForPlatform(
|
||||
TargetPlatform.android_arm,
|
||||
buildInfo.buildName ?? project.manifest.buildName,
|
||||
);
|
||||
changeIfNecessary('flutter.versionName', buildName);
|
||||
final String buildNumber = validatedBuildNumberForPlatform(
|
||||
TargetPlatform.android_arm,
|
||||
buildInfo.buildNumber ?? project.manifest.buildNumber,
|
||||
);
|
||||
changeIfNecessary('flutter.versionCode', buildNumber?.toString());
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
settings.writeContents(localProperties);
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes standard Android local properties to the specified [properties] file.
|
||||
///
|
||||
/// Writes the path to the Android SDK, if known.
|
||||
void writeLocalProperties(File properties) {
|
||||
final SettingsFile settings = SettingsFile();
|
||||
if (androidSdk != null) {
|
||||
settings.values['sdk.dir'] = escapePath(androidSdk.directory);
|
||||
}
|
||||
settings.writeContents(properties);
|
||||
}
|
||||
|
||||
void exitWithNoSdkMessage() {
|
||||
BuildEvent('unsupported-project', eventError: 'android-sdk-not-found').send();
|
||||
throwToolExit(
|
||||
'$warningMark No Android SDK found. '
|
||||
'Try setting the ANDROID_HOME environment variable.'
|
||||
);
|
||||
}
|
@ -23,6 +23,17 @@ enum TerminalColor {
|
||||
AnsiTerminal get terminal {
|
||||
return context?.get<AnsiTerminal>() ?? _defaultAnsiTerminal;
|
||||
}
|
||||
|
||||
/// Warning mark to use in stdout or stderr.
|
||||
String get warningMark {
|
||||
return terminal.bolden(terminal.color('[!]', TerminalColor.red));
|
||||
}
|
||||
|
||||
/// Success mark to use in stdout.
|
||||
String get successMark {
|
||||
return terminal.bolden(terminal.color('✓', TerminalColor.green));
|
||||
}
|
||||
|
||||
final AnsiTerminal _defaultAnsiTerminal = AnsiTerminal();
|
||||
|
||||
OutputPreferences get outputPreferences {
|
||||
|
@ -6,7 +6,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import 'android/gradle.dart';
|
||||
import 'android/gradle_utils.dart';
|
||||
import 'base/common.dart';
|
||||
import 'base/context.dart';
|
||||
import 'base/file_system.dart';
|
||||
@ -914,7 +914,7 @@ class AndroidMavenArtifacts extends ArtifactSet {
|
||||
'--project-cache-dir', tempDir.path,
|
||||
'resolveDependencies',
|
||||
],
|
||||
environment: gradleEnv);
|
||||
environment: gradleEnvironment);
|
||||
if (processResult.exitCode != 0) {
|
||||
printError('Failed to download the Android dependencies');
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import 'package:yaml/yaml.dart' as yaml;
|
||||
|
||||
import '../android/android.dart' as android;
|
||||
import '../android/android_sdk.dart' as android_sdk;
|
||||
import '../android/gradle.dart' as gradle;
|
||||
import '../android/gradle_utils.dart' as gradle;
|
||||
import '../base/common.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/net.dart';
|
||||
|
@ -7,7 +7,7 @@ import 'dart:async';
|
||||
import 'android/android_sdk.dart';
|
||||
import 'android/android_studio.dart';
|
||||
import 'android/android_workflow.dart';
|
||||
import 'android/gradle.dart';
|
||||
import 'android/gradle_utils.dart';
|
||||
import 'application_package.dart';
|
||||
import 'artifacts.dart';
|
||||
import 'asset.dart';
|
||||
|
@ -7,7 +7,7 @@ import 'dart:async';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
import 'android/gradle.dart' as gradle;
|
||||
import 'android/gradle_utils.dart' as gradle;
|
||||
import 'base/common.dart';
|
||||
import 'base/context.dart';
|
||||
import 'base/file_system.dart';
|
||||
@ -574,6 +574,11 @@ class AndroidProject {
|
||||
return _firstMatchInFile(gradleFile, _groupPattern)?.group(1);
|
||||
}
|
||||
|
||||
/// The build directory where the Android artifacts are placed.
|
||||
Directory get buildDirectory {
|
||||
return parent.directory.childDirectory('build');
|
||||
}
|
||||
|
||||
Future<void> ensureReadyForPlatformSpecificTooling() async {
|
||||
if (isModule && _shouldRegenerateFromTemplate()) {
|
||||
_regenerateLibrary();
|
||||
|
@ -0,0 +1,602 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:file/memory.dart';
|
||||
|
||||
import 'package:flutter_tools/src/android/gradle_utils.dart';
|
||||
import 'package:flutter_tools/src/android/gradle_errors.dart';
|
||||
import 'package:flutter_tools/src/base/context.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:flutter_tools/src/reporting/reporting.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
import '../../src/mocks.dart';
|
||||
|
||||
void main() {
|
||||
group('gradleErrors', () {
|
||||
test('list of errors', () {
|
||||
// If you added a new Gradle error, please update this test.
|
||||
expect(gradleErrors,
|
||||
equals(<GradleHandledError>[
|
||||
licenseNotAcceptedHandler,
|
||||
networkErrorHandler,
|
||||
permissionDeniedErrorHandler,
|
||||
flavorUndefinedHandler,
|
||||
r8FailureHandler,
|
||||
androidXFailureHandler,
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('network errors', () {
|
||||
testUsingContext('throws toolExit if gradle fails while downloading', () async {
|
||||
const String errorMessage = '''
|
||||
Exception in thread "main" java.io.FileNotFoundException: https://downloads.gradle.org/distributions/gradle-4.1.1-all.zip
|
||||
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1872)
|
||||
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1474)
|
||||
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
|
||||
at org.gradle.wrapper.Download.downloadInternal(Download.java:58)
|
||||
at org.gradle.wrapper.Download.download(Download.java:44)
|
||||
at org.gradle.wrapper.Install\$1.call(Install.java:61)
|
||||
at org.gradle.wrapper.Install\$1.call(Install.java:48)
|
||||
at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65)
|
||||
at org.gradle.wrapper.Install.createDist(Install.java:48)
|
||||
at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
|
||||
at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
|
||||
|
||||
expect(testErrorMessage(errorMessage, networkErrorHandler), isTrue);
|
||||
expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry));
|
||||
|
||||
final BufferLogger logger = context.get<Logger>();
|
||||
expect(logger.errorText,
|
||||
contains(
|
||||
'Gradle threw an error while trying to update itself. '
|
||||
'Retrying the update...'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
testUsingContext('throw toolExit if gradle fails downloading with proxy error', () async {
|
||||
const String errorMessage = '''
|
||||
Exception in thread "main" java.io.IOException: Unable to tunnel through proxy. Proxy returns "HTTP/1.1 400 Bad Request"
|
||||
at sun.net.www.protocol.http.HttpURLConnection.doTunneling(HttpURLConnection.java:2124)
|
||||
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:183)
|
||||
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1546)
|
||||
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1474)
|
||||
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
|
||||
at org.gradle.wrapper.Download.downloadInternal(Download.java:58)
|
||||
at org.gradle.wrapper.Download.download(Download.java:44)
|
||||
at org.gradle.wrapper.Install\$1.call(Install.java:61)
|
||||
at org.gradle.wrapper.Install\$1.call(Install.java:48)
|
||||
at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65)
|
||||
at org.gradle.wrapper.Install.createDist(Install.java:48)
|
||||
at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
|
||||
at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
|
||||
|
||||
expect(testErrorMessage(errorMessage, networkErrorHandler), isTrue);
|
||||
expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry));
|
||||
|
||||
final BufferLogger logger = context.get<Logger>();
|
||||
expect(logger.errorText,
|
||||
contains(
|
||||
'Gradle threw an error while trying to update itself. '
|
||||
'Retrying the update...'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
testUsingContext('throws toolExit if gradle times out waiting for exclusive access to zip', () async {
|
||||
const String errorMessage = '''
|
||||
Exception in thread "main" java.lang.RuntimeException: Timeout of 120000 reached waiting for exclusive access to file: /User/documents/gradle-5.6.2-all.zip
|
||||
at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:61)
|
||||
at org.gradle.wrapper.Install.createDist(Install.java:48)
|
||||
at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
|
||||
at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
|
||||
|
||||
expect(testErrorMessage(errorMessage, networkErrorHandler), isTrue);
|
||||
expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry));
|
||||
|
||||
final BufferLogger logger = context.get<Logger>();
|
||||
expect(logger.errorText,
|
||||
contains(
|
||||
'Gradle threw an error while trying to update itself. '
|
||||
'Retrying the update...'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
testUsingContext('throws toolExit if remote host closes connection', () async {
|
||||
const String errorMessage = '''
|
||||
Downloading https://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
||||
Exception in thread "main" javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
|
||||
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:994)
|
||||
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
|
||||
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
|
||||
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
|
||||
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
|
||||
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
|
||||
at sun.net.www.protocol.http.HttpURLConnection.followRedirect0(HttpURLConnection.java:2729)
|
||||
at sun.net.www.protocol.http.HttpURLConnection.followRedirect(HttpURLConnection.java:2641)
|
||||
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1824)
|
||||
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
|
||||
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:263)
|
||||
at org.gradle.wrapper.Download.downloadInternal(Download.java:58)
|
||||
at org.gradle.wrapper.Download.download(Download.java:44)
|
||||
at org.gradle.wrapper.Install\$1.call(Install.java:61)
|
||||
at org.gradle.wrapper.Install\$1.call(Install.java:48)
|
||||
at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65)
|
||||
at org.gradle.wrapper.Install.createDist(Install.java:48)
|
||||
at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
|
||||
at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
|
||||
|
||||
expect(testErrorMessage(errorMessage, networkErrorHandler), isTrue);
|
||||
expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry));
|
||||
|
||||
final BufferLogger logger = context.get<Logger>();
|
||||
expect(logger.errorText,
|
||||
contains(
|
||||
'Gradle threw an error while trying to update itself. '
|
||||
'Retrying the update...'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
testUsingContext('throws toolExit if file opening fails', () async {
|
||||
const String errorMessage = r'''
|
||||
Downloading https://services.gradle.org/distributions/gradle-3.5.0-all.zip
|
||||
Exception in thread "main" java.io.FileNotFoundException: https://downloads.gradle-dn.com/distributions/gradle-3.5.0-all.zip
|
||||
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1890)
|
||||
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
|
||||
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:263)
|
||||
at org.gradle.wrapper.Download.downloadInternal(Download.java:58)
|
||||
at org.gradle.wrapper.Download.download(Download.java:44)
|
||||
at org.gradle.wrapper.Install$1.call(Install.java:61)
|
||||
at org.gradle.wrapper.Install$1.call(Install.java:48)
|
||||
at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65)
|
||||
at org.gradle.wrapper.Install.createDist(Install.java:48)
|
||||
at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
|
||||
at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
|
||||
|
||||
expect(testErrorMessage(errorMessage, networkErrorHandler), isTrue);
|
||||
expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry));
|
||||
|
||||
final BufferLogger logger = context.get<Logger>();
|
||||
expect(logger.errorText,
|
||||
contains(
|
||||
'Gradle threw an error while trying to update itself. '
|
||||
'Retrying the update...'
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
testUsingContext('throws toolExit if the connection is reset', () async {
|
||||
const String errorMessage = '''
|
||||
Downloading https://services.gradle.org/distributions/gradle-5.6.2-all.zip
|
||||
Exception in thread "main" java.net.SocketException: Connection reset
|
||||
at java.net.SocketInputStream.read(SocketInputStream.java:210)
|
||||
at java.net.SocketInputStream.read(SocketInputStream.java:141)
|
||||
at sun.security.ssl.InputRecord.readFully(InputRecord.java:465)
|
||||
at sun.security.ssl.InputRecord.readV3Record(InputRecord.java:593)
|
||||
at sun.security.ssl.InputRecord.read(InputRecord.java:532)
|
||||
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:975)
|
||||
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
|
||||
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
|
||||
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
|
||||
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
|
||||
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
|
||||
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1564)
|
||||
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
|
||||
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:263)
|
||||
at org.gradle.wrapper.Download.downloadInternal(Download.java:58)
|
||||
at org.gradle.wrapper.Download.download(Download.java:44)
|
||||
at org.gradle.wrapper.Install\$1.call(Install.java:61)
|
||||
at org.gradle.wrapper.Install\$1.call(Install.java:48)
|
||||
at org.gradle.wrapper.ExclusiveFileAccessManager.access(ExclusiveFileAccessManager.java:65)
|
||||
at org.gradle.wrapper.Install.createDist(Install.java:48)
|
||||
at org.gradle.wrapper.WrapperExecutor.execute(WrapperExecutor.java:128)
|
||||
at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:61)''';
|
||||
|
||||
expect(testErrorMessage(errorMessage, networkErrorHandler), isTrue);
|
||||
expect(await networkErrorHandler.handler(), equals(GradleBuildStatus.retry));
|
||||
|
||||
final BufferLogger logger = context.get<Logger>();
|
||||
expect(logger.errorText,
|
||||
contains(
|
||||
'Gradle threw an error while trying to update itself. '
|
||||
'Retrying the update...'
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('permission errors', () {
|
||||
testUsingContext('throws toolExit if gradle is missing execute permissions', () async {
|
||||
const String errorMessage = '''
|
||||
Permission denied
|
||||
Command: /home/android/gradlew assembleRelease
|
||||
''';
|
||||
expect(testErrorMessage(errorMessage, permissionDeniedErrorHandler), isTrue);
|
||||
expect(await permissionDeniedErrorHandler.handler(), equals(GradleBuildStatus.exit));
|
||||
|
||||
final BufferLogger logger = context.get<Logger>();
|
||||
expect(
|
||||
logger.statusText,
|
||||
contains('Gradle does not have permission to execute by your user.'),
|
||||
);
|
||||
expect(
|
||||
logger.statusText,
|
||||
contains(
|
||||
'You should change the ownership of the project directory to your user, '
|
||||
'or move the project to a directory with execute permissions.'
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('AndroidX', () {
|
||||
final Usage mockUsage = MockUsage();
|
||||
|
||||
test('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.current());
|
||||
|
||||
verify(mockUsage.sendEvent(
|
||||
any,
|
||||
any,
|
||||
label: 'gradle--android-x-failure',
|
||||
parameters: <String, String>{
|
||||
'cd43': 'app-not-using-plugins',
|
||||
},
|
||||
)).called(1);
|
||||
|
||||
expect(status, equals(GradleBuildStatus.exit));
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => MemoryFileSystem(),
|
||||
ProcessManager: () => MockProcessManager(),
|
||||
Usage: () => mockUsage,
|
||||
});
|
||||
|
||||
testUsingContext('handler - plugins and no AndroidX', () async {
|
||||
fs.file('.flutter-plugins').createSync(recursive: true);
|
||||
|
||||
final GradleBuildStatus status = await androidXFailureHandler
|
||||
.handler(
|
||||
line: '',
|
||||
project: FlutterProject.current(),
|
||||
usesAndroidX: false,
|
||||
);
|
||||
|
||||
final BufferLogger logger = context.get<Logger>();
|
||||
expect(logger.statusText,
|
||||
contains(
|
||||
'AndroidX incompatibilities may have caused this build to fail. '
|
||||
'Please migrate your app to AndroidX. See https://goo.gl/CP92wY.'
|
||||
)
|
||||
);
|
||||
verify(mockUsage.sendEvent(
|
||||
any,
|
||||
any,
|
||||
label: 'gradle--android-x-failure',
|
||||
parameters: <String, String>{
|
||||
'cd43': 'app-not-using-androidx',
|
||||
},
|
||||
)).called(1);
|
||||
|
||||
expect(status, equals(GradleBuildStatus.exit));
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => MemoryFileSystem(),
|
||||
ProcessManager: () => MockProcessManager(),
|
||||
Usage: () => mockUsage,
|
||||
});
|
||||
|
||||
testUsingContext('handler - plugins, AndroidX, and AAR', () async {
|
||||
fs.file('.flutter-plugins').createSync(recursive: true);
|
||||
|
||||
final GradleBuildStatus status = await androidXFailureHandler.handler(
|
||||
line: '',
|
||||
project: FlutterProject.current(),
|
||||
usesAndroidX: true,
|
||||
shouldBuildPluginAsAar: true,
|
||||
);
|
||||
|
||||
verify(mockUsage.sendEvent(
|
||||
any,
|
||||
any,
|
||||
label: 'gradle--android-x-failure',
|
||||
parameters: <String, String>{
|
||||
'cd43': 'using-jetifier',
|
||||
},
|
||||
)).called(1);
|
||||
|
||||
expect(status, equals(GradleBuildStatus.exit));
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => MemoryFileSystem(),
|
||||
ProcessManager: () => MockProcessManager(),
|
||||
Usage: () => mockUsage,
|
||||
});
|
||||
|
||||
testUsingContext('handler - plugins, AndroidX, and no AAR', () async {
|
||||
fs.file('.flutter-plugins').createSync(recursive: true);
|
||||
|
||||
final GradleBuildStatus status = await androidXFailureHandler.handler(
|
||||
line: '',
|
||||
project: FlutterProject.current(),
|
||||
usesAndroidX: true,
|
||||
shouldBuildPluginAsAar: false,
|
||||
);
|
||||
|
||||
final BufferLogger logger = context.get<Logger>();
|
||||
expect(logger.statusText,
|
||||
contains(
|
||||
'The built failed likely due to AndroidX incompatibilities in a plugin. '
|
||||
'The tool is about to try using Jetfier to solve the incompatibility.'
|
||||
)
|
||||
);
|
||||
verify(mockUsage.sendEvent(
|
||||
any,
|
||||
any,
|
||||
label: 'gradle--android-x-failure',
|
||||
parameters: <String, String>{
|
||||
'cd43': 'not-using-jetifier',
|
||||
},
|
||||
)).called(1);
|
||||
expect(status, equals(GradleBuildStatus.retryWithAarPlugins));
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => MemoryFileSystem(),
|
||||
ProcessManager: () => MockProcessManager(),
|
||||
Usage: () => mockUsage,
|
||||
});
|
||||
});
|
||||
|
||||
group('permission errors', () {
|
||||
testUsingContext('pattern', () async {
|
||||
const String errorMessage = '''
|
||||
Permission denied
|
||||
Command: /home/android/gradlew assembleRelease
|
||||
''';
|
||||
expect(testErrorMessage(errorMessage, permissionDeniedErrorHandler), isTrue);
|
||||
});
|
||||
|
||||
testUsingContext('handler', () async {
|
||||
expect(await permissionDeniedErrorHandler.handler(), equals(GradleBuildStatus.exit));
|
||||
|
||||
final BufferLogger logger = context.get<Logger>();
|
||||
expect(
|
||||
logger.statusText,
|
||||
contains('Gradle does not have permission to execute by your user.'),
|
||||
);
|
||||
expect(
|
||||
logger.statusText,
|
||||
contains(
|
||||
'You should change the ownership of the project directory to your user, '
|
||||
'or move the project to a directory with execute permissions.'
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('license not accepted', () {
|
||||
test('pattern', () {
|
||||
expect(
|
||||
licenseNotAcceptedHandler.test(
|
||||
'You have not accepted the license agreements of the following SDK components'
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
testUsingContext('handler', () async {
|
||||
await licenseNotAcceptedHandler.handler(
|
||||
line: 'You have not accepted the license agreements of the following SDK components: [foo, bar]',
|
||||
project: FlutterProject.current(),
|
||||
);
|
||||
|
||||
final BufferLogger logger = context.get<Logger>();
|
||||
expect(
|
||||
logger.statusText,
|
||||
contains(
|
||||
'Unable to download needed Android SDK components, as the '
|
||||
'following licenses have not been accepted:\n'
|
||||
'foo, bar\n\n'
|
||||
'To resolve this, please run the following command in a Terminal:\n'
|
||||
'flutter doctor --android-licenses'
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('flavor undefined', () {
|
||||
MockProcessManager mockProcessManager;
|
||||
|
||||
setUp(() {
|
||||
mockProcessManager = MockProcessManager();
|
||||
});
|
||||
|
||||
test('pattern', () {
|
||||
expect(
|
||||
flavorUndefinedHandler.test(
|
||||
'Task assembleFooRelease not found in root project.'
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
flavorUndefinedHandler.test(
|
||||
'Task assembleBarRelease not found in root project.'
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
flavorUndefinedHandler.test(
|
||||
'Task assembleBar not found in root project.'
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
flavorUndefinedHandler.test(
|
||||
'Task assembleBar_foo not found in root project.'
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
testUsingContext('handler - with flavor', () async {
|
||||
when(mockProcessManager.run(
|
||||
<String>[
|
||||
'gradlew',
|
||||
'app:tasks' ,
|
||||
'--all',
|
||||
'--console=auto',
|
||||
],
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
environment: anyNamed('environment'),
|
||||
)).thenAnswer((_) async {
|
||||
return ProcessResult(
|
||||
1,
|
||||
0,
|
||||
'''
|
||||
assembleRelease
|
||||
assembleFlavor1
|
||||
assembleFlavor1Release
|
||||
assembleFlavor_2
|
||||
assembleFlavor_2Release
|
||||
assembleDebug
|
||||
assembleProfile
|
||||
assembles
|
||||
assembleFooTest
|
||||
''',
|
||||
'',
|
||||
);
|
||||
});
|
||||
|
||||
await flavorUndefinedHandler.handler(
|
||||
project: FlutterProject.current(),
|
||||
);
|
||||
|
||||
final BufferLogger logger = context.get<Logger>();
|
||||
expect(
|
||||
logger.statusText,
|
||||
contains(
|
||||
'Gradle project does not define a task suitable '
|
||||
'for the requested build.'
|
||||
)
|
||||
);
|
||||
expect(
|
||||
logger.statusText,
|
||||
contains(
|
||||
'The android/app/build.gradle file defines product '
|
||||
'flavors: flavor1, flavor_2 '
|
||||
'You must specify a --flavor option to select one of them.'
|
||||
)
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
GradleUtils: () => FakeGradleUtils(),
|
||||
Platform: () => fakePlatform('android'),
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
testUsingContext('handler - without flavor', () async {
|
||||
when(mockProcessManager.run(
|
||||
<String>[
|
||||
'gradlew',
|
||||
'app:tasks' ,
|
||||
'--all',
|
||||
'--console=auto',
|
||||
],
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
environment: anyNamed('environment'),
|
||||
)).thenAnswer((_) async {
|
||||
return ProcessResult(
|
||||
1,
|
||||
0,
|
||||
'''
|
||||
assembleRelease
|
||||
assembleDebug
|
||||
assembleProfile
|
||||
''',
|
||||
'',
|
||||
);
|
||||
});
|
||||
|
||||
await flavorUndefinedHandler.handler(
|
||||
project: FlutterProject.current(),
|
||||
);
|
||||
|
||||
final BufferLogger logger = context.get<Logger>();
|
||||
expect(
|
||||
logger.statusText,
|
||||
contains(
|
||||
'Gradle project does not define a task suitable '
|
||||
'for the requested build.'
|
||||
)
|
||||
);
|
||||
expect(
|
||||
logger.statusText,
|
||||
contains(
|
||||
'The android/app/build.gradle file does not define any custom product flavors. '
|
||||
'You cannot use the --flavor option.'
|
||||
)
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
GradleUtils: () => FakeGradleUtils(),
|
||||
Platform: () => fakePlatform('android'),
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class MockUsage extends Mock implements Usage {}
|
||||
|
||||
bool testErrorMessage(String errorMessage, GradleHandledError error) {
|
||||
return errorMessage
|
||||
.split('\n')
|
||||
.any((String line) => error.test(line));
|
||||
}
|
||||
|
||||
Platform fakePlatform(String name) {
|
||||
return FakePlatform
|
||||
.fromPlatform(const LocalPlatform())
|
||||
..operatingSystem = name;
|
||||
}
|
||||
|
||||
class FakeGradleUtils extends GradleUtils {
|
||||
@override
|
||||
String getExecutable(FlutterProject project) {
|
||||
return 'gradlew';
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,137 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/android/gradle_utils.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/os.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
|
||||
void main() {
|
||||
group('injectGradleWrapperIfNeeded', () {
|
||||
MemoryFileSystem memoryFileSystem;
|
||||
Directory tempDir;
|
||||
Directory gradleWrapperDirectory;
|
||||
|
||||
setUp(() {
|
||||
memoryFileSystem = MemoryFileSystem();
|
||||
tempDir = memoryFileSystem.systemTempDirectory.createTempSync('flutter_artifacts_test.');
|
||||
gradleWrapperDirectory = memoryFileSystem.directory(
|
||||
memoryFileSystem.path.join(tempDir.path, 'bin', 'cache', 'artifacts', 'gradle_wrapper'));
|
||||
gradleWrapperDirectory.createSync(recursive: true);
|
||||
gradleWrapperDirectory
|
||||
.childFile('gradlew')
|
||||
.writeAsStringSync('irrelevant');
|
||||
gradleWrapperDirectory
|
||||
.childDirectory('gradle')
|
||||
.childDirectory('wrapper')
|
||||
.createSync(recursive: true);
|
||||
gradleWrapperDirectory
|
||||
.childDirectory('gradle')
|
||||
.childDirectory('wrapper')
|
||||
.childFile('gradle-wrapper.jar')
|
||||
.writeAsStringSync('irrelevant');
|
||||
});
|
||||
|
||||
testUsingContext('Inject the wrapper when all files are missing', () {
|
||||
final Directory sampleAppAndroid = fs.directory('/sample-app/android');
|
||||
sampleAppAndroid.createSync(recursive: true);
|
||||
|
||||
injectGradleWrapperIfNeeded(sampleAppAndroid);
|
||||
|
||||
expect(sampleAppAndroid.childFile('gradlew').existsSync(), isTrue);
|
||||
|
||||
expect(sampleAppAndroid
|
||||
.childDirectory('gradle')
|
||||
.childDirectory('wrapper')
|
||||
.childFile('gradle-wrapper.jar')
|
||||
.existsSync(), isTrue);
|
||||
|
||||
expect(sampleAppAndroid
|
||||
.childDirectory('gradle')
|
||||
.childDirectory('wrapper')
|
||||
.childFile('gradle-wrapper.properties')
|
||||
.existsSync(), isTrue);
|
||||
|
||||
expect(sampleAppAndroid
|
||||
.childDirectory('gradle')
|
||||
.childDirectory('wrapper')
|
||||
.childFile('gradle-wrapper.properties')
|
||||
.readAsStringSync(),
|
||||
'distributionBase=GRADLE_USER_HOME\n'
|
||||
'distributionPath=wrapper/dists\n'
|
||||
'zipStoreBase=GRADLE_USER_HOME\n'
|
||||
'zipStorePath=wrapper/dists\n'
|
||||
'distributionUrl=https\\://services.gradle.org/distributions/gradle-5.6.2-all.zip\n');
|
||||
}, overrides: <Type, Generator>{
|
||||
Cache: () => Cache(rootOverride: tempDir),
|
||||
FileSystem: () => memoryFileSystem,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
});
|
||||
|
||||
testUsingContext('Inject the wrapper when some files are missing', () {
|
||||
final Directory sampleAppAndroid = fs.directory('/sample-app/android');
|
||||
sampleAppAndroid.createSync(recursive: true);
|
||||
|
||||
// There's an existing gradlew
|
||||
sampleAppAndroid.childFile('gradlew').writeAsStringSync('existing gradlew');
|
||||
|
||||
injectGradleWrapperIfNeeded(sampleAppAndroid);
|
||||
|
||||
expect(sampleAppAndroid.childFile('gradlew').existsSync(), isTrue);
|
||||
expect(sampleAppAndroid.childFile('gradlew').readAsStringSync(),
|
||||
equals('existing gradlew'));
|
||||
|
||||
expect(sampleAppAndroid
|
||||
.childDirectory('gradle')
|
||||
.childDirectory('wrapper')
|
||||
.childFile('gradle-wrapper.jar')
|
||||
.existsSync(), isTrue);
|
||||
|
||||
expect(sampleAppAndroid
|
||||
.childDirectory('gradle')
|
||||
.childDirectory('wrapper')
|
||||
.childFile('gradle-wrapper.properties')
|
||||
.existsSync(), isTrue);
|
||||
|
||||
expect(sampleAppAndroid
|
||||
.childDirectory('gradle')
|
||||
.childDirectory('wrapper')
|
||||
.childFile('gradle-wrapper.properties')
|
||||
.readAsStringSync(),
|
||||
'distributionBase=GRADLE_USER_HOME\n'
|
||||
'distributionPath=wrapper/dists\n'
|
||||
'zipStoreBase=GRADLE_USER_HOME\n'
|
||||
'zipStorePath=wrapper/dists\n'
|
||||
'distributionUrl=https\\://services.gradle.org/distributions/gradle-5.6.2-all.zip\n');
|
||||
}, overrides: <Type, Generator>{
|
||||
Cache: () => Cache(rootOverride: tempDir),
|
||||
FileSystem: () => memoryFileSystem,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
});
|
||||
|
||||
testUsingContext('Gives executable permission to gradle', () {
|
||||
final Directory sampleAppAndroid = fs.directory('/sample-app/android');
|
||||
sampleAppAndroid.createSync(recursive: true);
|
||||
|
||||
// Make gradlew in the wrapper executable.
|
||||
os.makeExecutable(gradleWrapperDirectory.childFile('gradlew'));
|
||||
|
||||
injectGradleWrapperIfNeeded(sampleAppAndroid);
|
||||
|
||||
final File gradlew = sampleAppAndroid.childFile('gradlew');
|
||||
expect(gradlew.existsSync(), isTrue);
|
||||
expect(gradlew.statSync().modeString().contains('x'), isTrue);
|
||||
}, overrides: <Type, Generator>{
|
||||
Cache: () => Cache(rootOverride: tempDir),
|
||||
FileSystem: () => memoryFileSystem,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
OperatingSystemUtils: () => OperatingSystemUtils(),
|
||||
});
|
||||
});
|
||||
}
|
@ -11,7 +11,7 @@ import 'package:mockito/mockito.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import 'package:flutter_tools/src/android/gradle.dart';
|
||||
import 'package:flutter_tools/src/android/gradle_utils.dart';
|
||||
import 'package:flutter_tools/src/base/common.dart';
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
@ -316,7 +316,7 @@ void main() {
|
||||
expect(args[1], '-b');
|
||||
expect(args[2].endsWith('resolve_dependencies.gradle'), isTrue);
|
||||
expect(args[5], 'resolveDependencies');
|
||||
expect(invocation.namedArguments[#environment], gradleEnv);
|
||||
expect(invocation.namedArguments[#environment], gradleEnvironment);
|
||||
return Future<ProcessResult>.value(ProcessResult(0, 0, '', ''));
|
||||
});
|
||||
|
||||
|
@ -8,7 +8,6 @@ import 'package:args/command_runner.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/android/android_builder.dart';
|
||||
import 'package:flutter_tools/src/android/android_sdk.dart';
|
||||
import 'package:flutter_tools/src/android/gradle.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
@ -17,6 +16,7 @@ import 'package:flutter_tools/src/reporting/reporting.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import '../../src/android_common.dart';
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
import '../../src/mocks.dart';
|
||||
@ -120,7 +120,9 @@ void main() {
|
||||
|
||||
group('AndroidSdk', () {
|
||||
testUsingContext('validateSdkWellFormed() not called, sdk reinitialized', () async {
|
||||
final Directory gradleCacheDir = memoryFileSystem.directory('/flutter_root/bin/cache/artifacts/gradle_wrapper')..createSync(recursive: true);
|
||||
final Directory gradleCacheDir = memoryFileSystem
|
||||
.directory('/flutter_root/bin/cache/artifacts/gradle_wrapper')
|
||||
..createSync(recursive: true);
|
||||
gradleCacheDir.childFile(platform.isWindows ? 'gradlew.bat' : 'gradlew').createSync();
|
||||
|
||||
tempDir.childFile('pubspec.yaml')
|
||||
@ -141,11 +143,31 @@ flutter:
|
||||
''');
|
||||
tempDir.childFile('.packages').createSync(recursive: true);
|
||||
final Directory androidDir = tempDir.childDirectory('android');
|
||||
androidDir.childFile('build.gradle').createSync(recursive: true);
|
||||
androidDir.childFile('gradle.properties').createSync(recursive: true);
|
||||
androidDir.childDirectory('gradle').childDirectory('wrapper').childFile('gradle-wrapper.properties').createSync(recursive: true);
|
||||
tempDir.childDirectory('build').childDirectory('outputs').childDirectory('repo').createSync(recursive: true);
|
||||
tempDir.childDirectory('lib').childFile('main.dart').createSync(recursive: true);
|
||||
androidDir
|
||||
.childFile('build.gradle')
|
||||
.createSync(recursive: true);
|
||||
androidDir
|
||||
.childDirectory('app')
|
||||
.childFile('build.gradle')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
|
||||
androidDir
|
||||
.childFile('gradle.properties')
|
||||
.createSync(recursive: true);
|
||||
androidDir
|
||||
.childDirectory('gradle')
|
||||
.childDirectory('wrapper')
|
||||
.childFile('gradle-wrapper.properties')
|
||||
.createSync(recursive: true);
|
||||
tempDir
|
||||
.childDirectory('build')
|
||||
.childDirectory('outputs')
|
||||
.childDirectory('repo')
|
||||
.createSync(recursive: true);
|
||||
tempDir
|
||||
.childDirectory('lib')
|
||||
.childFile('main.dart')
|
||||
.createSync(recursive: true);
|
||||
await runBuildAarCommand(tempDir.path);
|
||||
|
||||
verifyNever(mockAndroidSdk.validateSdkWellFormed());
|
||||
@ -153,7 +175,6 @@ flutter:
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
AndroidSdk: () => mockAndroidSdk,
|
||||
GradleUtils: () => GradleUtils(),
|
||||
ProcessManager: () => mockProcessManager,
|
||||
FileSystem: () => memoryFileSystem,
|
||||
});
|
||||
|
@ -8,7 +8,6 @@ import 'package:args/command_runner.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/android/android_builder.dart';
|
||||
import 'package:flutter_tools/src/android/android_sdk.dart';
|
||||
import 'package:flutter_tools/src/android/gradle.dart';
|
||||
import 'package:flutter_tools/src/base/context.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
@ -20,6 +19,7 @@ import 'package:flutter_tools/src/reporting/reporting.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import '../../src/android_common.dart';
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
import '../../src/mocks.dart';
|
||||
@ -152,7 +152,10 @@ void main() {
|
||||
platform.isWindows ? 'gradlew.bat' : 'gradlew');
|
||||
});
|
||||
testUsingContext('validateSdkWellFormed() not called, sdk reinitialized', () async {
|
||||
final Directory gradleCacheDir = memoryFileSystem.directory('/flutter_root/bin/cache/artifacts/gradle_wrapper')..createSync(recursive: true);
|
||||
final Directory gradleCacheDir = memoryFileSystem
|
||||
.directory('/flutter_root/bin/cache/artifacts/gradle_wrapper')
|
||||
..createSync(recursive: true);
|
||||
|
||||
gradleCacheDir.childFile(platform.isWindows ? 'gradlew.bat' : 'gradlew').createSync();
|
||||
|
||||
tempDir.childFile('pubspec.yaml')
|
||||
@ -170,11 +173,31 @@ flutter:
|
||||
''');
|
||||
tempDir.childFile('.packages').createSync(recursive: true);
|
||||
final Directory androidDir = tempDir.childDirectory('android');
|
||||
androidDir.childFile('build.gradle').createSync(recursive: true);
|
||||
androidDir.childFile('gradle.properties').createSync(recursive: true);
|
||||
androidDir.childDirectory('gradle').childDirectory('wrapper').childFile('gradle-wrapper.properties').createSync(recursive: true);
|
||||
tempDir.childDirectory('build').childDirectory('outputs').childDirectory('repo').createSync(recursive: true);
|
||||
tempDir.childDirectory('lib').childFile('main.dart').createSync(recursive: true);
|
||||
androidDir
|
||||
.childFile('build.gradle')
|
||||
.createSync(recursive: true);
|
||||
androidDir
|
||||
.childDirectory('app')
|
||||
.childFile('build.gradle')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
|
||||
androidDir
|
||||
.childFile('gradle.properties')
|
||||
.createSync(recursive: true);
|
||||
androidDir
|
||||
.childDirectory('gradle')
|
||||
.childDirectory('wrapper')
|
||||
.childFile('gradle-wrapper.properties')
|
||||
.createSync(recursive: true);
|
||||
tempDir
|
||||
.childDirectory('build')
|
||||
.childDirectory('outputs')
|
||||
.childDirectory('repo')
|
||||
.createSync(recursive: true);
|
||||
tempDir
|
||||
.childDirectory('lib')
|
||||
.childFile('main.dart')
|
||||
.createSync(recursive: true);
|
||||
when(mockProcessManager.run(any,
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
environment: anyNamed('environment')))
|
||||
@ -182,7 +205,7 @@ flutter:
|
||||
|
||||
await expectLater(
|
||||
runBuildApkCommand(tempDir.path, arguments: <String>['--no-pub', '--flutter-root=/flutter_root']),
|
||||
throwsToolExit(message: 'Gradle build failed: 1'),
|
||||
throwsToolExit(message: 'Gradle task assembleRelease failed with exit code 1'),
|
||||
);
|
||||
|
||||
verifyNever(mockAndroidSdk.validateSdkWellFormed());
|
||||
@ -190,7 +213,6 @@ flutter:
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
AndroidSdk: () => mockAndroidSdk,
|
||||
GradleUtils: () => GradleUtils(),
|
||||
FileSystem: () => memoryFileSystem,
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
@ -221,7 +243,6 @@ flutter:
|
||||
overrides: <Type, Generator>{
|
||||
AndroidSdk: () => mockAndroidSdk,
|
||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||
GradleUtils: () => GradleUtils(),
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
@ -252,7 +273,6 @@ flutter:
|
||||
overrides: <Type, Generator>{
|
||||
AndroidSdk: () => mockAndroidSdk,
|
||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||
GradleUtils: () => GradleUtils(),
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
@ -300,13 +320,12 @@ flutter:
|
||||
verify(mockUsage.sendEvent(
|
||||
'build',
|
||||
'apk',
|
||||
label: 'r8-failure',
|
||||
label: 'gradle--r8-failure',
|
||||
parameters: anyNamed('parameters'),
|
||||
)).called(1);
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
AndroidSdk: () => mockAndroidSdk,
|
||||
GradleUtils: () => GradleUtils(),
|
||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Usage: () => mockUsage,
|
||||
@ -344,7 +363,7 @@ flutter:
|
||||
}, throwsToolExit());
|
||||
|
||||
final BufferLogger logger = context.get<Logger>();
|
||||
expect(logger.statusText, contains('[!] Your app isn\'t using AndroidX'));
|
||||
expect(logger.statusText, contains('Your app isn\'t using AndroidX'));
|
||||
expect(logger.statusText, contains(
|
||||
'To avoid potential build failures, you can quickly migrate your app by '
|
||||
'following the steps on https://goo.gl/CP92wY'
|
||||
@ -359,7 +378,6 @@ flutter:
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
AndroidSdk: () => mockAndroidSdk,
|
||||
GradleUtils: () => GradleUtils(),
|
||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Usage: () => mockUsage,
|
||||
@ -414,7 +432,6 @@ flutter:
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
AndroidSdk: () => mockAndroidSdk,
|
||||
GradleUtils: () => GradleUtils(),
|
||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Usage: () => mockUsage,
|
||||
|
@ -8,7 +8,6 @@ import 'package:args/command_runner.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/android/android_builder.dart';
|
||||
import 'package:flutter_tools/src/android/android_sdk.dart';
|
||||
import 'package:flutter_tools/src/android/gradle.dart';
|
||||
import 'package:flutter_tools/src/base/context.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
@ -20,6 +19,7 @@ import 'package:flutter_tools/src/reporting/reporting.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import '../../src/android_common.dart';
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
import '../../src/mocks.dart';
|
||||
@ -139,7 +139,9 @@ void main() {
|
||||
});
|
||||
|
||||
testUsingContext('validateSdkWellFormed() not called, sdk reinitialized', () async {
|
||||
final Directory gradleCacheDir = memoryFileSystem.directory('/flutter_root/bin/cache/artifacts/gradle_wrapper')..createSync(recursive: true);
|
||||
final Directory gradleCacheDir = memoryFileSystem
|
||||
.directory('/flutter_root/bin/cache/artifacts/gradle_wrapper')
|
||||
..createSync(recursive: true);
|
||||
gradleCacheDir.childFile(platform.isWindows ? 'gradlew.bat' : 'gradlew').createSync();
|
||||
|
||||
tempDir.childFile('pubspec.yaml')
|
||||
@ -158,10 +160,26 @@ flutter:
|
||||
tempDir.childFile('.packages').createSync(recursive: true);
|
||||
final Directory androidDir = tempDir.childDirectory('android');
|
||||
androidDir.childFile('build.gradle').createSync(recursive: true);
|
||||
androidDir.childFile('gradle.properties').createSync(recursive: true);
|
||||
androidDir.childDirectory('gradle').childDirectory('wrapper').childFile('gradle-wrapper.properties').createSync(recursive: true);
|
||||
tempDir.childDirectory('build').childDirectory('outputs').childDirectory('repo').createSync(recursive: true);
|
||||
tempDir.childDirectory('lib').childFile('main.dart').createSync(recursive: true);
|
||||
androidDir
|
||||
.childDirectory('app')
|
||||
.childFile('build.gradle')
|
||||
..createSync(recursive: true)
|
||||
..writeAsStringSync('apply from: irrelevant/flutter.gradle');
|
||||
androidDir
|
||||
.childFile('gradle.properties')
|
||||
.createSync(recursive: true);
|
||||
androidDir
|
||||
.childDirectory('gradle')
|
||||
.childDirectory('wrapper')
|
||||
.childFile('gradle-wrapper.properties')
|
||||
.createSync(recursive: true);
|
||||
tempDir.childDirectory('build')
|
||||
.childDirectory('outputs')
|
||||
.childDirectory('repo')
|
||||
.createSync(recursive: true);
|
||||
tempDir.childDirectory('lib')
|
||||
.childFile('main.dart')
|
||||
.createSync(recursive: true);
|
||||
when(mockProcessManager.run(any,
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
environment: anyNamed('environment')))
|
||||
@ -169,7 +187,7 @@ flutter:
|
||||
|
||||
await expectLater(
|
||||
runBuildAppBundleCommand(tempDir.path, arguments: <String>['--no-pub', '--flutter-root=/flutter_root']),
|
||||
throwsToolExit(message: 'Gradle build failed: 1'),
|
||||
throwsToolExit(message: 'Gradle task bundleRelease failed with exit code 1'),
|
||||
);
|
||||
|
||||
verifyNever(mockAndroidSdk.validateSdkWellFormed());
|
||||
@ -177,7 +195,6 @@ flutter:
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
AndroidSdk: () => mockAndroidSdk,
|
||||
GradleUtils: () => GradleUtils(),
|
||||
ProcessManager: () => mockProcessManager,
|
||||
FileSystem: () => memoryFileSystem,
|
||||
});
|
||||
@ -210,7 +227,6 @@ flutter:
|
||||
overrides: <Type, Generator>{
|
||||
AndroidSdk: () => mockAndroidSdk,
|
||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||
GradleUtils: () => GradleUtils(),
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
@ -243,7 +259,6 @@ flutter:
|
||||
overrides: <Type, Generator>{
|
||||
AndroidSdk: () => mockAndroidSdk,
|
||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||
GradleUtils: () => GradleUtils(),
|
||||
ProcessManager: () => mockProcessManager,
|
||||
});
|
||||
|
||||
@ -291,13 +306,12 @@ flutter:
|
||||
verify(mockUsage.sendEvent(
|
||||
'build',
|
||||
'appbundle',
|
||||
label: 'r8-failure',
|
||||
label: 'gradle--r8-failure',
|
||||
parameters: anyNamed('parameters'),
|
||||
)).called(1);
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
AndroidSdk: () => mockAndroidSdk,
|
||||
GradleUtils: () => GradleUtils(),
|
||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Usage: () => mockUsage,
|
||||
@ -335,7 +349,7 @@ flutter:
|
||||
}, throwsToolExit());
|
||||
|
||||
final BufferLogger logger = context.get<Logger>();
|
||||
expect(logger.statusText, contains('[!] Your app isn\'t using AndroidX'));
|
||||
expect(logger.statusText, contains('Your app isn\'t using AndroidX'));
|
||||
expect(logger.statusText, contains(
|
||||
'To avoid potential build failures, you can quickly migrate your app by '
|
||||
'following the steps on https://goo.gl/CP92wY'
|
||||
@ -350,7 +364,6 @@ flutter:
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
AndroidSdk: () => mockAndroidSdk,
|
||||
GradleUtils: () => GradleUtils(),
|
||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Usage: () => mockUsage,
|
||||
@ -388,7 +401,7 @@ flutter:
|
||||
}, throwsToolExit());
|
||||
|
||||
final BufferLogger logger = context.get<Logger>();
|
||||
expect(logger.statusText.contains('[!] Your app isn\'t using AndroidX'), isFalse);
|
||||
expect(logger.statusText.contains('Your app isn\'t using AndroidX'), isFalse);
|
||||
expect(
|
||||
logger.statusText.contains(
|
||||
'To avoid potential build failures, you can quickly migrate your app by '
|
||||
@ -405,7 +418,6 @@ flutter:
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
AndroidSdk: () => mockAndroidSdk,
|
||||
GradleUtils: () => GradleUtils(),
|
||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Usage: () => mockUsage,
|
||||
|
34
packages/flutter_tools/test/src/android_common.dart
Normal file
34
packages/flutter_tools/test/src/android_common.dart
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import 'package:flutter_tools/src/android/android_builder.dart';
|
||||
import 'package:flutter_tools/src/build_info.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
|
||||
/// A fake implementation of [AndroidBuilder].
|
||||
class FakeAndroidBuilder implements AndroidBuilder {
|
||||
@override
|
||||
Future<void> buildAar({
|
||||
@required FlutterProject project,
|
||||
@required AndroidBuildInfo androidBuildInfo,
|
||||
@required String target,
|
||||
@required String outputDir,
|
||||
}) async {}
|
||||
|
||||
@override
|
||||
Future<void> buildApk({
|
||||
@required FlutterProject project,
|
||||
@required AndroidBuildInfo androidBuildInfo,
|
||||
@required String target,
|
||||
}) async {}
|
||||
|
||||
@override
|
||||
Future<void> buildAab({
|
||||
@required FlutterProject project,
|
||||
@required AndroidBuildInfo androidBuildInfo,
|
||||
@required String target,
|
||||
}) async {}
|
||||
}
|
Loading…
Reference in New Issue
Block a user