diff --git a/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy b/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy index c9acd58596a..bf276c7ee4f 100644 --- a/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy +++ b/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy @@ -1,3 +1,4 @@ +/* groovylint-disable LineLength, UnnecessaryGString, UnnecessaryGetter */ // Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -6,7 +7,6 @@ import com.android.build.OutputFile import groovy.json.JsonGenerator import groovy.xml.QName import java.nio.file.Paths -import java.util.Set import org.apache.tools.ant.taskdefs.condition.Os import org.gradle.api.DefaultTask import org.gradle.api.GradleException @@ -40,6 +40,7 @@ import org.gradle.internal.os.OperatingSystem * * https://docs.gradle.org/8.0.2/userguide/custom_plugins.html#sec:getting_input_from_the_build */ class FlutterExtension { + /** Sets the compileSdkVersion used by default in Flutter app projects. */ final int compileSdkVersion = 34 @@ -97,6 +98,7 @@ class FlutterExtension { return flutterVersionName } + } // This buildscript block supplies dependencies for this file's own import @@ -133,21 +135,22 @@ android { apply plugin: FlutterPlugin class FlutterPlugin implements Plugin { - private static final String DEFAULT_MAVEN_HOST = "https://storage.googleapis.com"; + + private static final String DEFAULT_MAVEN_HOST = "https://storage.googleapis.com" /** 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"; - private static final String PLATFORM_X86 = "android-x86"; - private static final String PLATFORM_X86_64 = "android-x64"; + private static final String PLATFORM_ARM32 = "android-arm" + private static final String PLATFORM_ARM64 = "android-arm64" + private static final String PLATFORM_X86 = "android-x86" + private static final String PLATFORM_X86_64 = "android-x64" /** The ABI architectures supported by Flutter. */ - private static final String ARCH_ARM32 = "armeabi-v7a"; - private static final String ARCH_ARM64 = "arm64-v8a"; - private static final String ARCH_X86 = "x86"; - private static final String ARCH_X86_64 = "x86_64"; + private static final String ARCH_ARM32 = "armeabi-v7a" + private static final String ARCH_ARM64 = "arm64-v8a" + private static final String ARCH_X86 = "x86" + private static final String ARCH_X86_64 = "x86_64" - private static final String INTERMEDIATES_DIR = "intermediates"; + private static final String INTERMEDIATES_DIR = "intermediates" /** Maps platforms to ABI architectures. */ private static final Map PLATFORM_ARCH_MAP = [ @@ -162,7 +165,7 @@ class FlutterPlugin implements Plugin { * For each APK variant, use the following versions to override the version of the Universal APK. * Otherwise, the Play Store will complain that the APK variants have the same version. */ - private static final Map ABI_VERSION = [ + private static final Map ABI_VERSION = [ (ARCH_ARM32) : 1, (ARCH_ARM64) : 2, (ARCH_X86) : 3, @@ -176,6 +179,9 @@ class FlutterPlugin implements Plugin { PLATFORM_X86_64, ] + private final static String propLocalEngineRepo = "local-engine-repo" + private final static String propProcessResourcesProvider = "processResourcesProvider" + /** * The name prefix for flutter builds. This is used to identify gradle tasks * where we expect the flutter tool to provide any error output, and skip the @@ -186,7 +192,6 @@ class FlutterPlugin implements Plugin { static final String FLUTTER_BUILD_PREFIX = "flutterBuild" private Project project - private Map baseJar = [:] private File flutterRoot private File flutterExecutable private String localEngine @@ -205,11 +210,11 @@ class FlutterPlugin implements Plugin { void apply(Project project) { this.project = project - def rootProject = project.rootProject + Project rootProject = project.rootProject if (isFlutterAppProject()) { rootProject.tasks.register("generateLockfiles") { rootProject.subprojects.each { subproject -> - def gradlew = (OperatingSystem.current().isWindows()) ? + String gradlew = (OperatingSystem.current().isWindows()) ? "${rootProject.projectDir}/gradlew.bat" : "${rootProject.projectDir}/gradlew" rootProject.exec { workingDir(rootProject.projectDir) @@ -241,7 +246,7 @@ class FlutterPlugin implements Plugin { // Configure the Maven repository. String hostedRepository = System.env.FLUTTER_STORAGE_BASE_URL ?: DEFAULT_MAVEN_HOST String repository = useLocalEngine() - ? project.property("local-engine-repo") + ? project.property(propLocalEngineRepo) : "$hostedRepository/${engineRealm}download.flutter.io" rootProject.allprojects { repositories { @@ -254,23 +259,22 @@ class FlutterPlugin implements Plugin { // Load shared gradle functions project.apply from: Paths.get(flutterRoot.absolutePath, "packages", "flutter_tools", "gradle", "src", "main", "groovy", "native_plugin_loader.groovy") - def extension = project.extensions.create("flutter", FlutterExtension) - def localProperties = new Properties() - def localPropertiesFile = rootProject.file("local.properties") + FlutterExtension extension = project.extensions.create("flutter", FlutterExtension) + Properties localProperties = new Properties() + File localPropertiesFile = rootProject.file("local.properties") if (localPropertiesFile.exists()) { localPropertiesFile.withReader("UTF-8") { reader -> localProperties.load(reader) } } - def flutterVersionCode = localProperties.getProperty("flutter.versionCode") + Object flutterVersionCode = localProperties.getProperty("flutter.versionCode") if (flutterVersionCode == null) { flutterVersionCode = "1" } extension.flutterVersionCode = flutterVersionCode - - def flutterVersionName = localProperties.getProperty("flutter.versionName") + Object flutterVersionName = localProperties.getProperty("flutter.versionName") if (flutterVersionName == null) { flutterVersionName = "1.0" } @@ -295,9 +299,9 @@ class FlutterPlugin implements Plugin { } } } - - if (project.hasProperty("deferred-component-names")) { - String[] componentNames = project.property("deferred-component-names").split(",").collect {":${it}"} + final String propDeferredComponentNames = "deferred-component-names" + if (project.hasProperty(propDeferredComponentNames)) { + String[] componentNames = project.property(propDeferredComponentNames).split(",").collect {":${it}"} project.android { dynamicFeatures = componentNames } @@ -317,10 +321,11 @@ class FlutterPlugin implements Plugin { } String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter" - flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile(); + flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile() - if (project.hasProperty("multidex-enabled") && - project.property("multidex-enabled").toBoolean()) { + final String propMultidexEnabled = "multidex-enabled" + if (project.hasProperty(propMultidexEnabled) && + project.property(propMultidexEnabled).toBoolean()) { String flutterMultidexKeepfile = Paths.get(flutterRoot.absolutePath, "packages", "flutter_tools", "gradle", "flutter_multidex_keepfile.txt") project.android { @@ -333,7 +338,7 @@ class FlutterPlugin implements Plugin { project.dependencies { implementation("androidx.multidex:multidex:2.0.1") } - } + } // Use Kotlin DSL to handle baseApplicationName logic due to Groovy dynamic dispatch bug. project.apply from: Paths.get(flutterRoot.absolutePath, "packages", "flutter_tools", "gradle", "src", "main", "kotlin", "flutter.gradle.kts") @@ -387,12 +392,213 @@ class FlutterPlugin implements Plugin { } private static Boolean shouldShrinkResources(Project project) { - if (project.hasProperty("shrink")) { - return project.property("shrink").toBoolean() + final String propShrink = "shrink" + if (project.hasProperty(propShrink)) { + return project.property(propShrink).toBoolean() } return true } + private static String toCamelCase(List parts) { + if (parts.empty) { + return "" + } + return "${parts[0]}${parts[1..-1].collect { it.capitalize() }.join('')}" + } + + private static Properties readPropertiesIfExist(File propertiesFile) { + Properties result = new Properties() + if (propertiesFile.exists()) { + propertiesFile.withReader("UTF-8") { reader -> result.load(reader) } + } + return result + } + + private static Boolean isBuiltAsApp(Project project) { + // Projects are built as applications when the they use the `com.android.application` + // plugin. + return project.plugins.hasPlugin("com.android.application") + } + + private static void addApiDependencies(Project project, String variantName, Object dependency, Closure config = null) { + String configuration + // `compile` dependencies are now `api` dependencies. + if (project.getConfigurations().findByName("api")) { + configuration = "${variantName}Api" + } else { + configuration = "${variantName}Compile" + } + project.dependencies.add(configuration, dependency, config) + } + + // Add a task that can be called on flutter projects that prints the Java version used in Gradle. + // + // Format of the output of this task can be used in debugging what version of Java Gradle is using. + // Not recommended for use in time sensitive commands like `flutter run` or `flutter build` as + // Gradle is slower than we want. Particularly in light of https://github.com/flutter/flutter/issues/119196. + private static void addTaskForJavaVersion(Project project) { + // Warning: the name of this task is used by other code. Change with caution. + project.tasks.register("javaVersion") { + description "Print the current java version used by gradle. " + "see: https://docs.gradle.org/current/javadoc/org/gradle/api/JavaVersion.html" + doLast { + println(JavaVersion.current()) + } + } + } + + // Add a task that can be called on Flutter projects that prints the available build variants + // in Gradle. + // + // This task prints variants in this format: + // + // BuildVariant: debug + // BuildVariant: release + // BuildVariant: profile + // + // Format of the output of this task is used by `AndroidProject.getBuildVariants`. + private static void addTaskForPrintBuildVariants(Project project) { + // Warning: The name of this task is used by `AndroidProject.getBuildVariants`. + project.tasks.register("printBuildVariants") { + description "Prints out all build variants for this Android project" + doLast { + project.android.applicationVariants.all { variant -> + println "BuildVariant: ${variant.name}" + } + } + } + } + + // Add a task that can be called on Flutter projects that outputs app link related project + // settings into a json file. + // + // See https://developer.android.com/training/app-links/ for more information about app link. + // + // The json will be saved in path stored in outputPath parameter. + // + // An example json: + // { + // applicationId: "com.example.app", + // deeplinks: [ + // {"scheme":"http", "host":"example.com", "path":".*"}, + // {"scheme":"https","host":"example.com","path":".*"} + // ] + // } + // + // The output file is parsed and used by devtool. + private static void addTasksForOutputsAppLinkSettings(Project project) { + project.android.applicationVariants.all { variant -> + // Warning: The name of this task is used by AndroidBuilder.outputsAppLinkSettings + project.tasks.register("output${variant.name.capitalize()}AppLinkSettings") { + description "stores app links settings for the given build variant of this Android project into a json file." + variant.outputs.all { output -> + // Deeplinks are defined in AndroidManifest.xml and is only available after + // `processResourcesProvider`. + Object processResources = output.hasProperty(propProcessResourcesProvider) ? + output.processResourcesProvider.get() : output.processResources + dependsOn processResources.name + } + doLast { + AppLinkSettings appLinkSettings = new AppLinkSettings() + appLinkSettings.applicationId = variant.applicationId + appLinkSettings.deeplinks = [] as Set + variant.outputs.all { output -> + Object processResources = output.hasProperty(propProcessResourcesProvider) ? + output.processResourcesProvider.get() : output.processResources + def manifest = new XmlParser().parse(processResources.manifestFile) + manifest.application.activity.each { activity -> + activity."meta-data".each { metadata -> + boolean nameAttribute = metadata.attributes().find { it.key == 'android:name' }?.value == 'flutter_deeplinking_enabled' + boolean valueAttribute = metadata.attributes().find { it.key == 'android:value' }?.value == 'true' + if (nameAttribute && valueAttribute) { + appLinkSettings.deeplinkingFlagEnabled = true + } + } + activity."intent-filter".each { appLinkIntent -> + // Print out the host attributes in data tags. + Set schemes = [] as Set + Set hosts = [] as Set + Set paths = [] as Set + IntentFilterCheck intentFilterCheck = new IntentFilterCheck() + + if (appLinkIntent.attributes().find { it.key == 'android:autoVerify' }?.value == 'true') { + intentFilterCheck.hasAutoVerify = true + } + appLinkIntent.'action'.each { action -> + if (action.attributes().find { it.key == 'android:name' }?.value == 'android.intent.action.VIEW') { + intentFilterCheck.hasActionView = true + } + } + appLinkIntent.'category'.each { category -> + if (category.attributes().find { it.key == 'android:name' }?.value == 'android.intent.category.DEFAULT') { + intentFilterCheck.hasDefaultCategory = true + } + if (category.attributes().find { it.key == 'android:name' }?.value == 'android.intent.category.BROWSABLE') { + intentFilterCheck.hasBrowsableCategory = true + } + } + appLinkIntent.data.each { data -> + data.attributes().each { entry -> + if (entry.key instanceof QName) { + switch (entry.key.getLocalPart()) { + case "scheme": + schemes.add(entry.value) + break + case "host": + hosts.add(entry.value) + break + case "pathAdvancedPattern": + case "pathPattern": + case "path": + paths.add(entry.value) + break + case "pathPrefix": + paths.add("${entry.value}.*") + break + case "pathSuffix": + paths.add(".*${entry.value}") + break + } + } + } + } + schemes.each { scheme -> + hosts.each { host -> + if (!paths) { + appLinkSettings.deeplinks.add(new Deeplink(scheme: scheme, host: host, path: ".*", intentFilterCheck: intentFilterCheck)) + } else { + paths.each { path -> + appLinkSettings.deeplinks.add(new Deeplink(scheme: scheme, host: host, path: path, intentFilterCheck: intentFilterCheck)) + } + } + } + } + } + } + } + JsonGenerator generator = new JsonGenerator.Options().build() + new File(project.getProperty("outputPath")).write(generator.toJson(appLinkSettings)) + } + } + } + } + + /** + * Returns a Flutter build mode suitable for the specified Android buildType. + * + * The BuildType DSL type is not public, and is therefore omitted from the signature. + * + * @return "debug", "profile", or "release" (fall-back). + */ + private static String buildModeFor(buildType) { + if (buildType.name == "profile") { + return "profile" + } else if (buildType.debuggable) { + return "debug" + } + return "release" + } + /** * Adds the dependencies required by the Flutter project. * This includes: @@ -428,17 +634,6 @@ class FlutterPlugin implements Plugin { } } - /** - * Returns the directory where the plugins are built. - */ - 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") - } - return project.buildDir - } - /** * Configures the Flutter plugin dependencies. * @@ -473,7 +668,7 @@ class FlutterPlugin implements Plugin { * and filtered then with the [doesSupportAndroidPlatform] method instead of * just using the `plugins.android` list. */ - private configureLegacyPluginEachProjects(Project project) { + private void configureLegacyPluginEachProjects(Project project) { try { if (!settingsGradleFile(project).text.contains("'.flutter-plugins'")) { return @@ -493,8 +688,9 @@ class FlutterPlugin implements Plugin { // Plugin has a functioning `android` folder and is included successfully, although it's not supported. // It must be configured nonetheless, to not throw an "Unresolved reference" exception. configurePluginProject(it) + /* groovylint-disable-next-line EmptyElseBlock */ } else { - // Plugin has no or an empty `android` folder. No action required. + // Plugin has no or an empty `android` folder. No action required. } } } @@ -594,16 +790,17 @@ class FlutterPlugin implements Plugin { * If minor or patch are omitted (non-conformant to semantic versioning), they are considered zero. * If the provided versions in both are equal, the longest version string is returned. * For example, "2.8.0" vs "2.8" will always consider "2.8.0" to be the most recent version. + * TODO: Remove this or compareVersionStrings. This does not handle strings like "8.6-rc-2". */ static String mostRecentSemanticVersion(String version1, String version2) { List version1Tokenized = version1.tokenize(".") List version2Tokenized = version2.tokenize(".") - def version1numTokens = version1Tokenized.size() - def version2numTokens = version2Tokenized.size() - def minNumTokens = Math.min(version1numTokens, version2numTokens) + int version1numTokens = version1Tokenized.size() + int version2numTokens = version2Tokenized.size() + int minNumTokens = Math.min(version1numTokens, version2numTokens) for (int i = 0; i < minNumTokens; i++) { - def num1 = version1Tokenized[i].toInteger() - def num2 = version2Tokenized[i].toInteger() + int num1 = version1Tokenized[i].toInteger() + int num2 = version2Tokenized[i].toInteger() if (num1 > num2) { return version1 } @@ -640,10 +837,10 @@ class FlutterPlugin implements Plugin { } pluginProject.afterEvaluate { // Default to int min if using a preview version to skip the sdk check. - int pluginCompileSdkVersion = Integer.MIN_VALUE; + int pluginCompileSdkVersion = Integer.MIN_VALUE // Stable versions use ints, legacy preview uses string. if (getCompileSdkFromProject(pluginProject).isInteger()) { - pluginCompileSdkVersion = getCompileSdkFromProject(pluginProject) as int; + pluginCompileSdkVersion = getCompileSdkFromProject(pluginProject) as int } maxPluginCompileSdkVersion = Math.max(pluginCompileSdkVersion, maxPluginCompileSdkVersion) String pluginNdkVersion = pluginProject.android.ndkVersion ?: ndkVersionIfUnspecified @@ -668,7 +865,7 @@ class FlutterPlugin implements Plugin { * or string version. */ private String getCompileSdkFromProject(Project gradleProject) { - return gradleProject.android.compileSdkVersion.substring(8); + return gradleProject.android.compileSdkVersion.substring(8) } /** @@ -724,13 +921,6 @@ class FlutterPlugin implements Plugin { return meta.dependencyGraph as List> } - private static String toCamelCase(List parts) { - if (parts.empty) { - return "" - } - return "${parts[0]}${parts[1..-1].collect { it.capitalize() }.join('')}" - } - private String resolveProperty(String name, String defaultValue) { if (localProperties == null) { localProperties = readPropertiesIfExist(new File(project.projectDir.parentFile, "local.properties")) @@ -748,19 +938,12 @@ class FlutterPlugin implements Plugin { return result } - private static Properties readPropertiesIfExist(File propertiesFile) { - Properties result = new Properties() - if (propertiesFile.exists()) { - propertiesFile.withReader("UTF-8") { reader -> result.load(reader) } - } - return result - } - private List getTargetPlatforms() { - if (!project.hasProperty("target-platform")) { + final String propTargetPlatform = "target-platform" + if (!project.hasProperty(propTargetPlatform)) { return DEFAULT_PLATFORMS } - return project.property("target-platform").split(",").collect { + return project.property(propTargetPlatform).split(",").collect { if (!PLATFORM_ARCH_MAP[it]) { throw new GradleException("Invalid platform: $it.") } @@ -769,26 +952,20 @@ class FlutterPlugin implements Plugin { } private Boolean shouldSplitPerAbi() { - return project.findProperty("split-per-abi")?.toBoolean() ?: false; + return project.findProperty("split-per-abi")?.toBoolean() ?: false } private Boolean useLocalEngine() { - return project.hasProperty("local-engine-repo") + return project.hasProperty(propLocalEngineRepo) } private Boolean isVerbose() { - return project.findProperty("verbose")?.toBoolean() ?: false; + return project.findProperty("verbose")?.toBoolean() ?: false } /** Whether to build the debug app in "fast-start" mode. */ private Boolean isFastStart() { - return project.findProperty("fast-start")?.toBoolean() ?: false; - } - - private static Boolean isBuiltAsApp(Project project) { - // Projects are built as applications when the they use the `com.android.application` - // plugin. - return project.plugins.hasPlugin("com.android.application"); + return project.findProperty("fast-start")?.toBoolean() ?: false } /** @@ -798,213 +975,13 @@ class FlutterPlugin implements Plugin { */ private Boolean supportsBuildMode(String flutterBuildMode) { if (!useLocalEngine()) { - return true; + return true } - assert(project.hasProperty("local-engine-build-mode")) + final String propLocalEngineBuildMode = "local-engine-build-mode" + assert(project.hasProperty(propLocalEngineBuildMode)) // Don't configure dependencies for a build mode that the local engine // doesn't support. - return project.property("local-engine-build-mode") == flutterBuildMode - } - - private void addCompileOnlyDependency(Project project, String variantName, Object dependency, Closure config = null) { - if (project.state.failure) { - return - } - String configuration; - if (project.getConfigurations().findByName("compileOnly")) { - configuration = "${variantName}CompileOnly"; - } else { - configuration = "${variantName}Provided"; - } - project.dependencies.add(configuration, dependency, config) - } - - private static void addApiDependencies(Project project, String variantName, Object dependency, Closure config = null) { - String configuration; - // `compile` dependencies are now `api` dependencies. - if (project.getConfigurations().findByName("api")) { - configuration = "${variantName}Api"; - } else { - configuration = "${variantName}Compile"; - } - project.dependencies.add(configuration, dependency, config) - } - - // Add a task that can be called on flutter projects that prints the Java version used in Gradle. - // - // Format of the output of this task can be used in debugging what version of Java Gradle is using. - // Not recommended for use in time sensitive commands like `flutter run` or `flutter build` as - // Gradle is slower than we want. Particularly in light of https://github.com/flutter/flutter/issues/119196. - private static void addTaskForJavaVersion(Project project) { - // Warning: the name of this task is used by other code. Change with caution. - project.tasks.register("javaVersion") { - description "Print the current java version used by gradle. " - "see: https://docs.gradle.org/current/javadoc/org/gradle/api/JavaVersion.html" - doLast { - println(JavaVersion.current()) - } - } - } - - // Add a task that can be called on Flutter projects that prints the available build variants - // in Gradle. - // - // This task prints variants in this format: - // - // BuildVariant: debug - // BuildVariant: release - // BuildVariant: profile - // - // Format of the output of this task is used by `AndroidProject.getBuildVariants`. - private static void addTaskForPrintBuildVariants(Project project) { - // Warning: The name of this task is used by `AndroidProject.getBuildVariants`. - project.tasks.register("printBuildVariants") { - description "Prints out all build variants for this Android project" - doLast { - project.android.applicationVariants.all { variant -> - println "BuildVariant: ${variant.name}"; - } - } - } - } - - // Add a task that can be called on Flutter projects that outputs app link related project - // settings into a json file. - // - // See https://developer.android.com/training/app-links/ for more information about app link. - // - // The json will be saved in path stored in outputPath parameter. - // - // An example json: - // { - // applicationId: "com.example.app", - // deeplinks: [ - // {"scheme":"http", "host":"example.com", "path":".*"}, - // {"scheme":"https","host":"example.com","path":".*"} - // ] - // } - // - // The output file is parsed and used by devtool. - private static void addTasksForOutputsAppLinkSettings(Project project) { - project.android.applicationVariants.all { variant -> - // Warning: The name of this task is used by AndroidBuilder.outputsAppLinkSettings - project.tasks.register("output${variant.name.capitalize()}AppLinkSettings") { - description "stores app links settings for the given build variant of this Android project into a json file." - variant.outputs.all { output -> - // Deeplinks are defined in AndroidManifest.xml and is only available after - // `processResourcesProvider`. - def processResources = output.hasProperty("processResourcesProvider") ? - output.processResourcesProvider.get() : output.processResources - dependsOn processResources.name - } - doLast { - def appLinkSettings = new AppLinkSettings() - appLinkSettings.applicationId = variant.applicationId - appLinkSettings.deeplinks = [] as Set - variant.outputs.all { output -> - def processResources = output.hasProperty("processResourcesProvider") ? - output.processResourcesProvider.get() : output.processResources - def manifest = new XmlParser().parse(processResources.manifestFile) - manifest.application.activity.each { activity -> - activity."meta-data".each { metadata -> - def nameAttribute = metadata.attributes().find { it.key == 'android:name' }?.value == 'flutter_deeplinking_enabled' - def valueAttribute = metadata.attributes().find { it.key == 'android:value' }?.value == 'true' - if (nameAttribute && valueAttribute) { - appLinkSettings.deeplinkingFlagEnabled = true - } - } - activity."intent-filter".each { appLinkIntent -> - // Print out the host attributes in data tags. - def schemes = [] as Set - def hosts = [] as Set - def paths = [] as Set - def intentFilterCheck = new IntentFilterCheck() - - if (appLinkIntent.attributes().find { it.key == 'android:autoVerify' }?.value == 'true') { - intentFilterCheck.hasAutoVerify = true - } - appLinkIntent.'action'.each { action -> - if (action.attributes().find { it.key == 'android:name' }?.value == 'android.intent.action.VIEW') { - intentFilterCheck.hasActionView = true - } - } - appLinkIntent.'category'.each { category -> - if (category.attributes().find { it.key == 'android:name' }?.value == 'android.intent.category.DEFAULT') { - intentFilterCheck.hasDefaultCategory = true - } - if (category.attributes().find { it.key == 'android:name' }?.value == 'android.intent.category.BROWSABLE') { - intentFilterCheck.hasBrowsableCategory = true - } - } - appLinkIntent.data.each { data -> - data.attributes().each { entry -> - if (entry.key instanceof QName) { - switch (entry.key.getLocalPart()) { - case "scheme": - schemes.add(entry.value) - break - case "host": - hosts.add(entry.value) - break - case "pathAdvancedPattern": - case "pathPattern": - case "path": - paths.add(entry.value) - break - case "pathPrefix": - paths.add("${entry.value}.*") - break - case "pathSuffix": - paths.add(".*${entry.value}") - break - } - } - } - } - schemes.each {scheme -> - hosts.each { host -> - if (!paths) { - appLinkSettings.deeplinks.add(new Deeplink(scheme: scheme, host: host, path: ".*", intentFilterCheck: intentFilterCheck)) - } else { - paths.each { path -> - appLinkSettings.deeplinks.add(new Deeplink(scheme: scheme, host: host, path: path, intentFilterCheck: intentFilterCheck)) - } - } - } - } - } - } - } - def generator = new JsonGenerator.Options().build() - new File(project.getProperty("outputPath")).write(generator.toJson(appLinkSettings)) - } - } - } - } - - /** - * Returns a Flutter build mode suitable for the specified Android buildType. - * - * The BuildType DSL type is not public, and is therefore omitted from the signature. - * - * @return "debug", "profile", or "release" (fall-back). - */ - private static String buildModeFor(buildType) { - if (buildType.name == "profile") { - return "profile" - } else if (buildType.debuggable) { - return "debug" - } - return "release" - } - - private static String getEngineArtifactDirName(buildType, targetArch) { - if (buildType.name == "profile") { - return "${targetArch}-profile" - } else if (buildType.debuggable) { - return "${targetArch}" - } - return "${targetArch}-release" + return project.property(propLocalEngineBuildMode) == flutterBuildMode } /** @@ -1026,8 +1003,9 @@ class FlutterPlugin implements Plugin { if (target == null) { target = "lib/main.dart" } - if (project.hasProperty("target")) { - target = project.property("target") + final String propTarget = "target" + if (project.hasProperty(propTarget)) { + target = project.property(propTarget) } return target } @@ -1062,11 +1040,11 @@ class FlutterPlugin implements Plugin { * https://issuetracker.google.com/issues/158753935 */ private boolean shouldConfigureFlutterTask(Task assembleTask) { - def cliTasksNames = project.gradle.startParameter.taskNames + List cliTasksNames = project.gradle.startParameter.taskNames if (cliTasksNames.size() != 1 || !cliTasksNames.first().contains("assemble")) { return true } - def taskName = cliTasksNames.first().split(":").last() + String taskName = cliTasksNames.first().split(":").last() if (taskName == "assemble") { return true } @@ -1099,71 +1077,86 @@ class FlutterPlugin implements Plugin { return } String[] fileSystemRootsValue = null - if (project.hasProperty("filesystem-roots")) { - fileSystemRootsValue = project.property("filesystem-roots").split("\\|") + final String propFileSystemRoots = "filesystem-roots" + if (project.hasProperty(propFileSystemRoots)) { + fileSystemRootsValue = project.property(propFileSystemRoots).split("\\|") } String fileSystemSchemeValue = null - if (project.hasProperty("filesystem-scheme")) { - fileSystemSchemeValue = project.property("filesystem-scheme") + final String propFileSystemScheme = "filesystem-scheme" + if (project.hasProperty(propFileSystemScheme)) { + fileSystemSchemeValue = project.property(propFileSystemScheme) } Boolean trackWidgetCreationValue = true - if (project.hasProperty("track-widget-creation")) { - trackWidgetCreationValue = project.property("track-widget-creation").toBoolean() + final String propTrackWidgetCreation = "track-widget-creation" + if (project.hasProperty(propTrackWidgetCreation)) { + trackWidgetCreationValue = project.property(propTrackWidgetCreation).toBoolean() } String frontendServerStarterPathValue = null - if (project.hasProperty("frontend-server-starter-path")) { - frontendServerStarterPathValue = project.property("frontend-server-starter-path") + final String propFrontendServerStarterPath = "frontend-server-starter-path" + if (project.hasProperty(propFrontendServerStarterPath)) { + frontendServerStarterPathValue = project.property(propFrontendServerStarterPath) } String extraFrontEndOptionsValue = null - if (project.hasProperty("extra-front-end-options")) { - extraFrontEndOptionsValue = project.property("extra-front-end-options") + final String propExtraFrontEndOptions = "extra-front-end-options" + if (project.hasProperty(propExtraFrontEndOptions)) { + extraFrontEndOptionsValue = project.property(propExtraFrontEndOptions) } String extraGenSnapshotOptionsValue = null - if (project.hasProperty("extra-gen-snapshot-options")) { - extraGenSnapshotOptionsValue = project.property("extra-gen-snapshot-options") + final String propExtraGenSnapshotOptions = "extra-gen-snapshot-options" + if (project.hasProperty(propExtraGenSnapshotOptions)) { + extraGenSnapshotOptionsValue = project.property(propExtraGenSnapshotOptions) } String splitDebugInfoValue = null - if (project.hasProperty("split-debug-info")) { - splitDebugInfoValue = project.property("split-debug-info") + final String propSplitDebugInfo = "split-debug-info" + if (project.hasProperty(propSplitDebugInfo)) { + splitDebugInfoValue = project.property(propSplitDebugInfo) } Boolean dartObfuscationValue = false - if (project.hasProperty("dart-obfuscation")) { - dartObfuscationValue = project.property("dart-obfuscation").toBoolean(); + final String propDartObfuscation = "dart-obfuscation" + if (project.hasProperty(propDartObfuscation)) { + dartObfuscationValue = project.property(propDartObfuscation).toBoolean() } Boolean treeShakeIconsOptionsValue = false - if (project.hasProperty("tree-shake-icons")) { - treeShakeIconsOptionsValue = project.property("tree-shake-icons").toBoolean() + final String propTreeShakeIcons = "tree-shake-icons" + if (project.hasProperty(propTreeShakeIcons)) { + treeShakeIconsOptionsValue = project.property(propTreeShakeIcons).toBoolean() } String dartDefinesValue = null - if (project.hasProperty("dart-defines")) { - dartDefinesValue = project.property("dart-defines") + final String propDartDefines = "dart-defines" + if (project.hasProperty(propDartDefines)) { + dartDefinesValue = project.property(propDartDefines) } - String bundleSkSLPathValue; - if (project.hasProperty("bundle-sksl-path")) { - bundleSkSLPathValue = project.property("bundle-sksl-path") + String bundleSkSLPathValue + final String propBundleSkslPath = "bundle-sksl-path" + if (project.hasProperty(propBundleSkslPath)) { + bundleSkSLPathValue = project.property(propBundleSkslPath) } - String performanceMeasurementFileValue; - if (project.hasProperty("performance-measurement-file")) { - performanceMeasurementFileValue = project.property("performance-measurement-file") + String performanceMeasurementFileValue + final String propPerformanceMesaurementFile = "performance-measurement-file" + if (project.hasProperty(propPerformanceMesaurementFile)) { + performanceMeasurementFileValue = project.property(propPerformanceMesaurementFile) } - String codeSizeDirectoryValue; - if (project.hasProperty("code-size-directory")) { - codeSizeDirectoryValue = project.property("code-size-directory") + String codeSizeDirectoryValue + final String propCodeSizeDirectory = "code-size-directory" + if (project.hasProperty(propCodeSizeDirectory)) { + codeSizeDirectoryValue = project.property(propCodeSizeDirectory) } Boolean deferredComponentsValue = false - if (project.hasProperty("deferred-components")) { - deferredComponentsValue = project.property("deferred-components").toBoolean() + final String propDeferredComponents = "deferred-components" + if (project.hasProperty(propDeferredComponents)) { + deferredComponentsValue = project.property(propDeferredComponents).toBoolean() } Boolean validateDeferredComponentsValue = true - if (project.hasProperty("validate-deferred-components")) { - validateDeferredComponentsValue = project.property("validate-deferred-components").toBoolean() + final String propValidateDeferredComponents = "validate-deferred-components" + if (project.hasProperty(propValidateDeferredComponents)) { + validateDeferredComponentsValue = project.property(propValidateDeferredComponents).toBoolean() } addTaskForJavaVersion(project) - if(isFlutterAppProject()) { + if (isFlutterAppProject()) { addTaskForPrintBuildVariants(project) addTasksForOutputsAppLinkSettings(project) } - def targetPlatforms = getTargetPlatforms() + List targetPlatforms = getTargetPlatforms() def addFlutterDeps = { variant -> if (shouldSplitPerAbi()) { variant.outputs.each { output -> @@ -1171,7 +1164,7 @@ class FlutterPlugin implements Plugin { // for only the output APK, not for the variant itself. Skipping this step simply // causes Gradle to use the value of variant.versionCode for the APK. // For more, see https://developer.android.com/studio/build/configure-apk-splits - def abiVersionCode = ABI_VERSION.get(output.getFilter(OutputFile.ABI)) + int abiVersionCode = ABI_VERSION.get(output.getFilter(OutputFile.ABI)) if (abiVersionCode != null) { output.versionCodeOverride = abiVersionCode * 1000 + variant.versionCode @@ -1242,8 +1235,8 @@ class FlutterPlugin implements Plugin { } // Copy the native assets created by build.dart and placed in build/native_assets by flutter assemble. // The `$project.buildDir` is '.android/Flutter/build/' instead of 'build/'. - def buildDir = "${getFlutterSourceDirectory()}/build" - def nativeAssetsDir = "${buildDir}/native_assets/android/jniLibs/lib" + String buildDir = "${getFlutterSourceDirectory()}/build" + String nativeAssetsDir = "${buildDir}/native_assets/android/jniLibs/lib" from("${nativeAssetsDir}/${abi}") { include "*.so" rename { String filename -> @@ -1261,7 +1254,7 @@ class FlutterPlugin implements Plugin { ) { dependsOn(compileTask) with(compileTask.assets) - def currentGradleVersion = project.getGradle().getGradleVersion() + String currentGradleVersion = project.getGradle().getGradleVersion() // See https://docs.gradle.org/current/javadoc/org/gradle/api/file/ConfigurableFilePermissions.html // See https://github.com/flutter/flutter/pull/50047 @@ -1293,7 +1286,7 @@ class FlutterPlugin implements Plugin { } if (!isUsedAsSubproject) { def variantOutput = variant.outputs.first() - def processResources = variantOutput.hasProperty("processResourcesProvider") ? + def processResources = variantOutput.hasProperty(propProcessResourcesProvider) ? variantOutput.processResourcesProvider.get() : variantOutput.processResources processResources.dependsOn(copyFlutterAssetsTask) } @@ -1317,11 +1310,11 @@ class FlutterPlugin implements Plugin { project.android.applicationVariants.all { variant -> Task assembleTask = getAssembleTask(variant) if (!shouldConfigureFlutterTask(assembleTask)) { - return + return } Task copyFlutterAssetsTask = addFlutterDeps(variant) def variantOutput = variant.outputs.first() - def processResources = variantOutput.hasProperty("processResourcesProvider") ? + def processResources = variantOutput.hasProperty(propProcessResourcesProvider) ? variantOutput.processResourcesProvider.get() : variantOutput.processResources processResources.dependsOn(copyFlutterAssetsTask) @@ -1354,7 +1347,7 @@ class FlutterPlugin implements Plugin { filename += "-${buildModeFor(variant.buildType)}" project.copy { from new File("$outputDirectoryStr/${output.outputFileName}") - into new File("${project.buildDir}/outputs/flutter-apk"); + into new File("${project.buildDir}/outputs/flutter-apk") rename { return "${filename}.apk" } @@ -1362,7 +1355,7 @@ class FlutterPlugin implements Plugin { } } // Copy the native assets created by build.dart and placed here by flutter assemble. - def nativeAssetsDir = "${project.buildDir}/../native_assets/android/jniLibs/lib/" + String nativeAssetsDir = "${project.buildDir}/../native_assets/android/jniLibs/lib/" project.android.sourceSets.main.jniLibs.srcDir(nativeAssetsDir) } configurePlugins(project) @@ -1428,12 +1421,12 @@ class FlutterPlugin implements Plugin { List firstVersion = firstString.tokenize(".") List secondVersion = secondString.tokenize(".") - def commonIndices = Math.min(firstVersion.size(), secondVersion.size()) + int commonIndices = Math.min(firstVersion.size(), secondVersion.size()) for (int i = 0; i < commonIndices; i++) { String firstAtIndex = firstVersion[i] String secondAtIndex = secondVersion[i] - int firstInt = 0; + int firstInt = 0 int secondInt = 0 try { if (firstAtIndex.contains("-")) { @@ -1467,26 +1460,32 @@ class FlutterPlugin implements Plugin { } class AppLinkSettings { + String applicationId Set deeplinks boolean deeplinkingFlagEnabled + } class IntentFilterCheck { + boolean hasAutoVerify boolean hasActionView boolean hasDefaultCategory boolean hasBrowsableCategory + } class Deeplink { String scheme, host, path IntentFilterCheck intentFilterCheck boolean equals(o) { - if (o == null) + if (o == null) { throw new NullPointerException() - if (o.getClass() != getClass()) + } + if (o.getClass() != getClass()) { return false + } return scheme == o.scheme && host == o.host && path == o.path @@ -1494,62 +1493,91 @@ class Deeplink { } abstract class BaseFlutterTask extends DefaultTask { + @Internal File flutterRoot + @Internal File flutterExecutable + @Input String buildMode + @Input int minSdkVersion + @Optional @Input String localEngine + @Optional @Input String localEngineHost + @Optional @Input String localEngineSrcPath + @Optional @Input Boolean fastStart + @Input String targetPath + @Optional @Input Boolean verbose + @Optional @Input String[] fileSystemRoots + @Optional @Input String fileSystemScheme + @Input Boolean trackWidgetCreation + @Optional @Input List targetPlatformValues + @Internal File sourceDir + @Internal File intermediateDir + @Optional @Input String frontendServerStarterPath + @Optional @Input String extraFrontEndOptions + @Optional @Input String extraGenSnapshotOptions + @Optional @Input String splitDebugInfo + @Optional @Input Boolean treeShakeIcons + @Optional @Input Boolean dartObfuscation + @Optional @Input String dartDefines + @Optional @Input String bundleSkSLPath + @Optional @Input - String codeSizeDirectory; + String codeSizeDirectory + @Optional @Input - String performanceMeasurementFile; + String performanceMeasurementFile + @Optional @Input Boolean deferredComponents + @Optional @Input Boolean validateDeferredComponents + @Optional @Input String flavor @@ -1573,7 +1601,7 @@ abstract class BaseFlutterTask extends DefaultTask { // multiple ABIs, the target name is used to communicate which ones are required // rather than the TargetPlatform. This allows multiple builds to share the same // cache. - String[] ruleNames; + String[] ruleNames if (buildMode == "debug") { ruleNames = ["debug_android_application"] } else if (deferredComponents) { @@ -1649,9 +1677,11 @@ abstract class BaseFlutterTask extends DefaultTask { args(ruleNames) } } + } class FlutterTask extends BaseFlutterTask { + @OutputDirectory File getOutputDirectory() { return intermediateDir @@ -1684,24 +1714,24 @@ class FlutterTask extends BaseFlutterTask { } FileCollection readDependencies(File dependenciesFile, Boolean inputs) { - if (dependenciesFile.exists()) { - // Dependencies file has Makefile syntax: - // : - String depText = dependenciesFile.text - // So we split list of files by non-escaped(by backslash) space, - def matcher = depText.split(": ")[inputs ? 1 : 0] =~ /(\\ |[^\s])+/ - // then we replace all escaped spaces with regular spaces - def depList = matcher.collect{it[0].replaceAll("\\\\ ", " ")} - return project.files(depList) - } - return project.files(); + if (dependenciesFile.exists()) { + // Dependencies file has Makefile syntax: + // : + String depText = dependenciesFile.text + // So we split list of files by non-escaped(by backslash) space, + def matcher = depText.split(": ")[inputs ? 1 : 0] =~ /(\\ |[^\s])+/ + // then we replace all escaped spaces with regular spaces + def depList = matcher.collect{ it[0].replaceAll("\\\\ ", " ") } + return project.files(depList) + } + return project.files() } @InputFiles FileCollection getSourceFiles() { FileCollection sources = project.files() for (File depfile in getDependenciesFiles()) { - sources += readDependencies(depfile, true) + sources += readDependencies(depfile, true) } return sources + project.files("pubspec.yaml") } @@ -1710,7 +1740,7 @@ class FlutterTask extends BaseFlutterTask { FileCollection getOutputFiles() { FileCollection sources = project.files() for (File depfile in getDependenciesFiles()) { - sources += readDependencies(depfile, false) + sources += readDependencies(depfile, false) } return sources } @@ -1719,4 +1749,5 @@ class FlutterTask extends BaseFlutterTask { void build() { buildBundle() } + }