diff --git a/dev/bots/test.dart b/dev/bots/test.dart index 48be0bb022..6d43102e30 100644 --- a/dev/bots/test.dart +++ b/dev/bots/test.dart @@ -965,5 +965,6 @@ Future _androidGradleTests(String subShard) async { if (subShard == 'gradle2') { await _runDevicelabTest('gradle_plugin_bundle_test', env: env); await _runDevicelabTest('module_test', env: env); + await _runDevicelabTest('module_host_with_custom_build_test', env: env); } } 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 new file mode 100644 index 0000000000..63ba0f311c --- /dev/null +++ b/dev/devicelab/bin/tasks/module_host_with_custom_build_test.dart @@ -0,0 +1,268 @@ +// Copyright (c) 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// 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'; +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'; + +/// Tests that the Android app containing a Flutter module can be built when +/// it has custom build types and flavors. +Future main() async { + await task(() async { + + section('Find Java'); + + final String javaHome = await findJavaHome(); + if (javaHome == null) { + return TaskResult.failure('Could not find Java'); + } + + print('\nUsing JAVA_HOME=$javaHome'); + + section('Create Flutter module project'); + + final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.'); + final Directory projectDir = Directory(path.join(tempDir.path, 'hello')); + try { + await inDirectory(tempDir, () async { + await flutter( + 'create', + options: ['--org', 'io.flutter.devicelab', '--template=module', 'hello'], + ); + }); + + section('Run flutter pub get'); + + await inDirectory(projectDir, () async { + await flutter( + 'pub', + options: ['get'], + ); + }); + + section('Add to existing Android app'); + + final Directory hostAppDir = Directory(path.join(tempDir.path, 'hello_host_app_with_custom_build')); + mkdir(hostAppDir); + recursiveCopy( + Directory(path.join(flutterDirectory.path, 'dev', 'integration_tests', 'module_host_with_custom_build')), + hostAppDir, + ); + copy( + File(path.join(projectDir.path, '.android', gradlew)), + hostAppDir, + ); + copy( + File(path.join(projectDir.path, '.android', 'gradle', 'wrapper', 'gradle-wrapper.jar')), + Directory(path.join(hostAppDir.path, 'gradle', 'wrapper')), + ); + + section('Build debug APKs'); + + section('Run app:assembleDemoDebug'); + + await inDirectory(hostAppDir, () async { + if (!Platform.isWindows) { + await exec('chmod', ['+x', 'gradlew']); + } + await exec(gradlewExecutable, + ['app:assembleDemoDebug'], + environment: { + 'JAVA_HOME': javaHome, + }, + ); + }); + + final String demoDebugApk = path.join( + hostAppDir.path, + 'app', + 'build', + 'outputs', + 'apk', + 'demo', + 'debug', + 'app-demo-debug.apk', + ); + + if (!exists(File(demoDebugApk))) { + return TaskResult.failure('Failed to build app-demo-debug.apk'); + } + + section('Verify snapshots in app-demo-debug.apk'); + + final Iterable demoDebugFiles = await getFilesInApk(demoDebugApk); + checkItContains([ + 'assets/flutter_assets/isolate_snapshot_data', + 'assets/flutter_assets/kernel_blob.bin', + 'assets/flutter_assets/vm_snapshot_data', + ], demoDebugFiles); + + section('Clean'); + + await inDirectory(hostAppDir, () async { + await exec(gradlewExecutable, + ['clean'], + environment: { + 'JAVA_HOME': javaHome, + }, + ); + }); + + section('Run app:assembleDemoStaging'); + + await inDirectory(hostAppDir, () async { + if (!Platform.isWindows) { + await exec('chmod', ['+x', 'gradlew']); + } + await exec(gradlewExecutable, + ['app:assembleDemoStaging'], + environment: { + 'JAVA_HOME': javaHome, + }, + ); + }); + + final String demoStagingApk = path.join( + hostAppDir.path, + 'app', + 'build', + 'outputs', + 'apk', + 'demo', + 'staging', + 'app-demo-staging.apk', + ); + + if (!exists(File(demoStagingApk))) { + return TaskResult.failure('Failed to build app-demo-staging.apk'); + } + + section('Verify snapshots in app-demo-staging.apk'); + + final Iterable demoStagingFiles = await getFilesInApk(demoStagingApk); + checkItContains([ + 'assets/flutter_assets/isolate_snapshot_data', + 'assets/flutter_assets/kernel_blob.bin', + 'assets/flutter_assets/vm_snapshot_data', + ], demoStagingFiles); + + section('Clean'); + + await inDirectory(hostAppDir, () async { + await exec(gradlewExecutable, + ['clean'], + environment: { + 'JAVA_HOME': javaHome, + }, + ); + }); + + section('Build release APKs'); + + section('Run app:assembleDemoRelease'); + + await inDirectory(hostAppDir, () async { + if (!Platform.isWindows) { + await exec('chmod', ['+x', 'gradlew']); + } + await exec(gradlewExecutable, + ['app:assembleDemoRelease'], + environment: { + 'JAVA_HOME': javaHome, + }, + ); + }); + + final String demoReleaseApk = path.join( + hostAppDir.path, + 'app', + 'build', + 'outputs', + 'apk', + 'demo', + 'release', + 'app-demo-release-unsigned.apk', + ); + + if (!exists(File(demoReleaseApk))) { + return TaskResult.failure('Failed to build app-demo-release-unsigned.apk'); + } + + section('Verify AOT blobs in app-demo-release-unsigned.apk'); + + final Iterable demoReleaseFiles = await getFilesInApk(demoReleaseApk); + checkItContains([ + 'lib/arm64-v8a/libapp.so', + 'lib/arm64-v8a/libflutter.so', + 'lib/armeabi-v7a/libapp.so', + 'lib/armeabi-v7a/libflutter.so', + ], demoReleaseFiles); + + section('Clean'); + + await inDirectory(hostAppDir, () async { + await exec(gradlewExecutable, + ['clean'], + environment: { + 'JAVA_HOME': javaHome, + }, + ); + }); + + section('Run app:assembleDemoProd'); + + await inDirectory(hostAppDir, () async { + if (!Platform.isWindows) { + await exec('chmod', ['+x', 'gradlew']); + } + await exec(gradlewExecutable, + ['app:assembleDemoProd'], + environment: { + 'JAVA_HOME': javaHome, + }, + ); + }); + + final String demoProdApk = path.join( + hostAppDir.path, + 'app', + 'build', + 'outputs', + 'apk', + 'demo', + 'prod', + 'app-demo-prod-unsigned.apk', + ); + + if (!exists(File(demoProdApk))) { + return TaskResult.failure('Failed to build app-demo-prod-unsigned.apk'); + } + + section('Verify AOT blobs in app-demo-prod-unsigned.apk'); + + final Iterable demoProdFiles = await getFilesInApk(demoProdApk); + checkItContains([ + 'lib/arm64-v8a/libapp.so', + 'lib/arm64-v8a/libflutter.so', + 'lib/armeabi-v7a/libapp.so', + 'lib/armeabi-v7a/libflutter.so', + ], demoProdFiles); + + return TaskResult.success(null); + } on TaskResult catch (taskResult) { + return taskResult; + } catch (e) { + return TaskResult.failure(e.toString()); + } finally { + rmTree(tempDir); + } + }); +} diff --git a/dev/integration_tests/module_host_with_custom_build/README.md b/dev/integration_tests/module_host_with_custom_build/README.md new file mode 100644 index 0000000000..85b88745cd --- /dev/null +++ b/dev/integration_tests/module_host_with_custom_build/README.md @@ -0,0 +1,8 @@ +# Android host app + +Android host app for a Flutter module created using +``` +$ flutter create -t module hello +``` +and placed in a sibling folder to (a clone of) the host app. +Used by the `module_host_with_custom_build_test.dart` device lab test. diff --git a/dev/integration_tests/module_host_with_custom_build/app/build.gradle b/dev/integration_tests/module_host_with_custom_build/app/build.gradle new file mode 100644 index 0000000000..254d777b3f --- /dev/null +++ b/dev/integration_tests/module_host_with_custom_build/app/build.gradle @@ -0,0 +1,43 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + defaultConfig { + applicationId "io.flutter.addtoapp" + minSdkVersion 16 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + // Test build types. + buildTypes { + staging { + initWith debug + // This is required because the `:flutter` project doesn't define this custom build type. + // Without the fallback, the Android plugin will make Gradle exit with the following error: + // `Unable to find a matching variant of project :flutter` + matchingFallbacks = ['debug'] + } + prod { + initWith release + matchingFallbacks = ['release'] + } + } + // Test flavors. + flavorDimensions "version" + productFlavors { + demo { + dimension "version" + } + } +} + +dependencies { + implementation project(':flutter') + implementation 'com.android.support:appcompat-v7:27.1.1' +} diff --git a/dev/integration_tests/module_host_with_custom_build/app/src/main/AndroidManifest.xml b/dev/integration_tests/module_host_with_custom_build/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..5a8fa3293a --- /dev/null +++ b/dev/integration_tests/module_host_with_custom_build/app/src/main/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/dev/integration_tests/module_host_with_custom_build/app/src/main/java/io/flutter/addtoapp/MainActivity.java b/dev/integration_tests/module_host_with_custom_build/app/src/main/java/io/flutter/addtoapp/MainActivity.java new file mode 100644 index 0000000000..60bb318ab4 --- /dev/null +++ b/dev/integration_tests/module_host_with_custom_build/app/src/main/java/io/flutter/addtoapp/MainActivity.java @@ -0,0 +1,14 @@ +package io.flutter.addtoapp; + +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import io.flutter.facade.Flutter; + +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(Flutter.createView(this, getLifecycle(), "route1")); + } +} diff --git a/dev/integration_tests/module_host_with_custom_build/build.gradle b/dev/integration_tests/module_host_with_custom_build/build.gradle new file mode 100644 index 0000000000..e006ab47b4 --- /dev/null +++ b/dev/integration_tests/module_host_with_custom_build/build.gradle @@ -0,0 +1,20 @@ +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/dev/integration_tests/module_host_with_custom_build/gradle.properties b/dev/integration_tests/module_host_with_custom_build/gradle.properties new file mode 100644 index 0000000000..6ed0f8f960 --- /dev/null +++ b/dev/integration_tests/module_host_with_custom_build/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xmx1536m diff --git a/dev/integration_tests/module_host_with_custom_build/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/module_host_with_custom_build/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..807a9a8414 --- /dev/null +++ b/dev/integration_tests/module_host_with_custom_build/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Jun 25 14:13:36 CEST 2018 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/dev/integration_tests/module_host_with_custom_build/settings.gradle b/dev/integration_tests/module_host_with_custom_build/settings.gradle new file mode 100644 index 0000000000..ecc9eb957c --- /dev/null +++ b/dev/integration_tests/module_host_with_custom_build/settings.gradle @@ -0,0 +1,3 @@ +include ':app' +setBinding(new Binding([gradle: this])) +evaluate(new File(settingsDir.parentFile, 'hello/.android/include_flutter.groovy')) diff --git a/packages/flutter_tools/gradle/flutter.gradle b/packages/flutter_tools/gradle/flutter.gradle index 50f40281b2..349aa8e609 100644 --- a/packages/flutter_tools/gradle/flutter.gradle +++ b/packages/flutter_tools/gradle/flutter.gradle @@ -611,24 +611,7 @@ class FlutterPlugin implements Plugin { with flutterTask.assets } } - if (packageAssets) { - String mainModuleName = "app" - try { - String tmpModuleName = project.rootProject.ext.mainModuleName - if (tmpModuleName != null && !tmpModuleName.empty) { - mainModuleName = tmpModuleName - } - } catch (Exception e) { - } - // Only include configurations that exist in parent project. - Task mergeAssets = project.tasks.findByPath(":${mainModuleName}:merge${variant.name.capitalize()}Assets") - if (mergeAssets) { - mergeAssets.dependsOn(copyFlutterAssetsTask) - } - } else { - def processResources = variant.outputs.first().processResources - processResources.dependsOn(copyFlutterAssetsTask) - } + variant.outputs.first().processResources.dependsOn(copyFlutterAssetsTask) } if (project.android.hasProperty("applicationVariants")) { project.android.applicationVariants.all addFlutterDeps