4
4
5
5
package com.flutter.gradle
6
6
7
+ import com.android.build.api.artifact.SingleArtifact
8
+ import com.android.build.api.variant.AndroidComponentsExtension
7
9
import com.android.build.gradle.AbstractAppExtension
8
10
import com.android.build.gradle.BaseExtension
9
11
import com.android.build.gradle.tasks.ProcessAndroidResources
10
12
import com.android.builder.model.BuildType
11
13
import com.flutter.gradle.plugins.PluginHandler
14
+ import com.flutter.gradle.tasks.DeepLinkJsonFromManifestTask
12
15
import groovy.lang.Closure
13
- import groovy.util.Node
14
16
import org.gradle.api.GradleException
15
17
import org.gradle.api.Project
16
18
import org.gradle.api.Task
@@ -24,10 +26,6 @@ import java.util.Properties
24
26
* A collection of static utility functions used by the Flutter Gradle Plugin.
25
27
*/
26
28
object FlutterPluginUtils {
27
- private const val MANIFEST_NAME_KEY = " android:name"
28
- private const val MANIFEST_VALUE_KEY = " android:value"
29
- private const val MANIFEST_VALUE_TRUE = " true"
30
-
31
29
// Gradle properties. These must correspond to the values used in
32
30
// flutter/packages/flutter_tools/lib/src/android/gradle.dart, and therefore it is not
33
31
// recommended to use these const values in tests.
@@ -399,6 +397,7 @@ object FlutterPluginUtils {
399
397
return project.extensions.findByType(BaseExtension ::class .java)!!
400
398
}
401
399
400
+ // Avoid new usages this class is not part of the public AGP DSL.
402
401
private fun getAndroidAppExtensionOrNull (project : Project ): AbstractAppExtension ? =
403
402
project.extensions.findByType(AbstractAppExtension ::class .java)
404
403
@@ -783,199 +782,43 @@ object FlutterPluginUtils {
783
782
* Add a task that can be called on Flutter projects that outputs app link related project
784
783
* settings into a json file.
785
784
* See https://developer.android.com/training/app-links/ for more information about app link.
786
- * The json will be saved in path stored in outputPath parameter.
785
+ * The json will be saved in path stored in "outputPath" parameter or in the projects build
786
+ * directory with the file deeplink.json if not specified.
787
+ *
788
+ * See DeepLinkJsonFromManifestTask for the structure of the json.
787
789
*
788
- * An example json:
789
- * {
790
- * applicationId: "com.example.app",
791
- * deeplinks: [
792
- * {"scheme":"http", "host":"example.com", "path":".*"},
793
- * {"scheme":"https","host":"example.com","path":".*"}
794
- * ]
795
- * }
796
790
* The output file is parsed and used by devtool.
797
791
*/
798
792
@JvmStatic
799
793
@JvmName(" addTasksForOutputsAppLinkSettings" )
800
794
internal fun addTasksForOutputsAppLinkSettings (project : Project ) {
801
795
// Integration test for AppLinkSettings task defined in
802
796
// flutter/flutter/packages/flutter_tools/test/integration.shard/android_gradle_outputs_app_link_settings_test.dart
803
- val android = getAndroidAppExtensionOrNull(project)
804
- if (android == null ) {
805
- project.logger.info(" addTasksForOutputsAppLinkSettings called on project without android extension." )
806
- return
807
- }
808
- android.applicationVariants.configureEach {
809
- val variant = this
810
- project.tasks.register(" output${capitalize(variant.name)} AppLinkSettings" ) {
811
- val task: Task = this
812
- task.description =
813
- " stores app links settings for the given build variant of this Android project into a json file."
814
- variant.outputs.configureEach {
815
- // TODO(gmackall): Migrate to AGPs variant api.
816
- // https://github.com/flutter/flutter/issues/166550
817
- @Suppress(" DEPRECATION" )
818
- val baseVariantOutput: com.android.build.gradle.api.BaseVariantOutput = this
819
- // Deeplinks are defined in AndroidManifest.xml and is only available after
820
- // processResourcesProvider.
821
- dependsOn(findProcessResources(baseVariantOutput))
822
- }
823
- doLast {
824
- // We are configuring the same object before a doLast and in a doLast.
825
- // without a clear reason why. That is not good.
826
- variant.outputs.configureEach {
827
- val appLinkSettings = createAppLinkSettings(variant, this )
828
- File (project.property(" outputPath" ).toString()).writeText(
829
- appLinkSettings.toJson().toString()
797
+ val androidComponents = project.extensions.getByType(AndroidComponentsExtension ::class .java)
798
+ androidComponents.onVariants { variant ->
799
+ val manifestUpdater =
800
+ project.tasks.register(" output${capitalize(variant.name)} AppLinkSettings" , DeepLinkJsonFromManifestTask ::class .java) {
801
+ namespace.set(variant.namespace)
802
+ // Flutter should always use project.layout.buildDirectory.file("deeplink.json")
803
+ // instead of relying on passing in a path.
804
+ if (project.hasProperty(" outputPath" )) {
805
+ deepLinkJson.set(
806
+ File (project.property(" outputPath" ).toString())
830
807
)
808
+ } else {
809
+ deepLinkJson.set(project.layout.buildDirectory.file(" deeplink.json" ))
831
810
}
832
811
}
833
- }
834
- }
835
- }
836
-
837
- /* *
838
- * Extracts app deeplink information from the Android manifest file of a variant then returns
839
- * an AppLinkSettings object.
840
- *
841
- * @param BaseVariantOutput The output of a specific build variant (e.g., debug, release).
842
- * @param variant The application variant being processed.
843
- */
844
- @Suppress(" KDocUnresolvedReference" )
845
- private fun createAppLinkSettings (
846
- // TODO(gmackall): Migrate to AGPs variant api.
847
- // https://github.com/flutter/flutter/issues/166550
848
- @Suppress(" DEPRECATION" ) variant : com.android.build.gradle.api.ApplicationVariant ,
849
- @Suppress(" DEPRECATION" ) baseVariantOutput : com.android.build.gradle.api.BaseVariantOutput
850
- ): AppLinkSettings {
851
- val appLinkSettings = AppLinkSettings (variant.applicationId)
852
-
853
- // XmlParser is not namespace aware because it makes querying nodes cumbersome.
854
- // TODO(gmackall): Migrate to AGPs variant api.
855
- // https://github.com/flutter/flutter/issues/166550
856
- @Suppress(" DEPRECATION" )
857
- val manifest: Node =
858
- groovy.xml
859
- .XmlParser (false , false )
860
- .parse(findProcessResources(baseVariantOutput).manifestFile)
861
- val applicationNode: Node ? =
862
- manifest.children().find { node ->
863
- node is Node && node.name() == " application"
864
- } as Node ?
865
- if (applicationNode == null ) {
866
- return appLinkSettings
867
- }
868
- val activities: List <Node > =
869
- applicationNode.children().filterIsInstance<Node >().filter { item ->
870
- item.name() == " activity"
871
- }
872
-
873
- activities.forEach { activity ->
874
- val metaDataItems: List <Node > =
875
- activity.children().filterIsInstance<Node >().filter { metaItem ->
876
- metaItem.name() == " meta-data"
877
- }
878
- metaDataItems.forEach { metaDataItem ->
879
- val nameAttribute: Boolean =
880
- metaDataItem.attribute(MANIFEST_NAME_KEY ) == " flutter_deeplinking_enabled"
881
- val valueAttribute: Boolean =
882
- metaDataItem.attribute(MANIFEST_VALUE_KEY ) == MANIFEST_VALUE_TRUE
883
- if (nameAttribute && valueAttribute) {
884
- appLinkSettings.deeplinkingFlagEnabled = true
885
- }
886
- }
887
- val intentFilterItems: List <Node > =
888
- activity.children().filterIsInstance<Node >().filter { filterItem ->
889
- filterItem.name() == " intent-filter"
890
- }
891
- intentFilterItems.forEach { appLinkIntent ->
892
- // Print out the host attributes in data tags.
893
- val schemes: MutableSet <String ?> = mutableSetOf ()
894
- val hosts: MutableSet <String ?> = mutableSetOf ()
895
- val paths: MutableSet <String ?> = mutableSetOf ()
896
- val intentFilterCheck = IntentFilterCheck ()
897
- if (appLinkIntent.attribute(" android:autoVerify" ) == MANIFEST_VALUE_TRUE ) {
898
- intentFilterCheck.hasAutoVerify = true
899
- }
900
-
901
- val actionItems: List <Node > =
902
- appLinkIntent.children().filterIsInstance<Node >().filter { item ->
903
- item.name() == " action"
904
- }
905
- // Any action item causes intentFilterCheck to always be true
906
- // and we keep looping instead of exiting out early.
907
- // TODO: Exit out early per intent filter action view.
908
- actionItems.forEach { action ->
909
- if (action.attribute(MANIFEST_NAME_KEY ) == " android.intent.action.VIEW" ) {
910
- intentFilterCheck.hasActionView = true
911
- }
912
- }
913
- val categoryItems: List <Node > =
914
- appLinkIntent.children().filterIsInstance<Node >().filter { item ->
915
- item.name() == " category"
916
- }
917
- categoryItems.forEach { category ->
918
- // TODO: Exit out early per intent filter default category.
919
- if (category.attribute(MANIFEST_NAME_KEY ) == " android.intent.category.DEFAULT" ) {
920
- intentFilterCheck.hasDefaultCategory = true
921
- }
922
- // TODO: Exit out early per intent filter browsable category.
923
- if (category.attribute(MANIFEST_NAME_KEY ) == " android.intent.category.BROWSABLE" ) {
924
- intentFilterCheck.hasBrowsableCategory =
925
- true
926
- }
927
- }
928
- val dataItems: List <Node > =
929
- appLinkIntent.children().filterIsInstance<Node >().filter { item ->
930
- item.name() == " data"
931
- }
932
- dataItems.forEach { data ->
933
- data.attributes().forEach { entry ->
934
- when (entry.key) {
935
- " android:scheme" -> schemes.add(entry.value.toString())
936
- " android:host" -> hosts.add(entry.value.toString())
937
- // All path patterns add to paths.
938
- " android:pathAdvancedPattern" ->
939
- paths.add(
940
- entry.value.toString()
941
- )
942
-
943
- " android:pathPattern" -> paths.add(entry.value.toString())
944
- " android:path" -> paths.add(entry.value.toString())
945
- " android:pathPrefix" -> paths.add(entry.value.toString() + " .*" )
946
- " android:pathSuffix" -> paths.add(" .*" + entry.value.toString())
947
- }
948
- }
949
- }
950
- if (hosts.isNotEmpty() || paths.isNotEmpty()) {
951
- if (schemes.isEmpty()) {
952
- schemes.add(null )
953
- }
954
- if (hosts.isEmpty()) {
955
- hosts.add(null )
956
- }
957
- if (paths.isEmpty()) {
958
- paths.add(" .*" )
959
- }
960
- // Sets are not ordered this could produce a bug.
961
- schemes.forEach { scheme ->
962
- hosts.forEach { host ->
963
- paths.forEach { path ->
964
- appLinkSettings.deeplinks.add(
965
- Deeplink (
966
- scheme,
967
- host,
968
- path,
969
- intentFilterCheck
970
- )
971
- )
972
- }
973
- }
974
- }
975
- }
976
- }
812
+ // This task does not modify the manifest despite using an api
813
+ // designed for modification. The task is responsible for an exact copy of the input
814
+ // manifest being used for the output manifest.
815
+ variant.artifacts
816
+ .use(manifestUpdater)
817
+ .wiredWithFiles(
818
+ DeepLinkJsonFromManifestTask ::manifestFile,
819
+ DeepLinkJsonFromManifestTask ::updatedManifest
820
+ ).toTransform(SingleArtifact .MERGED_MANIFEST ) // (3) Indicate the artifact and operation type.
977
821
}
978
- return appLinkSettings
979
822
}
980
823
}
981
824
0 commit comments