Unverified Commit 0a4f6cde authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Add split-debug and obfuscation to build aar (#56342)

parent eadd30eb
...@@ -12,7 +12,7 @@ import 'package:path/path.dart' as path; ...@@ -12,7 +12,7 @@ import 'package:path/path.dart' as path;
Future<void> main() async { Future<void> main() async {
await task(() async { await task(() async {
try { try {
bool foundProjectName = false; bool foundApkProjectName = false;
await runProjectTest((FlutterProject flutterProject) async { await runProjectTest((FlutterProject flutterProject) async {
section('APK content for task assembleRelease with --obfuscate'); section('APK content for task assembleRelease with --obfuscate');
await inDirectory(flutterProject.rootPath, () async { await inDirectory(flutterProject.rootPath, () async {
...@@ -21,13 +21,14 @@ Future<void> main() async { ...@@ -21,13 +21,14 @@ Future<void> main() async {
'--target-platform=android-arm', '--target-platform=android-arm',
'--obfuscate', '--obfuscate',
'--split-debug-info=foo/', '--split-debug-info=foo/',
'--verbose',
]); ]);
}); });
final String outputDirectory = path.join( final String outputApkDirectory = path.join(
flutterProject.rootPath, flutterProject.rootPath,
'build/app/outputs/apk/release/app-release.apk', 'build/app/outputs/apk/release/app-release.apk',
); );
final Iterable<String> apkFiles = await getFilesInApk(outputDirectory); final Iterable<String> apkFiles = await getFilesInApk(outputApkDirectory);
checkCollectionContains<String>(<String>[ checkCollectionContains<String>(<String>[
...flutterAssets, ...flutterAssets,
...@@ -38,20 +39,69 @@ Future<void> main() async { ...@@ -38,20 +39,69 @@ Future<void> main() async {
// Verify that an identifier from the Dart project code is not present // Verify that an identifier from the Dart project code is not present
// in the compiled binary. // in the compiled binary.
await inDirectory(flutterProject.rootPath, () async { await inDirectory(flutterProject.rootPath, () async {
await exec('unzip', <String>[outputDirectory]); await exec('unzip', <String>[outputApkDirectory]);
checkFileExists(path.join(flutterProject.rootPath, 'lib/armeabi-v7a/libapp.so'));
final String response = await eval( final String response = await eval(
'grep', 'grep',
<String>[flutterProject.name, 'lib/armeabi-v7a/libapp.so'], <String>[flutterProject.name, 'lib/armeabi-v7a/libapp.so'],
canFail: true, canFail: true,
); );
if (response.trim().contains('matches')) { if (response.trim().contains('matches')) {
foundProjectName = true; foundApkProjectName = true;
} }
}); });
}); });
if (foundProjectName) {
return TaskResult.failure('Found project name in obfuscated dart library'); bool foundAarProjectName = false;
await runModuleProjectTest((FlutterModuleProject flutterProject) async {
section('AAR content with --obfuscate');
await inDirectory(flutterProject.rootPath, () async {
await flutter('build', options: <String>[
'aar',
'--target-platform=android-arm',
'--obfuscate',
'--split-debug-info=foo/',
'--no-debug',
'--no-profile',
'--verbose',
]);
});
final String outputAarDirectory = path.join(
flutterProject.rootPath,
'build/host/outputs/repo/com/example/${flutterProject.name}/flutter_release/1.0/flutter_release-1.0.aar',
);
final Iterable<String> aarFiles = await getFilesInAar(outputAarDirectory);
checkCollectionContains<String>(<String>[
...flutterAssets,
'jni/armeabi-v7a/libapp.so',
], aarFiles);
// Verify that an identifier from the Dart project code is not present
// in the compiled binary.
await inDirectory(flutterProject.rootPath, () async {
await exec('unzip', <String>[outputAarDirectory]);
checkFileExists(path.join(flutterProject.rootPath, 'jni/armeabi-v7a/libapp.so'));
final String response = await eval(
'grep',
<String>[flutterProject.name, 'jni/armeabi-v7a/libapp.so'],
canFail: true,
);
if (response.trim().contains('matches')) {
foundAarProjectName = true;
}
});
});
if (foundApkProjectName) {
return TaskResult.failure('Found project name in obfuscated APK dart library');
}
if (foundAarProjectName) {
return TaskResult.failure('Found project name in obfuscated AAR dart library');
} }
return TaskResult.success(null); return TaskResult.success(null);
} on TaskResult catch (taskResult) { } on TaskResult catch (taskResult) {
return taskResult; return taskResult;
......
...@@ -51,6 +51,18 @@ Future<void> runPluginProjectTest(Future<void> testFunction(FlutterPluginProject ...@@ -51,6 +51,18 @@ Future<void> runPluginProjectTest(Future<void> testFunction(FlutterPluginProject
} }
} }
/// Runs the given [testFunction] on a freshly generated Flutter module project.
Future<void> runModuleProjectTest(Future<void> testFunction(FlutterModuleProject moduleProject)) async {
final Directory tempDir = Directory.systemTemp.createTempSync('flutter_devicelab_gradle_module_test.');
final FlutterModuleProject moduleProject = await FlutterModuleProject.create(tempDir, 'hello_module');
try {
await testFunction(moduleProject);
} finally {
rmTree(tempDir);
}
}
/// Returns the list of files inside an Android Package Kit. /// Returns the list of files inside an Android Package Kit.
Future<Iterable<String>> getFilesInApk(String apk) async { Future<Iterable<String>> getFilesInApk(String apk) async {
if (!File(apk).existsSync()) { if (!File(apk).existsSync()) {
...@@ -357,6 +369,22 @@ class FlutterPluginProject { ...@@ -357,6 +369,22 @@ class FlutterPluginProject {
} }
} }
class FlutterModuleProject {
FlutterModuleProject(this.parent, this.name);
final Directory parent;
final String name;
static Future<FlutterModuleProject> create(Directory directory, String name) async {
await inDirectory(directory, () async {
await flutter('create', options: <String>['--template=module', name]);
});
return FlutterModuleProject(directory, name);
}
String get rootPath => path.join(parent.path, name);
}
Future<void> _runGradleTask({String workingDirectory, String task, List<String> options}) async { Future<void> _runGradleTask({String workingDirectory, String task, List<String> options}) async {
final ProcessResult result = await _resultOfGradleTask( final ProcessResult result = await _resultOfGradleTask(
workingDirectory: workingDirectory, workingDirectory: workingDirectory,
......
...@@ -525,7 +525,8 @@ Future<void> buildGradleAar({ ...@@ -525,7 +525,8 @@ Future<void> buildGradleAar({
throwToolExit('AARs can only be built for plugin or module projects.'); throwToolExit('AARs can only be built for plugin or module projects.');
} }
final String aarTask = getAarTaskFor(androidBuildInfo.buildInfo); final BuildInfo buildInfo = androidBuildInfo.buildInfo;
final String aarTask = getAarTaskFor(buildInfo);
final Status status = globals.logger.startProgress( final Status status = globals.logger.startProgress(
"Running Gradle task '$aarTask'...", "Running Gradle task '$aarTask'...",
timeout: timeoutConfiguration.slowOperation, timeout: timeoutConfiguration.slowOperation,
...@@ -548,10 +549,28 @@ Future<void> buildGradleAar({ ...@@ -548,10 +549,28 @@ Future<void> buildGradleAar({
'-Pis-plugin=${manifest.isPlugin}', '-Pis-plugin=${manifest.isPlugin}',
'-PbuildNumber=$buildNumber' '-PbuildNumber=$buildNumber'
]; ];
if (globals.logger.isVerbose) {
command.add('-Pverbose=true');
} else {
command.add('-q');
}
if (target != null && target.isNotEmpty) { if (target != null && target.isNotEmpty) {
command.add('-Ptarget=$target'); command.add('-Ptarget=$target');
} }
if (buildInfo.splitDebugInfoPath != null) {
command.add('-Psplit-debug-info=${buildInfo.splitDebugInfoPath}');
}
if (buildInfo.treeShakeIcons) {
command.add('-Pfont-subset=true');
}
if (buildInfo.dartObfuscation) {
if (buildInfo.mode == BuildMode.debug || buildInfo.mode == BuildMode.profile) {
globals.printStatus('Dart obfuscation is not supported in ${toTitleCase(buildInfo.friendlyModeName)} mode, building as unobfuscated.');
} else {
command.add('-Pdart-obfuscation=true');
}
}
if (globals.artifacts is LocalEngineArtifacts) { if (globals.artifacts is LocalEngineArtifacts) {
final LocalEngineArtifacts localEngineArtifacts = globals.artifacts as LocalEngineArtifacts; final LocalEngineArtifacts localEngineArtifacts = globals.artifacts as LocalEngineArtifacts;
...@@ -564,11 +583,8 @@ Future<void> buildGradleAar({ ...@@ -564,11 +583,8 @@ Future<void> buildGradleAar({
'Local Maven repo: ${localEngineRepo.path}' 'Local Maven repo: ${localEngineRepo.path}'
); );
command.add('-Plocal-engine-repo=${localEngineRepo.path}'); command.add('-Plocal-engine-repo=${localEngineRepo.path}');
command.add('-Plocal-engine-build-mode=${androidBuildInfo.buildInfo.modeName}'); command.add('-Plocal-engine-build-mode=${buildInfo.modeName}');
command.add('-Plocal-engine-out=${localEngineArtifacts.engineOutPath}'); command.add('-Plocal-engine-out=${localEngineArtifacts.engineOutPath}');
if (androidBuildInfo.buildInfo.treeShakeIcons) {
command.add('-Pfont-subset=true');
}
// Copy the local engine repo in the output directory. // Copy the local engine repo in the output directory.
try { try {
......
...@@ -38,6 +38,8 @@ class BuildAarCommand extends BuildSubCommand { ...@@ -38,6 +38,8 @@ class BuildAarCommand extends BuildSubCommand {
usesFlavorOption(); usesFlavorOption();
usesBuildNumberOption(); usesBuildNumberOption();
usesPubOption(); usesPubOption();
addSplitDebugInfoOption();
addDartObfuscationOption();
argParser argParser
..addMultiOption( ..addMultiOption(
'target-platform', 'target-platform',
...@@ -104,15 +106,18 @@ class BuildAarCommand extends BuildSubCommand { ...@@ -104,15 +106,18 @@ class BuildAarCommand extends BuildSubCommand {
for (final String buildMode in const <String>['debug', 'profile', 'release']) { for (final String buildMode in const <String>['debug', 'profile', 'release']) {
if (boolArg(buildMode)) { if (boolArg(buildMode)) {
androidBuildInfo.add(AndroidBuildInfo( androidBuildInfo.add(
BuildInfo(BuildMode.fromName(buildMode), stringArg('flavor'), treeShakeIcons: boolArg('tree-shake-icons')), AndroidBuildInfo(
targetArchs: targetArchitectures, getBuildInfo(forcedBuildMode: BuildMode.fromName(buildMode)),
)); targetArchs: targetArchitectures,
)
);
} }
} }
if (androidBuildInfo.isEmpty) { if (androidBuildInfo.isEmpty) {
throwToolExit('Please specify a build mode and try again.'); throwToolExit('Please specify a build mode and try again.');
} }
await androidBuilder.buildAar( await androidBuilder.buildAar(
project: _getProject(), project: _getProject(),
target: '', // Not needed because this command only builds Android's code. target: '', // Not needed because this command only builds Android's code.
......
...@@ -558,10 +558,12 @@ abstract class FlutterCommand extends Command<void> { ...@@ -558,10 +558,12 @@ abstract class FlutterCommand extends Command<void> {
} }
/// Compute the [BuildInfo] for the current flutter command. /// Compute the [BuildInfo] for the current flutter command.
/// Commands that build multiple build modes can pass in a [forcedBuildMode]
/// to be used instead of parsing flags.
/// ///
/// Throws a [ToolExit] if the current set of options is not compatible with /// Throws a [ToolExit] if the current set of options is not compatible with
/// eachother. /// each other.
BuildInfo getBuildInfo() { BuildInfo getBuildInfo({ BuildMode forcedBuildMode }) {
final bool trackWidgetCreation = argParser.options.containsKey('track-widget-creation') && final bool trackWidgetCreation = argParser.options.containsKey('track-widget-creation') &&
boolArg('track-widget-creation'); boolArg('track-widget-creation');
...@@ -603,7 +605,7 @@ abstract class FlutterCommand extends Command<void> { ...@@ -603,7 +605,7 @@ abstract class FlutterCommand extends Command<void> {
'combination with "--${FlutterOptions.kSplitDebugInfoOption}"', 'combination with "--${FlutterOptions.kSplitDebugInfoOption}"',
); );
} }
final BuildMode buildMode = getBuildMode(); final BuildMode buildMode = forcedBuildMode ?? getBuildMode();
final bool treeShakeIcons = argParser.options.containsKey('tree-shake-icons') final bool treeShakeIcons = argParser.options.containsKey('tree-shake-icons')
&& buildMode.isPrecompiled && buildMode.isPrecompiled
&& boolArg('tree-shake-icons'); && boolArg('tree-shake-icons');
......
...@@ -761,19 +761,12 @@ flutter: ...@@ -761,19 +761,12 @@ flutter:
group('buildPluginsAsAar', () { group('buildPluginsAsAar', () {
FileSystem fs; FileSystem fs;
MockProcessManager mockProcessManager; FakeProcessManager fakeProcessManager;
MockAndroidSdk mockAndroidSdk; MockAndroidSdk mockAndroidSdk;
setUp(() { setUp(() {
fs = MemoryFileSystem(); fs = MemoryFileSystem();
fakeProcessManager = FakeProcessManager.list(<FakeCommand>[]);
mockProcessManager = MockProcessManager();
when(mockProcessManager.run(
any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
mockAndroidSdk = MockAndroidSdk(); mockAndroidSdk = MockAndroidSdk();
when(mockAndroidSdk.directory).thenReturn('irrelevant'); when(mockAndroidSdk.directory).thenReturn('irrelevant');
}); });
...@@ -830,12 +823,6 @@ plugin2=${plugin2.path} ...@@ -830,12 +823,6 @@ plugin2=${plugin2.path}
.childDirectory('repo') .childDirectory('repo')
.createSync(recursive: true); .createSync(recursive: true);
await buildPluginsAsAar(
FlutterProject.fromPath(androidDirectory.path),
const AndroidBuildInfo(BuildInfo.release),
buildDirectory: buildDirectory,
);
final String flutterRoot = globals.fs.path.absolute(Cache.flutterRoot); final String flutterRoot = globals.fs.path.absolute(Cache.flutterRoot);
final String initScript = globals.fs.path.join( final String initScript = globals.fs.path.join(
flutterRoot, flutterRoot,
...@@ -844,40 +831,55 @@ plugin2=${plugin2.path} ...@@ -844,40 +831,55 @@ plugin2=${plugin2.path}
'gradle', 'gradle',
'aar_init_script.gradle', 'aar_init_script.gradle',
); );
verify(mockProcessManager.run(
<String>[
'gradlew',
'-I=$initScript',
'-Pflutter-root=$flutterRoot',
'-Poutput-dir=${buildDirectory.path}',
'-Pis-plugin=true',
'-PbuildNumber=1.0',
'-Ptarget-platform=android-arm,android-arm64,android-x64',
'assembleAarRelease',
],
environment: anyNamed('environment'),
workingDirectory: plugin1.childDirectory('android').path),
).called(1);
verify(mockProcessManager.run( fakeProcessManager
<String>[ ..addCommand(FakeCommand(
'gradlew', command: <String>[
'-I=$initScript', 'gradlew',
'-Pflutter-root=$flutterRoot', '-I=$initScript',
'-Poutput-dir=${buildDirectory.path}', '-Pflutter-root=$flutterRoot',
'-Pis-plugin=true', '-Poutput-dir=${buildDirectory.path}',
'-PbuildNumber=1.0', '-Pis-plugin=true',
'-Ptarget-platform=android-arm,android-arm64,android-x64', '-PbuildNumber=1.0',
'assembleAarRelease', '-q',
], '-Pfont-subset=true',
environment: anyNamed('environment'), '-Ptarget-platform=android-arm,android-arm64,android-x64',
workingDirectory: plugin2.childDirectory('android').path), 'assembleAarRelease',
).called(1); ],
workingDirectory: plugin1.childDirectory('android').path,
))
..addCommand(FakeCommand(
command: <String>[
'gradlew',
'-I=$initScript',
'-Pflutter-root=$flutterRoot',
'-Poutput-dir=${buildDirectory.path}',
'-Pis-plugin=true',
'-PbuildNumber=1.0',
'-q',
'-Pfont-subset=true',
'-Ptarget-platform=android-arm,android-arm64,android-x64',
'assembleAarRelease',
],
workingDirectory: plugin2.childDirectory('android').path,
));
await buildPluginsAsAar(
FlutterProject.fromPath(androidDirectory.path),
const AndroidBuildInfo(BuildInfo(
BuildMode.release,
'',
treeShakeIcons: true,
dartObfuscation: true,
buildNumber: '2.0'
)),
buildDirectory: buildDirectory,
);
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk, AndroidSdk: () => mockAndroidSdk,
FileSystem: () => fs, FileSystem: () => fs,
ProcessManager: () => mockProcessManager, ProcessManager: () => fakeProcessManager,
GradleUtils: () => FakeGradleUtils(), GradleUtils: () => FakeGradleUtils(),
}); });
...@@ -920,32 +922,11 @@ plugin1=${plugin1.path} ...@@ -920,32 +922,11 @@ plugin1=${plugin1.path}
const AndroidBuildInfo(BuildInfo.release), const AndroidBuildInfo(BuildInfo.release),
buildDirectory: buildDirectory, buildDirectory: buildDirectory,
); );
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
final String flutterRoot = globals.fs.path.absolute(Cache.flutterRoot);
final String initScript = globals.fs.path.join(
flutterRoot,
'packages',
'flutter_tools',
'gradle',
'aar_init_script.gradle',
);
verifyNever(mockProcessManager.run(
<String>[
'gradlew',
'-I=$initScript',
'-Pflutter-root=$flutterRoot',
'-Poutput-dir=${buildDirectory.path}',
'-Pis-plugin=true',
'-Ptarget-platform=android-arm,android-arm64,android-x64',
'assembleAarRelease',
],
environment: anyNamed('environment'),
workingDirectory: plugin1.childDirectory('android').path),
);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk, AndroidSdk: () => mockAndroidSdk,
FileSystem: () => fs, FileSystem: () => fs,
ProcessManager: () => mockProcessManager, ProcessManager: () => fakeProcessManager,
GradleUtils: () => FakeGradleUtils(), GradleUtils: () => FakeGradleUtils(),
}); });
}); });
......
...@@ -8,6 +8,7 @@ import 'package:args/command_runner.dart'; ...@@ -8,6 +8,7 @@ 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/android_sdk.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build_aar.dart'; import 'package:flutter_tools/src/commands/build_aar.dart';
import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/project.dart';
...@@ -24,6 +25,18 @@ import '../../src/mocks.dart'; ...@@ -24,6 +25,18 @@ import '../../src/mocks.dart';
void main() { void main() {
Cache.disableLocking(); Cache.disableLocking();
Future<BuildAarCommand> runCommandIn(String target, { List<String> arguments }) async {
final BuildAarCommand command = BuildAarCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>[
'aar',
'--no-pub',
...?arguments,
target,
]);
return command;
}
group('Usage', () { group('Usage', () {
Directory tempDir; Directory tempDir;
Usage mockUsage; Usage mockUsage;
...@@ -37,18 +50,6 @@ void main() { ...@@ -37,18 +50,6 @@ void main() {
tryToDelete(tempDir); tryToDelete(tempDir);
}); });
Future<BuildAarCommand> runCommandIn(String target, { List<String> arguments }) async {
final BuildAarCommand command = BuildAarCommand();
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>[
'aar',
'--no-pub',
...?arguments,
target,
]);
return command;
}
testUsingContext('indicate that project is a module', () async { testUsingContext('indicate that project is a module', () async {
final String projectPath = await createProject(tempDir, final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=module']); arguments: <String>['--no-pub', '--template=module']);
...@@ -107,6 +108,93 @@ void main() { ...@@ -107,6 +108,93 @@ void main() {
}); });
}); });
group('flag parsing', () {
Directory tempDir;
MockAndroidBuilder mockAndroidBuilder;
setUp(() {
mockAndroidBuilder = MockAndroidBuilder();
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_build_aar_test.');
});
tearDown(() {
tryToDelete(tempDir);
});
testUsingContext('defaults', () async {
final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub']);
await runCommandIn(projectPath);
final Set<AndroidBuildInfo> androidBuildInfos = verify(mockAndroidBuilder.buildAar(
project: anyNamed('project'),
target: anyNamed('target'),
androidBuildInfo: captureAnyNamed('androidBuildInfo'),
outputDirectoryPath: anyNamed('outputDirectoryPath'),
buildNumber: '1.0',
)).captured[0] as Set<AndroidBuildInfo>;
expect(androidBuildInfos.length, 3);
final List<BuildMode> buildModes = <BuildMode>[];
for (final AndroidBuildInfo androidBuildInfo in androidBuildInfos) {
final BuildInfo buildInfo = androidBuildInfo.buildInfo;
buildModes.add(buildInfo.mode);
expect(buildInfo.treeShakeIcons, isFalse);
expect(buildInfo.flavor, isNull);
expect(buildInfo.splitDebugInfoPath, isNull);
expect(buildInfo.dartObfuscation, isFalse);
expect(androidBuildInfo.targetArchs, <AndroidArch>[AndroidArch.armeabi_v7a, AndroidArch.arm64_v8a, AndroidArch.x86_64]);
}
expect(buildModes.length, 3);
expect(buildModes, containsAll(<BuildMode>[BuildMode.debug, BuildMode.profile, BuildMode.release]));
}, overrides: <Type, Generator>{
AndroidBuilder: () => mockAndroidBuilder,
});
testUsingContext('parses flags', () async {
final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub']);
await runCommandIn(
projectPath,
arguments: <String>[
'--no-debug',
'--no-profile',
'--target-platform',
'android-x86',
'--tree-shake-icons',
'--flavor',
'free',
'--build-number',
'200',
'--split-debug-info',
'/project-name/v1.2.3/',
'--obfuscate',
],
);
final Set<AndroidBuildInfo> androidBuildInfos = verify(mockAndroidBuilder.buildAar(
project: anyNamed('project'),
target: anyNamed('target'),
androidBuildInfo: captureAnyNamed('androidBuildInfo'),
outputDirectoryPath: anyNamed('outputDirectoryPath'),
buildNumber: '200',
)).captured[0] as Set<AndroidBuildInfo>;
final AndroidBuildInfo androidBuildInfo = androidBuildInfos.single;
expect(androidBuildInfo.targetArchs, <AndroidArch>[AndroidArch.x86]);
final BuildInfo buildInfo = androidBuildInfo.buildInfo;
expect(buildInfo.mode, BuildMode.release);
expect(buildInfo.treeShakeIcons, isTrue);
expect(buildInfo.flavor, 'free');
expect(buildInfo.splitDebugInfoPath, '/project-name/v1.2.3/');
expect(buildInfo.dartObfuscation, isTrue);
}, overrides: <Type, Generator>{
AndroidBuilder: () => mockAndroidBuilder,
});
});
group('Gradle', () { group('Gradle', () {
ProcessManager mockProcessManager; ProcessManager mockProcessManager;
Directory tempDir; Directory tempDir;
...@@ -199,6 +287,7 @@ Future<BuildAarCommand> runBuildAarCommand( ...@@ -199,6 +287,7 @@ Future<BuildAarCommand> runBuildAarCommand(
return command; return command;
} }
class MockAndroidBuilder extends Mock implements AndroidBuilder {}
class MockAndroidSdk extends Mock implements AndroidSdk {} class MockAndroidSdk extends Mock implements AndroidSdk {}
class MockProcessManager extends Mock implements ProcessManager {} class MockProcessManager extends Mock implements ProcessManager {}
class MockProcess extends Mock implements Process {} class MockProcess extends Mock implements Process {}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment