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 'package:meta/meta.dart';
|
||||||
|
|
||||||
import '../base/common.dart';
|
import '../android/gradle_errors.dart';
|
||||||
import '../base/context.dart';
|
import '../base/context.dart';
|
||||||
|
import '../base/file_system.dart';
|
||||||
import '../build_info.dart';
|
import '../build_info.dart';
|
||||||
import '../project.dart';
|
import '../project.dart';
|
||||||
|
|
||||||
import 'android_sdk.dart';
|
import 'android_sdk.dart';
|
||||||
import 'gradle.dart';
|
import 'gradle.dart';
|
||||||
|
|
||||||
/// The builder in the current context.
|
/// 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.
|
/// Provides the methods to build Android artifacts.
|
||||||
|
// TODO(egarciad): https://github.com/flutter/flutter/issues/43863
|
||||||
abstract class AndroidBuilder {
|
abstract class AndroidBuilder {
|
||||||
|
const AndroidBuilder();
|
||||||
/// Builds an AAR artifact.
|
/// Builds an AAR artifact.
|
||||||
Future<void> buildAar({
|
Future<void> buildAar({
|
||||||
@required FlutterProject project,
|
@required FlutterProject project,
|
||||||
@ -44,7 +48,7 @@ abstract class AndroidBuilder {
|
|||||||
|
|
||||||
/// Default implementation of [AarBuilder].
|
/// Default implementation of [AarBuilder].
|
||||||
class _AndroidBuilderImpl extends AndroidBuilder {
|
class _AndroidBuilderImpl extends AndroidBuilder {
|
||||||
_AndroidBuilderImpl();
|
const _AndroidBuilderImpl();
|
||||||
|
|
||||||
/// Builds the AAR and POM files for the current Flutter module or plugin.
|
/// Builds the AAR and POM files for the current Flutter module or plugin.
|
||||||
@override
|
@override
|
||||||
@ -54,27 +58,18 @@ class _AndroidBuilderImpl extends AndroidBuilder {
|
|||||||
@required String target,
|
@required String target,
|
||||||
@required String outputDir,
|
@required String outputDir,
|
||||||
}) async {
|
}) 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 {
|
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(
|
await buildGradleAar(
|
||||||
project: project,
|
project: project,
|
||||||
androidBuildInfo: androidBuildInfo,
|
androidBuildInfo: androidBuildInfo,
|
||||||
target: target,
|
target: target,
|
||||||
outputDir: outputDir,
|
outputDir: outputDirectory,
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
androidSdk.reinitialize();
|
androidSdk.reinitialize();
|
||||||
@ -88,24 +83,13 @@ class _AndroidBuilderImpl extends AndroidBuilder {
|
|||||||
@required AndroidBuildInfo androidBuildInfo,
|
@required AndroidBuildInfo androidBuildInfo,
|
||||||
@required String target,
|
@required String target,
|
||||||
}) async {
|
}) 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 {
|
try {
|
||||||
await buildGradleProject(
|
await buildGradleApp(
|
||||||
project: project,
|
project: project,
|
||||||
androidBuildInfo: androidBuildInfo,
|
androidBuildInfo: androidBuildInfo,
|
||||||
target: target,
|
target: target,
|
||||||
isBuildingBundle: false,
|
isBuildingBundle: false,
|
||||||
|
localGradleErrors: gradleErrors,
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
androidSdk.reinitialize();
|
androidSdk.reinitialize();
|
||||||
@ -119,54 +103,16 @@ class _AndroidBuilderImpl extends AndroidBuilder {
|
|||||||
@required AndroidBuildInfo androidBuildInfo,
|
@required AndroidBuildInfo androidBuildInfo,
|
||||||
@required String target,
|
@required String target,
|
||||||
}) async {
|
}) 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 {
|
try {
|
||||||
await buildGradleProject(
|
await buildGradleApp(
|
||||||
project: project,
|
project: project,
|
||||||
androidBuildInfo: androidBuildInfo,
|
androidBuildInfo: androidBuildInfo,
|
||||||
target: target,
|
target: target,
|
||||||
isBuildingBundle: true,
|
isBuildingBundle: true,
|
||||||
|
localGradleErrors: gradleErrors,
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
androidSdk.reinitialize();
|
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
|
// Read all $HOME/.AndroidStudio*/system/.home files. There may be several
|
||||||
// pointing to the same installation, so we grab only the latest one.
|
// 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)) {
|
for (FileSystemEntity entity in fs.directory(homeDirPath).listSync(followLinks: false)) {
|
||||||
if (entity is Directory && entity.basename.startsWith('.AndroidStudio')) {
|
if (entity is Directory && entity.basename.startsWith('.AndroidStudio')) {
|
||||||
final AndroidStudio studio = AndroidStudio.fromHomeDot(entity);
|
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 {
|
AnsiTerminal get terminal {
|
||||||
return context?.get<AnsiTerminal>() ?? _defaultAnsiTerminal;
|
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();
|
final AnsiTerminal _defaultAnsiTerminal = AnsiTerminal();
|
||||||
|
|
||||||
OutputPreferences get outputPreferences {
|
OutputPreferences get outputPreferences {
|
||||||
|
@ -6,7 +6,7 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
import 'android/gradle.dart';
|
import 'android/gradle_utils.dart';
|
||||||
import 'base/common.dart';
|
import 'base/common.dart';
|
||||||
import 'base/context.dart';
|
import 'base/context.dart';
|
||||||
import 'base/file_system.dart';
|
import 'base/file_system.dart';
|
||||||
@ -914,7 +914,7 @@ class AndroidMavenArtifacts extends ArtifactSet {
|
|||||||
'--project-cache-dir', tempDir.path,
|
'--project-cache-dir', tempDir.path,
|
||||||
'resolveDependencies',
|
'resolveDependencies',
|
||||||
],
|
],
|
||||||
environment: gradleEnv);
|
environment: gradleEnvironment);
|
||||||
if (processResult.exitCode != 0) {
|
if (processResult.exitCode != 0) {
|
||||||
printError('Failed to download the Android dependencies');
|
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.dart' as android;
|
||||||
import '../android/android_sdk.dart' as android_sdk;
|
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/common.dart';
|
||||||
import '../base/file_system.dart';
|
import '../base/file_system.dart';
|
||||||
import '../base/net.dart';
|
import '../base/net.dart';
|
||||||
|
@ -7,7 +7,7 @@ import 'dart:async';
|
|||||||
import 'android/android_sdk.dart';
|
import 'android/android_sdk.dart';
|
||||||
import 'android/android_studio.dart';
|
import 'android/android_studio.dart';
|
||||||
import 'android/android_workflow.dart';
|
import 'android/android_workflow.dart';
|
||||||
import 'android/gradle.dart';
|
import 'android/gradle_utils.dart';
|
||||||
import 'application_package.dart';
|
import 'application_package.dart';
|
||||||
import 'artifacts.dart';
|
import 'artifacts.dart';
|
||||||
import 'asset.dart';
|
import 'asset.dart';
|
||||||
|
@ -7,7 +7,7 @@ import 'dart:async';
|
|||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:yaml/yaml.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/common.dart';
|
||||||
import 'base/context.dart';
|
import 'base/context.dart';
|
||||||
import 'base/file_system.dart';
|
import 'base/file_system.dart';
|
||||||
@ -574,6 +574,11 @@ class AndroidProject {
|
|||||||
return _firstMatchInFile(gradleFile, _groupPattern)?.group(1);
|
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 {
|
Future<void> ensureReadyForPlatformSpecificTooling() async {
|
||||||
if (isModule && _shouldRegenerateFromTemplate()) {
|
if (isModule && _shouldRegenerateFromTemplate()) {
|
||||||
_regenerateLibrary();
|
_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:platform/platform.dart';
|
||||||
import 'package:process/process.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/common.dart';
|
||||||
import 'package:flutter_tools/src/base/io.dart';
|
import 'package:flutter_tools/src/base/io.dart';
|
||||||
import 'package:flutter_tools/src/cache.dart';
|
import 'package:flutter_tools/src/cache.dart';
|
||||||
@ -316,7 +316,7 @@ void main() {
|
|||||||
expect(args[1], '-b');
|
expect(args[1], '-b');
|
||||||
expect(args[2].endsWith('resolve_dependencies.gradle'), isTrue);
|
expect(args[2].endsWith('resolve_dependencies.gradle'), isTrue);
|
||||||
expect(args[5], 'resolveDependencies');
|
expect(args[5], 'resolveDependencies');
|
||||||
expect(invocation.namedArguments[#environment], gradleEnv);
|
expect(invocation.namedArguments[#environment], gradleEnvironment);
|
||||||
return Future<ProcessResult>.value(ProcessResult(0, 0, '', ''));
|
return Future<ProcessResult>.value(ProcessResult(0, 0, '', ''));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ import 'package:args/command_runner.dart';
|
|||||||
import 'package:file/memory.dart';
|
import 'package:file/memory.dart';
|
||||||
import 'package:flutter_tools/src/android/android_builder.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/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/file_system.dart';
|
||||||
import 'package:flutter_tools/src/base/platform.dart';
|
import 'package:flutter_tools/src/base/platform.dart';
|
||||||
import 'package:flutter_tools/src/cache.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:mockito/mockito.dart';
|
||||||
import 'package:process/process.dart';
|
import 'package:process/process.dart';
|
||||||
|
|
||||||
|
import '../../src/android_common.dart';
|
||||||
import '../../src/common.dart';
|
import '../../src/common.dart';
|
||||||
import '../../src/context.dart';
|
import '../../src/context.dart';
|
||||||
import '../../src/mocks.dart';
|
import '../../src/mocks.dart';
|
||||||
@ -120,7 +120,9 @@ void main() {
|
|||||||
|
|
||||||
group('AndroidSdk', () {
|
group('AndroidSdk', () {
|
||||||
testUsingContext('validateSdkWellFormed() not called, sdk reinitialized', () async {
|
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();
|
gradleCacheDir.childFile(platform.isWindows ? 'gradlew.bat' : 'gradlew').createSync();
|
||||||
|
|
||||||
tempDir.childFile('pubspec.yaml')
|
tempDir.childFile('pubspec.yaml')
|
||||||
@ -141,11 +143,31 @@ flutter:
|
|||||||
''');
|
''');
|
||||||
tempDir.childFile('.packages').createSync(recursive: true);
|
tempDir.childFile('.packages').createSync(recursive: true);
|
||||||
final Directory androidDir = tempDir.childDirectory('android');
|
final Directory androidDir = tempDir.childDirectory('android');
|
||||||
androidDir.childFile('build.gradle').createSync(recursive: true);
|
androidDir
|
||||||
androidDir.childFile('gradle.properties').createSync(recursive: true);
|
.childFile('build.gradle')
|
||||||
androidDir.childDirectory('gradle').childDirectory('wrapper').childFile('gradle-wrapper.properties').createSync(recursive: true);
|
.createSync(recursive: true);
|
||||||
tempDir.childDirectory('build').childDirectory('outputs').childDirectory('repo').createSync(recursive: true);
|
androidDir
|
||||||
tempDir.childDirectory('lib').childFile('main.dart').createSync(recursive: true);
|
.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);
|
await runBuildAarCommand(tempDir.path);
|
||||||
|
|
||||||
verifyNever(mockAndroidSdk.validateSdkWellFormed());
|
verifyNever(mockAndroidSdk.validateSdkWellFormed());
|
||||||
@ -153,7 +175,6 @@ flutter:
|
|||||||
},
|
},
|
||||||
overrides: <Type, Generator>{
|
overrides: <Type, Generator>{
|
||||||
AndroidSdk: () => mockAndroidSdk,
|
AndroidSdk: () => mockAndroidSdk,
|
||||||
GradleUtils: () => GradleUtils(),
|
|
||||||
ProcessManager: () => mockProcessManager,
|
ProcessManager: () => mockProcessManager,
|
||||||
FileSystem: () => memoryFileSystem,
|
FileSystem: () => memoryFileSystem,
|
||||||
});
|
});
|
||||||
|
@ -8,7 +8,6 @@ import 'package:args/command_runner.dart';
|
|||||||
import 'package:file/memory.dart';
|
import 'package:file/memory.dart';
|
||||||
import 'package:flutter_tools/src/android/android_builder.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/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/context.dart';
|
||||||
import 'package:flutter_tools/src/base/file_system.dart';
|
import 'package:flutter_tools/src/base/file_system.dart';
|
||||||
import 'package:flutter_tools/src/base/logger.dart';
|
import 'package:flutter_tools/src/base/logger.dart';
|
||||||
@ -20,6 +19,7 @@ import 'package:flutter_tools/src/reporting/reporting.dart';
|
|||||||
import 'package:mockito/mockito.dart';
|
import 'package:mockito/mockito.dart';
|
||||||
import 'package:process/process.dart';
|
import 'package:process/process.dart';
|
||||||
|
|
||||||
|
import '../../src/android_common.dart';
|
||||||
import '../../src/common.dart';
|
import '../../src/common.dart';
|
||||||
import '../../src/context.dart';
|
import '../../src/context.dart';
|
||||||
import '../../src/mocks.dart';
|
import '../../src/mocks.dart';
|
||||||
@ -152,7 +152,10 @@ void main() {
|
|||||||
platform.isWindows ? 'gradlew.bat' : 'gradlew');
|
platform.isWindows ? 'gradlew.bat' : 'gradlew');
|
||||||
});
|
});
|
||||||
testUsingContext('validateSdkWellFormed() not called, sdk reinitialized', () async {
|
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();
|
gradleCacheDir.childFile(platform.isWindows ? 'gradlew.bat' : 'gradlew').createSync();
|
||||||
|
|
||||||
tempDir.childFile('pubspec.yaml')
|
tempDir.childFile('pubspec.yaml')
|
||||||
@ -170,11 +173,31 @@ flutter:
|
|||||||
''');
|
''');
|
||||||
tempDir.childFile('.packages').createSync(recursive: true);
|
tempDir.childFile('.packages').createSync(recursive: true);
|
||||||
final Directory androidDir = tempDir.childDirectory('android');
|
final Directory androidDir = tempDir.childDirectory('android');
|
||||||
androidDir.childFile('build.gradle').createSync(recursive: true);
|
androidDir
|
||||||
androidDir.childFile('gradle.properties').createSync(recursive: true);
|
.childFile('build.gradle')
|
||||||
androidDir.childDirectory('gradle').childDirectory('wrapper').childFile('gradle-wrapper.properties').createSync(recursive: true);
|
.createSync(recursive: true);
|
||||||
tempDir.childDirectory('build').childDirectory('outputs').childDirectory('repo').createSync(recursive: true);
|
androidDir
|
||||||
tempDir.childDirectory('lib').childFile('main.dart').createSync(recursive: true);
|
.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,
|
when(mockProcessManager.run(any,
|
||||||
workingDirectory: anyNamed('workingDirectory'),
|
workingDirectory: anyNamed('workingDirectory'),
|
||||||
environment: anyNamed('environment')))
|
environment: anyNamed('environment')))
|
||||||
@ -182,7 +205,7 @@ flutter:
|
|||||||
|
|
||||||
await expectLater(
|
await expectLater(
|
||||||
runBuildApkCommand(tempDir.path, arguments: <String>['--no-pub', '--flutter-root=/flutter_root']),
|
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());
|
verifyNever(mockAndroidSdk.validateSdkWellFormed());
|
||||||
@ -190,7 +213,6 @@ flutter:
|
|||||||
},
|
},
|
||||||
overrides: <Type, Generator>{
|
overrides: <Type, Generator>{
|
||||||
AndroidSdk: () => mockAndroidSdk,
|
AndroidSdk: () => mockAndroidSdk,
|
||||||
GradleUtils: () => GradleUtils(),
|
|
||||||
FileSystem: () => memoryFileSystem,
|
FileSystem: () => memoryFileSystem,
|
||||||
ProcessManager: () => mockProcessManager,
|
ProcessManager: () => mockProcessManager,
|
||||||
});
|
});
|
||||||
@ -221,7 +243,6 @@ flutter:
|
|||||||
overrides: <Type, Generator>{
|
overrides: <Type, Generator>{
|
||||||
AndroidSdk: () => mockAndroidSdk,
|
AndroidSdk: () => mockAndroidSdk,
|
||||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||||
GradleUtils: () => GradleUtils(),
|
|
||||||
ProcessManager: () => mockProcessManager,
|
ProcessManager: () => mockProcessManager,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -252,7 +273,6 @@ flutter:
|
|||||||
overrides: <Type, Generator>{
|
overrides: <Type, Generator>{
|
||||||
AndroidSdk: () => mockAndroidSdk,
|
AndroidSdk: () => mockAndroidSdk,
|
||||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||||
GradleUtils: () => GradleUtils(),
|
|
||||||
ProcessManager: () => mockProcessManager,
|
ProcessManager: () => mockProcessManager,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -300,13 +320,12 @@ flutter:
|
|||||||
verify(mockUsage.sendEvent(
|
verify(mockUsage.sendEvent(
|
||||||
'build',
|
'build',
|
||||||
'apk',
|
'apk',
|
||||||
label: 'r8-failure',
|
label: 'gradle--r8-failure',
|
||||||
parameters: anyNamed('parameters'),
|
parameters: anyNamed('parameters'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
overrides: <Type, Generator>{
|
overrides: <Type, Generator>{
|
||||||
AndroidSdk: () => mockAndroidSdk,
|
AndroidSdk: () => mockAndroidSdk,
|
||||||
GradleUtils: () => GradleUtils(),
|
|
||||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||||
ProcessManager: () => mockProcessManager,
|
ProcessManager: () => mockProcessManager,
|
||||||
Usage: () => mockUsage,
|
Usage: () => mockUsage,
|
||||||
@ -344,7 +363,7 @@ flutter:
|
|||||||
}, throwsToolExit());
|
}, throwsToolExit());
|
||||||
|
|
||||||
final BufferLogger logger = context.get<Logger>();
|
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(
|
expect(logger.statusText, contains(
|
||||||
'To avoid potential build failures, you can quickly migrate your app by '
|
'To avoid potential build failures, you can quickly migrate your app by '
|
||||||
'following the steps on https://goo.gl/CP92wY'
|
'following the steps on https://goo.gl/CP92wY'
|
||||||
@ -359,7 +378,6 @@ flutter:
|
|||||||
},
|
},
|
||||||
overrides: <Type, Generator>{
|
overrides: <Type, Generator>{
|
||||||
AndroidSdk: () => mockAndroidSdk,
|
AndroidSdk: () => mockAndroidSdk,
|
||||||
GradleUtils: () => GradleUtils(),
|
|
||||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||||
ProcessManager: () => mockProcessManager,
|
ProcessManager: () => mockProcessManager,
|
||||||
Usage: () => mockUsage,
|
Usage: () => mockUsage,
|
||||||
@ -414,7 +432,6 @@ flutter:
|
|||||||
},
|
},
|
||||||
overrides: <Type, Generator>{
|
overrides: <Type, Generator>{
|
||||||
AndroidSdk: () => mockAndroidSdk,
|
AndroidSdk: () => mockAndroidSdk,
|
||||||
GradleUtils: () => GradleUtils(),
|
|
||||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||||
ProcessManager: () => mockProcessManager,
|
ProcessManager: () => mockProcessManager,
|
||||||
Usage: () => mockUsage,
|
Usage: () => mockUsage,
|
||||||
|
@ -8,7 +8,6 @@ import 'package:args/command_runner.dart';
|
|||||||
import 'package:file/memory.dart';
|
import 'package:file/memory.dart';
|
||||||
import 'package:flutter_tools/src/android/android_builder.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/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/context.dart';
|
||||||
import 'package:flutter_tools/src/base/file_system.dart';
|
import 'package:flutter_tools/src/base/file_system.dart';
|
||||||
import 'package:flutter_tools/src/base/logger.dart';
|
import 'package:flutter_tools/src/base/logger.dart';
|
||||||
@ -20,6 +19,7 @@ import 'package:flutter_tools/src/reporting/reporting.dart';
|
|||||||
import 'package:mockito/mockito.dart';
|
import 'package:mockito/mockito.dart';
|
||||||
import 'package:process/process.dart';
|
import 'package:process/process.dart';
|
||||||
|
|
||||||
|
import '../../src/android_common.dart';
|
||||||
import '../../src/common.dart';
|
import '../../src/common.dart';
|
||||||
import '../../src/context.dart';
|
import '../../src/context.dart';
|
||||||
import '../../src/mocks.dart';
|
import '../../src/mocks.dart';
|
||||||
@ -139,7 +139,9 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testUsingContext('validateSdkWellFormed() not called, sdk reinitialized', () async {
|
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();
|
gradleCacheDir.childFile(platform.isWindows ? 'gradlew.bat' : 'gradlew').createSync();
|
||||||
|
|
||||||
tempDir.childFile('pubspec.yaml')
|
tempDir.childFile('pubspec.yaml')
|
||||||
@ -158,10 +160,26 @@ flutter:
|
|||||||
tempDir.childFile('.packages').createSync(recursive: true);
|
tempDir.childFile('.packages').createSync(recursive: true);
|
||||||
final Directory androidDir = tempDir.childDirectory('android');
|
final Directory androidDir = tempDir.childDirectory('android');
|
||||||
androidDir.childFile('build.gradle').createSync(recursive: true);
|
androidDir.childFile('build.gradle').createSync(recursive: true);
|
||||||
androidDir.childFile('gradle.properties').createSync(recursive: true);
|
androidDir
|
||||||
androidDir.childDirectory('gradle').childDirectory('wrapper').childFile('gradle-wrapper.properties').createSync(recursive: true);
|
.childDirectory('app')
|
||||||
tempDir.childDirectory('build').childDirectory('outputs').childDirectory('repo').createSync(recursive: true);
|
.childFile('build.gradle')
|
||||||
tempDir.childDirectory('lib').childFile('main.dart').createSync(recursive: true);
|
..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,
|
when(mockProcessManager.run(any,
|
||||||
workingDirectory: anyNamed('workingDirectory'),
|
workingDirectory: anyNamed('workingDirectory'),
|
||||||
environment: anyNamed('environment')))
|
environment: anyNamed('environment')))
|
||||||
@ -169,7 +187,7 @@ flutter:
|
|||||||
|
|
||||||
await expectLater(
|
await expectLater(
|
||||||
runBuildAppBundleCommand(tempDir.path, arguments: <String>['--no-pub', '--flutter-root=/flutter_root']),
|
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());
|
verifyNever(mockAndroidSdk.validateSdkWellFormed());
|
||||||
@ -177,7 +195,6 @@ flutter:
|
|||||||
},
|
},
|
||||||
overrides: <Type, Generator>{
|
overrides: <Type, Generator>{
|
||||||
AndroidSdk: () => mockAndroidSdk,
|
AndroidSdk: () => mockAndroidSdk,
|
||||||
GradleUtils: () => GradleUtils(),
|
|
||||||
ProcessManager: () => mockProcessManager,
|
ProcessManager: () => mockProcessManager,
|
||||||
FileSystem: () => memoryFileSystem,
|
FileSystem: () => memoryFileSystem,
|
||||||
});
|
});
|
||||||
@ -210,7 +227,6 @@ flutter:
|
|||||||
overrides: <Type, Generator>{
|
overrides: <Type, Generator>{
|
||||||
AndroidSdk: () => mockAndroidSdk,
|
AndroidSdk: () => mockAndroidSdk,
|
||||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||||
GradleUtils: () => GradleUtils(),
|
|
||||||
ProcessManager: () => mockProcessManager,
|
ProcessManager: () => mockProcessManager,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -243,7 +259,6 @@ flutter:
|
|||||||
overrides: <Type, Generator>{
|
overrides: <Type, Generator>{
|
||||||
AndroidSdk: () => mockAndroidSdk,
|
AndroidSdk: () => mockAndroidSdk,
|
||||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||||
GradleUtils: () => GradleUtils(),
|
|
||||||
ProcessManager: () => mockProcessManager,
|
ProcessManager: () => mockProcessManager,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -291,13 +306,12 @@ flutter:
|
|||||||
verify(mockUsage.sendEvent(
|
verify(mockUsage.sendEvent(
|
||||||
'build',
|
'build',
|
||||||
'appbundle',
|
'appbundle',
|
||||||
label: 'r8-failure',
|
label: 'gradle--r8-failure',
|
||||||
parameters: anyNamed('parameters'),
|
parameters: anyNamed('parameters'),
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
overrides: <Type, Generator>{
|
overrides: <Type, Generator>{
|
||||||
AndroidSdk: () => mockAndroidSdk,
|
AndroidSdk: () => mockAndroidSdk,
|
||||||
GradleUtils: () => GradleUtils(),
|
|
||||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||||
ProcessManager: () => mockProcessManager,
|
ProcessManager: () => mockProcessManager,
|
||||||
Usage: () => mockUsage,
|
Usage: () => mockUsage,
|
||||||
@ -335,7 +349,7 @@ flutter:
|
|||||||
}, throwsToolExit());
|
}, throwsToolExit());
|
||||||
|
|
||||||
final BufferLogger logger = context.get<Logger>();
|
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(
|
expect(logger.statusText, contains(
|
||||||
'To avoid potential build failures, you can quickly migrate your app by '
|
'To avoid potential build failures, you can quickly migrate your app by '
|
||||||
'following the steps on https://goo.gl/CP92wY'
|
'following the steps on https://goo.gl/CP92wY'
|
||||||
@ -350,7 +364,6 @@ flutter:
|
|||||||
},
|
},
|
||||||
overrides: <Type, Generator>{
|
overrides: <Type, Generator>{
|
||||||
AndroidSdk: () => mockAndroidSdk,
|
AndroidSdk: () => mockAndroidSdk,
|
||||||
GradleUtils: () => GradleUtils(),
|
|
||||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||||
ProcessManager: () => mockProcessManager,
|
ProcessManager: () => mockProcessManager,
|
||||||
Usage: () => mockUsage,
|
Usage: () => mockUsage,
|
||||||
@ -388,7 +401,7 @@ flutter:
|
|||||||
}, throwsToolExit());
|
}, throwsToolExit());
|
||||||
|
|
||||||
final BufferLogger logger = context.get<Logger>();
|
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(
|
expect(
|
||||||
logger.statusText.contains(
|
logger.statusText.contains(
|
||||||
'To avoid potential build failures, you can quickly migrate your app by '
|
'To avoid potential build failures, you can quickly migrate your app by '
|
||||||
@ -405,7 +418,6 @@ flutter:
|
|||||||
},
|
},
|
||||||
overrides: <Type, Generator>{
|
overrides: <Type, Generator>{
|
||||||
AndroidSdk: () => mockAndroidSdk,
|
AndroidSdk: () => mockAndroidSdk,
|
||||||
GradleUtils: () => GradleUtils(),
|
|
||||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||||
ProcessManager: () => mockProcessManager,
|
ProcessManager: () => mockProcessManager,
|
||||||
Usage: () => mockUsage,
|
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