Unverified Commit c08a3c7a authored by Emmanuel Garcia's avatar Emmanuel Garcia Committed by GitHub

Add metadata to indicate if the host app contains a Flutter module (#37731)

parent 36b8725a
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter_devicelab/framework/apk_utils.dart';
import 'package:flutter_devicelab/framework/framework.dart'; import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/framework/utils.dart'; import 'package:flutter_devicelab/framework/utils.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
...@@ -156,6 +157,8 @@ Future<void> main() async { ...@@ -156,6 +157,8 @@ Future<void> main() async {
final File analyticsOutputFile = File(path.join(tempDir.path, 'analytics.log')); final File analyticsOutputFile = File(path.join(tempDir.path, 'analytics.log'));
section('Build debug host APK');
await inDirectory(hostApp, () async { await inDirectory(hostApp, () async {
if (!Platform.isWindows) { if (!Platform.isWindows) {
await exec('chmod', <String>['+x', 'gradlew']); await exec('chmod', <String>['+x', 'gradlew']);
...@@ -169,7 +172,9 @@ Future<void> main() async { ...@@ -169,7 +172,9 @@ Future<void> main() async {
); );
}); });
final bool existingAppBuilt = exists(File(path.join( section('Check debug APK exists');
final String debugHostApk = path.join(
hostApp.path, hostApp.path,
'app', 'app',
'build', 'build',
...@@ -177,9 +182,29 @@ Future<void> main() async { ...@@ -177,9 +182,29 @@ Future<void> main() async {
'apk', 'apk',
'debug', 'debug',
'app-debug.apk', 'app-debug.apk',
))); );
if (!existingAppBuilt) { if (!exists(File(debugHostApk))) {
return TaskResult.failure('Failed to build existing app .apk'); return TaskResult.failure('Failed to build debug host APK');
}
section('Check files in debug APK');
checkItContains<String>(<String>[
'AndroidManifest.xml',
'assets/flutter_assets/isolate_snapshot_data',
'assets/flutter_assets/kernel_blob.bin',
'assets/flutter_assets/vm_snapshot_data',
], await getFilesInApk(debugHostApk));
section('Check debug AndroidManifest.xml');
final String androidManifestDebug = await getAndroidManifest(debugHostApk);
if (!androidManifestDebug.contains('''
<meta-data
android:name="flutterProjectType"
android:value="module" />''')
) {
return TaskResult.failure('Debug host APK doesn\'t contain metadata: flutterProjectType = module ');
} }
final String analyticsOutput = analyticsOutputFile.readAsStringSync(); final String analyticsOutput = analyticsOutputFile.readAsStringSync();
...@@ -193,7 +218,54 @@ Future<void> main() async { ...@@ -193,7 +218,54 @@ Future<void> main() async {
); );
} }
section('Build release host APK');
await inDirectory(hostApp, () async {
await exec(gradlewExecutable,
<String>['app:assembleRelease'],
environment: <String, String>{
'JAVA_HOME': javaHome,
'FLUTTER_ANALYTICS_LOG_FILE': analyticsOutputFile.path,
},
);
});
final String releaseHostApk = path.join(
hostApp.path,
'app',
'build',
'outputs',
'apk',
'release',
'app-release-unsigned.apk',
);
if (!exists(File(releaseHostApk))) {
return TaskResult.failure('Failed to build release host APK');
}
section('Check files in release APK');
checkItContains<String>(<String>[
'AndroidManifest.xml',
'lib/arm64-v8a/libapp.so',
'lib/arm64-v8a/libflutter.so',
'lib/armeabi-v7a/libapp.so',
'lib/armeabi-v7a/libflutter.so',
], await getFilesInApk(releaseHostApk));
section('Check release AndroidManifest.xml');
final String androidManifestRelease = await getAndroidManifest(debugHostApk);
if (!androidManifestRelease.contains('''
<meta-data
android:name="flutterProjectType"
android:value="module" />''')
) {
return TaskResult.failure('Release host APK doesn\'t contain metadata: flutterProjectType = module ');
}
return TaskResult.success(null); return TaskResult.success(null);
} on TaskResult catch (taskResult) {
return taskResult;
} catch (e) { } catch (e) {
return TaskResult.failure(e.toString()); return TaskResult.failure(e.toString());
} finally { } finally {
......
...@@ -89,6 +89,16 @@ bool hasMultipleOccurrences(String text, Pattern pattern) { ...@@ -89,6 +89,16 @@ bool hasMultipleOccurrences(String text, Pattern pattern) {
return text.indexOf(pattern) != text.lastIndexOf(pattern); return text.indexOf(pattern) != text.lastIndexOf(pattern);
} }
/// The Android home directory.
String get _androidHome {
final String androidHome = Platform.environment['ANDROID_HOME'] ??
Platform.environment['ANDROID_SDK_ROOT'];
if (androidHome == null || androidHome.isEmpty) {
throw Exception('Unset env flag: `ANDROID_HOME` or `ANDROID_SDK_ROOT`.');
}
return androidHome;
}
/// Utility class to analyze the content inside an APK using dexdump, /// Utility class to analyze the content inside an APK using dexdump,
/// which is provided by the Android SDK. /// which is provided by the Android SDK.
/// https://android.googlesource.com/platform/art/+/master/dexdump/dexdump.cc /// https://android.googlesource.com/platform/art/+/master/dexdump/dexdump.cc
...@@ -117,18 +127,12 @@ class ApkExtractor { ...@@ -117,18 +127,12 @@ class ApkExtractor {
/// Returns the full path to the [dexdump] tool. /// Returns the full path to the [dexdump] tool.
Future<String> _findDexDump() async { Future<String> _findDexDump() async {
final String androidHome = Platform.environment['ANDROID_HOME'] ??
Platform.environment['ANDROID_SDK_ROOT'];
if (androidHome == null || androidHome.isEmpty) {
throw Exception('Unset env flag: `ANDROID_HOME` or `ANDROID_SDK_ROOT`.');
}
String dexdumps; String dexdumps;
if (Platform.isWindows) { if (Platform.isWindows) {
dexdumps = await eval('dir', <String>['/s/b', 'dexdump.exe'], dexdumps = await eval('dir', <String>['/s/b', 'dexdump.exe'],
workingDirectory: androidHome); workingDirectory: _androidHome);
} else { } else {
dexdumps = await eval('find', <String>[androidHome, '-name', 'dexdump']); dexdumps = await eval('find', <String>[_androidHome, '-name', 'dexdump']);
} }
if (dexdumps.isEmpty) { if (dexdumps.isEmpty) {
throw Exception('Couldn\'t find a dexdump executable.'); throw Exception('Couldn\'t find a dexdump executable.');
...@@ -165,6 +169,13 @@ class ApkExtractor { ...@@ -165,6 +169,13 @@ class ApkExtractor {
} }
} }
/// Gets the content of the `AndroidManifest.xml`.
Future<String> getAndroidManifest(String apk) {
final String apkAnalyzer = path.join(_androidHome, 'tools', 'bin', 'apkanalyzer');
return eval(apkAnalyzer, <String>['manifest', 'print', apk],
workingDirectory: _androidHome);
}
/// Checks that the classes are contained in the APK, throws otherwise. /// Checks that the classes are contained in the APK, throws otherwise.
Future<void> checkApkContainsClasses(File apk, List<String> classes) async { Future<void> checkApkContainsClasses(File apk, List<String> classes) async {
final ApkExtractor extractor = ApkExtractor(apk); final ApkExtractor extractor = ApkExtractor(apk);
......
<!-- Generated file. Do not edit. --> <!-- Generated file. Do not edit. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="{{androidIdentifier}}"> package="{{androidIdentifier}}"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<application tools:node="merge">
<meta-data
android:name="flutterProjectType"
android:value="module" />
</application>
</manifest> </manifest>
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