Enable Proguard by default on release mode (#39986)

This commit is contained in:
Emmanuel Garcia 2019-09-10 17:22:55 -07:00 committed by GitHub
parent 362cde43ff
commit f098de1fde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 586 additions and 122 deletions

View File

@ -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) String flutterRootPath = resolveProperty(project, "flutter.sdk", System.env.FLUTTER_ROOT)
if (flutterRootPath == null) { 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.") 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" String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter"
flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile(); 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)) { if (useLocalEngine(project)) {
String engineOutPath = project.property('localEngineOut') String engineOutPath = project.property('localEngineOut')
File engineOut = project.file(engineOutPath) File engineOut = project.file(engineOutPath)
@ -375,6 +389,14 @@ class FlutterPlugin implements Plugin<Project> {
return false return false
} }
private static Boolean useProguard(Project project) {
if (project.hasProperty('proguard')) {
return project.property('proguard').toBoolean()
}
return false
}
private static Boolean buildPluginAsAar() { private static Boolean buildPluginAsAar() {
return System.getProperty('build-plugins-as-aars') == 'true' return System.getProperty('build-plugins-as-aars') == 'true'
} }

View 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.**

View File

@ -10,6 +10,7 @@ import 'package:meta/meta.dart';
import '../android/android_sdk.dart'; import '../android/android_sdk.dart';
import '../artifacts.dart'; import '../artifacts.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/context.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/os.dart'; import '../base/os.dart';
@ -28,11 +29,39 @@ import '../reporting/reporting.dart';
import 'android_sdk.dart'; import 'android_sdk.dart';
import 'android_studio.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; /// Provides utilities to run a Gradle task,
GradleProject _cachedGradleLibraryProject; /// such as finding the Gradle executable or constructing a Gradle project.
String _cachedGradleExecutable; 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 { enum FlutterPluginVersion {
none, none,
@ -103,29 +132,20 @@ Future<File> getGradleAppOut(AndroidProject androidProject) async {
case FlutterPluginVersion.managed: case FlutterPluginVersion.managed:
// Fall through. The managed plugin matches plugin v2 for now. // Fall through. The managed plugin matches plugin v2 for now.
case FlutterPluginVersion.v2: 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; 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 /// Runs `gradlew dependencies`, ensuring that dependencies are resolved and
/// potentially downloaded. /// potentially downloaded.
Future<void> checkGradleDependencies() async { Future<void> checkGradleDependencies() async {
final Status progress = logger.startProgress('Ensuring gradle dependencies are up to date...', timeout: timeoutConfiguration.slowOperation); final Status progress = logger.startProgress('Ensuring gradle dependencies are up to date...', timeout: timeoutConfiguration.slowOperation);
final FlutterProject flutterProject = FlutterProject.current(); final FlutterProject flutterProject = FlutterProject.current();
final String gradle = await _ensureGradle(flutterProject); final String gradlew = await gradleUtils.getExecutable(flutterProject);
await runCheckedAsync( await runCheckedAsync(
<String>[gradle, 'dependencies'], <String>[gradlew, 'dependencies'],
workingDirectory: flutterProject.android.hostAppGradleRoot.path, workingDirectory: flutterProject.android.hostAppGradleRoot.path,
environment: _gradleEnv, environment: _gradleEnv,
); );
@ -189,7 +209,8 @@ void createSettingsAarGradle(Directory androidDirectory) {
// of calculating the app properties using Gradle. This may take minutes. // of calculating the app properties using Gradle. This may take minutes.
Future<GradleProject> _readGradleProject({bool isLibrary = false}) async { Future<GradleProject> _readGradleProject({bool isLibrary = false}) async {
final FlutterProject flutterProject = FlutterProject.current(); final FlutterProject flutterProject = FlutterProject.current();
final String gradle = await _ensureGradle(flutterProject); final String gradlew = await gradleUtils.getExecutable(flutterProject);
updateLocalProperties(project: flutterProject); updateLocalProperties(project: flutterProject);
final FlutterManifest manifest = flutterProject.manifest; 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 // flavors and build types defined in the project. If gradle fails, then check if the failure is due to t
try { try {
final RunResult propertiesRunResult = await runCheckedAsync( final RunResult propertiesRunResult = await runCheckedAsync(
<String>[gradle, isLibrary ? 'properties' : 'app:properties'], <String>[gradlew, isLibrary ? 'properties' : 'app:properties'],
workingDirectory: hostAppGradleRoot.path, workingDirectory: hostAppGradleRoot.path,
environment: _gradleEnv, environment: _gradleEnv,
); );
final RunResult tasksRunResult = await runCheckedAsync( 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, workingDirectory: hostAppGradleRoot.path,
environment: _gradleEnv, environment: _gradleEnv,
); );
@ -274,11 +295,6 @@ String _locateGradlewExecutable(Directory directory) {
return null; 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 // Note: Gradle may be bootstrapped and possibly downloaded as a side-effect
// of validating the Gradle executable. This may take several seconds. // of validating the Gradle executable. This may take several seconds.
Future<String> _initializeGradle(FlutterProject project) async { Future<String> _initializeGradle(FlutterProject project) async {
@ -492,17 +508,15 @@ Future<void> buildGradleProject({
// from the local.properties file. // from the local.properties file.
updateLocalProperties(project: project, buildInfo: androidBuildInfo.buildInfo); updateLocalProperties(project: project, buildInfo: androidBuildInfo.buildInfo);
final String gradle = await _ensureGradle(project);
switch (getFlutterPluginVersion(project.android)) { switch (getFlutterPluginVersion(project.android)) {
case FlutterPluginVersion.none: case FlutterPluginVersion.none:
// Fall through. Pretend it's v1, and just go for it. // Fall through. Pretend it's v1, and just go for it.
case FlutterPluginVersion.v1: case FlutterPluginVersion.v1:
return _buildGradleProjectV1(project, gradle); return _buildGradleProjectV1(project);
case FlutterPluginVersion.managed: case FlutterPluginVersion.managed:
// Fall through. Managed plugin builds the same way as plugin v2. // Fall through. Managed plugin builds the same way as plugin v2.
case FlutterPluginVersion.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; GradleProject gradleProject;
if (manifest.isModule) { if (manifest.isModule) {
gradleProject = await _gradleAppProject(); gradleProject = await gradleUtils.appProject;
} else if (manifest.isPlugin) { } else if (manifest.isPlugin) {
gradleProject = await _gradleLibraryProject(); gradleProject = await gradleUtils.libraryProject;
} else { } else {
throwToolExit('AARs can only be built for plugin or module projects.'); throwToolExit('AARs can only be built for plugin or module projects.');
} }
@ -538,12 +552,11 @@ Future<void> buildGradleAar({
multilineOutput: true, multilineOutput: true,
); );
final String gradle = await _ensureGradle(project); final String gradlew = await gradleUtils.getExecutable(project);
final String gradlePath = fs.file(gradle).absolute.path;
final String flutterRoot = fs.path.absolute(Cache.flutterRoot); final String flutterRoot = fs.path.absolute(Cache.flutterRoot);
final String initScript = fs.path.join(flutterRoot, 'packages','flutter_tools', 'gradle', 'aar_init_script.gradle'); final String initScript = fs.path.join(flutterRoot, 'packages','flutter_tools', 'gradle', 'aar_init_script.gradle');
final List<String> command = <String>[ final List<String> command = <String>[
gradlePath, gradlew,
'-I=$initScript', '-I=$initScript',
'-Pflutter-root=$flutterRoot', '-Pflutter-root=$flutterRoot',
'-Poutput-dir=${gradleProject.buildDirectory}', '-Poutput-dir=${gradleProject.buildDirectory}',
@ -601,7 +614,8 @@ Future<void> buildGradleAar({
printStatus('Built ${fs.path.relative(repoDirectory.path)}.', color: TerminalColor.green); 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'. // Run 'gradlew build'.
final Status status = logger.startProgress( final Status status = logger.startProgress(
'Running \'gradlew build\'...', 'Running \'gradlew build\'...',
@ -610,7 +624,7 @@ Future<void> _buildGradleProjectV1(FlutterProject project, String gradle) async
); );
final Stopwatch sw = Stopwatch()..start(); final Stopwatch sw = Stopwatch()..start();
final int exitCode = await runCommandAndStreamOutput( final int exitCode = await runCommandAndStreamOutput(
<String>[fs.file(gradle).absolute.path, 'build'], <String>[fs.file(gradlew).absolute.path, 'build'],
workingDirectory: project.android.hostAppGradleRoot.path, workingDirectory: project.android.hostAppGradleRoot.path,
allowReentrantFlutter: true, allowReentrantFlutter: true,
environment: _gradleEnv, environment: _gradleEnv,
@ -661,12 +675,12 @@ void printUndefinedTask(GradleProject project, BuildInfo buildInfo) {
Future<void> _buildGradleProjectV2( Future<void> _buildGradleProjectV2(
FlutterProject flutterProject, FlutterProject flutterProject,
String gradle,
AndroidBuildInfo androidBuildInfo, AndroidBuildInfo androidBuildInfo,
String target, String target,
bool isBuildingBundle, bool isBuildingBundle,
) async { ) async {
final GradleProject project = await _gradleAppProject(); final String gradlew = await gradleUtils.getExecutable(flutterProject);
final GradleProject project = await gradleUtils.appProject;
final BuildInfo buildInfo = androidBuildInfo.buildInfo; final BuildInfo buildInfo = androidBuildInfo.buildInfo;
String assembleTask; String assembleTask;
@ -685,8 +699,7 @@ Future<void> _buildGradleProjectV2(
timeout: timeoutConfiguration.slowOperation, timeout: timeoutConfiguration.slowOperation,
multilineOutput: true, multilineOutput: true,
); );
final String gradlePath = fs.file(gradle).absolute.path; final List<String> command = <String>[gradlew];
final List<String> command = <String>[gradlePath];
if (logger.isVerbose) { if (logger.isVerbose) {
command.add('-Pverbose=true'); command.add('-Pverbose=true');
} else { } else {
@ -712,6 +725,8 @@ Future<void> _buildGradleProjectV2(
command.add('-Pfilesystem-scheme=${buildInfo.fileSystemScheme}'); command.add('-Pfilesystem-scheme=${buildInfo.fileSystemScheme}');
if (androidBuildInfo.splitPerAbi) if (androidBuildInfo.splitPerAbi)
command.add('-Psplit-per-abi=true'); command.add('-Psplit-per-abi=true');
if (androidBuildInfo.proguard)
command.add('-Pproguard=true');
if (androidBuildInfo.targetArchs.isNotEmpty) { if (androidBuildInfo.targetArchs.isNotEmpty) {
final String targetPlatforms = androidBuildInfo.targetArchs final String targetPlatforms = androidBuildInfo.targetArchs
.map(getPlatformNameForAndroidArch).join(','); .map(getPlatformNameForAndroidArch).join(',');
@ -727,6 +742,7 @@ Future<void> _buildGradleProjectV2(
} }
command.add(assembleTask); command.add(assembleTask);
bool potentialAndroidXFailure = false; bool potentialAndroidXFailure = false;
bool potentialProguardFailure = false;
final Stopwatch sw = Stopwatch()..start(); final Stopwatch sw = Stopwatch()..start();
int exitCode = 1; int exitCode = 1;
try { try {
@ -743,13 +759,17 @@ Future<void> _buildGradleProjectV2(
if (!isAndroidXPluginWarning && androidXFailureRegex.hasMatch(line)) { if (!isAndroidXPluginWarning && androidXFailureRegex.hasMatch(line)) {
potentialAndroidXFailure = true; 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. // Always print the full line in verbose mode.
if (logger.isVerbose) { if (logger.isVerbose) {
return line; return line;
} else if (isAndroidXPluginWarning || !ndkMessageFilter.hasMatch(line)) { } else if (isAndroidXPluginWarning || !ndkMessageFilter.hasMatch(line)) {
return null; return null;
} }
return line; return line;
}, },
); );
@ -758,7 +778,13 @@ Future<void> _buildGradleProjectV2(
} }
if (exitCode != 0) { 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.'); printStatus('AndroidX incompatibilities may have caused this build to fail. See https://goo.gl/CP92wY.');
BuildEvent('android-x-failure').send(); BuildEvent('android-x-failure').send();
} }

View File

@ -92,6 +92,7 @@ class AndroidBuildInfo {
AndroidArch.arm64_v8a, AndroidArch.arm64_v8a,
], ],
this.splitPerAbi = false, this.splitPerAbi = false,
this.proguard = false,
}); });
// The build info containing the mode and flavor. // The build info containing the mode and flavor.
@ -104,6 +105,9 @@ class AndroidBuildInfo {
/// will be produced. /// will be produced.
final bool splitPerAbi; final bool splitPerAbi;
/// Whether to enable Proguard on release mode.
final bool proguard;
/// The target platforms for the build. /// The target platforms for the build.
final Iterable<AndroidArch> targetArchs; final Iterable<AndroidArch> targetArchs;
} }

View File

@ -25,9 +25,15 @@ class BuildApkCommand extends BuildSubCommand {
argParser argParser
..addFlag('split-per-abi', ..addFlag('split-per-abi',
negatable: false, 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', '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', ..addMultiOption('target-platform',
splitCommas: true, splitCommas: true,
defaultsTo: <String>['android-arm', 'android-arm64'], defaultsTo: <String>['android-arm', 'android-arm64'],
@ -79,7 +85,8 @@ class BuildApkCommand extends BuildSubCommand {
final BuildInfo buildInfo = getBuildInfo(); final BuildInfo buildInfo = getBuildInfo();
final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(buildInfo, final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(buildInfo,
splitPerAbi: argResults['split-per-abi'], 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) { if (buildInfo.isRelease && !androidBuildInfo.splitPerAbi && androidBuildInfo.targetArchs.length > 1) {

View File

@ -22,6 +22,12 @@ class BuildAppBundleCommand extends BuildSubCommand {
argParser argParser
..addFlag('track-widget-creation', negatable: false, hide: !verboseHelp) ..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', ..addMultiOption('target-platform',
splitCommas: true, splitCommas: true,
defaultsTo: <String>['android-arm', 'android-arm64'], defaultsTo: <String>['android-arm', 'android-arm64'],
@ -63,7 +69,8 @@ class BuildAppBundleCommand extends BuildSubCommand {
@override @override
Future<FlutterCommandResult> runCommand() async { Future<FlutterCommandResult> runCommand() async {
final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(getBuildInfo(), 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( await androidBuilder.buildAab(
project: FlutterProject.current(), project: FlutterProject.current(),

View File

@ -7,6 +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 'application_package.dart'; import 'application_package.dart';
import 'artifacts.dart'; import 'artifacts.dart';
import 'asset.dart'; import 'asset.dart';
@ -89,6 +90,7 @@ Future<T> runInContext<T>(
FuchsiaSdk: () => FuchsiaSdk(), FuchsiaSdk: () => FuchsiaSdk(),
FuchsiaWorkflow: () => FuchsiaWorkflow(), FuchsiaWorkflow: () => FuchsiaWorkflow(),
GenSnapshot: () => const GenSnapshot(), GenSnapshot: () => const GenSnapshot(),
GradleUtils: () => GradleUtils(),
HotRunnerConfig: () => HotRunnerConfig(), HotRunnerConfig: () => HotRunnerConfig(),
IMobileDevice: () => IMobileDevice(), IMobileDevice: () => IMobileDevice(),
IOSDeploy: () => const IOSDeploy(), IOSDeploy: () => const IOSDeploy(),

View File

@ -3,7 +3,6 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'dart:io' hide File; import 'dart:io' hide File;
import 'package:args/command_runner.dart'; import 'package:args/command_runner.dart';
@ -17,21 +16,7 @@ import 'package:process/process.dart';
import '../src/common.dart'; import '../src/common.dart';
import '../src/context.dart'; import '../src/context.dart';
import '../src/mocks.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;
}
void main() { void main() {
group('channel', () { group('channel', () {

View File

@ -2,15 +2,24 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:io';
import 'package:args/command_runner.dart'; import 'package:args/command_runner.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/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/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build_apk.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:flutter_tools/src/reporting/reporting.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
import '../../src/mocks.dart';
void main() { void main() {
Cache.disableLocking(); Cache.disableLocking();
@ -26,21 +35,10 @@ void main() {
tryToDelete(tempDir); 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 { testUsingContext('indicate the default target platforms', () async {
final String projectPath = await createProject(tempDir, final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=app']); arguments: <String>['--no-pub', '--template=app']);
final BuildApkCommand command = await runCommandIn(projectPath); final BuildApkCommand command = await runBuildApkCommand(projectPath);
expect(await command.usageValues, expect(await command.usageValues,
containsPair(CustomDimensions.commandBuildApkTargetPlatform, 'android-arm,android-arm64')); containsPair(CustomDimensions.commandBuildApkTargetPlatform, 'android-arm,android-arm64'));
@ -53,12 +51,12 @@ void main() {
final String projectPath = await createProject(tempDir, final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=app']); arguments: <String>['--no-pub', '--template=app']);
final BuildApkCommand commandWithFlag = await runCommandIn(projectPath, final BuildApkCommand commandWithFlag = await runBuildApkCommand(projectPath,
arguments: <String>['--split-per-abi']); arguments: <String>['--split-per-abi']);
expect(await commandWithFlag.usageValues, expect(await commandWithFlag.usageValues,
containsPair(CustomDimensions.commandBuildApkSplitPerAbi, 'true')); containsPair(CustomDimensions.commandBuildApkSplitPerAbi, 'true'));
final BuildApkCommand commandWithoutFlag = await runCommandIn(projectPath); final BuildApkCommand commandWithoutFlag = await runBuildApkCommand(projectPath);
expect(await commandWithoutFlag.usageValues, expect(await commandWithoutFlag.usageValues,
containsPair(CustomDimensions.commandBuildApkSplitPerAbi, 'false')); containsPair(CustomDimensions.commandBuildApkSplitPerAbi, 'false'));
@ -70,21 +68,21 @@ void main() {
final String projectPath = await createProject(tempDir, final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=app']); arguments: <String>['--no-pub', '--template=app']);
final BuildApkCommand commandDefault = await runCommandIn(projectPath); final BuildApkCommand commandDefault = await runBuildApkCommand(projectPath);
expect(await commandDefault.usageValues, expect(await commandDefault.usageValues,
containsPair(CustomDimensions.commandBuildApkBuildMode, 'release')); containsPair(CustomDimensions.commandBuildApkBuildMode, 'release'));
final BuildApkCommand commandInRelease = await runCommandIn(projectPath, final BuildApkCommand commandInRelease = await runBuildApkCommand(projectPath,
arguments: <String>['--release']); arguments: <String>['--release']);
expect(await commandInRelease.usageValues, expect(await commandInRelease.usageValues,
containsPair(CustomDimensions.commandBuildApkBuildMode, 'release')); containsPair(CustomDimensions.commandBuildApkBuildMode, 'release'));
final BuildApkCommand commandInDebug = await runCommandIn(projectPath, final BuildApkCommand commandInDebug = await runBuildApkCommand(projectPath,
arguments: <String>['--debug']); arguments: <String>['--debug']);
expect(await commandInDebug.usageValues, expect(await commandInDebug.usageValues,
containsPair(CustomDimensions.commandBuildApkBuildMode, 'debug')); containsPair(CustomDimensions.commandBuildApkBuildMode, 'debug'));
final BuildApkCommand commandInProfile = await runCommandIn(projectPath, final BuildApkCommand commandInProfile = await runBuildApkCommand(projectPath,
arguments: <String>['--profile']); arguments: <String>['--profile']);
expect(await commandInProfile.usageValues, expect(await commandInProfile.usageValues,
containsPair(CustomDimensions.commandBuildApkBuildMode, 'profile')); containsPair(CustomDimensions.commandBuildApkBuildMode, 'profile'));
@ -93,4 +91,201 @@ void main() {
AndroidBuilder: () => FakeAndroidBuilder(), AndroidBuilder: () => FakeAndroidBuilder(),
}, timeout: allowForCreateFlutterProject); }, 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 {}

View File

@ -2,15 +2,24 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:io';
import 'package:args/command_runner.dart'; import 'package:args/command_runner.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/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/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build_appbundle.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:flutter_tools/src/reporting/reporting.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
import '../../src/mocks.dart';
void main() { void main() {
Cache.disableLocking(); Cache.disableLocking();
@ -26,21 +35,10 @@ void main() {
tryToDelete(tempDir); 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 { testUsingContext('indicate the default target platforms', () async {
final String projectPath = await createProject(tempDir, final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=app']); arguments: <String>['--no-pub', '--template=app']);
final BuildAppBundleCommand command = await runCommandIn(projectPath); final BuildAppBundleCommand command = await runBuildAppBundleCommand(projectPath);
expect(await command.usageValues, expect(await command.usageValues,
containsPair(CustomDimensions.commandBuildAppBundleTargetPlatform, 'android-arm,android-arm64')); containsPair(CustomDimensions.commandBuildAppBundleTargetPlatform, 'android-arm,android-arm64'));
@ -53,21 +51,21 @@ void main() {
final String projectPath = await createProject(tempDir, final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=app']); arguments: <String>['--no-pub', '--template=app']);
final BuildAppBundleCommand commandDefault = await runCommandIn(projectPath); final BuildAppBundleCommand commandDefault = await runBuildAppBundleCommand(projectPath);
expect(await commandDefault.usageValues, expect(await commandDefault.usageValues,
containsPair(CustomDimensions.commandBuildAppBundleBuildMode, 'release')); containsPair(CustomDimensions.commandBuildAppBundleBuildMode, 'release'));
final BuildAppBundleCommand commandInRelease = await runCommandIn(projectPath, final BuildAppBundleCommand commandInRelease = await runBuildAppBundleCommand(projectPath,
arguments: <String>['--release']); arguments: <String>['--release']);
expect(await commandInRelease.usageValues, expect(await commandInRelease.usageValues,
containsPair(CustomDimensions.commandBuildAppBundleBuildMode, 'release')); containsPair(CustomDimensions.commandBuildAppBundleBuildMode, 'release'));
final BuildAppBundleCommand commandInDebug = await runCommandIn(projectPath, final BuildAppBundleCommand commandInDebug = await runBuildAppBundleCommand(projectPath,
arguments: <String>['--debug']); arguments: <String>['--debug']);
expect(await commandInDebug.usageValues, expect(await commandInDebug.usageValues,
containsPair(CustomDimensions.commandBuildAppBundleBuildMode, 'debug')); containsPair(CustomDimensions.commandBuildAppBundleBuildMode, 'debug'));
final BuildAppBundleCommand commandInProfile = await runCommandIn(projectPath, final BuildAppBundleCommand commandInProfile = await runBuildAppBundleCommand(projectPath,
arguments: <String>['--profile']); arguments: <String>['--profile']);
expect(await commandInProfile.usageValues, expect(await commandInProfile.usageValues,
containsPair(CustomDimensions.commandBuildAppBundleBuildMode, 'profile')); containsPair(CustomDimensions.commandBuildAppBundleBuildMode, 'profile'));
@ -76,4 +74,207 @@ void main() {
AndroidBuilder: () => FakeAndroidBuilder(), AndroidBuilder: () => FakeAndroidBuilder(),
}, timeout: allowForCreateFlutterProject); }, 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 {}

View File

@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:convert';
import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/common.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/io.dart'; import 'package:flutter_tools/src/base/io.dart';
@ -17,21 +15,7 @@ import 'package:process/process.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
import '../../src/mocks.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;
}
void main() { void main() {
group('UpgradeCommandRunner', () { group('UpgradeCommandRunner', () {

View File

@ -116,6 +116,8 @@ Future<String> createProject(Directory temp, { List<String> arguments }) async {
final CreateCommand command = CreateCommand(); final CreateCommand command = CreateCommand();
final CommandRunner<void> runner = createTestCommandRunner(command); final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['create', ...arguments, projectPath]); 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; return projectPath;
} }

View File

@ -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. /// A process that exits successfully with no output and ignores all input.
class MockProcess extends Mock implements Process { class MockProcess extends Mock implements Process {
MockProcess({ MockProcess({