From 30493a35b6c33ae4fa875bdae7adb8abfe37747d Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 21 Oct 2019 16:42:46 -0700 Subject: [PATCH] Test Gradle on Windows (#42709) --- dev/bots/test.dart | 5 - .../bin/tasks/build_aar_module_test.dart | 7 +- .../bin/tasks/build_aar_plugin_test.dart | 2 +- .../bin/tasks/gradle_jetifier_test.dart | 8 +- .../tasks/gradle_migrate_settings_test.dart | 2 +- .../bin/tasks/gradle_plugin_bundle_test.dart | 31 +-- .../gradle_plugin_dependencies_test.dart | 2 +- .../bin/tasks/gradle_plugin_fat_apk_test.dart | 44 ++--- .../tasks/gradle_plugin_light_apk_test.dart | 31 ++- ...adle_plugins_without_annotations_test.dart | 8 +- .../module_host_with_custom_build_test.dart | 18 +- dev/devicelab/bin/tasks/module_test.dart | 11 +- dev/devicelab/lib/framework/apk_utils.dart | 178 +++++++++--------- dev/devicelab/lib/framework/utils.dart | 7 +- 14 files changed, 164 insertions(+), 190 deletions(-) diff --git a/dev/bots/test.dart b/dev/bots/test.dart index fea54fb5c2a..542eed74aca 100644 --- a/dev/bots/test.dart +++ b/dev/bots/test.dart @@ -722,11 +722,6 @@ Map _initGradleEnvironment() { final Map gradleEnvironment = _initGradleEnvironment(); Future _runHostOnlyDeviceLabTests() async { - if (Platform.isWindows) { - // TODO(ianh): remove when https://github.com/flutter/flutter/issues/36311 fixed by https://github.com/flutter/flutter/pull/42709 - return; - } - // Please don't add more tests here. We should not be using the devicelab // logic to run tests outside devicelab, that's just confusing. // Instead, create tests that are not devicelab tests, and run those. diff --git a/dev/devicelab/bin/tasks/build_aar_module_test.dart b/dev/devicelab/bin/tasks/build_aar_module_test.dart index 91043626d65..64a42eed353 100644 --- a/dev/devicelab/bin/tasks/build_aar_module_test.dart +++ b/dev/devicelab/bin/tasks/build_aar_module_test.dart @@ -11,7 +11,7 @@ import 'package:flutter_devicelab/framework/utils.dart'; import 'package:path/path.dart' as path; final String gradlew = Platform.isWindows ? 'gradlew.bat' : 'gradlew'; -final String gradlewExecutable = Platform.isWindows ? gradlew : './$gradlew'; +final String gradlewExecutable = Platform.isWindows ? '.\\$gradlew' : './$gradlew'; /// Tests that AARs can be built on module projects. Future main() async { @@ -182,10 +182,7 @@ Future main() async { checkItContains([ ...flutterAssets, - // JIT snapshots. - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', + ...debugAssets, ], debugAar); return TaskResult.success(null); diff --git a/dev/devicelab/bin/tasks/build_aar_plugin_test.dart b/dev/devicelab/bin/tasks/build_aar_plugin_test.dart index 70738106ded..1dedf8a4d93 100644 --- a/dev/devicelab/bin/tasks/build_aar_plugin_test.dart +++ b/dev/devicelab/bin/tasks/build_aar_plugin_test.dart @@ -10,7 +10,7 @@ import 'package:flutter_devicelab/framework/utils.dart'; import 'package:path/path.dart' as path; final String gradlew = Platform.isWindows ? 'gradlew.bat' : 'gradlew'; -final String gradlewExecutable = Platform.isWindows ? gradlew : './$gradlew'; +final String gradlewExecutable = Platform.isWindows ? '.\\$gradlew' : './$gradlew'; /// Tests that AARs can be built on plugin projects. Future main() async { diff --git a/dev/devicelab/bin/tasks/gradle_jetifier_test.dart b/dev/devicelab/bin/tasks/gradle_jetifier_test.dart index 86ee0ed5b78..28048b5b8ed 100644 --- a/dev/devicelab/bin/tasks/gradle_jetifier_test.dart +++ b/dev/devicelab/bin/tasks/gradle_jetifier_test.dart @@ -11,7 +11,7 @@ import 'package:flutter_devicelab/framework/utils.dart'; import 'package:path/path.dart' as path; final String gradlew = Platform.isWindows ? 'gradlew.bat' : 'gradlew'; -final String gradlewExecutable = Platform.isWindows ? gradlew : './$gradlew'; +final String gradlewExecutable = Platform.isWindows ? '.\\$gradlew' : './$gradlew'; /// Tests that Jetifier can translate plugins that use support libraries. Future main() async { @@ -56,6 +56,12 @@ Future main() async { ); }); + section('Update proguard rules'); + + // Don't obfuscate the input class files, since the test is checking if some classes are in the DEX. + final File proguardRules = File(path.join(projectDir.path, 'android', 'app', 'proguard-rules.pro')); + proguardRules.writeAsStringSync('-dontobfuscate', flush: true); + section('Build release APK'); await inDirectory(projectDir, () async { diff --git a/dev/devicelab/bin/tasks/gradle_migrate_settings_test.dart b/dev/devicelab/bin/tasks/gradle_migrate_settings_test.dart index 72a1708ad2f..49873c13ae7 100644 --- a/dev/devicelab/bin/tasks/gradle_migrate_settings_test.dart +++ b/dev/devicelab/bin/tasks/gradle_migrate_settings_test.dart @@ -10,7 +10,7 @@ import 'package:flutter_devicelab/framework/utils.dart'; import 'package:path/path.dart' as path; final String gradlew = Platform.isWindows ? 'gradlew.bat' : 'gradlew'; -final String gradlewExecutable = Platform.isWindows ? gradlew : './$gradlew'; +final String gradlewExecutable = Platform.isWindows ? '.\\$gradlew' : './$gradlew'; /// Tests that [settings_aar.gradle] is created when possible. Future main() async { diff --git a/dev/devicelab/bin/tasks/gradle_plugin_bundle_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_bundle_test.dart index 204743e6770..9d4de903b71 100644 --- a/dev/devicelab/bin/tasks/gradle_plugin_bundle_test.dart +++ b/dev/devicelab/bin/tasks/gradle_plugin_bundle_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:io'; import 'package:flutter_devicelab/framework/apk_utils.dart'; import 'package:flutter_devicelab/framework/framework.dart'; @@ -10,6 +11,11 @@ import 'package:flutter_devicelab/framework/utils.dart'; import 'package:path/path.dart' as path; Future main() async { + final Iterable baseAabFiles = [ + 'base/dex/classes.dex', + 'base/manifest/AndroidManifest.xml', + ]; + final Iterable flutterAabAssets = flutterAssets.map((String file) => 'base/$file'); await task(() async { try { await runProjectTest((FlutterProject project) async { @@ -26,8 +32,8 @@ Future main() async { 'app-release.aab', ); checkItContains([ - 'base/manifest/AndroidManifest.xml', - 'base/dex/classes.dex', + ...baseAabFiles, + ...flutterAabAssets, 'base/lib/arm64-v8a/libapp.so', 'base/lib/arm64-v8a/libflutter.so', 'base/lib/armeabi-v7a/libapp.so', @@ -36,6 +42,10 @@ Future main() async { }); await runProjectTest((FlutterProject project) async { + if (Platform.isWindows) { + // https://github.com/flutter/flutter/issues/42985 + return; + } section('App bundle content using flavors without explicit target platform'); // Add a few flavors. await project.addProductFlavors( [ @@ -57,8 +67,8 @@ Future main() async { 'app-production-release.aab', ); checkItContains([ - 'base/manifest/AndroidManifest.xml', - 'base/dex/classes.dex', + ...baseAabFiles, + ...flutterAabAssets, 'base/lib/arm64-v8a/libapp.so', 'base/lib/arm64-v8a/libflutter.so', 'base/lib/armeabi-v7a/libapp.so', @@ -93,8 +103,8 @@ Future main() async { 'app-flavor_underscore-release.aab', ); checkItContains([ - 'base/manifest/AndroidManifest.xml', - 'base/dex/classes.dex', + ...baseAabFiles, + ...flutterAabAssets, 'base/lib/arm64-v8a/libapp.so', 'base/lib/arm64-v8a/libflutter.so', 'base/lib/armeabi-v7a/libapp.so', @@ -128,8 +138,8 @@ Future main() async { 'app-production-release.aab', ); checkItContains([ - 'base/manifest/AndroidManifest.xml', - 'base/dex/classes.dex', + ...baseAabFiles, + ...flutterAabAssets, 'base/lib/arm64-v8a/libapp.so', 'base/lib/arm64-v8a/libflutter.so', 'base/lib/armeabi-v7a/libapp.so', @@ -153,10 +163,9 @@ Future main() async { ); final Iterable bundleFiles = await getFilesInAppBundle(releaseBundle); - checkItContains([ - 'base/manifest/AndroidManifest.xml', - 'base/dex/classes.dex', + ...baseAabFiles, + ...flutterAabAssets, 'base/lib/armeabi-v7a/libapp.so', 'base/lib/armeabi-v7a/libflutter.so', ], bundleFiles); diff --git a/dev/devicelab/bin/tasks/gradle_plugin_dependencies_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_dependencies_test.dart index 2525bdfa1a6..6df1afdd5fc 100644 --- a/dev/devicelab/bin/tasks/gradle_plugin_dependencies_test.dart +++ b/dev/devicelab/bin/tasks/gradle_plugin_dependencies_test.dart @@ -11,7 +11,7 @@ import 'package:flutter_devicelab/framework/utils.dart'; import 'package:path/path.dart' as path; final String gradlew = Platform.isWindows ? 'gradlew.bat' : 'gradlew'; -final String gradlewExecutable = Platform.isWindows ? gradlew : './$gradlew'; +final String gradlewExecutable = Platform.isWindows ? '.\\$gradlew' : './$gradlew'; /// Tests that projects can include plugins that have a transtive dependency in common. /// For more info see: https://github.com/flutter/flutter/issues/27254. diff --git a/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart index 431c815eb33..a76879f7f74 100644 --- a/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart +++ b/dev/devicelab/bin/tasks/gradle_plugin_fat_apk_test.dart @@ -21,12 +21,10 @@ Future main() async { checkItContains([ ...flutterAssets, - 'classes.dex', - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', - 'lib/arm64-v8a/libflutter.so', + ...debugAssets, + ...baseApkFiles, 'lib/armeabi-v7a/libflutter.so', + 'lib/arm64-v8a/libflutter.so', // Debug mode intentionally includes `x86` and `x86_64`. 'lib/x86/libflutter.so', 'lib/x86_64/libflutter.so', @@ -48,18 +46,14 @@ Future main() async { checkItContains([ ...flutterAssets, - 'classes.dex', - 'lib/arm64-v8a/libflutter.so', - 'lib/arm64-v8a/libapp.so', + ...baseApkFiles, 'lib/armeabi-v7a/libflutter.so', 'lib/armeabi-v7a/libapp.so', + 'lib/arm64-v8a/libflutter.so', + 'lib/arm64-v8a/libapp.so', ], apkFiles); - checkItDoesNotContain([ - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', - ], apkFiles); + checkItDoesNotContain(debugAssets, apkFiles); }); await runPluginProjectTest((FlutterPluginProject pluginProject) async { @@ -71,18 +65,14 @@ Future main() async { checkItContains([ ...flutterAssets, - 'classes.dex', + ...baseApkFiles, 'lib/armeabi-v7a/libflutter.so', 'lib/armeabi-v7a/libapp.so', 'lib/arm64-v8a/libflutter.so', 'lib/arm64-v8a/libapp.so', ], apkFiles); - checkItDoesNotContain([ - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', - ], apkFiles); + checkItDoesNotContain(debugAssets, apkFiles); }); await runPluginProjectTest((FlutterPluginProject pluginProject) async { @@ -95,31 +85,23 @@ Future main() async { checkItContains([ ...flutterAssets, - 'classes.dex', + ...baseApkFiles, 'lib/armeabi-v7a/libflutter.so', 'lib/armeabi-v7a/libapp.so', ], armApkFiles); - checkItDoesNotContain([ - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', - ], armApkFiles); + checkItDoesNotContain(debugAssets, armApkFiles); final Iterable arm64ApkFiles = await getFilesInApk(pluginProject.releaseArm64ApkPath); checkItContains([ ...flutterAssets, - 'classes.dex', + ...baseApkFiles, 'lib/arm64-v8a/libflutter.so', 'lib/arm64-v8a/libapp.so', ], arm64ApkFiles); - checkItDoesNotContain([ - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', - ], arm64ApkFiles); + checkItDoesNotContain(debugAssets, arm64ApkFiles); }); await runProjectTest((FlutterProject project) async { diff --git a/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart index 935e79cc078..2c6b1627774 100644 --- a/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart +++ b/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart @@ -21,10 +21,8 @@ Future main() async { checkItContains([ ...flutterAssets, - 'classes.dex', - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', + ...debugAssets, + ...baseApkFiles, 'lib/armeabi-v7a/libflutter.so', // Debug mode intentionally includes `x86` and `x86_64`. 'lib/x86/libflutter.so', @@ -32,6 +30,7 @@ Future main() async { ], apkFiles); checkItDoesNotContain([ + 'lib/arm64-v8a/libapp.so', 'lib/armeabi-v7a/libapp.so', 'lib/x86/libapp.so', 'lib/x86_64/libapp.so', @@ -48,10 +47,8 @@ Future main() async { checkItContains([ ...flutterAssets, - 'classes.dex', - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', + ...debugAssets, + ...baseApkFiles, // Debug mode intentionally includes `x86` and `x86_64`. 'lib/x86/libflutter.so', 'lib/x86_64/libflutter.so', @@ -74,10 +71,8 @@ Future main() async { checkItContains([ ...flutterAssets, - 'classes.dex', - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', + ...debugAssets, + ...baseApkFiles, // Debug mode intentionally includes `x86` and `x86_64`. 'lib/x86/libflutter.so', 'lib/x86_64/libflutter.so', @@ -99,17 +94,15 @@ Future main() async { checkItContains([ ...flutterAssets, - 'classes.dex', + ...baseApkFiles, 'lib/armeabi-v7a/libflutter.so', 'lib/armeabi-v7a/libapp.so', ], apkFiles); checkItDoesNotContain([ + ...debugAssets, 'lib/arm64-v8a/libflutter.so', 'lib/arm64-v8a/libapp.so', - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', ], apkFiles); }); @@ -122,17 +115,15 @@ Future main() async { checkItContains([ ...flutterAssets, - 'classes.dex', + ...baseApkFiles, 'lib/arm64-v8a/libflutter.so', 'lib/arm64-v8a/libapp.so', ], apkFiles); checkItDoesNotContain([ + ...debugAssets, 'lib/armeabi-v7a/libflutter.so', 'lib/armeabi-v7a/libapp.so', - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', ], apkFiles); }); diff --git a/dev/devicelab/bin/tasks/gradle_plugins_without_annotations_test.dart b/dev/devicelab/bin/tasks/gradle_plugins_without_annotations_test.dart index f357c5ff4df..efa7eb21b34 100644 --- a/dev/devicelab/bin/tasks/gradle_plugins_without_annotations_test.dart +++ b/dev/devicelab/bin/tasks/gradle_plugins_without_annotations_test.dart @@ -11,7 +11,7 @@ import 'package:flutter_devicelab/framework/utils.dart'; import 'package:path/path.dart' as path; final String gradlew = Platform.isWindows ? 'gradlew.bat' : 'gradlew'; -final String gradlewExecutable = Platform.isWindows ? gradlew : './$gradlew'; +final String gradlewExecutable = Platform.isWindows ? '.\\$gradlew' : './$gradlew'; /// Tests that plugins that don't define a `androidx.annotation:annotation:+` or /// `com.android.support:support-annotations:+` dependency can be built as AAR. @@ -60,6 +60,12 @@ Future main() async { ); }); + section('Update proguard rules'); + + // Don't obfuscate the input class files, since the test is checking if some classes are in the DEX. + final File proguardRules = File(path.join(projectDir.path, 'android', 'app', 'proguard-rules.pro')); + proguardRules.writeAsStringSync('-dontobfuscate', flush: true); + section('Build release APK'); await inDirectory(projectDir, () async { diff --git a/dev/devicelab/bin/tasks/module_host_with_custom_build_test.dart b/dev/devicelab/bin/tasks/module_host_with_custom_build_test.dart index 2615ae0cc9a..cb8dd9de010 100644 --- a/dev/devicelab/bin/tasks/module_host_with_custom_build_test.dart +++ b/dev/devicelab/bin/tasks/module_host_with_custom_build_test.dart @@ -11,7 +11,7 @@ import 'package:flutter_devicelab/framework/utils.dart'; import 'package:path/path.dart' as path; final String gradlew = Platform.isWindows ? 'gradlew.bat' : 'gradlew'; -final String gradlewExecutable = Platform.isWindows ? gradlew : './$gradlew'; +final String gradlewExecutable = Platform.isWindows ? '.\\$gradlew' : './$gradlew'; final bool useAndroidEmbeddingV2 = Platform.environment['ENABLE_ANDROID_EMBEDDING_V2'] == 'true'; @@ -125,9 +125,7 @@ Future main() async { checkItContains([ ...flutterAssets, - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', + ...debugAssets, ], await getFilesInApk(demoDebugApk)); await clean(); @@ -170,9 +168,7 @@ Future main() async { checkItContains([ ...flutterAssets, - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', + ...debugAssets, ], await getFilesInApk(demoDebugApk2)); await clean(); @@ -207,9 +203,7 @@ Future main() async { checkItContains([ ...flutterAssets, - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', + ...debugAssets, ], await getFilesInApk(demoStagingApk)); await clean(); @@ -246,10 +240,10 @@ Future main() async { checkItContains([ ...flutterAssets, - 'lib/arm64-v8a/libapp.so', 'lib/arm64-v8a/libflutter.so', - 'lib/armeabi-v7a/libapp.so', + 'lib/arm64-v8a/libapp.so', 'lib/armeabi-v7a/libflutter.so', + 'lib/armeabi-v7a/libapp.so', ], await getFilesInApk(demoReleaseApk)); await clean(); diff --git a/dev/devicelab/bin/tasks/module_test.dart b/dev/devicelab/bin/tasks/module_test.dart index a6cd77eb903..d650d89582c 100644 --- a/dev/devicelab/bin/tasks/module_test.dart +++ b/dev/devicelab/bin/tasks/module_test.dart @@ -11,14 +11,13 @@ import 'package:flutter_devicelab/framework/utils.dart'; import 'package:path/path.dart' as path; final String gradlew = Platform.isWindows ? 'gradlew.bat' : 'gradlew'; -final String gradlewExecutable = Platform.isWindows ? gradlew : './$gradlew'; +final String gradlewExecutable = Platform.isWindows ? '.\\$gradlew' : './$gradlew'; final bool useAndroidEmbeddingV2 = Platform.environment['ENABLE_ANDROID_EMBEDDING_V2'] == 'true'; /// Tests that the Flutter module project template works and supports /// adding Flutter to an existing Android app. Future main() async { - print(useAndroidEmbeddingV2); await task(() async { section('Find Java'); @@ -201,10 +200,8 @@ Future main() async { checkItContains([ ...flutterAssets, - 'AndroidManifest.xml', - 'assets/flutter_assets/isolate_snapshot_data', - 'assets/flutter_assets/kernel_blob.bin', - 'assets/flutter_assets/vm_snapshot_data', + ...debugAssets, + ...baseApkFiles, ], await getFilesInApk(debugHostApk)); section('Check debug AndroidManifest.xml'); @@ -258,7 +255,7 @@ Future main() async { checkItContains([ ...flutterAssets, - 'AndroidManifest.xml', + ...baseApkFiles, 'lib/arm64-v8a/libapp.so', 'lib/arm64-v8a/libflutter.so', 'lib/armeabi-v7a/libapp.so', diff --git a/dev/devicelab/lib/framework/apk_utils.dart b/dev/devicelab/lib/framework/apk_utils.dart index 883cf51cec0..3abcf9fed15 100644 --- a/dev/devicelab/lib/framework/apk_utils.dart +++ b/dev/devicelab/lib/framework/apk_utils.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:convert'; import 'dart:io'; import 'package:path/path.dart' as path; @@ -17,6 +16,17 @@ final List flutterAssets = [ 'assets/flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf', ]; +final List debugAssets = [ + 'assets/flutter_assets/isolate_snapshot_data', + 'assets/flutter_assets/kernel_blob.bin', + 'assets/flutter_assets/vm_snapshot_data', +]; + +final List baseApkFiles = [ + 'classes.dex', + 'AndroidManifest.xml', +]; + /// Runs the given [testFunction] on a freshly generated Flutter project. Future runProjectTest(Future testFunction(FlutterProject project)) async { final Directory tempDir = Directory.systemTemp.createTempSync('flutter_devicelab_gradle_plugin_test.'); @@ -43,20 +53,18 @@ Future runPluginProjectTest(Future testFunction(FlutterPluginProject /// Returns the list of files inside an Android Package Kit. Future> getFilesInApk(String apk) async { - if (!File(apk).existsSync()) + if (!File(apk).existsSync()) { throw TaskResult.failure( 'Gradle did not produce an output artifact file at: $apk'); - - final Process unzip = await startProcess( - 'unzip', - ['-v', apk], - isBot: false, // we just want to test the output, not have any debugging info + } + final String files = await _evalApkAnalyzer( + [ + 'files', + 'list', + apk, + ] ); - return unzip.stdout - .transform(utf8.decoder) - .transform(const LineSplitter()) - .map((String line) => line.split(' ').last) - .toList(); + return files.split('\n').map((String file) => file.substring(1).trim()); } /// Returns the list of files inside an Android App Bundle. Future> getFilesInAppBundle(String bundle) { @@ -71,7 +79,7 @@ Future> getFilesInAar(String aar) { void checkItContains(Iterable values, Iterable collection) { for (T value in values) { if (!collection.contains(value)) { - throw TaskResult.failure('Expected to find `$value` in `$collection`.'); + throw TaskResult.failure('Expected to find `$value` in `${collection.toString()}`.'); } } } @@ -120,9 +128,36 @@ String get _androidHome { return androidHome; } -/// Utility class to analyze the content inside an APK using dexdump, -/// which is provided by the Android SDK. -/// https://android.googlesource.com/platform/art/+/master/dexdump/dexdump.cc +/// Executes an APK analyzer subcommand. +Future _evalApkAnalyzer( + List args, { + bool printStdout = true, + String workingDirectory, +}) async { + final String javaHome = await findJavaHome(); + final String javaBinary = path.join(javaHome, 'bin', 'java'); + assert(canRun(javaBinary)); + + final String androidTools = path.join(_androidHome, 'tools'); + final String libs = path.join(androidTools, 'lib'); + assert(Directory(libs).existsSync()); + + final String classSeparator = Platform.isWindows ? ';' : ':'; + return eval( + javaBinary, + [ + '-Dcom.android.sdklib.toolsdir=$androidTools', + '-classpath', + '.$classSeparator$libs${Platform.pathSeparator}*', + 'com.android.tools.apk.analyzer.ApkAnalyzerCli', + ...args, + ], + printStdout: printStdout, + workingDirectory: workingDirectory, + ); +} + +/// Utility class to analyze the content inside an APK using the APK analyzer. class ApkExtractor { ApkExtractor(this.apkFile); @@ -131,70 +166,56 @@ class ApkExtractor { bool _extracted = false; - Directory _outputDir; + Set _classes = const {}; - Future _extractApk() async { + Future _extractDex() async { if (_extracted) { return; } - _outputDir = apkFile.parent.createTempSync('apk'); - if (Platform.isWindows) { - await eval('7za', ['x', apkFile.path], workingDirectory: _outputDir.path); - } else { - await eval('unzip', [apkFile.path], workingDirectory: _outputDir.path); - } + final String packages = await _evalApkAnalyzer( + [ + 'dex', + 'packages', + apkFile.path, + ], + printStdout: false, + ); + _classes = Set.from( + packages + .split('\n') + .where((String line) => line.startsWith('C')) + .map((String line) => line.split('\t').last), + ); + assert(_classes.isNotEmpty); _extracted = true; } - /// Returns the full path to the [dexdump] tool. - Future _findDexDump() async { - String dexdumps; - if (Platform.isWindows) { - dexdumps = await eval('dir', ['/s/b', 'dexdump.exe'], - workingDirectory: _androidHome); - } else { - dexdumps = await eval('find', [_androidHome, '-name', 'dexdump']); - } - if (dexdumps.isEmpty) { - throw Exception('Couldn\'t find a dexdump executable.'); - } - return dexdumps.split('\n').first; - } - // Removes any temporary directory. void dispose() { if (!_extracted) { return; } - rmTree(_outputDir); + _classes = const {}; _extracted = true; } /// Returns true if the APK contains a given class. Future containsClass(String className) async { - await _extractApk(); - - final String dexDump = await _findDexDump(); - final String classesDex = path.join(_outputDir.path, 'classes.dex'); - - if (!File(classesDex).existsSync()) { - throw Exception('Couldn\'t find classes.dex in the APK.'); - } - final String classDescriptors = await eval(dexDump, - [classesDex], printStdout: false); - - if (classDescriptors.isEmpty) { - throw Exception('No descriptors found in classes.dex.'); - } - return classDescriptors.contains(className.replaceAll('.', '/')); + await _extractDex(); + return _classes.contains(className); } } /// Gets the content of the `AndroidManifest.xml`. -Future getAndroidManifest(String apk) { - final String apkAnalyzer = path.join(_androidHome, 'tools', 'bin', 'apkanalyzer'); - return eval(apkAnalyzer, ['manifest', 'print', apk], - workingDirectory: _androidHome); +Future getAndroidManifest(String apk) async { + return await _evalApkAnalyzer( + [ + 'manifest', + 'print', + apk, + ], + workingDirectory: _androidHome, + ); } /// Checks that the classes are contained in the APK, throws otherwise. @@ -318,7 +339,7 @@ android { Future resultOfFlutterCommand(String command, List options) { return Process.run( - path.join(flutterDirectory.path, 'bin', 'flutter'), + path.join(flutterDirectory.path, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter'), [command, ...options], workingDirectory: rootPath, ); @@ -401,39 +422,14 @@ Future _resultOfGradleTask({String workingDirectory, String task, ); } -class _Dependencies { - _Dependencies(String depfilePath) { - // Depfile format: - // outfile1 outfile2 : file1.dart file2.dart file3.dart file\ 4.dart - final String contents = File(depfilePath).readAsStringSync(); - final List colonSeparated = contents.split(':'); - targets = _processList(colonSeparated[0].trim()); - dependencies = _processList(colonSeparated[1].trim()); - } - - final RegExp _separatorExpr = RegExp(r'([^\\]) '); - final RegExp _escapeExpr = RegExp(r'\\(.)'); - - Set _processList(String rawText) { - return rawText - // Put every file on right-hand side on the separate line - .replaceAllMapped(_separatorExpr, (Match match) => '${match.group(1)}\n') - .split('\n') - // Expand escape sequences, so that '\ ', for example,ß becomes ' ' - .map((String path) => path.replaceAllMapped(_escapeExpr, (Match match) => match.group(1)).trim()) - .where((String path) => path.isNotEmpty) - .toSet(); - } - - Set targets; - Set dependencies; -} - /// Returns [null] if target matches [expectedTarget], otherwise returns an error message. String validateSnapshotDependency(FlutterProject project, String expectedTarget) { - final _Dependencies deps = _Dependencies( + final File snapshotBlob = File( path.join(project.rootPath, 'build', 'app', 'intermediates', 'flutter', 'debug', 'android-arm', 'snapshot_blob.bin.d')); - return deps.targets.any((String target) => target.contains(expectedTarget)) ? null : - 'Dependency file should have $expectedTarget as target. Instead has ${deps.targets}'; + + assert(snapshotBlob.existsSync()); + final String contentSnapshot = snapshotBlob.readAsStringSync(); + return contentSnapshot.contains('$expectedTarget ') + ? null : 'Dependency file should have $expectedTarget as target. Instead found $contentSnapshot'; } diff --git a/dev/devicelab/lib/framework/utils.dart b/dev/devicelab/lib/framework/utils.dart index 307de3b77a3..8141ba748e9 100644 --- a/dev/devicelab/lib/framework/utils.dart +++ b/dev/devicelab/lib/framework/utils.dart @@ -255,13 +255,14 @@ Future startProcess( }) async { assert(isBot != null); final String command = '$executable ${arguments?.join(" ") ?? ""}'; - print('\nExecuting: $command'); + final String finalWorkingDirectory = workingDirectory ?? cwd; + print('\nExecuting: $command in $finalWorkingDirectory'); environment ??= {}; environment['BOT'] = isBot ? 'true' : 'false'; final Process process = await _processManager.start( [executable, ...arguments], environment: environment, - workingDirectory: workingDirectory ?? cwd, + workingDirectory: finalWorkingDirectory, ); final ProcessInfo processInfo = ProcessInfo(command, process); _runningProcesses.add(processInfo); @@ -447,7 +448,7 @@ void cd(dynamic directory) { throw 'Cannot cd into directory that does not exist: $directory'; } -Directory get flutterDirectory => dir('../..').absolute; +Directory get flutterDirectory => Directory.current.parent.parent; String requireEnvVar(String name) { final String value = Platform.environment[name];