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

Use new Maven artifacts from Gradle (#39157)

parent d30e4228
......@@ -52,7 +52,6 @@ Future<void> 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<void> 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',
......
......@@ -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'
}
......@@ -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<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 @@ class FlutterPlugin implements Plugin<Project> {
// 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<Project> {
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<Project> {
}
}
}
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<Project> {
}
}
}
// Add custom build types
project.android.buildTypes {
profile {
......@@ -141,8 +141,7 @@ class FlutterPlugin implements Plugin<Project> {
}
}
}
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<Project> {
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<Project> {
})
}
} 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"
project.android.buildTypes.each this.&addFlutterDependencies
project.android.buildTypes.whenObjectAdded this.&addFlutterDependencies
}
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()
// 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")
......@@ -221,13 +225,75 @@ class FlutterPlugin implements Plugin<Project> {
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.
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> {
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.
......@@ -260,7 +326,7 @@ class FlutterPlugin implements Plugin<Project> {
projects.each { hostProject ->
hostProject.repositories {
maven {
url "${getPluginBuildDir(project)}/outputs/repo"
url "${getPluginBuildDir()}/outputs/repo"
}
}
}
......@@ -295,7 +361,7 @@ class FlutterPlugin implements Plugin<Project> {
* 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 ->
......@@ -320,7 +386,7 @@ class FlutterPlugin implements Plugin<Project> {
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<Project> {
return result
}
private static List<String> getTargetPlatforms(Project project) {
private List<String> getTargetPlatforms() {
if (!project.hasProperty('target-platform')) {
return DEFAULT_PLATFORMS
}
......@@ -357,18 +423,18 @@ class FlutterPlugin implements Plugin<Project> {
}
}
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<Project> {
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<Project> {
} 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<Project> {
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<Project> {
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<Project> {
}
}
}
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<Project> {
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")
......@@ -618,57 +628,7 @@ class FlutterPlugin implements Plugin<Project> {
} 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()
}
}
......
// 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()
}
}
}
......@@ -84,7 +84,7 @@ Future<void> main(List<String> args) async {
LogsCommand(),
MakeHostAppEditableCommand(),
PackagesCommand(),
PrecacheCommand(),
PrecacheCommand(verboseHelp: verboseHelp),
RunCommand(verboseHelp: verboseHelp),
ScreenshotCommand(),
ShellCompletionCommand(),
......
......@@ -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<DevelopmentArtifact> values = <DevelopmentArtifact>[
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<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 @@ class Cache {
];
final Directory _rootOverride;
final List<CachedArtifact> _artifacts = <CachedArtifact>[];
final List<ArtifactSet> _artifacts = <ArtifactSet>[];
// Initialized by FlutterCommandRunner on startup.
static String flutterRoot;
......@@ -245,11 +256,16 @@ class Cache {
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;
......@@ -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<String> getThirdPartyFile(String urlStr, String serviceName) async {
final Uri url = Uri.parse(urlStr);
......@@ -318,12 +334,16 @@ 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(
......@@ -336,6 +356,7 @@ class Cache {
rethrow;
}
}
}
Future<bool> areRemoteArtifactsAvailable({
String engineVersion,
......@@ -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<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 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<DevelopmentArtifact> 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<File> _downloadedFiles = <File>[];
@override
bool isUpToDate() {
if (!location.existsSync()) {
return false;
......@@ -390,13 +431,8 @@ abstract class CachedArtifact {
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);
......@@ -499,7 +535,7 @@ class MaterialFonts extends CachedArtifact {
MaterialFonts(Cache cache) : super(
'material_fonts',
cache,
const <DevelopmentArtifact>{ DevelopmentArtifact.universal },
DevelopmentArtifact.universal,
);
@override
......@@ -517,7 +553,7 @@ class FlutterWebSdk extends CachedArtifact {
FlutterWebSdk(Cache cache) : super(
'flutter_web_sdk',
cache,
const <DevelopmentArtifact>{ DevelopmentArtifact.web },
DevelopmentArtifact.web,
);
@override
......@@ -558,8 +594,8 @@ abstract class EngineCachedArtifact extends CachedArtifact {
EngineCachedArtifact(
this.stampName,
Cache cache,
Set<DevelopmentArtifact> 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>{ DevelopmentArtifact.universal },
DevelopmentArtifact.universal,
);
@override
......@@ -714,7 +750,7 @@ class MacOSEngineArtifacts extends EngineCachedArtifact {
MacOSEngineArtifacts(Cache cache) : super(
'macos-sdk',
cache,
const <DevelopmentArtifact> { DevelopmentArtifact.macOS },
DevelopmentArtifact.macOS,
);
@override
......@@ -736,7 +772,7 @@ class WindowsEngineArtifacts extends EngineCachedArtifact {
WindowsEngineArtifacts(Cache cache) : super(
'windows-sdk',
cache,
const <DevelopmentArtifact> { DevelopmentArtifact.windows },
DevelopmentArtifact.windows,
);
@override
......@@ -758,7 +794,7 @@ class LinuxEngineArtifacts extends EngineCachedArtifact {
LinuxEngineArtifacts(Cache cache) : super(
'linux-sdk',
cache,
const <DevelopmentArtifact> { DevelopmentArtifact.linux },
DevelopmentArtifact.linux,
);
@override
......@@ -776,11 +812,12 @@ class LinuxEngineArtifacts extends EngineCachedArtifact {
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
......@@ -794,23 +831,19 @@ class AndroidEngineArtifacts extends EngineCachedArtifact {
..._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,
]
];
}
......@@ -819,11 +852,79 @@ class AndroidEngineArtifacts extends EngineCachedArtifact {
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 Process process = await runCommand(
<String>[
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>{ DevelopmentArtifact.iOS },
DevelopmentArtifact.iOS,
);
@override
......@@ -856,7 +957,7 @@ class GradleWrapper extends CachedArtifact {
GradleWrapper(Cache cache) : super(
'gradle_wrapper',
cache,
const <DevelopmentArtifact>{ DevelopmentArtifact.universal },
DevelopmentArtifact.universal,
);
List<String> get _gradleScripts => <String>['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> {
_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 <DevelopmentArtifact>{
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>{ DevelopmentArtifact.universal },
DevelopmentArtifact.universal,
);
static const List<String> artifactNames = <String>[
......@@ -984,8 +1088,10 @@ class IosUsbArtifacts extends CachedArtifact {
];
@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
......@@ -1093,6 +1199,12 @@ const List<List<String>> _windowsBinaryDirs = <List<String>>[
<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'],
......@@ -1104,12 +1216,6 @@ const List<List<String>> _androidBinaryDirs = <List<String>>[
<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'],
......
......@@ -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<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'
......
......@@ -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<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{
DevelopmentArtifact.universal,
DevelopmentArtifact.android,
};
@override
Future<FlutterCommandResult> runCommand() async {
final BuildInfo buildInfo = getBuildInfo();
......
......@@ -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,
};
}
......
......@@ -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()) {
......
......@@ -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:
......
......@@ -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}}
......
......@@ -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: <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 +177,20 @@ void main() {
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 +242,7 @@ void main() {
binaryDirs: <List<String>>[
<String>['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>{
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());
expect(() => fakeCachedArtifact.update(<DevelopmentArtifact>{
DevelopmentArtifact.android,
}), throwsA(isInstanceOf<ToolExit>()));
group('AndroidMavenArtifacts', () {
MemoryFileSystem memoryFileSystem;
MockProcessManager processManager;
MockCache mockCache;
MockProcess mockProcess;
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<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');
return Future<Process>.value(mockProcess);
});
when(mockProcess.exitCode).thenAnswer((_) async => 0);
await mavenArtifacts.update();
expect(mavenArtifacts.isUpToDate(), isFalse);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem(),
Cache: ()=> mockCache,
FileSystem: () => memoryFileSystem,
ProcessManager: () => processManager,
});
});
}
......@@ -270,7 +318,7 @@ class FakeCachedArtifact extends EngineCachedArtifact {
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 +338,15 @@ class FakeCachedArtifact extends EngineCachedArtifact {
List<String> 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 {}
......@@ -27,12 +27,24 @@ void main() {
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 @@ 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 <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 @@ void main() {
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,
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.
......@@ -75,7 +148,9 @@ void main() {
expect(artifacts, unorderedEquals(<DevelopmentArtifact>{
DevelopmentArtifact.universal,
DevelopmentArtifact.iOS,
DevelopmentArtifact.android,
DevelopmentArtifact.androidGenSnapshot,
DevelopmentArtifact.androidMaven,
DevelopmentArtifact.androidInternalBuild,
}));
}, overrides: <Type, Generator>{
Cache: () => cache,
......
......@@ -138,7 +138,7 @@ void main() {
expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
DevelopmentArtifact.universal,
DevelopmentArtifact.android,
DevelopmentArtifact.androidGenSnapshot,
}));
when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
......@@ -162,7 +162,7 @@ void main() {
expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
DevelopmentArtifact.universal,
DevelopmentArtifact.iOS,
DevelopmentArtifact.android,
DevelopmentArtifact.androidGenSnapshot,
}));
when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment