Reland #39157 (#39798)
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 3f3b5c1..d50bab8 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 @@ '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 @@ '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 b80bbcd..14ce0b1 100644 --- a/examples/platform_view/android/app/build.gradle +++ b/examples/platform_view/android/app/build.gradle
@@ -57,6 +57,6 @@ 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 c3328d7..3e76802 100644 --- a/packages/flutter_tools/gradle/flutter.gradle +++ b/packages/flutter_tools/gradle/flutter.gradle
@@ -42,6 +42,8 @@ apply plugin: FlutterPlugin class FlutterPlugin implements Plugin<Project> { + 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 @@ // 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 @@ 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 @@ } } } - 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 @@ -132,7 +133,7 @@ } } - 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.") } @@ -141,6 +142,9 @@ 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(); @@ -173,7 +177,7 @@ } } - if (useLocalEngine(project)) { + if (useLocalEngine()) { String engineOutPath = project.property('localEngineOut') File engineOut = project.file(engineOutPath) if (!engineOut.isDirectory()) { @@ -194,45 +198,48 @@ }) } } 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 <buildType>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] - }) + /** + * 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 + } } - project.android.buildTypes.whenObjectAdded { - def buildMode = buildModeFor(it) - addApiDependencies(project, it.name, project.files { - baseJar[buildMode] - }) - } + } + String flutterBuildMode = buildModeFor(buildType) + // Add the embedding dependency. + addApiDependencies(project, buildType.name, + "io.flutter:flutter_embedding_$flutterBuildMode:1.0.0-$engineVersion") + + List<String> 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") @@ -240,13 +247,93 @@ 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<String> 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. + if (flutterJar) { + addCompileOnlyDependency( + pluginProject, + buildType.name, + project.files { + flutterJar + } + ) + return + } + addCompileOnlyDependency( + pluginProject, + buildType.name, + "io.flutter:flutter_embedding_$flutterBuildMode:1.0.0-$engineVersion", + { + // We only need to expose io.flutter.plugin.* + // No need for the embedding transitive dependencies. + transitive = false + } + ) + } + 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" @@ -259,17 +346,17 @@ 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<Project> 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. @@ -279,7 +366,7 @@ projects.each { hostProject -> hostProject.repositories { maven { - url "${getPluginBuildDir(project)}/outputs/repo" + url "${getPluginBuildDir()}/outputs/repo" } } } @@ -314,7 +401,7 @@ * 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<String> tasksToExecute) { + private Set getBuildTypesForTasks(List<String> tasksToExecute) { Set buildTypes = [] tasksToExecute.each { task -> project.android.buildTypes.each { buildType -> @@ -339,7 +426,7 @@ 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")) } @@ -364,7 +451,7 @@ return result } - private static List<String> getTargetPlatforms(Project project) { + private List<String> getTargetPlatforms() { if (!project.hasProperty('target-platform')) { return DEFAULT_PLATFORMS } @@ -376,18 +463,18 @@ } } - 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() } @@ -411,20 +498,7 @@ 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 addCompileOnlyDependency(Project project, String variantName, Object dependency, Closure config = null) { if (project.state.failure) { return } @@ -434,7 +508,7 @@ } else { configuration = "${variantName}Provided"; } - project.dependencies.add(configuration, files) + project.dependencies.add(configuration, dependency, config) } private static void addApiDependencies(Project project, String variantName, Object dependency, Closure config = null) { @@ -487,7 +561,6 @@ if (project.hasProperty('target')) { target = project.property('target') } - String[] fileSystemRootsValue = null if (project.hasProperty('filesystem-roots')) { fileSystemRootsValue = project.property('filesystem-roots').split('\\|') @@ -524,10 +597,9 @@ 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 @@ -540,31 +612,17 @@ } } } - - 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 @@ -579,51 +637,24 @@ 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/<abi>/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") @@ -650,57 +681,7 @@ } else { project.android.libraryVariants.all addFlutterDeps } - - if (buildPluginAsAar()) { - addPluginTasks(project) - - List<String> 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/flutter_proguard_rules.pro b/packages/flutter_tools/gradle/flutter_proguard_rules.pro index 8fc13e7..10390ef 100644 --- a/packages/flutter_tools/gradle/flutter_proguard_rules.pro +++ b/packages/flutter_tools/gradle/flutter_proguard_rules.pro
@@ -1,7 +1,3 @@ -# Prevents `Fragment and FragmentActivity not found`. -# TODO(blasten): Remove once we bring the Maven dependencies. --dontwarn io.flutter.embedding.** - # Build the ephemeral app in a module project. # Prevents: Warning: library class <plugin-package> depends on program class io.flutter.plugin.** # This is due to plugins (libraries) depending on the embedding (the program jar)
diff --git a/packages/flutter_tools/gradle/resolve_dependencies.gradle b/packages/flutter_tools/gradle/resolve_dependencies.gradle new file mode 100644 index 0000000..2061bc8 --- /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 <flutter-sdk>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 6072917..fec973c 100644 --- a/packages/flutter_tools/lib/executable.dart +++ b/packages/flutter_tools/lib/executable.dart
@@ -84,7 +84,7 @@ LogsCommand(), MakeHostAppEditableCommand(), PackagesCommand(), - PrecacheCommand(), + PrecacheCommand(verboseHelp: verboseHelp), RunCommand(verboseHelp: verboseHelp), ScreenshotCommand(), ShellCompletionCommand(),
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 84523c3..f383329 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -149,7 +149,7 @@ <String>[gradlew, 'dependencies'], throwOnError: true, workingDirectory: flutterProject.android.hostAppGradleRoot.path, - environment: _gradleEnv, + environment: gradleEnv, ); androidSdk.reinitialize(); progress.stop(); @@ -239,13 +239,13 @@ <String>[gradlew, if (isLibrary) 'properties' else 'app:properties'], throwOnError: true, workingDirectory: hostAppGradleRoot.path, - environment: _gradleEnv, + environment: gradleEnv, ); final RunResult tasksRunResult = await processUtils.run( <String>[gradlew, if (isLibrary) 'tasks' else 'app:tasks', '--all', '--console=auto'], throwOnError: true, workingDirectory: hostAppGradleRoot.path, - environment: _gradleEnv, + environment: gradleEnv, ); project = GradleProject.fromAppProperties(propertiesRunResult.stdout, tasksRunResult.stdout); } catch (exception) { @@ -324,7 +324,7 @@ await processUtils.run( <String>[gradle, '-v'], throwOnError: true, - environment: _gradleEnv, + environment: gradleEnv, ); } on ProcessException catch (e) { final String error = e.toString(); @@ -640,7 +640,7 @@ command, workingDirectory: project.android.hostAppGradleRoot.path, allowReentrantFlutter: true, - environment: _gradleEnv, + environment: gradleEnv, mapFunction: (String line) { // Always print the full line in verbose mode. if (logger.isVerbose) { @@ -678,7 +678,7 @@ <String>[fs.file(gradlew).absolute.path, 'build'], workingDirectory: project.android.hostAppGradleRoot.path, allowReentrantFlutter: true, - environment: _gradleEnv, + environment: gradleEnv, ); status.stop(); flutterUsage.sendTiming('build', 'gradle-v1', Duration(milliseconds: sw.elapsedMilliseconds)); @@ -809,7 +809,7 @@ command, workingDirectory: flutterProject.android.hostAppGradleRoot.path, allowReentrantFlutter: true, - environment: _gradleEnv, + environment: gradleEnv, // TODO(mklim): if AndroidX warnings are no longer required, this // mapFunction and all its associated variabled can be replaced with just // `filter: ndkMessagefilter`. @@ -961,7 +961,8 @@ return null; } -Map<String, String> get _gradleEnv { +/// The environment variables needed to run Gradle. +Map<String, String> get gradleEnv { final Map<String, String> env = Map<String, String>.from(platform.environment); if (javaPath != null) { // Use java bundled with Android Studio.
diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart index af4d749..fa57b92 100644 --- a/packages/flutter_tools/lib/src/cache.dart +++ b/packages/flutter_tools/lib/src/cache.dart
@@ -6,6 +6,7 @@ import 'package:meta/meta.dart'; +import 'android/gradle.dart'; import 'base/common.dart'; import 'base/context.dart'; import 'base/file_system.dart'; @@ -14,6 +15,7 @@ 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 @@ 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 @@ /// The values of DevelopmentArtifacts. static final List<DevelopmentArtifact> values = <DevelopmentArtifact>[ - android, + androidGenSnapshot, + androidMaven, + androidInternalBuild, iOS, web, macOS, @@ -74,12 +81,16 @@ class Cache { /// [rootOverride] is configurable for testing. /// [artifacts] is configurable for testing. - Cache({ Directory rootOverride, List<CachedArtifact> artifacts }) : _rootOverride = rootOverride { + Cache({ Directory rootOverride, List<ArtifactSet> 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 @@ ]; final Directory _rootOverride; - final List<CachedArtifact> _artifacts = <CachedArtifact>[]; + final List<ArtifactSet> _artifacts = <ArtifactSet>[]; // Initialized by FlutterCommandRunner on startup. static String flutterRoot; @@ -248,11 +259,16 @@ return _dyLdLibEntry; } final List<String> paths = <String>[]; - for (CachedArtifact artifact in _artifacts) { - final String currentPath = artifact.dyLdLibPath; - if (currentPath.isNotEmpty) { - paths.add(currentPath); + for (ArtifactSet artifact in _artifacts) { + final Map<String, String> 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<String, String>('DYLD_LIBRARY_PATH', paths.join(':')); return _dyLdLibEntry; @@ -292,7 +308,7 @@ return isOlderThanReference(entity: entity, referenceFile: flutterToolsStamp); } - bool isUpToDate() => _artifacts.every((CachedArtifact artifact) => artifact.isUpToDate()); + bool isUpToDate() => _artifacts.every((ArtifactSet artifact) => artifact.isUpToDate()); Future<String> getThirdPartyFile(String urlStr, String serviceName) async { final Uri url = Uri.parse(urlStr); @@ -321,22 +337,27 @@ 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; } } @@ -357,23 +378,42 @@ } } -/// 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); - final String name; + /// 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<String, String> get environment { + return const <String, String>{}; + } + + /// Updates the artifact. + Future<void> update(); +} + +/// An artifact set managed by the cache. +abstract class CachedArtifact extends ArtifactSet { + CachedArtifact( + this.name, + this.cache, + DevelopmentArtifact developmentArtifact, + ) : super(developmentArtifact); + 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<DevelopmentArtifact> developmentArtifacts; - Directory get location => cache.getArtifactDirectory(name); String get version => cache.getVersionFor(name); @@ -383,6 +423,7 @@ /// starting from scratch. final List<File> _downloadedFiles = <File>[]; + @override bool isUpToDate() { if (!location.existsSync()) { return false; @@ -393,13 +434,8 @@ return isUpToDateInner(); } - Future<void> update(Set<DevelopmentArtifact> 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<void> update() async { if (!location.existsSync()) { try { location.createSync(recursive: true); @@ -504,7 +540,7 @@ MaterialFonts(Cache cache) : super( 'material_fonts', cache, - const <DevelopmentArtifact>{ DevelopmentArtifact.universal }, + DevelopmentArtifact.universal, ); @override @@ -522,7 +558,7 @@ FlutterWebSdk(Cache cache) : super( 'flutter_web_sdk', cache, - const <DevelopmentArtifact>{ DevelopmentArtifact.web }, + DevelopmentArtifact.web, ); @override @@ -563,8 +599,8 @@ EngineCachedArtifact( this.stampName, Cache cache, - Set<DevelopmentArtifact> requiredArtifacts, - ) : super('engine', cache, requiredArtifacts); + DevelopmentArtifact developmentArtifact, + ) : super('engine', cache, developmentArtifact); @override final String stampName; @@ -685,7 +721,7 @@ FlutterSdk(Cache cache) : super( 'flutter_sdk', cache, - const <DevelopmentArtifact>{ DevelopmentArtifact.universal }, + DevelopmentArtifact.universal, ); @override @@ -719,7 +755,7 @@ MacOSEngineArtifacts(Cache cache) : super( 'macos-sdk', cache, - const <DevelopmentArtifact> { DevelopmentArtifact.macOS }, + DevelopmentArtifact.macOS, ); @override @@ -741,7 +777,7 @@ WindowsEngineArtifacts(Cache cache) : super( 'windows-sdk', cache, - const <DevelopmentArtifact> { DevelopmentArtifact.windows }, + DevelopmentArtifact.windows, ); @override @@ -763,7 +799,7 @@ LinuxEngineArtifacts(Cache cache) : super( 'linux-sdk', cache, - const <DevelopmentArtifact> { DevelopmentArtifact.linux }, + DevelopmentArtifact.linux, ); @override @@ -781,11 +817,12 @@ List<String> getLicenseDirs() => const <String>[]; } -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>{ DevelopmentArtifact.android }, + DevelopmentArtifact.androidGenSnapshot, ); @override @@ -799,23 +836,19 @@ ..._osxBinaryDirs, ..._linuxBinaryDirs, ..._windowsBinaryDirs, - ..._androidBinaryDirs, ..._dartSdks, ] else if (platform.isWindows) ...<List<String>>[ ..._windowsBinaryDirs, - ..._androidBinaryDirs, ] else if (platform.isMacOS) ...<List<String>>[ ..._osxBinaryDirs, - ..._androidBinaryDirs, ] else if (platform.isLinux) ...<List<String>>[ ..._linuxBinaryDirs, - ..._androidBinaryDirs, ] ]; } @@ -824,11 +857,79 @@ List<String> getLicenseDirs() { return <String>[]; } } +/// 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<String> getPackageDirs() => const <String>[]; + + @override + List<List<String>> getBinaryDirs() { + return _androidBinaryDirs; + } + + @override + List<String> getLicenseDirs() { return <String>[]; } +} + +/// A cached artifact containing the Maven dependencies used to build Android projects. +class AndroidMavenArtifacts extends ArtifactSet { + AndroidMavenArtifacts() : super(DevelopmentArtifact.androidMaven); + + @override + Future<void> 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 RunResult processResult = await processUtils.run( + <String>[ + gradleExecutable, + '-b', fs.path.join(flutterSdk, 'packages', 'flutter_tools', 'gradle', 'resolve_dependencies.gradle'), + '--project-cache-dir', tempDir.path, + 'resolveDependencies', + ], + environment: gradleEnv); + if (processResult.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>{ DevelopmentArtifact.iOS }, + DevelopmentArtifact.iOS, ); @override @@ -861,7 +962,7 @@ GradleWrapper(Cache cache) : super( 'gradle_wrapper', cache, - const <DevelopmentArtifact>{ DevelopmentArtifact.universal }, + DevelopmentArtifact.universal, ); List<String> get _gradleScripts => <String>['gradlew', 'gradlew.bat']; @@ -904,11 +1005,13 @@ /// 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> { - DevelopmentArtifact.fuchsia, - }); + _FuchsiaSDKArtifacts(Cache cache, String platform) : + _path = 'fuchsia/sdk/core/$platform-amd64', + super( + 'fuchsia-$platform', + cache, + DevelopmentArtifact.fuchsia, + ); final String _path; @@ -924,10 +1027,11 @@ /// The pre-built flutter runner for Fuchsia development. class FlutterRunnerSDKArtifacts extends CachedArtifact { - FlutterRunnerSDKArtifacts(Cache cache) - : super('flutter_runner', cache, const <DevelopmentArtifact>{ + FlutterRunnerSDKArtifacts(Cache cache) : super( + 'flutter_runner', + cache, DevelopmentArtifact.flutterRunner, - }); + ); @override Directory get location => cache.getArtifactDirectory('flutter_runner'); @@ -978,7 +1082,7 @@ name, cache, // This is universal to ensure every command checks for them first - const <DevelopmentArtifact>{ DevelopmentArtifact.universal }, + DevelopmentArtifact.universal, ); static const List<String> artifactNames = <String>[ @@ -991,8 +1095,10 @@ ]; @override - String get dyLdLibPath { - return cache.getArtifactDirectory(name).path; + Map<String, String> get environment { + return <String, String>{ + 'DYLD_LIBRARY_PATH': cache.getArtifactDirectory(name).path, + }; } @override @@ -1100,6 +1206,12 @@ <String>['android-arm64-release/windows-x64', 'android-arm64-release/windows-x64.zip'], ]; +const List<List<String>> _iosBinaryDirs = <List<String>>[ + <String>['ios', 'ios/artifacts.zip'], + <String>['ios-profile', 'ios-profile/artifacts.zip'], + <String>['ios-release', 'ios-release/artifacts.zip'], +]; + const List<List<String>> _androidBinaryDirs = <List<String>>[ <String>['android-x86', 'android-x86/artifacts.zip'], <String>['android-x64', 'android-x64/artifacts.zip'], @@ -1111,12 +1223,6 @@ <String>['android-arm64-release', 'android-arm64-release/artifacts.zip'], ]; -const List<List<String>> _iosBinaryDirs = <List<String>>[ - <String>['ios', 'ios/artifacts.zip'], - <String>['ios-profile', 'ios-profile/artifacts.zip'], - <String>['ios-release', 'ios-release/artifacts.zip'], -]; - const List<List<String>> _dartSdks = <List<String>> [ <String>['darwin-x64', 'dart-sdk-darwin-x64.zip'], <String>['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 a7ef675..fd9eb3a 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 '../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 { @@ -53,12 +53,6 @@ } @override - Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{ - 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' 'To learn more about how to use these artifacts, see '
diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart index 8936f0f..507ed5a 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 '../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 { @@ -70,12 +70,6 @@ } @override - Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{ - DevelopmentArtifact.universal, - DevelopmentArtifact.android, - }; - - @override Future<FlutterCommandResult> runCommand() async { final BuildInfo buildInfo = getBuildInfo(); final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(
diff --git a/packages/flutter_tools/lib/src/commands/doctor.dart b/packages/flutter_tools/lib/src/commands/doctor.dart index b2d2b51..7a3f06e 100644 --- a/packages/flutter_tools/lib/src/commands/doctor.dart +++ b/packages/flutter_tools/lib/src/commands/doctor.dart
@@ -37,7 +37,7 @@ // 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 473fea5..e7f7103 100644 --- a/packages/flutter_tools/lib/src/commands/precache.dart +++ b/packages/flutter_tools/lib/src/commands/precache.dart
@@ -10,13 +10,23 @@ 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 @@ 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/commands/upgrade.dart b/packages/flutter_tools/lib/src/commands/upgrade.dart index cd31444..23e885f 100644 --- a/packages/flutter_tools/lib/src/commands/upgrade.dart +++ b/packages/flutter_tools/lib/src/commands/upgrade.dart
@@ -10,6 +10,7 @@ import '../base/file_system.dart'; import '../base/io.dart'; import '../base/os.dart'; +import '../base/platform.dart'; import '../base/process.dart'; import '../cache.dart'; import '../dart/pub.dart'; @@ -130,6 +131,7 @@ ], workingDirectory: Cache.flutterRoot, allowReentrantFlutter: true, + environment: platform.environment, ); if (code != 0) { throwToolExit(null, exitCode: code); @@ -248,6 +250,7 @@ ], workingDirectory: Cache.flutterRoot, allowReentrantFlutter: true, + environment: platform.environment, ); if (code != 0) { throwToolExit(null, exitCode: code);
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index 597143e..e26ecb3 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -676,7 +676,7 @@ 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 44f4667..926beae 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 @@ 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 aa9d940..5a15cb7 100644 --- a/packages/flutter_tools/test/general.shard/cache_test.dart +++ b/packages/flutter_tools/test/general.shard/cache_test.dart
@@ -8,8 +8,11 @@ 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/android/gradle.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 +139,30 @@ when(artifact1.isUpToDate()).thenReturn(true); when(artifact2.isUpToDate()).thenReturn(false); final Cache cache = Cache(artifacts: <CachedArtifact>[artifact1, artifact2]); - await cache.updateAll(<DevelopmentArtifact>{}); - verifyNever(artifact1.update(<DevelopmentArtifact>{})); - verify(artifact2.update(<DevelopmentArtifact>{})); + await cache.updateAll(<DevelopmentArtifact>{ + 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(<String, String>{ + 'DYLD_LIBRARY_PATH': '/path/to/alpha:/path/to/beta', + }); + when(artifact2.environment) + .thenReturn(<String, String>{ + 'DYLD_LIBRARY_PATH': '/path/to/gamma:/path/to/delta:/path/to/epsilon', + }); + when(artifact3.environment) + .thenReturn(<String, String>{ + 'DYLD_LIBRARY_PATH': '', + }); final Cache cache = Cache(artifacts: <CachedArtifact>[artifact1, artifact2, artifact3]); + expect(cache.dyLdLibEntry.key, 'DYLD_LIBRARY_PATH'); expect( cache.dyLdLibEntry.value, @@ -163,18 +178,20 @@ when(artifact2.isUpToDate()).thenReturn(false); final MockInternetAddress address = MockInternetAddress(); when(address.host).thenReturn('storage.googleapis.com'); - when(artifact1.update(<DevelopmentArtifact>{})).thenThrow(SocketException( + when(artifact1.update()).thenThrow(SocketException( 'Connection reset by peer', address: address, )); final Cache cache = Cache(artifacts: <CachedArtifact>[artifact1, artifact2]); try { - await cache.updateAll(<DevelopmentArtifact>{}); + await cache.updateAll(<DevelopmentArtifact>{ + null, + }); fail('Mock thrown exception expected'); } catch (e) { - verify(artifact1.update(<DevelopmentArtifact>{})); + verify(artifact1.update()); // Don't continue when retrieval fails. - verifyNever(artifact2.update(<DevelopmentArtifact>{})); + verifyNever(artifact2.update()); expect( testLogger.errorText, contains('https://flutter.dev/community/china'), @@ -226,6 +243,7 @@ binaryDirs: <List<String>>[ <String>['bin_dir', 'unused_url_path'], ], + requiredArtifacts: DevelopmentArtifact.universal, ); await artifact.updateInner(); final Directory dir = memoryFileSystem.systemTempDirectory @@ -244,25 +262,53 @@ }); }); - testUsingContext('throws tool exit on fs exception', () async { - final FakeCachedArtifact fakeCachedArtifact = FakeCachedArtifact( - cache: MockCache(), - requiredArtifacts: <DevelopmentArtifact>{ - 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; - expect(() => fakeCachedArtifact.update(<DevelopmentArtifact>{ - DevelopmentArtifact.android, - }), throwsA(isInstanceOf<ToolExit>())); - }, overrides: <Type, Generator>{ - FileSystem: () => MemoryFileSystem(), + setUp(() { + memoryFileSystem = MemoryFileSystem(); + processManager = MockProcessManager(); + mockCache = MockCache(); + }); + + 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.run(any, environment: captureAnyNamed('environment'))) + .thenAnswer((Invocation invocation) { + final List<String> args = invocation.positionalArguments[0]; + expect(args.length, 6); + expect(args[1], '-b'); + expect(args[2].endsWith('resolve_dependencies.gradle'), isTrue); + expect(args[5], 'resolveDependencies'); + expect(invocation.namedArguments[#environment], gradleEnv); + return Future<ProcessResult>.value(ProcessResult(0, 0, '', '')); + }); + + await mavenArtifacts.update(); + + expect(mavenArtifacts.isUpToDate(), isFalse); + }, overrides: <Type, Generator>{ + Cache: ()=> mockCache, + FileSystem: () => memoryFileSystem, + ProcessManager: () => processManager, + }); }); } @@ -270,7 +316,7 @@ FakeCachedArtifact({ String stampName = 'STAMP', @required Cache cache, - Set<DevelopmentArtifact> requiredArtifacts = const <DevelopmentArtifact>{}, + DevelopmentArtifact requiredArtifacts, this.binaryDirs = const <List<String>>[], this.licenseDirs = const <String>[], this.packageDirs = const <String>[], @@ -290,12 +336,14 @@ List<String> getPackageDirs() => packageDirs; } +class MockProcessManager extends Mock implements ProcessManager {} 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 466fe51..d9241c9 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 @@ final PrecacheCommand command = PrecacheCommand(); applyMocksToCommand(command); await createTestCommandRunner(command).run( - const <String>['precache', '--ios', '--android', '--web', '--macos', '--linux', '--windows', '--fuchsia', '--flutter_runner'] + const <String>[ + 'precache', + '--ios', + '--android', + '--web', + '--macos', + '--linux', + '--windows', + '--fuchsia', + '--flutter_runner', + ] ); expect(artifacts, unorderedEquals(<DevelopmentArtifact>{ DevelopmentArtifact.universal, DevelopmentArtifact.iOS, - DevelopmentArtifact.android, + DevelopmentArtifact.androidGenSnapshot, + DevelopmentArtifact.androidMaven, + DevelopmentArtifact.androidInternalBuild, DevelopmentArtifact.web, DevelopmentArtifact.macOS, DevelopmentArtifact.linux, @@ -44,6 +56,54 @@ 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 <String>[ + 'precache', + '--no-ios', + '--android', + ] + ); + expect(artifacts, unorderedEquals(<DevelopmentArtifact>{ + DevelopmentArtifact.universal, + DevelopmentArtifact.androidGenSnapshot, + DevelopmentArtifact.androidMaven, + DevelopmentArtifact.androidInternalBuild, + })); + }, overrides: <Type, Generator>{ + 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 <String>[ + 'precache', + '--no-ios', + '--android_gen_snapshot', + '--android_maven', + '--android_internal_build', + ] + ); + expect(artifacts, unorderedEquals(<DevelopmentArtifact>{ + DevelopmentArtifact.universal, + DevelopmentArtifact.androidGenSnapshot, + DevelopmentArtifact.androidMaven, + DevelopmentArtifact.androidInternalBuild, + })); + }, overrides: <Type, Generator>{ + Cache: () => cache, + }); + final MockFlutterVersion flutterVersion = MockFlutterVersion(); when(flutterVersion.isMaster).thenReturn(false); @@ -53,18 +113,31 @@ final PrecacheCommand command = PrecacheCommand(); applyMocksToCommand(command); await createTestCommandRunner(command).run( - const <String>['precache', '--ios', '--android', '--web', '--macos', '--linux', '--windows', '--fuchsia', '--flutter_runner'] + const <String>[ + 'precache', + '--ios', + '--android_gen_snapshot', + '--android_maven', + '--android_internal_build', + '--web', + '--macos', + '--linux', + '--windows', + '--fuchsia', + '--flutter_runner', + ] ); - expect(artifacts, unorderedEquals(<DevelopmentArtifact>{ - DevelopmentArtifact.universal, - DevelopmentArtifact.iOS, - DevelopmentArtifact.android, - })); + expect(artifacts, unorderedEquals(<DevelopmentArtifact>{ + DevelopmentArtifact.universal, + DevelopmentArtifact.iOS, + DevelopmentArtifact.androidGenSnapshot, + DevelopmentArtifact.androidMaven, + DevelopmentArtifact.androidInternalBuild, + })); }, overrides: <Type, Generator>{ 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 @@ applyMocksToCommand(command); await createTestCommandRunner(command).run(const <String>['precache', '--force']); expect(artifacts, unorderedEquals(<DevelopmentArtifact>{ - DevelopmentArtifact.universal, - DevelopmentArtifact.iOS, - DevelopmentArtifact.android, - })); + DevelopmentArtifact.universal, + DevelopmentArtifact.iOS, + DevelopmentArtifact.androidGenSnapshot, + DevelopmentArtifact.androidMaven, + DevelopmentArtifact.androidInternalBuild, + })); }, overrides: <Type, Generator>{ 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 b743a36..4285535 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 @@ expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{ DevelopmentArtifact.universal, - DevelopmentArtifact.android, + DevelopmentArtifact.androidGenSnapshot, })); when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) { @@ -162,7 +162,7 @@ expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{ DevelopmentArtifact.universal, DevelopmentArtifact.iOS, - DevelopmentArtifact.android, + DevelopmentArtifact.androidGenSnapshot, })); when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
diff --git a/packages/flutter_tools/test/general.shard/commands/upgrade_test.dart b/packages/flutter_tools/test/general.shard/commands/upgrade_test.dart index 94cefa6..306d8a0 100644 --- a/packages/flutter_tools/test/general.shard/commands/upgrade_test.dart +++ b/packages/flutter_tools/test/general.shard/commands/upgrade_test.dart
@@ -5,12 +5,14 @@ import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; +import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/upgrade.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart'; import 'package:flutter_tools/src/version.dart'; import 'package:mockito/mockito.dart'; +import 'package:platform/platform.dart'; import 'package:process/process.dart'; import '../../src/common.dart'; @@ -22,6 +24,7 @@ FakeUpgradeCommandRunner fakeCommandRunner; UpgradeCommandRunner realCommandRunner; MockProcessManager processManager; + FakePlatform fakePlatform; final MockFlutterVersion flutterVersion = MockFlutterVersion(); const GitTagVersion gitTagVersion = GitTagVersion(1, 2, 3, 4, 5, 'asd'); when(flutterVersion.channel).thenReturn('dev'); @@ -43,9 +46,13 @@ return Future<Process>.value(createMockProcess()); }); fakeCommandRunner.willHaveUncomittedChanges = false; + fakePlatform = FakePlatform()..environment = <String, String>{ + 'ENV1': 'irrelevant', + 'ENV2': 'irrelevant', + }; }); - test('throws on unknown tag, official branch, noforce', () async { + testUsingContext('throws on unknown tag, official branch, noforce', () async { final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand( false, false, @@ -53,6 +60,8 @@ flutterVersion, ); expect(result, throwsA(isInstanceOf<ToolExit>())); + }, overrides: <Type, Generator>{ + Platform: () => fakePlatform, }); testUsingContext('does not throw on unknown tag, official branch, force', () async { @@ -65,9 +74,10 @@ expect(await result, null); }, overrides: <Type, Generator>{ ProcessManager: () => processManager, + Platform: () => fakePlatform, }); - test('throws tool exit with uncommitted changes', () async { + testUsingContext('throws tool exit with uncommitted changes', () async { fakeCommandRunner.willHaveUncomittedChanges = true; final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand( false, @@ -76,6 +86,8 @@ flutterVersion, ); expect(result, throwsA(isA<ToolExit>())); + }, overrides: <Type, Generator>{ + Platform: () => fakePlatform, }); testUsingContext('does not throw tool exit with uncommitted changes and force', () async { @@ -90,6 +102,7 @@ expect(await result, null); }, overrides: <Type, Generator>{ ProcessManager: () => processManager, + Platform: () => fakePlatform, }); testUsingContext('Doesn\'t throw on known tag, dev branch, no force', () async { @@ -102,6 +115,7 @@ expect(await result, null); }, overrides: <Type, Generator>{ ProcessManager: () => processManager, + Platform: () => fakePlatform, }); testUsingContext('verifyUpstreamConfigured', () async { @@ -116,6 +130,57 @@ await realCommandRunner.verifyUpstreamConfigured(); }, overrides: <Type, Generator>{ ProcessManager: () => processManager, + Platform: () => fakePlatform, + }); + + testUsingContext('flutterUpgradeContinue passes env variables to child process', () async { + await realCommandRunner.flutterUpgradeContinue(); + + final VerificationResult result = verify(processManager.start( + <String>[ + fs.path.join('bin', 'flutter'), + 'upgrade', + '--continue', + '--no-version-check', + ], + environment: captureAnyNamed('environment'), + workingDirectory: anyNamed('workingDirectory'), + )); + + expect(result.captured.first, fakePlatform.environment); + }, overrides: <Type, Generator>{ + ProcessManager: () => processManager, + Platform: () => fakePlatform, + }); + + testUsingContext('precacheArtifacts passes env variables to child process', () async { + final List<String> precacheCommand = <String>[ + fs.path.join('bin', 'flutter'), + '--no-color', + '--no-version-check', + 'precache', + ]; + + when(processManager.start( + precacheCommand, + environment: anyNamed('environment'), + workingDirectory: anyNamed('workingDirectory'), + )).thenAnswer((Invocation invocation) async { + return Future<Process>.value(createMockProcess()); + }); + + await realCommandRunner.precacheArtifacts(); + + final VerificationResult result = verify(processManager.start( + precacheCommand, + environment: captureAnyNamed('environment'), + workingDirectory: anyNamed('workingDirectory'), + )); + + expect(result.captured.first, fakePlatform.environment); + }, overrides: <Type, Generator>{ + ProcessManager: () => processManager, + Platform: () => fakePlatform, }); });