mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Enable Proguard by default on release mode (#39986)
This commit is contained in:
parent
362cde43ff
commit
f098de1fde
@ -132,16 +132,6 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
}
|
||||
}
|
||||
|
||||
// Add custom build types
|
||||
project.android.buildTypes {
|
||||
profile {
|
||||
initWith debug
|
||||
if (it.hasProperty('matchingFallbacks')) {
|
||||
matchingFallbacks = ['debug', 'release']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String flutterRootPath = resolveProperty(project, "flutter.sdk", System.env.FLUTTER_ROOT)
|
||||
if (flutterRootPath == null) {
|
||||
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file or with a FLUTTER_ROOT environment variable.")
|
||||
@ -154,6 +144,30 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter"
|
||||
flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile();
|
||||
|
||||
// Add custom build types.
|
||||
project.android.buildTypes {
|
||||
profile {
|
||||
initWith debug
|
||||
if (it.hasProperty("matchingFallbacks")) {
|
||||
matchingFallbacks = ["debug", "release"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (useProguard(project)) {
|
||||
String flutterProguardRules = Paths.get(flutterRoot.absolutePath, "packages", "flutter_tools",
|
||||
"gradle", "flutter_proguard_rules.pro")
|
||||
project.android.buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
useProguard true
|
||||
// Fallback to `android/app/proguard-rules.pro`.
|
||||
// This way, custom Proguard rules can be configured as needed.
|
||||
proguardFiles project.android.getDefaultProguardFile("proguard-android.txt"), flutterProguardRules, "proguard-rules.pro"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (useLocalEngine(project)) {
|
||||
String engineOutPath = project.property('localEngineOut')
|
||||
File engineOut = project.file(engineOutPath)
|
||||
@ -375,6 +389,14 @@ class FlutterPlugin implements Plugin<Project> {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
private static Boolean useProguard(Project project) {
|
||||
if (project.hasProperty('proguard')) {
|
||||
return project.property('proguard').toBoolean()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private static Boolean buildPluginAsAar() {
|
||||
return System.getProperty('build-plugins-as-aars') == 'true'
|
||||
}
|
||||
|
11
packages/flutter_tools/gradle/flutter_proguard_rules.pro
Normal file
11
packages/flutter_tools/gradle/flutter_proguard_rules.pro
Normal file
@ -0,0 +1,11 @@
|
||||
# Prevents `Fragment and FragmentActivity not found`.
|
||||
# TODO(blasten): Remove once we bring the Maven dependencies.
|
||||
-dontwarn io.flutter.embedding.**
|
||||
|
||||
# Build the ephemeral app in a module project.
|
||||
# Prevents: Warning: library class <plugin-package> depends on program class io.flutter.plugin.**
|
||||
# This is due to plugins (libraries) depending on the embedding (the program jar)
|
||||
-dontwarn io.flutter.plugin.**
|
||||
|
||||
# The android.** package is provided by the OS at runtime.
|
||||
-dontwarn android.**
|
@ -10,6 +10,7 @@ import 'package:meta/meta.dart';
|
||||
import '../android/android_sdk.dart';
|
||||
import '../artifacts.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/context.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/os.dart';
|
||||
@ -28,11 +29,39 @@ import '../reporting/reporting.dart';
|
||||
import 'android_sdk.dart';
|
||||
import 'android_studio.dart';
|
||||
|
||||
final RegExp _assembleTaskPattern = RegExp(r'assemble(\S+)');
|
||||
/// Gradle utils in the current [AppContext].
|
||||
GradleUtils get gradleUtils => context.get<GradleUtils>();
|
||||
|
||||
GradleProject _cachedGradleAppProject;
|
||||
GradleProject _cachedGradleLibraryProject;
|
||||
String _cachedGradleExecutable;
|
||||
/// Provides utilities to run a Gradle task,
|
||||
/// such as finding the Gradle executable or constructing a Gradle project.
|
||||
class GradleUtils {
|
||||
/// Empty constructor.
|
||||
GradleUtils();
|
||||
|
||||
String _cachedExecutable;
|
||||
/// Gets the Gradle executable path.
|
||||
/// This is the `gradlew` or `gradlew.bat` script in the `android/` directory.
|
||||
Future<String> getExecutable(FlutterProject project) async {
|
||||
_cachedExecutable ??= await _initializeGradle(project);
|
||||
return _cachedExecutable;
|
||||
}
|
||||
|
||||
GradleProject _cachedAppProject;
|
||||
/// Gets the [GradleProject] for the current [FlutterProject] if built as an app.
|
||||
Future<GradleProject> get appProject async {
|
||||
_cachedAppProject ??= await _readGradleProject(isLibrary: false);
|
||||
return _cachedAppProject;
|
||||
}
|
||||
|
||||
GradleProject _cachedLibraryProject;
|
||||
/// Gets the [GradleProject] for the current [FlutterProject] if built as a library.
|
||||
Future<GradleProject> get libraryProject async {
|
||||
_cachedLibraryProject ??= await _readGradleProject(isLibrary: true);
|
||||
return _cachedLibraryProject;
|
||||
}
|
||||
}
|
||||
|
||||
final RegExp _assembleTaskPattern = RegExp(r'assemble(\S+)');
|
||||
|
||||
enum FlutterPluginVersion {
|
||||
none,
|
||||
@ -103,29 +132,20 @@ Future<File> getGradleAppOut(AndroidProject androidProject) async {
|
||||
case FlutterPluginVersion.managed:
|
||||
// Fall through. The managed plugin matches plugin v2 for now.
|
||||
case FlutterPluginVersion.v2:
|
||||
return fs.file((await _gradleAppProject()).apkDirectory.childFile('app.apk'));
|
||||
final GradleProject gradleProject = await gradleUtils.appProject;
|
||||
return fs.file(gradleProject.apkDirectory.childFile('app.apk'));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<GradleProject> _gradleAppProject() async {
|
||||
_cachedGradleAppProject ??= await _readGradleProject(isLibrary: false);
|
||||
return _cachedGradleAppProject;
|
||||
}
|
||||
|
||||
Future<GradleProject> _gradleLibraryProject() async {
|
||||
_cachedGradleLibraryProject ??= await _readGradleProject(isLibrary: true);
|
||||
return _cachedGradleLibraryProject;
|
||||
}
|
||||
|
||||
/// Runs `gradlew dependencies`, ensuring that dependencies are resolved and
|
||||
/// potentially downloaded.
|
||||
Future<void> checkGradleDependencies() async {
|
||||
final Status progress = logger.startProgress('Ensuring gradle dependencies are up to date...', timeout: timeoutConfiguration.slowOperation);
|
||||
final FlutterProject flutterProject = FlutterProject.current();
|
||||
final String gradle = await _ensureGradle(flutterProject);
|
||||
final String gradlew = await gradleUtils.getExecutable(flutterProject);
|
||||
await runCheckedAsync(
|
||||
<String>[gradle, 'dependencies'],
|
||||
<String>[gradlew, 'dependencies'],
|
||||
workingDirectory: flutterProject.android.hostAppGradleRoot.path,
|
||||
environment: _gradleEnv,
|
||||
);
|
||||
@ -189,7 +209,8 @@ void createSettingsAarGradle(Directory androidDirectory) {
|
||||
// of calculating the app properties using Gradle. This may take minutes.
|
||||
Future<GradleProject> _readGradleProject({bool isLibrary = false}) async {
|
||||
final FlutterProject flutterProject = FlutterProject.current();
|
||||
final String gradle = await _ensureGradle(flutterProject);
|
||||
final String gradlew = await gradleUtils.getExecutable(flutterProject);
|
||||
|
||||
updateLocalProperties(project: flutterProject);
|
||||
|
||||
final FlutterManifest manifest = flutterProject.manifest;
|
||||
@ -213,12 +234,12 @@ Future<GradleProject> _readGradleProject({bool isLibrary = false}) async {
|
||||
// flavors and build types defined in the project. If gradle fails, then check if the failure is due to t
|
||||
try {
|
||||
final RunResult propertiesRunResult = await runCheckedAsync(
|
||||
<String>[gradle, isLibrary ? 'properties' : 'app:properties'],
|
||||
<String>[gradlew, isLibrary ? 'properties' : 'app:properties'],
|
||||
workingDirectory: hostAppGradleRoot.path,
|
||||
environment: _gradleEnv,
|
||||
);
|
||||
final RunResult tasksRunResult = await runCheckedAsync(
|
||||
<String>[gradle, isLibrary ? 'tasks': 'app:tasks', '--all', '--console=auto'],
|
||||
<String>[gradlew, isLibrary ? 'tasks': 'app:tasks', '--all', '--console=auto'],
|
||||
workingDirectory: hostAppGradleRoot.path,
|
||||
environment: _gradleEnv,
|
||||
);
|
||||
@ -274,11 +295,6 @@ String _locateGradlewExecutable(Directory directory) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<String> _ensureGradle(FlutterProject project) async {
|
||||
_cachedGradleExecutable ??= await _initializeGradle(project);
|
||||
return _cachedGradleExecutable;
|
||||
}
|
||||
|
||||
// Note: Gradle may be bootstrapped and possibly downloaded as a side-effect
|
||||
// of validating the Gradle executable. This may take several seconds.
|
||||
Future<String> _initializeGradle(FlutterProject project) async {
|
||||
@ -492,17 +508,15 @@ Future<void> buildGradleProject({
|
||||
// from the local.properties file.
|
||||
updateLocalProperties(project: project, buildInfo: androidBuildInfo.buildInfo);
|
||||
|
||||
final String gradle = await _ensureGradle(project);
|
||||
|
||||
switch (getFlutterPluginVersion(project.android)) {
|
||||
case FlutterPluginVersion.none:
|
||||
// Fall through. Pretend it's v1, and just go for it.
|
||||
case FlutterPluginVersion.v1:
|
||||
return _buildGradleProjectV1(project, gradle);
|
||||
return _buildGradleProjectV1(project);
|
||||
case FlutterPluginVersion.managed:
|
||||
// Fall through. Managed plugin builds the same way as plugin v2.
|
||||
case FlutterPluginVersion.v2:
|
||||
return _buildGradleProjectV2(project, gradle, androidBuildInfo, target, isBuildingBundle);
|
||||
return _buildGradleProjectV2(project, androidBuildInfo, target, isBuildingBundle);
|
||||
}
|
||||
}
|
||||
|
||||
@ -516,9 +530,9 @@ Future<void> buildGradleAar({
|
||||
|
||||
GradleProject gradleProject;
|
||||
if (manifest.isModule) {
|
||||
gradleProject = await _gradleAppProject();
|
||||
gradleProject = await gradleUtils.appProject;
|
||||
} else if (manifest.isPlugin) {
|
||||
gradleProject = await _gradleLibraryProject();
|
||||
gradleProject = await gradleUtils.libraryProject;
|
||||
} else {
|
||||
throwToolExit('AARs can only be built for plugin or module projects.');
|
||||
}
|
||||
@ -538,12 +552,11 @@ Future<void> buildGradleAar({
|
||||
multilineOutput: true,
|
||||
);
|
||||
|
||||
final String gradle = await _ensureGradle(project);
|
||||
final String gradlePath = fs.file(gradle).absolute.path;
|
||||
final String gradlew = await gradleUtils.getExecutable(project);
|
||||
final String flutterRoot = fs.path.absolute(Cache.flutterRoot);
|
||||
final String initScript = fs.path.join(flutterRoot, 'packages','flutter_tools', 'gradle', 'aar_init_script.gradle');
|
||||
final List<String> command = <String>[
|
||||
gradlePath,
|
||||
gradlew,
|
||||
'-I=$initScript',
|
||||
'-Pflutter-root=$flutterRoot',
|
||||
'-Poutput-dir=${gradleProject.buildDirectory}',
|
||||
@ -601,7 +614,8 @@ Future<void> buildGradleAar({
|
||||
printStatus('Built ${fs.path.relative(repoDirectory.path)}.', color: TerminalColor.green);
|
||||
}
|
||||
|
||||
Future<void> _buildGradleProjectV1(FlutterProject project, String gradle) async {
|
||||
Future<void> _buildGradleProjectV1(FlutterProject project) async {
|
||||
final String gradlew = await gradleUtils.getExecutable(project);
|
||||
// Run 'gradlew build'.
|
||||
final Status status = logger.startProgress(
|
||||
'Running \'gradlew build\'...',
|
||||
@ -610,7 +624,7 @@ Future<void> _buildGradleProjectV1(FlutterProject project, String gradle) async
|
||||
);
|
||||
final Stopwatch sw = Stopwatch()..start();
|
||||
final int exitCode = await runCommandAndStreamOutput(
|
||||
<String>[fs.file(gradle).absolute.path, 'build'],
|
||||
<String>[fs.file(gradlew).absolute.path, 'build'],
|
||||
workingDirectory: project.android.hostAppGradleRoot.path,
|
||||
allowReentrantFlutter: true,
|
||||
environment: _gradleEnv,
|
||||
@ -661,12 +675,12 @@ void printUndefinedTask(GradleProject project, BuildInfo buildInfo) {
|
||||
|
||||
Future<void> _buildGradleProjectV2(
|
||||
FlutterProject flutterProject,
|
||||
String gradle,
|
||||
AndroidBuildInfo androidBuildInfo,
|
||||
String target,
|
||||
bool isBuildingBundle,
|
||||
) async {
|
||||
final GradleProject project = await _gradleAppProject();
|
||||
final String gradlew = await gradleUtils.getExecutable(flutterProject);
|
||||
final GradleProject project = await gradleUtils.appProject;
|
||||
final BuildInfo buildInfo = androidBuildInfo.buildInfo;
|
||||
|
||||
String assembleTask;
|
||||
@ -685,8 +699,7 @@ Future<void> _buildGradleProjectV2(
|
||||
timeout: timeoutConfiguration.slowOperation,
|
||||
multilineOutput: true,
|
||||
);
|
||||
final String gradlePath = fs.file(gradle).absolute.path;
|
||||
final List<String> command = <String>[gradlePath];
|
||||
final List<String> command = <String>[gradlew];
|
||||
if (logger.isVerbose) {
|
||||
command.add('-Pverbose=true');
|
||||
} else {
|
||||
@ -712,6 +725,8 @@ Future<void> _buildGradleProjectV2(
|
||||
command.add('-Pfilesystem-scheme=${buildInfo.fileSystemScheme}');
|
||||
if (androidBuildInfo.splitPerAbi)
|
||||
command.add('-Psplit-per-abi=true');
|
||||
if (androidBuildInfo.proguard)
|
||||
command.add('-Pproguard=true');
|
||||
if (androidBuildInfo.targetArchs.isNotEmpty) {
|
||||
final String targetPlatforms = androidBuildInfo.targetArchs
|
||||
.map(getPlatformNameForAndroidArch).join(',');
|
||||
@ -727,6 +742,7 @@ Future<void> _buildGradleProjectV2(
|
||||
}
|
||||
command.add(assembleTask);
|
||||
bool potentialAndroidXFailure = false;
|
||||
bool potentialProguardFailure = false;
|
||||
final Stopwatch sw = Stopwatch()..start();
|
||||
int exitCode = 1;
|
||||
try {
|
||||
@ -743,13 +759,17 @@ Future<void> _buildGradleProjectV2(
|
||||
if (!isAndroidXPluginWarning && androidXFailureRegex.hasMatch(line)) {
|
||||
potentialAndroidXFailure = true;
|
||||
}
|
||||
// Proguard errors include this url.
|
||||
if (!potentialProguardFailure && androidBuildInfo.proguard &&
|
||||
line.contains('http://proguard.sourceforge.net')) {
|
||||
potentialProguardFailure = true;
|
||||
}
|
||||
// Always print the full line in verbose mode.
|
||||
if (logger.isVerbose) {
|
||||
return line;
|
||||
} else if (isAndroidXPluginWarning || !ndkMessageFilter.hasMatch(line)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return line;
|
||||
},
|
||||
);
|
||||
@ -758,7 +778,13 @@ Future<void> _buildGradleProjectV2(
|
||||
}
|
||||
|
||||
if (exitCode != 0) {
|
||||
if (potentialAndroidXFailure) {
|
||||
if (potentialProguardFailure) {
|
||||
final String exclamationMark = terminal.color('[!]', TerminalColor.red);
|
||||
printStatus('$exclamationMark Proguard may have failed to optimize the Java bytecode.', emphasis: true);
|
||||
printStatus('To disable proguard, pass the `--no-proguard` flag to this command.', indent: 4);
|
||||
printStatus('To learn more about Proguard, see: https://flutter.dev/docs/deployment/android#enabling-proguard', indent: 4);
|
||||
BuildEvent('proguard-failure').send();
|
||||
} else if (potentialAndroidXFailure) {
|
||||
printStatus('AndroidX incompatibilities may have caused this build to fail. See https://goo.gl/CP92wY.');
|
||||
BuildEvent('android-x-failure').send();
|
||||
}
|
||||
|
@ -92,6 +92,7 @@ class AndroidBuildInfo {
|
||||
AndroidArch.arm64_v8a,
|
||||
],
|
||||
this.splitPerAbi = false,
|
||||
this.proguard = false,
|
||||
});
|
||||
|
||||
// The build info containing the mode and flavor.
|
||||
@ -104,6 +105,9 @@ class AndroidBuildInfo {
|
||||
/// will be produced.
|
||||
final bool splitPerAbi;
|
||||
|
||||
/// Whether to enable Proguard on release mode.
|
||||
final bool proguard;
|
||||
|
||||
/// The target platforms for the build.
|
||||
final Iterable<AndroidArch> targetArchs;
|
||||
}
|
||||
|
@ -25,9 +25,15 @@ class BuildApkCommand extends BuildSubCommand {
|
||||
argParser
|
||||
..addFlag('split-per-abi',
|
||||
negatable: false,
|
||||
help: 'Whether to split the APKs per ABIs.'
|
||||
help: 'Whether to split the APKs per ABIs. '
|
||||
'To learn more, see: https://developer.android.com/studio/build/configure-apk-splits#configure-abi-split',
|
||||
)
|
||||
..addFlag('proguard',
|
||||
negatable: true,
|
||||
defaultsTo: true,
|
||||
help: 'Whether to enable Proguard on release mode. '
|
||||
'To learn more, see: https://flutter.dev/docs/deployment/android#enabling-proguard',
|
||||
)
|
||||
..addMultiOption('target-platform',
|
||||
splitCommas: true,
|
||||
defaultsTo: <String>['android-arm', 'android-arm64'],
|
||||
@ -79,7 +85,8 @@ class BuildApkCommand extends BuildSubCommand {
|
||||
final BuildInfo buildInfo = getBuildInfo();
|
||||
final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(buildInfo,
|
||||
splitPerAbi: argResults['split-per-abi'],
|
||||
targetArchs: argResults['target-platform'].map<AndroidArch>(getAndroidArchForName)
|
||||
targetArchs: argResults['target-platform'].map<AndroidArch>(getAndroidArchForName),
|
||||
proguard: argResults['proguard'],
|
||||
);
|
||||
|
||||
if (buildInfo.isRelease && !androidBuildInfo.splitPerAbi && androidBuildInfo.targetArchs.length > 1) {
|
||||
|
@ -22,6 +22,12 @@ class BuildAppBundleCommand extends BuildSubCommand {
|
||||
|
||||
argParser
|
||||
..addFlag('track-widget-creation', negatable: false, hide: !verboseHelp)
|
||||
..addFlag('proguard',
|
||||
negatable: true,
|
||||
defaultsTo: true,
|
||||
help: 'Whether to enable Proguard on release mode. '
|
||||
'To learn more, see: https://flutter.dev/docs/deployment/android#enabling-proguard',
|
||||
)
|
||||
..addMultiOption('target-platform',
|
||||
splitCommas: true,
|
||||
defaultsTo: <String>['android-arm', 'android-arm64'],
|
||||
@ -63,7 +69,8 @@ class BuildAppBundleCommand extends BuildSubCommand {
|
||||
@override
|
||||
Future<FlutterCommandResult> runCommand() async {
|
||||
final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(getBuildInfo(),
|
||||
targetArchs: argResults['target-platform'].map<AndroidArch>(getAndroidArchForName)
|
||||
targetArchs: argResults['target-platform'].map<AndroidArch>(getAndroidArchForName),
|
||||
proguard: argResults['proguard'],
|
||||
);
|
||||
await androidBuilder.buildAab(
|
||||
project: FlutterProject.current(),
|
||||
|
@ -7,6 +7,7 @@ import 'dart:async';
|
||||
import 'android/android_sdk.dart';
|
||||
import 'android/android_studio.dart';
|
||||
import 'android/android_workflow.dart';
|
||||
import 'android/gradle.dart';
|
||||
import 'application_package.dart';
|
||||
import 'artifacts.dart';
|
||||
import 'asset.dart';
|
||||
@ -89,6 +90,7 @@ Future<T> runInContext<T>(
|
||||
FuchsiaSdk: () => FuchsiaSdk(),
|
||||
FuchsiaWorkflow: () => FuchsiaWorkflow(),
|
||||
GenSnapshot: () => const GenSnapshot(),
|
||||
GradleUtils: () => GradleUtils(),
|
||||
HotRunnerConfig: () => HotRunnerConfig(),
|
||||
IMobileDevice: () => IMobileDevice(),
|
||||
IOSDeploy: () => const IOSDeploy(),
|
||||
|
@ -3,7 +3,6 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io' hide File;
|
||||
|
||||
import 'package:args/command_runner.dart';
|
||||
@ -17,21 +16,7 @@ import 'package:process/process.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
import '../src/context.dart';
|
||||
|
||||
Process createMockProcess({ int exitCode = 0, String stdout = '', String stderr = '' }) {
|
||||
final Stream<List<int>> stdoutStream = Stream<List<int>>.fromIterable(<List<int>>[
|
||||
utf8.encode(stdout),
|
||||
]);
|
||||
final Stream<List<int>> stderrStream = Stream<List<int>>.fromIterable(<List<int>>[
|
||||
utf8.encode(stderr),
|
||||
]);
|
||||
final Process process = MockProcess();
|
||||
|
||||
when(process.stdout).thenAnswer((_) => stdoutStream);
|
||||
when(process.stderr).thenAnswer((_) => stderrStream);
|
||||
when(process.exitCode).thenAnswer((_) => Future<int>.value(exitCode));
|
||||
return process;
|
||||
}
|
||||
import '../src/mocks.dart';
|
||||
|
||||
void main() {
|
||||
group('channel', () {
|
||||
|
@ -2,15 +2,24 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:args/command_runner.dart';
|
||||
import 'package:flutter_tools/src/android/android_builder.dart';
|
||||
import 'package:flutter_tools/src/android/android_sdk.dart';
|
||||
import 'package:flutter_tools/src/android/gradle.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/commands/build_apk.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:flutter_tools/src/reporting/reporting.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
import '../../src/mocks.dart';
|
||||
|
||||
void main() {
|
||||
Cache.disableLocking();
|
||||
@ -26,21 +35,10 @@ void main() {
|
||||
tryToDelete(tempDir);
|
||||
});
|
||||
|
||||
Future<BuildApkCommand> runCommandIn(String target, { List<String> arguments }) async {
|
||||
final BuildApkCommand command = BuildApkCommand();
|
||||
final CommandRunner<void> runner = createTestCommandRunner(command);
|
||||
await runner.run(<String>[
|
||||
'apk',
|
||||
...?arguments,
|
||||
fs.path.join(target, 'lib', 'main.dart'),
|
||||
]);
|
||||
return command;
|
||||
}
|
||||
|
||||
testUsingContext('indicate the default target platforms', () async {
|
||||
final String projectPath = await createProject(tempDir,
|
||||
arguments: <String>['--no-pub', '--template=app']);
|
||||
final BuildApkCommand command = await runCommandIn(projectPath);
|
||||
final BuildApkCommand command = await runBuildApkCommand(projectPath);
|
||||
|
||||
expect(await command.usageValues,
|
||||
containsPair(CustomDimensions.commandBuildApkTargetPlatform, 'android-arm,android-arm64'));
|
||||
@ -53,12 +51,12 @@ void main() {
|
||||
final String projectPath = await createProject(tempDir,
|
||||
arguments: <String>['--no-pub', '--template=app']);
|
||||
|
||||
final BuildApkCommand commandWithFlag = await runCommandIn(projectPath,
|
||||
final BuildApkCommand commandWithFlag = await runBuildApkCommand(projectPath,
|
||||
arguments: <String>['--split-per-abi']);
|
||||
expect(await commandWithFlag.usageValues,
|
||||
containsPair(CustomDimensions.commandBuildApkSplitPerAbi, 'true'));
|
||||
|
||||
final BuildApkCommand commandWithoutFlag = await runCommandIn(projectPath);
|
||||
final BuildApkCommand commandWithoutFlag = await runBuildApkCommand(projectPath);
|
||||
expect(await commandWithoutFlag.usageValues,
|
||||
containsPair(CustomDimensions.commandBuildApkSplitPerAbi, 'false'));
|
||||
|
||||
@ -70,21 +68,21 @@ void main() {
|
||||
final String projectPath = await createProject(tempDir,
|
||||
arguments: <String>['--no-pub', '--template=app']);
|
||||
|
||||
final BuildApkCommand commandDefault = await runCommandIn(projectPath);
|
||||
final BuildApkCommand commandDefault = await runBuildApkCommand(projectPath);
|
||||
expect(await commandDefault.usageValues,
|
||||
containsPair(CustomDimensions.commandBuildApkBuildMode, 'release'));
|
||||
|
||||
final BuildApkCommand commandInRelease = await runCommandIn(projectPath,
|
||||
final BuildApkCommand commandInRelease = await runBuildApkCommand(projectPath,
|
||||
arguments: <String>['--release']);
|
||||
expect(await commandInRelease.usageValues,
|
||||
containsPair(CustomDimensions.commandBuildApkBuildMode, 'release'));
|
||||
|
||||
final BuildApkCommand commandInDebug = await runCommandIn(projectPath,
|
||||
final BuildApkCommand commandInDebug = await runBuildApkCommand(projectPath,
|
||||
arguments: <String>['--debug']);
|
||||
expect(await commandInDebug.usageValues,
|
||||
containsPair(CustomDimensions.commandBuildApkBuildMode, 'debug'));
|
||||
|
||||
final BuildApkCommand commandInProfile = await runCommandIn(projectPath,
|
||||
final BuildApkCommand commandInProfile = await runBuildApkCommand(projectPath,
|
||||
arguments: <String>['--profile']);
|
||||
expect(await commandInProfile.usageValues,
|
||||
containsPair(CustomDimensions.commandBuildApkBuildMode, 'profile'));
|
||||
@ -93,4 +91,201 @@ void main() {
|
||||
AndroidBuilder: () => FakeAndroidBuilder(),
|
||||
}, timeout: allowForCreateFlutterProject);
|
||||
});
|
||||
|
||||
group('Gradle', () {
|
||||
Directory tempDir;
|
||||
ProcessManager mockProcessManager;
|
||||
String gradlew;
|
||||
AndroidSdk mockAndroidSdk;
|
||||
Usage mockUsage;
|
||||
|
||||
setUp(() {
|
||||
mockUsage = MockUsage();
|
||||
when(mockUsage.isFirstRun).thenReturn(true);
|
||||
|
||||
tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_packages_test.');
|
||||
gradlew = fs.path.join(tempDir.path, 'flutter_project', 'android',
|
||||
platform.isWindows ? 'gradlew.bat' : 'gradlew');
|
||||
|
||||
mockProcessManager = MockProcessManager();
|
||||
when(mockProcessManager.run(<String>[gradlew, '-v'],
|
||||
environment: anyNamed('environment')))
|
||||
.thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(0, 0, '', '')));
|
||||
|
||||
when(mockProcessManager.run(<String>[gradlew, 'app:properties'],
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
environment: anyNamed('environment')))
|
||||
.thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(0, 0, 'buildDir: irrelevant', '')));
|
||||
|
||||
when(mockProcessManager.run(<String>[gradlew, 'app:tasks', '--all', '--console=auto'],
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
environment: anyNamed('environment')))
|
||||
.thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(0, 0, 'assembleRelease', '')));
|
||||
// Fallback with error.
|
||||
final Process process = createMockProcess(exitCode: 1);
|
||||
when(mockProcessManager.start(any,
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
environment: anyNamed('environment')))
|
||||
.thenAnswer((_) => Future<Process>.value(process));
|
||||
when(mockProcessManager.canRun(any)).thenReturn(false);
|
||||
|
||||
mockAndroidSdk = MockAndroidSdk();
|
||||
when(mockAndroidSdk.directory).thenReturn('irrelevant');
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
tryToDelete(tempDir);
|
||||
});
|
||||
|
||||
testUsingContext('proguard is enabled by default on release mode', () async {
|
||||
final String projectPath = await createProject(tempDir,
|
||||
arguments: <String>['--no-pub', '--template=app']);
|
||||
|
||||
await expectLater(() async {
|
||||
await runBuildApkCommand(projectPath);
|
||||
}, throwsToolExit(message: 'Gradle task assembleRelease failed with exit code 1'));
|
||||
|
||||
verify(mockProcessManager.start(
|
||||
<String>[
|
||||
gradlew,
|
||||
'-q',
|
||||
'-Ptarget=${fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
|
||||
'-Ptrack-widget-creation=false',
|
||||
'-Pproguard=true',
|
||||
'-Ptarget-platform=android-arm,android-arm64',
|
||||
'assembleRelease',
|
||||
],
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
environment: anyNamed('environment'),
|
||||
)).called(1);
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
AndroidSdk: () => mockAndroidSdk,
|
||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||
GradleUtils: () => GradleUtils(),
|
||||
ProcessManager: () => mockProcessManager,
|
||||
},
|
||||
timeout: allowForCreateFlutterProject);
|
||||
|
||||
testUsingContext('proguard is disabled when --no-proguard is passed', () async {
|
||||
final String projectPath = await createProject(tempDir,
|
||||
arguments: <String>['--no-pub', '--template=app']);
|
||||
|
||||
await expectLater(() async {
|
||||
await runBuildApkCommand(
|
||||
projectPath,
|
||||
arguments: <String>['--no-proguard'],
|
||||
);
|
||||
}, throwsToolExit(message: 'Gradle task assembleRelease failed with exit code 1'));
|
||||
|
||||
verify(mockProcessManager.start(
|
||||
<String>[
|
||||
gradlew,
|
||||
'-q',
|
||||
'-Ptarget=${fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
|
||||
'-Ptrack-widget-creation=false',
|
||||
'-Ptarget-platform=android-arm,android-arm64',
|
||||
'assembleRelease',
|
||||
],
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
environment: anyNamed('environment'),
|
||||
)).called(1);
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
AndroidSdk: () => mockAndroidSdk,
|
||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||
GradleUtils: () => GradleUtils(),
|
||||
ProcessManager: () => mockProcessManager,
|
||||
},
|
||||
timeout: allowForCreateFlutterProject);
|
||||
|
||||
testUsingContext('guides the user when proguard fails', () async {
|
||||
final String projectPath = await createProject(tempDir,
|
||||
arguments: <String>['--no-pub', '--template=app']);
|
||||
|
||||
when(mockProcessManager.start(
|
||||
<String>[
|
||||
gradlew,
|
||||
'-q',
|
||||
'-Ptarget=${fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
|
||||
'-Ptrack-widget-creation=false',
|
||||
'-Pproguard=true',
|
||||
'-Ptarget-platform=android-arm,android-arm64',
|
||||
'assembleRelease',
|
||||
],
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
environment: anyNamed('environment'),
|
||||
)).thenAnswer((_) {
|
||||
const String proguardStdoutWarning =
|
||||
'Warning: there were 6 unresolved references to program class members.'
|
||||
'Your input classes appear to be inconsistent.'
|
||||
'You may need to recompile the code.'
|
||||
'(http://proguard.sourceforge.net/manual/troubleshooting.html#unresolvedprogramclassmember)';
|
||||
return Future<Process>.value(
|
||||
createMockProcess(
|
||||
exitCode: 1,
|
||||
stdout: proguardStdoutWarning,
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
await expectLater(() async {
|
||||
await runBuildApkCommand(
|
||||
projectPath,
|
||||
);
|
||||
}, throwsToolExit(message: 'Gradle task assembleRelease failed with exit code 1'));
|
||||
|
||||
expect(testLogger.statusText,
|
||||
contains('Proguard may have failed to optimize the Java bytecode.'));
|
||||
expect(testLogger.statusText,
|
||||
contains('To disable proguard, pass the `--no-proguard` flag to this command.'));
|
||||
expect(testLogger.statusText,
|
||||
contains('To learn more about Proguard, see: https://flutter.dev/docs/deployment/android#enabling-proguard'));
|
||||
|
||||
verify(mockUsage.sendEvent(
|
||||
'build-apk',
|
||||
'proguard-failure',
|
||||
parameters: anyNamed('parameters'),
|
||||
)).called(1);
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
AndroidSdk: () => mockAndroidSdk,
|
||||
GradleUtils: () => GradleUtils(),
|
||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Usage: () => mockUsage,
|
||||
},
|
||||
timeout: allowForCreateFlutterProject);
|
||||
});
|
||||
}
|
||||
|
||||
Future<BuildApkCommand> runBuildApkCommand(
|
||||
String target,
|
||||
{ List<String> arguments }
|
||||
) async {
|
||||
final BuildApkCommand command = BuildApkCommand();
|
||||
final CommandRunner<void> runner = createTestCommandRunner(command);
|
||||
await runner.run(<String>[
|
||||
'apk',
|
||||
...?arguments,
|
||||
fs.path.join(target, 'lib', 'main.dart'),
|
||||
]);
|
||||
return command;
|
||||
}
|
||||
|
||||
class FakeFlutterProjectFactory extends FlutterProjectFactory {
|
||||
FakeFlutterProjectFactory(this.directoryOverride) :
|
||||
assert(directoryOverride != null);
|
||||
|
||||
final Directory directoryOverride;
|
||||
|
||||
@override
|
||||
FlutterProject fromDirectory(Directory _) {
|
||||
return super.fromDirectory(directoryOverride.childDirectory('flutter_project'));
|
||||
}
|
||||
}
|
||||
|
||||
class MockAndroidSdk extends Mock implements AndroidSdk {}
|
||||
class MockProcessManager extends Mock implements ProcessManager {}
|
||||
class MockProcess extends Mock implements Process {}
|
||||
class MockUsage extends Mock implements Usage {}
|
||||
|
@ -2,15 +2,24 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:args/command_runner.dart';
|
||||
import 'package:flutter_tools/src/android/android_builder.dart';
|
||||
import 'package:flutter_tools/src/android/android_sdk.dart';
|
||||
import 'package:flutter_tools/src/android/gradle.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/commands/build_appbundle.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:flutter_tools/src/reporting/reporting.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
import '../../src/mocks.dart';
|
||||
|
||||
void main() {
|
||||
Cache.disableLocking();
|
||||
@ -26,21 +35,10 @@ void main() {
|
||||
tryToDelete(tempDir);
|
||||
});
|
||||
|
||||
Future<BuildAppBundleCommand> runCommandIn(String target, { List<String> arguments }) async {
|
||||
final BuildAppBundleCommand command = BuildAppBundleCommand();
|
||||
final CommandRunner<void> runner = createTestCommandRunner(command);
|
||||
await runner.run(<String>[
|
||||
'appbundle',
|
||||
...?arguments,
|
||||
fs.path.join(target, 'lib', 'main.dart'),
|
||||
]);
|
||||
return command;
|
||||
}
|
||||
|
||||
testUsingContext('indicate the default target platforms', () async {
|
||||
final String projectPath = await createProject(tempDir,
|
||||
arguments: <String>['--no-pub', '--template=app']);
|
||||
final BuildAppBundleCommand command = await runCommandIn(projectPath);
|
||||
final BuildAppBundleCommand command = await runBuildAppBundleCommand(projectPath);
|
||||
|
||||
expect(await command.usageValues,
|
||||
containsPair(CustomDimensions.commandBuildAppBundleTargetPlatform, 'android-arm,android-arm64'));
|
||||
@ -53,21 +51,21 @@ void main() {
|
||||
final String projectPath = await createProject(tempDir,
|
||||
arguments: <String>['--no-pub', '--template=app']);
|
||||
|
||||
final BuildAppBundleCommand commandDefault = await runCommandIn(projectPath);
|
||||
final BuildAppBundleCommand commandDefault = await runBuildAppBundleCommand(projectPath);
|
||||
expect(await commandDefault.usageValues,
|
||||
containsPair(CustomDimensions.commandBuildAppBundleBuildMode, 'release'));
|
||||
|
||||
final BuildAppBundleCommand commandInRelease = await runCommandIn(projectPath,
|
||||
final BuildAppBundleCommand commandInRelease = await runBuildAppBundleCommand(projectPath,
|
||||
arguments: <String>['--release']);
|
||||
expect(await commandInRelease.usageValues,
|
||||
containsPair(CustomDimensions.commandBuildAppBundleBuildMode, 'release'));
|
||||
|
||||
final BuildAppBundleCommand commandInDebug = await runCommandIn(projectPath,
|
||||
final BuildAppBundleCommand commandInDebug = await runBuildAppBundleCommand(projectPath,
|
||||
arguments: <String>['--debug']);
|
||||
expect(await commandInDebug.usageValues,
|
||||
containsPair(CustomDimensions.commandBuildAppBundleBuildMode, 'debug'));
|
||||
|
||||
final BuildAppBundleCommand commandInProfile = await runCommandIn(projectPath,
|
||||
final BuildAppBundleCommand commandInProfile = await runBuildAppBundleCommand(projectPath,
|
||||
arguments: <String>['--profile']);
|
||||
expect(await commandInProfile.usageValues,
|
||||
containsPair(CustomDimensions.commandBuildAppBundleBuildMode, 'profile'));
|
||||
@ -76,4 +74,207 @@ void main() {
|
||||
AndroidBuilder: () => FakeAndroidBuilder(),
|
||||
}, timeout: allowForCreateFlutterProject);
|
||||
});
|
||||
|
||||
group('Flags', () {
|
||||
Directory tempDir;
|
||||
ProcessManager mockProcessManager;
|
||||
MockAndroidSdk mockAndroidSdk;
|
||||
String gradlew;
|
||||
Usage mockUsage;
|
||||
|
||||
|
||||
setUp(() {
|
||||
mockUsage = MockUsage();
|
||||
when(mockUsage.isFirstRun).thenReturn(true);
|
||||
|
||||
tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_packages_test.');
|
||||
gradlew = fs.path.join(tempDir.path, 'flutter_project', 'android',
|
||||
platform.isWindows ? 'gradlew.bat' : 'gradlew');
|
||||
|
||||
mockProcessManager = MockProcessManager();
|
||||
when(mockProcessManager.run(<String>[gradlew, '-v'],
|
||||
environment: anyNamed('environment')))
|
||||
.thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(0, 0, '', '')));
|
||||
|
||||
when(mockProcessManager.run(<String>[gradlew, 'app:properties'],
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
environment: anyNamed('environment')))
|
||||
.thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(0, 0, 'buildDir: irrelevant', '')));
|
||||
|
||||
when(mockProcessManager.run(<String>[gradlew, 'app:tasks', '--all', '--console=auto'],
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
environment: anyNamed('environment')))
|
||||
.thenAnswer((_) => Future<ProcessResult>.value(ProcessResult(0, 0, 'assembleRelease', '')));
|
||||
// Fallback with error.
|
||||
final Process process = createMockProcess(exitCode: 1);
|
||||
when(mockProcessManager.start(any,
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
environment: anyNamed('environment')))
|
||||
.thenAnswer((_) => Future<Process>.value(process));
|
||||
when(mockProcessManager.canRun(any)).thenReturn(false);
|
||||
|
||||
mockAndroidSdk = MockAndroidSdk();
|
||||
when(mockAndroidSdk.validateSdkWellFormed()).thenReturn(const <String>[]);
|
||||
when(mockAndroidSdk.directory).thenReturn('irrelevant');
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
tryToDelete(tempDir);
|
||||
});
|
||||
|
||||
testUsingContext('proguard is enabled by default on release mode', () async {
|
||||
final String projectPath = await createProject(
|
||||
tempDir,
|
||||
arguments: <String>['--no-pub', '--template=app'],
|
||||
);
|
||||
|
||||
await expectLater(() async {
|
||||
await runBuildAppBundleCommand(projectPath);
|
||||
}, throwsToolExit(message: 'Gradle task bundleRelease failed with exit code 1'));
|
||||
|
||||
verify(mockProcessManager.start(
|
||||
<String>[
|
||||
gradlew,
|
||||
'-q',
|
||||
'-Ptarget=${fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
|
||||
'-Ptrack-widget-creation=false',
|
||||
'-Pproguard=true',
|
||||
'-Ptarget-platform=android-arm,android-arm64',
|
||||
'bundleRelease',
|
||||
],
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
environment: anyNamed('environment'),
|
||||
)).called(1);
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
AndroidSdk: () => mockAndroidSdk,
|
||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||
GradleUtils: () => GradleUtils(),
|
||||
ProcessManager: () => mockProcessManager,
|
||||
},
|
||||
timeout: allowForCreateFlutterProject);
|
||||
|
||||
testUsingContext('proguard is disabled when --no-proguard is passed', () async {
|
||||
final String projectPath = await createProject(
|
||||
tempDir,
|
||||
arguments: <String>['--no-pub', '--template=app'],
|
||||
);
|
||||
|
||||
await expectLater(() async {
|
||||
await runBuildAppBundleCommand(
|
||||
projectPath,
|
||||
arguments: <String>['--no-proguard'],
|
||||
);
|
||||
}, throwsToolExit(message: 'Gradle task bundleRelease failed with exit code 1'));
|
||||
|
||||
verify(mockProcessManager.start(
|
||||
<String>[
|
||||
gradlew,
|
||||
'-q',
|
||||
'-Ptarget=${fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
|
||||
'-Ptrack-widget-creation=false',
|
||||
'-Ptarget-platform=android-arm,android-arm64',
|
||||
'bundleRelease',
|
||||
],
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
environment: anyNamed('environment'),
|
||||
)).called(1);
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
AndroidSdk: () => mockAndroidSdk,
|
||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||
GradleUtils: () => GradleUtils(),
|
||||
ProcessManager: () => mockProcessManager,
|
||||
},
|
||||
timeout: allowForCreateFlutterProject);
|
||||
|
||||
testUsingContext('guides the user when proguard fails', () async {
|
||||
final String projectPath = await createProject(tempDir,
|
||||
arguments: <String>['--no-pub', '--template=app']);
|
||||
|
||||
when(mockProcessManager.start(
|
||||
<String>[
|
||||
gradlew,
|
||||
'-q',
|
||||
'-Ptarget=${fs.path.join(tempDir.path, 'flutter_project', 'lib', 'main.dart')}',
|
||||
'-Ptrack-widget-creation=false',
|
||||
'-Pproguard=true',
|
||||
'-Ptarget-platform=android-arm,android-arm64',
|
||||
'bundleRelease',
|
||||
],
|
||||
workingDirectory: anyNamed('workingDirectory'),
|
||||
environment: anyNamed('environment'),
|
||||
)).thenAnswer((_) {
|
||||
const String proguardStdoutWarning =
|
||||
'Warning: there were 6 unresolved references to program class members.'
|
||||
'Your input classes appear to be inconsistent.'
|
||||
'You may need to recompile the code.'
|
||||
'(http://proguard.sourceforge.net/manual/troubleshooting.html#unresolvedprogramclassmember)';
|
||||
return Future<Process>.value(
|
||||
createMockProcess(
|
||||
exitCode: 1,
|
||||
stdout: proguardStdoutWarning,
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
await expectLater(() async {
|
||||
await runBuildAppBundleCommand(
|
||||
projectPath,
|
||||
);
|
||||
}, throwsToolExit(message: 'Gradle task bundleRelease failed with exit code 1'));
|
||||
|
||||
expect(testLogger.statusText,
|
||||
contains('Proguard may have failed to optimize the Java bytecode.'));
|
||||
expect(testLogger.statusText,
|
||||
contains('To disable proguard, pass the `--no-proguard` flag to this command.'));
|
||||
expect(testLogger.statusText,
|
||||
contains('To learn more about Proguard, see: https://flutter.dev/docs/deployment/android#enabling-proguard'));
|
||||
|
||||
verify(mockUsage.sendEvent(
|
||||
'build-appbundle',
|
||||
'proguard-failure',
|
||||
parameters: anyNamed('parameters'),
|
||||
)).called(1);
|
||||
},
|
||||
overrides: <Type, Generator>{
|
||||
AndroidSdk: () => mockAndroidSdk,
|
||||
GradleUtils: () => GradleUtils(),
|
||||
FlutterProjectFactory: () => FakeFlutterProjectFactory(tempDir),
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Usage: () => mockUsage,
|
||||
},
|
||||
timeout: allowForCreateFlutterProject);
|
||||
});
|
||||
}
|
||||
|
||||
Future<BuildAppBundleCommand> runBuildAppBundleCommand(
|
||||
String target,
|
||||
{ List<String> arguments }
|
||||
) async {
|
||||
final BuildAppBundleCommand command = BuildAppBundleCommand();
|
||||
final CommandRunner<void> runner = createTestCommandRunner(command);
|
||||
await runner.run(<String>[
|
||||
'appbundle',
|
||||
...?arguments,
|
||||
fs.path.join(target, 'lib', 'main.dart'),
|
||||
]);
|
||||
return command;
|
||||
}
|
||||
|
||||
class FakeFlutterProjectFactory extends FlutterProjectFactory {
|
||||
FakeFlutterProjectFactory(this._directoryOverride) :
|
||||
assert(_directoryOverride != null);
|
||||
|
||||
final Directory _directoryOverride;
|
||||
|
||||
@override
|
||||
FlutterProject fromDirectory(Directory _) {
|
||||
return super.fromDirectory(_directoryOverride.childDirectory('flutter_project'));
|
||||
}
|
||||
}
|
||||
|
||||
class MockAndroidSdk extends Mock implements AndroidSdk {}
|
||||
class MockProcessManager extends Mock implements ProcessManager {}
|
||||
class MockProcess extends Mock implements Process {}
|
||||
class MockUsage extends Mock implements Usage {}
|
||||
|
@ -2,8 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_tools/src/base/common.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
@ -17,21 +15,7 @@ import 'package:process/process.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
|
||||
Process createMockProcess({ int exitCode = 0, String stdout = '', String stderr = '' }) {
|
||||
final Stream<List<int>> stdoutStream = Stream<List<int>>.fromIterable(<List<int>>[
|
||||
utf8.encode(stdout),
|
||||
]);
|
||||
final Stream<List<int>> stderrStream = Stream<List<int>>.fromIterable(<List<int>>[
|
||||
utf8.encode(stderr),
|
||||
]);
|
||||
final Process process = MockProcess();
|
||||
|
||||
when(process.stdout).thenAnswer((_) => stdoutStream);
|
||||
when(process.stderr).thenAnswer((_) => stderrStream);
|
||||
when(process.exitCode).thenAnswer((_) => Future<int>.value(exitCode));
|
||||
return process;
|
||||
}
|
||||
import '../../src/mocks.dart';
|
||||
|
||||
void main() {
|
||||
group('UpgradeCommandRunner', () {
|
||||
|
@ -116,6 +116,8 @@ Future<String> createProject(Directory temp, { List<String> arguments }) async {
|
||||
final CreateCommand command = CreateCommand();
|
||||
final CommandRunner<void> runner = createTestCommandRunner(command);
|
||||
await runner.run(<String>['create', ...arguments, projectPath]);
|
||||
// Created `.packages` since it's not created when the flag `--no-pub` is passed.
|
||||
fs.file(fs.path.join(projectPath, '.packages')).createSync();
|
||||
return projectPath;
|
||||
}
|
||||
|
||||
|
@ -221,6 +221,24 @@ ProcessFactory flakyProcessFactory({
|
||||
};
|
||||
}
|
||||
|
||||
/// Creates a mock process that returns with the given [exitCode], [stdout] and [stderr].
|
||||
Process createMockProcess({ int exitCode = 0, String stdout = '', String stderr = '' }) {
|
||||
final Stream<List<int>> stdoutStream = Stream<List<int>>.fromIterable(<List<int>>[
|
||||
utf8.encode(stdout),
|
||||
]);
|
||||
final Stream<List<int>> stderrStream = Stream<List<int>>.fromIterable(<List<int>>[
|
||||
utf8.encode(stderr),
|
||||
]);
|
||||
final Process process = MockBasicProcess();
|
||||
|
||||
when(process.stdout).thenAnswer((_) => stdoutStream);
|
||||
when(process.stderr).thenAnswer((_) => stderrStream);
|
||||
when(process.exitCode).thenAnswer((_) => Future<int>.value(exitCode));
|
||||
return process;
|
||||
}
|
||||
|
||||
class MockBasicProcess extends Mock implements Process {}
|
||||
|
||||
/// A process that exits successfully with no output and ignores all input.
|
||||
class MockProcess extends Mock implements Process {
|
||||
MockProcess({
|
||||
|
Loading…
Reference in New Issue
Block a user