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;
Future<void> main() async {
await task(() async {
try {
bool foundProjectName = false;
bool foundApkProjectName = false;
await runProjectTest((FlutterProject flutterProject) async {
section('APK content for task assembleRelease with --obfuscate');
await inDirectory(flutterProject.rootPath, () async {
......@@ -21,13 +21,14 @@ Future<void> main() async {
'--target-platform=android-arm',
'--obfuscate',
'--split-debug-info=foo/',
'--verbose',
]);
});
final String outputDirectory = path.join(
final String outputApkDirectory = path.join(
flutterProject.rootPath,
'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>[
...flutterAssets,
......@@ -38,20 +39,69 @@ Future<void> main() async {
// 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>[outputDirectory]);
await exec('unzip', <String>[outputApkDirectory]);
checkFileExists(path.join(flutterProject.rootPath, 'lib/armeabi-v7a/libapp.so'));
final String response = await eval(
'grep',
<String>[flutterProject.name, 'lib/armeabi-v7a/libapp.so'],
canFail: true,
);
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);
} on TaskResult catch (taskResult) {
return taskResult;
......
......@@ -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.
Future<Iterable<String>> getFilesInApk(String apk) async {
if (!File(apk).existsSync()) {
......@@ -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 {
final ProcessResult result = await _resultOfGradleTask(
workingDirectory: workingDirectory,
......
......@@ -525,7 +525,8 @@ Future<void> buildGradleAar({
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(
"Running Gradle task '$aarTask'...",
timeout: timeoutConfiguration.slowOperation,
......@@ -548,10 +549,28 @@ Future<void> buildGradleAar({
'-Pis-plugin=${manifest.isPlugin}',
'-PbuildNumber=$buildNumber'
];
if (globals.logger.isVerbose) {
command.add('-Pverbose=true');
} else {
command.add('-q');
}
if (target != null && target.isNotEmpty) {
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) {
final LocalEngineArtifacts localEngineArtifacts = globals.artifacts as LocalEngineArtifacts;
......@@ -564,11 +583,8 @@ Future<void> buildGradleAar({
'Local Maven 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}');
if (androidBuildInfo.buildInfo.treeShakeIcons) {
command.add('-Pfont-subset=true');
}
// Copy the local engine repo in the output directory.
try {
......
......@@ -38,6 +38,8 @@ class BuildAarCommand extends BuildSubCommand {
usesFlavorOption();
usesBuildNumberOption();
usesPubOption();
addSplitDebugInfoOption();
addDartObfuscationOption();
argParser
..addMultiOption(
'target-platform',
......@@ -104,15 +106,18 @@ class BuildAarCommand extends BuildSubCommand {
for (final String buildMode in const <String>['debug', 'profile', 'release']) {
if (boolArg(buildMode)) {
androidBuildInfo.add(AndroidBuildInfo(
BuildInfo(BuildMode.fromName(buildMode), stringArg('flavor'), treeShakeIcons: boolArg('tree-shake-icons')),
targetArchs: targetArchitectures,
));
androidBuildInfo.add(
AndroidBuildInfo(
getBuildInfo(forcedBuildMode: BuildMode.fromName(buildMode)),
targetArchs: targetArchitectures,
)
);
}
}
if (androidBuildInfo.isEmpty) {
throwToolExit('Please specify a build mode and try again.');
}
await androidBuilder.buildAar(
project: _getProject(),
target: '', // Not needed because this command only builds Android's code.
......
......@@ -558,10 +558,12 @@ abstract class FlutterCommand extends Command<void> {
}
/// 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
/// eachother.
BuildInfo getBuildInfo() {
/// each other.
BuildInfo getBuildInfo({ BuildMode forcedBuildMode }) {
final bool trackWidgetCreation = argParser.options.containsKey('track-widget-creation') &&
boolArg('track-widget-creation');
......@@ -603,7 +605,7 @@ abstract class FlutterCommand extends Command<void> {
'combination with "--${FlutterOptions.kSplitDebugInfoOption}"',
);
}
final BuildMode buildMode = getBuildMode();
final BuildMode buildMode = forcedBuildMode ?? getBuildMode();
final bool treeShakeIcons = argParser.options.containsKey('tree-shake-icons')
&& buildMode.isPrecompiled
&& boolArg('tree-shake-icons');
......
......@@ -761,19 +761,12 @@ flutter:
group('buildPluginsAsAar', () {
FileSystem fs;
MockProcessManager mockProcessManager;
FakeProcessManager fakeProcessManager;
MockAndroidSdk mockAndroidSdk;
setUp(() {
fs = MemoryFileSystem();
mockProcessManager = MockProcessManager();
when(mockProcessManager.run(
any,
workingDirectory: anyNamed('workingDirectory'),
environment: anyNamed('environment'),
)).thenAnswer((_) async => ProcessResult(1, 0, '', ''));
fakeProcessManager = FakeProcessManager.list(<FakeCommand>[]);
mockAndroidSdk = MockAndroidSdk();
when(mockAndroidSdk.directory).thenReturn('irrelevant');
});
......@@ -830,12 +823,6 @@ plugin2=${plugin2.path}
.childDirectory('repo')
.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 initScript = globals.fs.path.join(
flutterRoot,
......@@ -844,40 +831,55 @@ plugin2=${plugin2.path}
'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(
<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: plugin2.childDirectory('android').path),
).called(1);
fakeProcessManager
..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: 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>{
AndroidSdk: () => mockAndroidSdk,
FileSystem: () => fs,
ProcessManager: () => mockProcessManager,
ProcessManager: () => fakeProcessManager,
GradleUtils: () => FakeGradleUtils(),
});
......@@ -920,32 +922,11 @@ plugin1=${plugin1.path}
const AndroidBuildInfo(BuildInfo.release),
buildDirectory: buildDirectory,
);
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),
);
expect(fakeProcessManager.hasRemainingExpectations, isFalse);
}, overrides: <Type, Generator>{
AndroidSdk: () => mockAndroidSdk,
FileSystem: () => fs,
ProcessManager: () => mockProcessManager,
ProcessManager: () => fakeProcessManager,
GradleUtils: () => FakeGradleUtils(),
});
});
......
......@@ -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_sdk.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/commands/build_aar.dart';
import 'package:flutter_tools/src/project.dart';
......@@ -24,6 +25,18 @@ import '../../src/mocks.dart';
void main() {
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', () {
Directory tempDir;
Usage mockUsage;
......@@ -37,18 +50,6 @@ void main() {
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 {
final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=module']);
......@@ -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', () {
ProcessManager mockProcessManager;
Directory tempDir;
......@@ -199,6 +287,7 @@ Future<BuildAarCommand> runBuildAarCommand(
return command;
}
class MockAndroidBuilder extends Mock implements AndroidBuilder {}
class MockAndroidSdk extends Mock implements AndroidSdk {}
class MockProcessManager extends Mock implements ProcessManager {}
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