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 3f3b5c1cc8..d50bab8bd2 100644 --- a/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart +++ b/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart @@ -52,7 +52,6 @@ Future main() async { 'assets/flutter_assets/isolate_snapshot_data', 'assets/flutter_assets/kernel_blob.bin', 'assets/flutter_assets/vm_snapshot_data', - 'lib/armeabi-v7a/libflutter.so', // Debug mode intentionally includes `x86` and `x86_64`. 'lib/x86/libflutter.so', 'lib/x86_64/libflutter.so', @@ -79,7 +78,6 @@ Future main() async { 'assets/flutter_assets/isolate_snapshot_data', 'assets/flutter_assets/kernel_blob.bin', 'assets/flutter_assets/vm_snapshot_data', - 'lib/armeabi-v7a/libflutter.so', // Debug mode intentionally includes `x86` and `x86_64`. 'lib/x86/libflutter.so', 'lib/x86_64/libflutter.so', diff --git a/examples/platform_view/android/app/build.gradle b/examples/platform_view/android/app/build.gradle index b80bbcd8f5..14ce0b1d4e 100644 --- a/examples/platform_view/android/app/build.gradle +++ b/examples/platform_view/android/app/build.gradle @@ -57,6 +57,6 @@ dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' - implementation 'com.android.support:appcompat-v7:26.1.0' - implementation 'com.android.support:design:26.1.0' + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.android.support:design:28.0.0' } diff --git a/packages/flutter_tools/gradle/flutter.gradle b/packages/flutter_tools/gradle/flutter.gradle index 349aa8e609..59d28dbeb8 100644 --- a/packages/flutter_tools/gradle/flutter.gradle +++ b/packages/flutter_tools/gradle/flutter.gradle @@ -28,7 +28,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' + classpath "com.android.tools.build:gradle:3.2.1" } } @@ -42,6 +42,8 @@ android { apply plugin: FlutterPlugin class FlutterPlugin implements Plugin { + private static final String MAVEN_REPO = "http://download.flutter.io"; + // The platforms that can be passed to the `--Ptarget-platform` flag. private static final String PLATFORM_ARM32 = "android-arm"; private static final String PLATFORM_ARM64 = "android-arm64"; @@ -85,6 +87,7 @@ class FlutterPlugin implements Plugin { // to match. static final String FLUTTER_BUILD_PREFIX = "flutterBuild" + private Project project private Map baseJar = [:] private File flutterRoot private File flutterExecutable @@ -92,16 +95,18 @@ class FlutterPlugin implements Plugin { private String localEngineSrcPath private Properties localProperties private File flutterJar + private String engineVersion @Override void apply(Project project) { + this.project = project + project.extensions.create("flutter", FlutterExtension) project.afterEvaluate this.&addFlutterTasks - // By default, assembling APKs generates fat APKs if multiple platforms are passed. // Configuring split per ABI allows to generate separate APKs for each abi. // This is a noop when building a bundle. - if (splitPerAbi(project)) { + if (shouldSplitPerAbi()) { project.android { splits { abi { @@ -115,14 +120,10 @@ class FlutterPlugin implements Plugin { } } } - getTargetPlatforms(project).each { targetArch -> + getTargetPlatforms().each { targetArch -> String abiValue = PLATFORM_ARCH_MAP[targetArch] project.android { - packagingOptions { - // Prevent the ELF library from getting corrupted. - doNotStrip "*/${abiValue}/libapp.so" - } - if (splitPerAbi(project)) { + if (shouldSplitPerAbi()) { splits { abi { include abiValue @@ -131,7 +132,6 @@ class FlutterPlugin implements Plugin { } } } - // Add custom build types project.android.buildTypes { profile { @@ -141,8 +141,7 @@ class FlutterPlugin implements Plugin { } } } - - String flutterRootPath = resolveProperty(project, "flutter.sdk", System.env.FLUTTER_ROOT) + String flutterRootPath = resolveProperty("flutter.sdk", System.env.FLUTTER_ROOT) if (flutterRootPath == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file or with a FLUTTER_ROOT environment variable.") } @@ -151,12 +150,14 @@ class FlutterPlugin implements Plugin { throw new GradleException("flutter.sdk must point to the Flutter SDK directory") } + engineVersion = Paths.get(flutterRoot.absolutePath, "bin", "internal", "engine.version") + .toFile().text.trim() + String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter" flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile(); - if (useLocalEngine(project)) { - String engineOutPath = project.property('localEngineOut') - File engineOut = project.file(engineOutPath) + if (useLocalEngine()) { + File engineOut = project.file(project.property('localEngineOut')) if (!engineOut.isDirectory()) { throw new GradleException('localEngineOut must point to a local engine build') } @@ -175,45 +176,48 @@ class FlutterPlugin implements Plugin { }) } } else { - String basePlatformArch = getBasePlatform(project) - // This is meant to include the compiled classes only, however it will include `libflutter.so` as well. - Path baseEnginePath = Paths.get(flutterRoot.absolutePath, "bin", "cache", "artifacts", "engine") - File debugJar = baseEnginePath.resolve("${basePlatformArch}").resolve("flutter.jar").toFile() - baseJar["debug"] = debugJar - if (!debugJar.isFile()) { - project.exec { - executable flutterExecutable.absolutePath - args "--suppress-analytics" - args "precache" - } - if (!debugJar.isFile()) { - throw new GradleException("Unable to find flutter.jar in SDK: ${debugJar}") - } - } - baseJar["profile"] = baseEnginePath.resolve("${basePlatformArch}-profile").resolve("flutter.jar").toFile() - baseJar["release"] = baseEnginePath.resolve("${basePlatformArch}-release").resolve("flutter.jar").toFile() + project.android.buildTypes.each this.&addFlutterDependencies + project.android.buildTypes.whenObjectAdded this.&addFlutterDependencies + } + } - // Add flutter.jar dependencies to all Api configurations, including custom ones - // added after applying the Flutter plugin. - project.android.buildTypes.each { - def buildMode = buildModeFor(it) - addApiDependencies(project, it.name, project.files { - baseJar[buildMode] - }) - } - project.android.buildTypes.whenObjectAdded { - def buildMode = buildModeFor(it) - addApiDependencies(project, it.name, project.files { - baseJar[buildMode] - }) + /** + * Adds the dependencies required by the Flutter project. + * This includes: + * 1. The embedding + * 2. libflutter.so + */ + void addFlutterDependencies(buildType) { + project.rootProject.allprojects { + repositories { + maven { + url MAVEN_REPO + } } } + String flutterBuildMode = buildModeFor(buildType) + // Add the embedding dependency. + addApiDependencies(project, buildType.name, + "io.flutter:flutter_embedding_$flutterBuildMode:1.0.0-$engineVersion") + + List platforms = getTargetPlatforms().collect() + // Debug mode includes x86 and x64, which are commonly used in emulators. + if (flutterBuildMode == "debug") { + platforms.add("android-x86") + platforms.add("android-x64") + } + platforms.each { platform -> + String arch = PLATFORM_ARCH_MAP[platform].replace("-", "_") + // Add the `libflutter.so` dependency. + addApiDependencies(project, buildType.name, + "io.flutter:${arch}_$flutterBuildMode:1.0.0-$engineVersion") + } } /** * Returns the directory where the plugins are built. */ - private File getPluginBuildDir(Project project) { + private File getPluginBuildDir() { // Module projects specify this flag to include plugins in the same repo as the module project. if (project.ext.has("pluginBuildDir")) { return project.ext.get("pluginBuildDir") @@ -221,13 +225,75 @@ class FlutterPlugin implements Plugin { return project.buildDir } - private Properties getPluginList(Project project) { + /** + * Configures the Flutter plugin dependencies. + * + * The plugins are added to pubspec.yaml. Then, upon running `flutter pub get`, + * the tool generates a `.flutter-plugins` file, which contains a 1:1 map to each plugin location. + * Finally, the project's `settings.gradle` loads each plugin's android directory as a subproject. + */ + private void configurePlugins() { + if (!buildPluginAsAar()) { + getPluginList().each this.&configurePlugin + return + } + addPluginTasks() + List tasksToExecute = project.gradle.startParameter.taskNames + Set buildTypes = getBuildTypesForTasks(tasksToExecute) + if (tasksToExecute.contains("clean")) { + // Because the plugins are built during configuration, the task "clean" + // cannot run in conjunction with an assembly task. + if (!buildTypes.empty) { + throw new GradleException("Can't run the clean task along with other assemble tasks") + } + } + // Build plugins when a task "assembly*" will be called later. + if (!buildTypes.empty) { + // Build the plugin during configuration. + // This is required when Jetifier is enabled, otherwise the implementation dependency + // cannot be added. + buildAarPlugins(buildTypes) + } + } + + private void configurePlugin(String name, String _) { + Project pluginProject = project.rootProject.findProject(":$name") + if (pluginProject == null) { + project.logger.error("Plugin project :$name not found. Please update settings.gradle.") + return + } + // Add plugin dependency to the app project. + project.dependencies { + if (project.getConfigurations().findByName("implementation")) { + implementation pluginProject + } else { + compile pluginProject + } + } + Closure addEmbeddingCompileOnlyDependency = { buildType -> + String flutterBuildMode = buildModeFor(buildType) + // Add the embedding as a compile only dependency to the plugin. + addFlutterJarCompileOnlyDependency(pluginProject, buildType.name, + flutterJar ?: "io.flutter:flutter_embedding_$flutterBuildMode:1.0.0-$engineVersion") + } + pluginProject.afterEvaluate { + pluginProject.android.buildTypes { + profile { + initWith debug + } + } + pluginProject.android.buildTypes.each addEmbeddingCompileOnlyDependency + pluginProject.android.buildTypes.whenObjectAdded addEmbeddingCompileOnlyDependency + } + } + + private Properties getPluginList() { File pluginsFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins') return readPropertiesIfExist(pluginsFile) } - private void addPluginTasks(Project project) { - Properties plugins = getPluginList(project) + private void addPluginTasks() { + Properties plugins = getPluginList() project.android.buildTypes.each { buildType -> plugins.each { name, path -> String buildModeValue = buildType.debuggable ? "debug" : "release" @@ -240,17 +306,17 @@ class FlutterPlugin implements Plugin { project.tasks.create(name: taskName, type: FlutterPluginTask) { flutterExecutable this.flutterExecutable buildMode buildModeValue - verbose isVerbose(project) + verbose isVerbose() pluginDir project.file(path) sourceDir project.file(project.flutter.source) - intermediateDir getPluginBuildDir(project) + intermediateDir getPluginBuildDir() } } } } } - private void buildPlugins(Project project, Set buildTypes) { + private void buildAarPlugins(Set buildTypes) { List projects = [project] // Module projects set the `hostProjects` extra property in `include_flutter.groovy`. // This is required to set the local repository in each host app project. @@ -260,7 +326,7 @@ class FlutterPlugin implements Plugin { projects.each { hostProject -> hostProject.repositories { maven { - url "${getPluginBuildDir(project)}/outputs/repo" + url "${getPluginBuildDir()}/outputs/repo" } } } @@ -295,7 +361,7 @@ class FlutterPlugin implements Plugin { * Returns a set with the build type names that apply to the given list of tasks * required to configure the plugin dependencies. */ - private Set getBuildTypesForTasks(Project project, List tasksToExecute) { + private Set getBuildTypesForTasks(List tasksToExecute) { Set buildTypes = [] tasksToExecute.each { task -> project.android.buildTypes.each { buildType -> @@ -320,7 +386,7 @@ class FlutterPlugin implements Plugin { return "${parts[0]}${parts[1..-1].collect { it.capitalize() }.join('')}" } - private String resolveProperty(Project project, String name, String defaultValue) { + private String resolveProperty(String name, String defaultValue) { if (localProperties == null) { localProperties = readPropertiesIfExist(new File(project.projectDir.parentFile, "local.properties")) } @@ -345,7 +411,7 @@ class FlutterPlugin implements Plugin { return result } - private static List getTargetPlatforms(Project project) { + private List getTargetPlatforms() { if (!project.hasProperty('target-platform')) { return DEFAULT_PLATFORMS } @@ -357,18 +423,18 @@ class FlutterPlugin implements Plugin { } } - private static Boolean splitPerAbi(Project project) { + private Boolean shouldSplitPerAbi() { if (project.hasProperty('split-per-abi')) { return project.property('split-per-abi').toBoolean() } return false; } - private static Boolean useLocalEngine(Project project) { + private Boolean useLocalEngine() { return project.hasProperty('localEngineOut') } - private static Boolean isVerbose(Project project) { + private Boolean isVerbose() { if (project.hasProperty('verbose')) { return project.property('verbose').toBoolean() } @@ -379,20 +445,7 @@ class FlutterPlugin implements Plugin { return System.getProperty('build-plugins-as-aars') == 'true' } - /** - * Returns the platform that is used to extract the `libflutter.so` and the .class files. - * - * Note: This is only needed to add the .class files. - * Unfortunately, the engine artifacts include the .class and libflutter.so files. - */ - private static String getBasePlatform(Project project) { - if (PLATFORM_ARM64 in getTargetPlatforms(project)) { - return PLATFORM_ARM64; - } - return PLATFORM_ARM32; - } - - private void addFlutterJarCompileOnlyDependency(Project project, String variantName, FileCollection files) { + private void addFlutterJarCompileOnlyDependency(Project project, String variantName, Object dependency) { if (project.state.failure) { return } @@ -402,7 +455,7 @@ class FlutterPlugin implements Plugin { } else { configuration = "${variantName}Provided"; } - project.dependencies.add(configuration, files) + project.dependencies.add(configuration, dependency) } private static void addApiDependencies(Project project, String variantName, Object dependency, Closure config = null) { @@ -455,7 +508,6 @@ class FlutterPlugin implements Plugin { if (project.hasProperty('target')) { target = project.property('target') } - String[] fileSystemRootsValue = null if (project.hasProperty('filesystem-roots')) { fileSystemRootsValue = project.property('filesystem-roots').split('\\|') @@ -492,10 +544,9 @@ class FlutterPlugin implements Plugin { if (project.hasProperty('extra-gen-snapshot-options')) { extraGenSnapshotOptionsValue = project.property('extra-gen-snapshot-options') } - - def targetPlatforms = getTargetPlatforms(project) + def targetPlatforms = getTargetPlatforms() def addFlutterDeps = { variant -> - if (splitPerAbi(project)) { + if (shouldSplitPerAbi()) { variant.outputs.each { output -> // Assigns the new version code to versionCodeOverride, which changes the version code // for only the output APK, not for the variant itself. Skipping this step simply @@ -508,31 +559,17 @@ class FlutterPlugin implements Plugin { } } } - - String flutterBuildMode = buildModeFor(variant.buildType) - if (flutterBuildMode == 'debug' && project.tasks.findByName("${FLUTTER_BUILD_PREFIX}X86Jar")) { - Task task = project.tasks.findByName("compile${variant.name.capitalize()}JavaWithJavac") - if (task) { - task.dependsOn project.flutterBuildX86Jar - } - task = project.tasks.findByName("compile${variant.name.capitalize()}Kotlin") - if (task) { - task.dependsOn project.flutterBuildX86Jar - } - } - def compileTasks = targetPlatforms.collect { targetArch -> - String abiValue = PLATFORM_ARCH_MAP[targetArch] String taskName = toCammelCase(["compile", FLUTTER_BUILD_PREFIX, variant.name, targetArch.replace('android-', '')]) - FlutterTask compileTask = project.tasks.create(name: taskName, type: FlutterTask) { + project.tasks.create(name: taskName, type: FlutterTask) { flutterRoot this.flutterRoot flutterExecutable this.flutterExecutable - buildMode flutterBuildMode + buildMode buildModeFor(variant.buildType) localEngine this.localEngine localEngineSrcPath this.localEngineSrcPath - abi abiValue + abi PLATFORM_ARCH_MAP[targetArch] targetPath target - verbose isVerbose(project) + verbose isVerbose() fileSystemRoots fileSystemRootsValue fileSystemScheme fileSystemSchemeValue trackWidgetCreation trackWidgetCreationValue @@ -547,51 +584,24 @@ class FlutterPlugin implements Plugin { extraGenSnapshotOptions extraGenSnapshotOptionsValue } } - def libJar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/libs.jar") - def libFlutterPlatforms = targetPlatforms.collect() - // x86/x86_64 native library used for debugging only, for now. - if (flutterBuildMode == 'debug') { - libFlutterPlatforms.add('android-x86') - libFlutterPlatforms.add('android-x64') - } - Task packFlutterSnapshotsAndLibsTask = project.tasks.create(name: "packLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}", type: Jar) { + Task packFlutterAppAotTask = project.tasks.create(name: "packLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}", type: Jar) { destinationDir libJar.parentFile archiveName libJar.name - libFlutterPlatforms.each { targetArch -> - // This check prevents including `libflutter.so` twice, since it's included in the base platform jar. - // Unfortunately, the `pickFirst` setting in `packagingOptions` does not work when the project `:flutter` - // is included as an implementation dependency, which causes duplicated `libflutter.so`. - if (getBasePlatform(project) == targetArch) { - return - } - // Don't include `libflutter.so` for other architectures when a local engine is specified. - if (useLocalEngine(project)) { - return - } - def engineArtifactSubdir = getEngineArtifactDirName(variant.buildType, targetArch); - // Include `libflutter.so`. - // TODO(blasten): The libs should be outside `flutter.jar` when the artifacts are downloaded. - from(project.zipTree("${flutterRoot}/bin/cache/artifacts/engine/${engineArtifactSubdir}/flutter.jar")) { - include 'lib/**' - } - } dependsOn compileTasks - // Add the ELF library. compileTasks.each { compileTask -> from(compileTask.intermediateDir) { include '*.so' + // Move `app.so` to `lib//libapp.so` rename { String filename -> return "lib/${compileTask.abi}/lib${filename}" } } } } - // Include the snapshots and libflutter.so in `lib/`. addApiDependencies(project, variant.name, project.files { - packFlutterSnapshotsAndLibsTask + packFlutterAppAotTask }) - // We know that the flutter app is a subproject in another Android app when these tasks exist. Task packageAssets = project.tasks.findByPath(":flutter:package${variant.name.capitalize()}Assets") Task cleanPackageAssets = project.tasks.findByPath(":flutter:cleanPackage${variant.name.capitalize()}Assets") @@ -618,57 +628,7 @@ class FlutterPlugin implements Plugin { } else { project.android.libraryVariants.all addFlutterDeps } - - if (buildPluginAsAar()) { - addPluginTasks(project) - - List tasksToExecute = project.gradle.startParameter.taskNames - Set buildTypes = getBuildTypesForTasks(project, tasksToExecute) - if (tasksToExecute.contains("clean")) { - // Because the plugins are built during configuration, the task "clean" - // cannot run in conjunction with an assembly task. - if (!buildTypes.empty) { - throw new GradleException("Can't run the clean task along with other assemble tasks") - } - } - // Build plugins when a task "assembly*" will be called later. - if (!buildTypes.empty) { - // Build the plugin during configuration. - // This is required when Jetifier is enabled, otherwise the implementation dependency - // cannot be added. - buildPlugins(project, buildTypes) - } - } else { - getPluginList(project).each { name, _ -> - def pluginProject = project.rootProject.findProject(":$name") - if (pluginProject != null) { - project.dependencies { - if (project.getConfigurations().findByName("implementation")) { - implementation pluginProject - } else { - compile pluginProject - } - } - pluginProject.afterEvaluate { - pluginProject.android.buildTypes { - profile { - initWith debug - } - } - pluginProject.android.buildTypes.each { - def buildMode = buildModeFor(it) - addFlutterJarCompileOnlyDependency(pluginProject, it.name, project.files( flutterJar ?: baseJar[buildMode] )) - } - pluginProject.android.buildTypes.whenObjectAdded { - def buildMode = buildModeFor(it) - addFlutterJarCompileOnlyDependency(pluginProject, it.name, project.files( flutterJar ?: baseJar[buildMode] )) - } - } - } else { - project.logger.error("Plugin project :$name not found. Please update settings.gradle.") - } - } - } + configurePlugins() } } diff --git a/packages/flutter_tools/gradle/resolve_dependencies.gradle b/packages/flutter_tools/gradle/resolve_dependencies.gradle new file mode 100644 index 0000000000..2061bc8aec --- /dev/null +++ b/packages/flutter_tools/gradle/resolve_dependencies.gradle @@ -0,0 +1,64 @@ +// Copyright 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. +// +// +// This script is used to warm the Gradle cache by downloading the Flutter dependencies +// used during the build. This script is invoked when `flutter precache` is run. +// +// Command: +// gradle -b packages/flutter_tools/gradle/resolve_dependencies.gradle +// resolveDependencies +// +// This way, Gradle can run with the `--offline` flag later on to eliminate any +// network request during the build process. +// +// This includes: +// 1. The embedding +// 2. libflutter.so + +import java.nio.file.Paths + +repositories { + google() + jcenter() + maven { + url "http://download.flutter.io" + } +} + +File flutterRoot = projectDir.parentFile.parentFile.parentFile + +assert flutterRoot.isDirectory() +String engineVersion = Paths.get(flutterRoot.absolutePath, "bin", "internal", "engine.version") + .toFile().text.trim() + +configurations { + flutterRelease.extendsFrom releaseImplementation + flutterDebug.extendsFrom debugImplementation + flutterProfile.extendsFrom debugImplementation +} + +dependencies { + flutterRelease "io.flutter:flutter_embedding_release:1.0.0-$engineVersion" + flutterRelease "io.flutter:armeabi_v7a_release:1.0.0-$engineVersion" + flutterRelease "io.flutter:arm64_v8a_release:1.0.0-$engineVersion" + + flutterProfile "io.flutter:flutter_embedding_profile:1.0.0-$engineVersion" + flutterProfile "io.flutter:armeabi_v7a_profile:1.0.0-$engineVersion" + flutterProfile "io.flutter:arm64_v8a_profile:1.0.0-$engineVersion" + + flutterDebug "io.flutter:flutter_embedding_debug:1.0.0-$engineVersion" + flutterDebug "io.flutter:armeabi_v7a_debug:1.0.0-$engineVersion" + flutterDebug "io.flutter:arm64_v8a_debug:1.0.0-$engineVersion" + flutterDebug "io.flutter:x86_debug:1.0.0-$engineVersion" + flutterDebug "io.flutter:x86_64_debug:1.0.0-$engineVersion" +} + +task resolveDependencies { + configurations.each { configuration -> + if (configuration.name.startsWith("flutter")) { + configuration.resolve() + } + } +} diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart index 607291788b..fec973cd05 100644 --- a/packages/flutter_tools/lib/executable.dart +++ b/packages/flutter_tools/lib/executable.dart @@ -84,7 +84,7 @@ Future main(List args) async { LogsCommand(), MakeHostAppEditableCommand(), PackagesCommand(), - PrecacheCommand(), + PrecacheCommand(verboseHelp: verboseHelp), RunCommand(verboseHelp: verboseHelp), ScreenshotCommand(), ShellCompletionCommand(), diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart index 05d6641979..7b1eac39a8 100644 --- a/packages/flutter_tools/lib/src/cache.dart +++ b/packages/flutter_tools/lib/src/cache.dart @@ -6,14 +6,16 @@ import 'dart:async'; import 'package:meta/meta.dart'; +import 'android/gradle.dart'; import 'base/common.dart'; import 'base/context.dart'; import 'base/file_system.dart'; -import 'base/io.dart' show SocketException; +import 'base/io.dart' show Process, SocketException; import 'base/logger.dart'; import 'base/net.dart'; import 'base/os.dart'; import 'base/platform.dart'; +import 'base/process.dart'; import 'globals.dart'; /// A tag for a set of development artifacts that need to be cached. @@ -30,7 +32,10 @@ class DevelopmentArtifact { final bool unstable; /// Artifacts required for Android development. - static const DevelopmentArtifact android = DevelopmentArtifact._('android'); + static const DevelopmentArtifact androidGenSnapshot = DevelopmentArtifact._('android_gen_snapshot'); + static const DevelopmentArtifact androidMaven = DevelopmentArtifact._('android_maven'); + // Artifacts used for internal builds. + static const DevelopmentArtifact androidInternalBuild = DevelopmentArtifact._('android_internal_build'); /// Artifacts required for iOS development. static const DevelopmentArtifact iOS = DevelopmentArtifact._('ios'); @@ -58,7 +63,9 @@ class DevelopmentArtifact { /// The values of DevelopmentArtifacts. static final List values = [ - android, + androidGenSnapshot, + androidMaven, + androidInternalBuild, iOS, web, macOS, @@ -74,12 +81,16 @@ class DevelopmentArtifact { class Cache { /// [rootOverride] is configurable for testing. /// [artifacts] is configurable for testing. - Cache({ Directory rootOverride, List artifacts }) : _rootOverride = rootOverride { + Cache({ Directory rootOverride, List artifacts }) : _rootOverride = rootOverride { if (artifacts == null) { _artifacts.add(MaterialFonts(this)); - _artifacts.add(AndroidEngineArtifacts(this)); - _artifacts.add(IOSEngineArtifacts(this)); + _artifacts.add(GradleWrapper(this)); + _artifacts.add(AndroidMavenArtifacts()); + _artifacts.add(AndroidGenSnapshotArtifacts(this)); + _artifacts.add(AndroidInternalBuildArtifacts(this)); + + _artifacts.add(IOSEngineArtifacts(this)); _artifacts.add(FlutterWebSdk(this)); _artifacts.add(FlutterSdk(this)); _artifacts.add(WindowsEngineArtifacts(this)); @@ -101,7 +112,7 @@ class Cache { ]; final Directory _rootOverride; - final List _artifacts = []; + final List _artifacts = []; // Initialized by FlutterCommandRunner on startup. static String flutterRoot; @@ -245,11 +256,16 @@ class Cache { return _dyLdLibEntry; } final List paths = []; - for (CachedArtifact artifact in _artifacts) { - final String currentPath = artifact.dyLdLibPath; - if (currentPath.isNotEmpty) { - paths.add(currentPath); + for (ArtifactSet artifact in _artifacts) { + final Map env = artifact.environment; + if (env == null || !env.containsKey('DYLD_LIBRARY_PATH')) { + continue; } + final String path = env['DYLD_LIBRARY_PATH']; + if (path.isEmpty) { + continue; + } + paths.add(path); } _dyLdLibEntry = MapEntry('DYLD_LIBRARY_PATH', paths.join(':')); return _dyLdLibEntry; @@ -289,7 +305,7 @@ class Cache { return isOlderThanReference(entity: entity, referenceFile: flutterToolsStamp); } - bool isUpToDate() => _artifacts.every((CachedArtifact artifact) => artifact.isUpToDate()); + bool isUpToDate() => _artifacts.every((ArtifactSet artifact) => artifact.isUpToDate()); Future getThirdPartyFile(String urlStr, String serviceName) async { final Uri url = Uri.parse(urlStr); @@ -318,22 +334,27 @@ class Cache { if (!_lockEnabled) { return; } - try { - for (CachedArtifact artifact in _artifacts) { - if (!artifact.isUpToDate()) { - await artifact.update(requiredArtifacts); + for (ArtifactSet artifact in _artifacts) { + if (!requiredArtifacts.contains(artifact.developmentArtifact)) { + printTrace('Artifact $artifact is not required, skipping update.'); + continue; + } + if (artifact.isUpToDate()) { + continue; + } + try { + await artifact.update(); + } on SocketException catch (e) { + if (_hostsBlockedInChina.contains(e.address?.host)) { + printError( + 'Failed to retrieve Flutter tool dependencies: ${e.message}.\n' + 'If you\'re in China, please see this page: ' + 'https://flutter.dev/community/china', + emphasis: true, + ); } + rethrow; } - } on SocketException catch (e) { - if (_hostsBlockedInChina.contains(e.address?.host)) { - printError( - 'Failed to retrieve Flutter tool dependencies: ${e.message}.\n' - 'If you\'re in China, please see this page: ' - 'https://flutter.dev/community/china', - emphasis: true, - ); - } - rethrow; } } @@ -354,23 +375,42 @@ class Cache { } } -/// An artifact managed by the cache. -abstract class CachedArtifact { - CachedArtifact(this.name, this.cache, this.developmentArtifacts); +/// Representation of a set of artifacts used by the tool. +abstract class ArtifactSet { + ArtifactSet(this.developmentArtifact) : assert(developmentArtifact != null); + + /// The development artifact. + final DevelopmentArtifact developmentArtifact; + + /// [true] if the artifact is up to date. + bool isUpToDate(); + + /// The environment variables (if any) required to consume the artifacts. + Map get environment { + return const {}; + } + + /// Updates the artifact. + Future update(); +} + +/// An artifact set managed by the cache. +abstract class CachedArtifact extends ArtifactSet { + CachedArtifact( + this.name, + this.cache, + DevelopmentArtifact developmentArtifact, + ) : super(developmentArtifact); - final String name; final Cache cache; + /// The canonical name of the artifact. + final String name; + // The name of the stamp file. Defaults to the same as the // artifact name. String get stampName => name; - /// Returns a string to be set as environment DYLD_LIBARY_PATH variable - String get dyLdLibPath => ''; - - /// All development artifacts this cache provides. - final Set developmentArtifacts; - Directory get location => cache.getArtifactDirectory(name); String get version => cache.getVersionFor(name); @@ -380,6 +420,7 @@ abstract class CachedArtifact { /// starting from scratch. final List _downloadedFiles = []; + @override bool isUpToDate() { if (!location.existsSync()) { return false; @@ -390,13 +431,8 @@ abstract class CachedArtifact { return isUpToDateInner(); } - Future update(Set requiredArtifacts) async { - // If the set of required artifacts does not include any from this cache, - // then we can claim we are up to date to skip downloading. - if (!requiredArtifacts.any(developmentArtifacts.contains)) { - printTrace('Artifact $this is not required, skipping update.'); - return; - } + @override + Future update() async { if (!location.existsSync()) { try { location.createSync(recursive: true); @@ -499,7 +535,7 @@ class MaterialFonts extends CachedArtifact { MaterialFonts(Cache cache) : super( 'material_fonts', cache, - const { DevelopmentArtifact.universal }, + DevelopmentArtifact.universal, ); @override @@ -517,7 +553,7 @@ class FlutterWebSdk extends CachedArtifact { FlutterWebSdk(Cache cache) : super( 'flutter_web_sdk', cache, - const { DevelopmentArtifact.web }, + DevelopmentArtifact.web, ); @override @@ -558,8 +594,8 @@ abstract class EngineCachedArtifact extends CachedArtifact { EngineCachedArtifact( this.stampName, Cache cache, - Set requiredArtifacts, - ) : super('engine', cache, requiredArtifacts); + DevelopmentArtifact developmentArtifact, + ) : super('engine', cache, developmentArtifact); @override final String stampName; @@ -680,7 +716,7 @@ class FlutterSdk extends EngineCachedArtifact { FlutterSdk(Cache cache) : super( 'flutter_sdk', cache, - const { DevelopmentArtifact.universal }, + DevelopmentArtifact.universal, ); @override @@ -714,7 +750,7 @@ class MacOSEngineArtifacts extends EngineCachedArtifact { MacOSEngineArtifacts(Cache cache) : super( 'macos-sdk', cache, - const { DevelopmentArtifact.macOS }, + DevelopmentArtifact.macOS, ); @override @@ -736,7 +772,7 @@ class WindowsEngineArtifacts extends EngineCachedArtifact { WindowsEngineArtifacts(Cache cache) : super( 'windows-sdk', cache, - const { DevelopmentArtifact.windows }, + DevelopmentArtifact.windows, ); @override @@ -758,7 +794,7 @@ class LinuxEngineArtifacts extends EngineCachedArtifact { LinuxEngineArtifacts(Cache cache) : super( 'linux-sdk', cache, - const { DevelopmentArtifact.linux }, + DevelopmentArtifact.linux, ); @override @@ -776,11 +812,12 @@ class LinuxEngineArtifacts extends EngineCachedArtifact { List getLicenseDirs() => const []; } -class AndroidEngineArtifacts extends EngineCachedArtifact { - AndroidEngineArtifacts(Cache cache) : super( +/// The artifact used to generate snapshots for Android builds. +class AndroidGenSnapshotArtifacts extends EngineCachedArtifact { + AndroidGenSnapshotArtifacts(Cache cache) : super( 'android-sdk', cache, - const { DevelopmentArtifact.android }, + DevelopmentArtifact.androidGenSnapshot, ); @override @@ -794,23 +831,19 @@ class AndroidEngineArtifacts extends EngineCachedArtifact { ..._osxBinaryDirs, ..._linuxBinaryDirs, ..._windowsBinaryDirs, - ..._androidBinaryDirs, ..._dartSdks, ] else if (platform.isWindows) ...>[ ..._windowsBinaryDirs, - ..._androidBinaryDirs, ] else if (platform.isMacOS) ...>[ ..._osxBinaryDirs, - ..._androidBinaryDirs, ] else if (platform.isLinux) ...>[ ..._linuxBinaryDirs, - ..._androidBinaryDirs, ] ]; } @@ -819,11 +852,79 @@ class AndroidEngineArtifacts extends EngineCachedArtifact { List getLicenseDirs() { return []; } } +/// Artifacts used for internal builds. The flutter tool builds Android projects +/// using the artifacts cached by [AndroidMavenArtifacts]. +class AndroidInternalBuildArtifacts extends EngineCachedArtifact { + AndroidInternalBuildArtifacts(Cache cache) : super( + 'android-internal-build-artifacts', + cache, + DevelopmentArtifact.androidInternalBuild, + ); + + @override + List getPackageDirs() => const []; + + @override + List> getBinaryDirs() { + return _androidBinaryDirs; + } + + @override + List getLicenseDirs() { return []; } +} + +/// A cached artifact containing the Maven dependencies used to build Android projects. +class AndroidMavenArtifacts extends ArtifactSet { + AndroidMavenArtifacts() : super(DevelopmentArtifact.androidMaven); + + @override + Future update() async { + final Directory tempDir = + fs.systemTempDirectory.createTempSync('gradle_wrapper.'); + injectGradleWrapperIfNeeded(tempDir); + + final Status status = logger.startProgress('Downloading Android Maven dependencies...', + timeout: timeoutConfiguration.slowOperation); + final File gradle = tempDir.childFile( + platform.isWindows ? 'gradlew.bat' : 'gradlew', + ); + assert(gradle.existsSync()); + os.makeExecutable(gradle); + + try { + final String gradleExecutable = gradle.absolute.path; + final String flutterSdk = escapePath(Cache.flutterRoot); + final Process process = await runCommand( + [ + gradleExecutable, + '-b', fs.path.join(flutterSdk, 'packages', 'flutter_tools', 'gradle', 'resolve_dependencies.gradle'), + '--project-cache-dir', tempDir.path, + 'resolveDependencies', + ]); + final int exitCode = await process.exitCode; + if (exitCode != 0) { + printError('Failed to download the Android dependencies'); + } + } finally { + status.stop(); + tempDir.deleteSync(recursive: true); + } + } + + @override + bool isUpToDate() { + // The dependencies are downloaded and cached by Gradle. + // The tool doesn't know if the dependencies are already cached at this point. + // Therefore, call Gradle to figure this out. + return false; + } +} + class IOSEngineArtifacts extends EngineCachedArtifact { IOSEngineArtifacts(Cache cache) : super( 'ios-sdk', cache, - { DevelopmentArtifact.iOS }, + DevelopmentArtifact.iOS, ); @override @@ -856,7 +957,7 @@ class GradleWrapper extends CachedArtifact { GradleWrapper(Cache cache) : super( 'gradle_wrapper', cache, - const { DevelopmentArtifact.universal }, + DevelopmentArtifact.universal, ); List get _gradleScripts => ['gradlew', 'gradlew.bat']; @@ -897,11 +998,13 @@ class GradleWrapper extends CachedArtifact { /// Common functionality for pulling Fuchsia SDKs. abstract class _FuchsiaSDKArtifacts extends CachedArtifact { - _FuchsiaSDKArtifacts(Cache cache, String platform) - :_path = 'fuchsia/sdk/core/$platform-amd64', - super('fuchsia-$platform', cache, const { - DevelopmentArtifact.fuchsia, - }); + _FuchsiaSDKArtifacts(Cache cache, String platform) : + _path = 'fuchsia/sdk/core/$platform-amd64', + super( + 'fuchsia-$platform', + cache, + DevelopmentArtifact.fuchsia, + ); final String _path; @@ -917,10 +1020,11 @@ abstract class _FuchsiaSDKArtifacts extends CachedArtifact { /// The pre-built flutter runner for Fuchsia development. class FlutterRunnerSDKArtifacts extends CachedArtifact { - FlutterRunnerSDKArtifacts(Cache cache) - : super('flutter_runner', cache, const { + FlutterRunnerSDKArtifacts(Cache cache) : super( + 'flutter_runner', + cache, DevelopmentArtifact.flutterRunner, - }); + ); @override Directory get location => cache.getArtifactDirectory('flutter_runner'); @@ -971,7 +1075,7 @@ class IosUsbArtifacts extends CachedArtifact { name, cache, // This is universal to ensure every command checks for them first - const { DevelopmentArtifact.universal }, + DevelopmentArtifact.universal, ); static const List artifactNames = [ @@ -984,8 +1088,10 @@ class IosUsbArtifacts extends CachedArtifact { ]; @override - String get dyLdLibPath { - return cache.getArtifactDirectory(name).path; + Map get environment { + return { + 'DYLD_LIBRARY_PATH': cache.getArtifactDirectory(name).path, + }; } @override @@ -1093,6 +1199,12 @@ const List> _windowsBinaryDirs = >[ ['android-arm64-release/windows-x64', 'android-arm64-release/windows-x64.zip'], ]; +const List> _iosBinaryDirs = >[ + ['ios', 'ios/artifacts.zip'], + ['ios-profile', 'ios-profile/artifacts.zip'], + ['ios-release', 'ios-release/artifacts.zip'], +]; + const List> _androidBinaryDirs = >[ ['android-x86', 'android-x86/artifacts.zip'], ['android-x64', 'android-x64/artifacts.zip'], @@ -1104,12 +1216,6 @@ const List> _androidBinaryDirs = >[ ['android-arm64-release', 'android-arm64-release/artifacts.zip'], ]; -const List> _iosBinaryDirs = >[ - ['ios', 'ios/artifacts.zip'], - ['ios-profile', 'ios-profile/artifacts.zip'], - ['ios-release', 'ios-release/artifacts.zip'], -]; - const List> _dartSdks = > [ ['darwin-x64', 'dart-sdk-darwin-x64.zip'], ['linux-x64', 'dart-sdk-linux-x64.zip'], diff --git a/packages/flutter_tools/lib/src/commands/build_aar.dart b/packages/flutter_tools/lib/src/commands/build_aar.dart index a7ef6750a3..fd9eb3a43e 100644 --- a/packages/flutter_tools/lib/src/commands/build_aar.dart +++ b/packages/flutter_tools/lib/src/commands/build_aar.dart @@ -9,7 +9,7 @@ import '../base/os.dart'; import '../build_info.dart'; import '../project.dart'; import '../reporting/reporting.dart'; -import '../runner/flutter_command.dart' show DevelopmentArtifact, FlutterCommandResult; +import '../runner/flutter_command.dart' show FlutterCommandResult; import 'build.dart'; class BuildAarCommand extends BuildSubCommand { @@ -52,12 +52,6 @@ class BuildAarCommand extends BuildSubCommand { return usage; } - @override - Future> get requiredArtifacts async => const { - DevelopmentArtifact.universal, - DevelopmentArtifact.android, - }; - @override final String description = 'Build a repository containing an AAR and a POM file.\n\n' 'The POM file is used to include the dependencies that the AAR was compiled against.\n\n' diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart index 95b230ce09..51824f6206 100644 --- a/packages/flutter_tools/lib/src/commands/build_apk.dart +++ b/packages/flutter_tools/lib/src/commands/build_apk.dart @@ -10,7 +10,7 @@ import '../build_info.dart'; import '../globals.dart'; import '../project.dart'; import '../reporting/reporting.dart'; -import '../runner/flutter_command.dart' show DevelopmentArtifact, FlutterCommandResult; +import '../runner/flutter_command.dart' show FlutterCommandResult; import 'build.dart'; class BuildApkCommand extends BuildSubCommand { @@ -68,12 +68,6 @@ class BuildApkCommand extends BuildSubCommand { return usage; } - @override - Future> get requiredArtifacts async => const { - DevelopmentArtifact.universal, - DevelopmentArtifact.android, - }; - @override Future runCommand() async { final BuildInfo buildInfo = getBuildInfo(); diff --git a/packages/flutter_tools/lib/src/commands/doctor.dart b/packages/flutter_tools/lib/src/commands/doctor.dart index b2d2b516f3..7a3f06ee38 100644 --- a/packages/flutter_tools/lib/src/commands/doctor.dart +++ b/packages/flutter_tools/lib/src/commands/doctor.dart @@ -37,7 +37,7 @@ class DoctorCommand extends FlutterCommand { // This is required because we use gen_snapshot to check if the host // machine can execute the provided artifacts. See `_genSnapshotRuns` // in `doctor.dart`. - DevelopmentArtifact.android, + DevelopmentArtifact.androidGenSnapshot, }; } diff --git a/packages/flutter_tools/lib/src/commands/precache.dart b/packages/flutter_tools/lib/src/commands/precache.dart index 473fea5fd9..e7f7103923 100644 --- a/packages/flutter_tools/lib/src/commands/precache.dart +++ b/packages/flutter_tools/lib/src/commands/precache.dart @@ -10,13 +10,23 @@ import '../runner/flutter_command.dart'; import '../version.dart'; class PrecacheCommand extends FlutterCommand { - PrecacheCommand() { + PrecacheCommand({bool verboseHelp = false}) { argParser.addFlag('all-platforms', abbr: 'a', negatable: false, help: 'Precache artifacts for all host platforms.'); argParser.addFlag('force', abbr: 'f', negatable: false, help: 'Force downloading of artifacts.'); argParser.addFlag('android', negatable: true, defaultsTo: true, - help: 'Precache artifacts for Android development.'); + help: 'Precache artifacts for Android development.', + hide: verboseHelp); + argParser.addFlag('android_gen_snapshot', negatable: true, defaultsTo: true, + help: 'Precache gen_snapshot for Android development.', + hide: !verboseHelp); + argParser.addFlag('android_maven', negatable: true, defaultsTo: true, + help: 'Precache Gradle dependencies for Android development.', + hide: !verboseHelp); + argParser.addFlag('android_internal_build', negatable: true, defaultsTo: false, + help: 'Precache dependencies for internal Android development.', + hide: !verboseHelp); argParser.addFlag('ios', negatable: true, defaultsTo: true, help: 'Precache artifacts for iOS development.'); argParser.addFlag('web', negatable: true, defaultsTo: false, @@ -58,6 +68,10 @@ class PrecacheCommand extends FlutterCommand { if (argResults[artifact.name]) { requiredArtifacts.add(artifact); } + // The `android` flag expands to android_gen_snapshot, android_maven, android_internal_build. + if (artifact.name.startsWith('android_') && argResults['android']) { + requiredArtifacts.add(artifact); + } } final bool forceUpdate = argResults['force']; if (forceUpdate || !cache.isUpToDate()) { diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index c82a00d387..a2e4dfe4a3 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -644,7 +644,7 @@ DevelopmentArtifact _artifactFromTargetPlatform(TargetPlatform targetPlatform) { case TargetPlatform.android_arm64: case TargetPlatform.android_x64: case TargetPlatform.android_x86: - return DevelopmentArtifact.android; + return DevelopmentArtifact.androidGenSnapshot; case TargetPlatform.web_javascript: return DevelopmentArtifact.web; case TargetPlatform.ios: diff --git a/packages/flutter_tools/templates/module/android/host_app_common/app.tmpl/build.gradle.tmpl b/packages/flutter_tools/templates/module/android/host_app_common/app.tmpl/build.gradle.tmpl index 44f4667baf..926beae221 100644 --- a/packages/flutter_tools/templates/module/android/host_app_common/app.tmpl/build.gradle.tmpl +++ b/packages/flutter_tools/templates/module/android/host_app_common/app.tmpl/build.gradle.tmpl @@ -47,9 +47,9 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' {{/androidX}} {{^androidX}} - implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.2' - implementation 'com.android.support:design:27.1.1' + implementation 'com.android.support:design:28.0.0' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' {{/androidX}} diff --git a/packages/flutter_tools/test/general.shard/cache_test.dart b/packages/flutter_tools/test/general.shard/cache_test.dart index aa9d940911..417efaa5b6 100644 --- a/packages/flutter_tools/test/general.shard/cache_test.dart +++ b/packages/flutter_tools/test/general.shard/cache_test.dart @@ -8,8 +8,10 @@ import 'package:file_testing/file_testing.dart'; import 'package:meta/meta.dart'; import 'package:mockito/mockito.dart'; import 'package:platform/platform.dart'; +import 'package:process/process.dart'; import 'package:flutter_tools/src/base/common.dart'; +import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart' show InternetAddress, SocketException; @@ -136,18 +138,30 @@ void main() { when(artifact1.isUpToDate()).thenReturn(true); when(artifact2.isUpToDate()).thenReturn(false); final Cache cache = Cache(artifacts: [artifact1, artifact2]); - await cache.updateAll({}); - verifyNever(artifact1.update({})); - verify(artifact2.update({})); + await cache.updateAll({ + null, + }); + verifyNever(artifact1.update()); + verify(artifact2.update()); }); testUsingContext('getter dyLdLibEntry concatenates the output of each artifact\'s dyLdLibEntry getter', () async { - final CachedArtifact artifact1 = MockCachedArtifact(); - final CachedArtifact artifact2 = MockCachedArtifact(); - final CachedArtifact artifact3 = MockCachedArtifact(); - when(artifact1.dyLdLibPath).thenReturn('/path/to/alpha:/path/to/beta'); - when(artifact2.dyLdLibPath).thenReturn('/path/to/gamma:/path/to/delta:/path/to/epsilon'); - when(artifact3.dyLdLibPath).thenReturn(''); // Empty output + final IosUsbArtifacts artifact1 = MockIosUsbArtifacts(); + final IosUsbArtifacts artifact2 = MockIosUsbArtifacts(); + final IosUsbArtifacts artifact3 = MockIosUsbArtifacts(); + when(artifact1.environment) + .thenReturn({ + 'DYLD_LIBRARY_PATH': '/path/to/alpha:/path/to/beta', + }); + when(artifact2.environment) + .thenReturn({ + 'DYLD_LIBRARY_PATH': '/path/to/gamma:/path/to/delta:/path/to/epsilon', + }); + when(artifact3.environment) + .thenReturn({ + 'DYLD_LIBRARY_PATH': '', + }); final Cache cache = Cache(artifacts: [artifact1, artifact2, artifact3]); + expect(cache.dyLdLibEntry.key, 'DYLD_LIBRARY_PATH'); expect( cache.dyLdLibEntry.value, @@ -163,18 +177,20 @@ void main() { when(artifact2.isUpToDate()).thenReturn(false); final MockInternetAddress address = MockInternetAddress(); when(address.host).thenReturn('storage.googleapis.com'); - when(artifact1.update({})).thenThrow(SocketException( + when(artifact1.update()).thenThrow(SocketException( 'Connection reset by peer', address: address, )); final Cache cache = Cache(artifacts: [artifact1, artifact2]); try { - await cache.updateAll({}); + await cache.updateAll({ + null, + }); fail('Mock thrown exception expected'); } catch (e) { - verify(artifact1.update({})); + verify(artifact1.update()); // Don't continue when retrieval fails. - verifyNever(artifact2.update({})); + verifyNever(artifact2.update()); expect( testLogger.errorText, contains('https://flutter.dev/community/china'), @@ -226,6 +242,7 @@ void main() { binaryDirs: >[ ['bin_dir', 'unused_url_path'], ], + requiredArtifacts: DevelopmentArtifact.universal, ); await artifact.updateInner(); final Directory dir = memoryFileSystem.systemTempDirectory @@ -244,25 +261,56 @@ void main() { }); }); - testUsingContext('throws tool exit on fs exception', () async { - final FakeCachedArtifact fakeCachedArtifact = FakeCachedArtifact( - cache: MockCache(), - requiredArtifacts: { - DevelopmentArtifact.android, - } - ); - final Directory mockDirectory = MockDirectory(); - when(fakeCachedArtifact.cache.getArtifactDirectory(any)) - .thenReturn(mockDirectory); - when(mockDirectory.existsSync()).thenReturn(false); - when(mockDirectory.createSync(recursive: true)) - .thenThrow(const FileSystemException()); + group('AndroidMavenArtifacts', () { + MemoryFileSystem memoryFileSystem; + MockProcessManager processManager; + MockCache mockCache; + MockProcess mockProcess; - expect(() => fakeCachedArtifact.update({ - DevelopmentArtifact.android, - }), throwsA(isInstanceOf())); - }, overrides: { - FileSystem: () => MemoryFileSystem(), + setUp(() { + memoryFileSystem = MemoryFileSystem(); + processManager = MockProcessManager(); + mockCache = MockCache(); + mockProcess = MockProcess(); + }); + + test('development artifact', () async { + final AndroidMavenArtifacts mavenArtifacts = AndroidMavenArtifacts(); + expect(mavenArtifacts.developmentArtifact, DevelopmentArtifact.androidMaven); + }); + + testUsingContext('update', () async { + final AndroidMavenArtifacts mavenArtifacts = AndroidMavenArtifacts(); + expect(mavenArtifacts.isUpToDate(), isFalse); + + final Directory gradleWrapperDir = fs.systemTempDirectory.createTempSync('gradle_wrapper.'); + when(mockCache.getArtifactDirectory('gradle_wrapper')).thenReturn(gradleWrapperDir); + + fs.directory(gradleWrapperDir.childDirectory('gradle').childDirectory('wrapper')) + .createSync(recursive: true); + fs.file(fs.path.join(gradleWrapperDir.path, 'gradlew')).writeAsStringSync('irrelevant'); + fs.file(fs.path.join(gradleWrapperDir.path, 'gradlew.bat')).writeAsStringSync('irrelevant'); + + when(processManager.start(any)) + .thenAnswer((Invocation invocation){ + final List args = invocation.positionalArguments[0]; + expect(args.length, 6); + expect(args[1], '-b'); + expect(args[2].endsWith('resolve_dependencies.gradle'), isTrue); + expect(args[5], 'resolveDependencies'); + + return Future.value(mockProcess); + }); + when(mockProcess.exitCode).thenAnswer((_) async => 0); + + await mavenArtifacts.update(); + + expect(mavenArtifacts.isUpToDate(), isFalse); + }, overrides: { + Cache: ()=> mockCache, + FileSystem: () => memoryFileSystem, + ProcessManager: () => processManager, + }); }); } @@ -270,7 +318,7 @@ class FakeCachedArtifact extends EngineCachedArtifact { FakeCachedArtifact({ String stampName = 'STAMP', @required Cache cache, - Set requiredArtifacts = const {}, + DevelopmentArtifact requiredArtifacts, this.binaryDirs = const >[], this.licenseDirs = const [], this.packageDirs = const [], @@ -290,12 +338,15 @@ class FakeCachedArtifact extends EngineCachedArtifact { List getPackageDirs() => packageDirs; } +class MockProcessManager extends Mock implements ProcessManager {} +class MockProcess extends Mock implements Process {} class MockFileSystem extends Mock implements FileSystem {} class MockFile extends Mock implements File {} class MockDirectory extends Mock implements Directory {} class MockRandomAccessFile extends Mock implements RandomAccessFile {} class MockCachedArtifact extends Mock implements CachedArtifact {} +class MockIosUsbArtifacts extends Mock implements IosUsbArtifacts {} class MockInternetAddress extends Mock implements InternetAddress {} class MockCache extends Mock implements Cache {} class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {} diff --git a/packages/flutter_tools/test/general.shard/commands/precache_test.dart b/packages/flutter_tools/test/general.shard/commands/precache_test.dart index 466fe5177e..d9241c9232 100644 --- a/packages/flutter_tools/test/general.shard/commands/precache_test.dart +++ b/packages/flutter_tools/test/general.shard/commands/precache_test.dart @@ -27,12 +27,24 @@ void main() { final PrecacheCommand command = PrecacheCommand(); applyMocksToCommand(command); await createTestCommandRunner(command).run( - const ['precache', '--ios', '--android', '--web', '--macos', '--linux', '--windows', '--fuchsia', '--flutter_runner'] + const [ + 'precache', + '--ios', + '--android', + '--web', + '--macos', + '--linux', + '--windows', + '--fuchsia', + '--flutter_runner', + ] ); expect(artifacts, unorderedEquals({ DevelopmentArtifact.universal, DevelopmentArtifact.iOS, - DevelopmentArtifact.android, + DevelopmentArtifact.androidGenSnapshot, + DevelopmentArtifact.androidMaven, + DevelopmentArtifact.androidInternalBuild, DevelopmentArtifact.web, DevelopmentArtifact.macOS, DevelopmentArtifact.linux, @@ -44,6 +56,54 @@ void main() { Cache: () => cache, }); + testUsingContext('Expands android artifacts when the android flag is used', () async { + // Release lock between test cases. + Cache.releaseLockEarly(); + + final PrecacheCommand command = PrecacheCommand(); + applyMocksToCommand(command); + await createTestCommandRunner(command).run( + const [ + 'precache', + '--no-ios', + '--android', + ] + ); + expect(artifacts, unorderedEquals({ + DevelopmentArtifact.universal, + DevelopmentArtifact.androidGenSnapshot, + DevelopmentArtifact.androidMaven, + DevelopmentArtifact.androidInternalBuild, + })); + }, overrides: { + Cache: () => cache, + }); + + testUsingContext('Adds artifact flags to requested android artifacts', () async { + // Release lock between test cases. + Cache.releaseLockEarly(); + + final PrecacheCommand command = PrecacheCommand(); + applyMocksToCommand(command); + await createTestCommandRunner(command).run( + const [ + 'precache', + '--no-ios', + '--android_gen_snapshot', + '--android_maven', + '--android_internal_build', + ] + ); + expect(artifacts, unorderedEquals({ + DevelopmentArtifact.universal, + DevelopmentArtifact.androidGenSnapshot, + DevelopmentArtifact.androidMaven, + DevelopmentArtifact.androidInternalBuild, + })); + }, overrides: { + Cache: () => cache, + }); + final MockFlutterVersion flutterVersion = MockFlutterVersion(); when(flutterVersion.isMaster).thenReturn(false); @@ -53,18 +113,31 @@ void main() { final PrecacheCommand command = PrecacheCommand(); applyMocksToCommand(command); await createTestCommandRunner(command).run( - const ['precache', '--ios', '--android', '--web', '--macos', '--linux', '--windows', '--fuchsia', '--flutter_runner'] + const [ + 'precache', + '--ios', + '--android_gen_snapshot', + '--android_maven', + '--android_internal_build', + '--web', + '--macos', + '--linux', + '--windows', + '--fuchsia', + '--flutter_runner', + ] ); - expect(artifacts, unorderedEquals({ - DevelopmentArtifact.universal, - DevelopmentArtifact.iOS, - DevelopmentArtifact.android, - })); + expect(artifacts, unorderedEquals({ + DevelopmentArtifact.universal, + DevelopmentArtifact.iOS, + DevelopmentArtifact.androidGenSnapshot, + DevelopmentArtifact.androidMaven, + DevelopmentArtifact.androidInternalBuild, + })); }, overrides: { Cache: () => cache, FlutterVersion: () => flutterVersion, }); - testUsingContext('Downloads artifacts when --force is provided', () async { when(cache.isUpToDate()).thenReturn(true); // Release lock between test cases. @@ -73,10 +146,12 @@ void main() { applyMocksToCommand(command); await createTestCommandRunner(command).run(const ['precache', '--force']); expect(artifacts, unorderedEquals({ - DevelopmentArtifact.universal, - DevelopmentArtifact.iOS, - DevelopmentArtifact.android, - })); + DevelopmentArtifact.universal, + DevelopmentArtifact.iOS, + DevelopmentArtifact.androidGenSnapshot, + DevelopmentArtifact.androidMaven, + DevelopmentArtifact.androidInternalBuild, + })); }, overrides: { Cache: () => cache, FlutterVersion: () => flutterVersion, diff --git a/packages/flutter_tools/test/general.shard/commands/run_test.dart b/packages/flutter_tools/test/general.shard/commands/run_test.dart index b743a36c61..4285535887 100644 --- a/packages/flutter_tools/test/general.shard/commands/run_test.dart +++ b/packages/flutter_tools/test/general.shard/commands/run_test.dart @@ -138,7 +138,7 @@ void main() { expect(await RunCommand().requiredArtifacts, unorderedEquals({ DevelopmentArtifact.universal, - DevelopmentArtifact.android, + DevelopmentArtifact.androidGenSnapshot, })); when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) { @@ -162,7 +162,7 @@ void main() { expect(await RunCommand().requiredArtifacts, unorderedEquals({ DevelopmentArtifact.universal, DevelopmentArtifact.iOS, - DevelopmentArtifact.android, + DevelopmentArtifact.androidGenSnapshot, })); when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {