1 /*
<lambda>null2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.build.playground
18 
19 import com.google.common.annotations.VisibleForTesting
20 import java.io.File
21 import java.util.Properties
22 import org.gradle.api.DefaultTask
23 import org.gradle.api.GradleException
24 import org.gradle.api.Project
25 import org.gradle.api.file.RegularFileProperty
26 import org.gradle.api.tasks.CacheableTask
27 import org.gradle.api.tasks.InputFile
28 import org.gradle.api.tasks.OutputFile
29 import org.gradle.api.tasks.PathSensitive
30 import org.gradle.api.tasks.PathSensitivity
31 import org.gradle.api.tasks.TaskAction
32 import org.gradle.api.tasks.TaskProvider
33 
34 /**
35  * Compares the playground Gradle configuration with the main androidx Gradle configuration to
36  * ensure playgrounds do not define any property in their own build that conflicts with the main
37  * build.
38  */
39 @CacheableTask
40 abstract class VerifyPlaygroundGradleConfigurationTask : DefaultTask() {
41     @get:InputFile
42     @get:PathSensitive(PathSensitivity.NONE)
43     abstract val androidxProperties: RegularFileProperty
44 
45     @get:InputFile
46     @get:PathSensitive(PathSensitivity.NONE)
47     abstract val playgroundProperties: RegularFileProperty
48 
49     @get:InputFile
50     @get:PathSensitive(PathSensitivity.NONE)
51     abstract val androidxGradleWrapper: RegularFileProperty
52 
53     @get:InputFile
54     @get:PathSensitive(PathSensitivity.NONE)
55     abstract val playgroundGradleWrapper: RegularFileProperty
56 
57     @get:OutputFile abstract val outputFile: RegularFileProperty
58 
59     @TaskAction
60     fun checkPlaygroundGradleConfiguration() {
61         compareProperties()
62         // TODO: re-enable when https://github.com/gradle/gradle/issues/20778
63         //  is fixed.
64         // compareGradleWrapperVersion()
65         // put the success into an output so that task can be up to date.
66         outputFile.get().asFile.writeText("valid", Charsets.UTF_8)
67     }
68 
69     private fun compareProperties() {
70         val rootProperties = loadPropertiesFile(androidxProperties.get().asFile)
71         val playgroundProperties = loadPropertiesFile(playgroundProperties.get().asFile)
72         validateProperties(rootProperties, playgroundProperties)
73     }
74 
75     private fun compareGradleWrapperVersion() {
76         val androidxGradleVersion =
77             readGradleVersionFromWrapperProperties(androidxGradleWrapper.get().asFile)
78         val playgroundGradleVersion =
79             readGradleVersionFromWrapperProperties(playgroundGradleWrapper.get().asFile)
80         if (androidxGradleVersion != playgroundGradleVersion) {
81             throw GradleException(
82                 """
83                 Playground gradle version ($playgroundGradleVersion) must match the AndroidX main
84                 build gradle version ($androidxGradleVersion).
85                 """
86                     .trimIndent()
87             )
88         }
89     }
90 
91     private fun readGradleVersionFromWrapperProperties(file: File): String {
92         val distributionUrl = loadPropertiesFile(file).getProperty("distributionUrl")
93         checkNotNull(distributionUrl) {
94             "cannot read distribution url from gradle wrapper file: ${file.canonicalPath}"
95         }
96         val gradleVersion = extractGradleVersion(distributionUrl)
97         return checkNotNull(gradleVersion) {
98             "Failed to extract gradle version from gradle wrapper file. Input: $distributionUrl"
99         }
100     }
101 
102     private fun validateProperties(rootProperties: Properties, playgroundProperties: Properties) {
103         // ensure we don't define properties that do not match the root file
104         // this includes properties that are not defined in the root androidx build as they might
105         // be properties which can alter the build output. We might consider allow listing certain
106         // properties in the future if necessary.
107         val propertyKeys = rootProperties.keys + playgroundProperties.keys
108         propertyKeys.forEach { key ->
109             val rootValue = rootProperties[key]
110             val playgroundValue = playgroundProperties[key]
111 
112             if (
113                 rootValue != playgroundValue &&
114                     !ignoredProperties.contains(key) &&
115                     exceptedProperties[key] != playgroundValue
116             ) {
117                 throw GradleException(
118                     """
119                     $key is defined in ${androidxProperties.get().asFile.absolutePath} as
120                     $rootValue, which differs from $playgroundValue defined in
121                     ${this.playgroundProperties.get().asFile.absolutePath}. If this change is
122                     intentional, you can ignore it by adding it to ignoredProperties in
123                     VerifyPlaygroundGradleConfigurationTask.kt
124 
125                     Note: Having inconsistent properties in playground projects might trigger wrong
126                     compilation output in the main AndroidX build, so if a property is defined in
127                     playground properties, its value **MUST** match that of regular AndroidX build.
128                     """
129                         .trimIndent()
130                 )
131             }
132         }
133     }
134 
135     private fun loadPropertiesFile(file: File) =
136         file.inputStream().use { inputStream -> Properties().apply { load(inputStream) } }
137 
138     companion object {
139         private const val TASK_NAME = "verifyPlaygroundGradleConfiguration"
140 
141         // A mapping of the expected override in playground, which should generally follow AOSP on
142         // androidx-main. Generally, should only be used for conflicting properties which have
143         // different values in different built targets on AOSP, but still should be declared in
144         // playground.
145         private val exceptedProperties =
146             mapOf(
147                 "androidx.writeVersionedApiFiles" to "true",
148             )
149 
150         private val ignoredProperties =
151             setOf(
152                 "org.gradle.jvmargs",
153                 "org.gradle.daemon",
154                 "android.builder.sdkDownload",
155                 "android.suppressUnsupportedCompileSdk",
156                 "androidx.constraints",
157             )
158 
159         /**
160          * Regular expression to extract the gradle version from a distributionUrl property. Sample
161          * input looks like: <some-path>/gradle-7.3-rc-2-all.zip
162          */
163         private val GRADLE_VERSION_REGEX = """/gradle-(.+)-(all|bin)\.zip$""".toRegex()
164 
165         @VisibleForTesting // make it accessible for buildSrc-tests
166         fun extractGradleVersion(distributionUrl: String): String? {
167             return GRADLE_VERSION_REGEX.find(distributionUrl)?.groupValues?.getOrNull(1)
168         }
169 
170         /**
171          * Creates the task to verify playground properties if an only if we have the
172          * playground-common folder to check against.
173          */
174         fun createIfNecessary(
175             project: Project
176         ): TaskProvider<VerifyPlaygroundGradleConfigurationTask>? {
177             return if (project.projectDir.resolve("playground-common").exists()) {
178                 project.tasks.register(
179                     TASK_NAME,
180                     VerifyPlaygroundGradleConfigurationTask::class.java
181                 ) {
182                     it.androidxProperties.set(
183                         project.layout.projectDirectory.file("gradle.properties")
184                     )
185                     it.playgroundProperties.set(
186                         project.layout.projectDirectory.file(
187                             "playground-common/androidx-shared.properties"
188                         )
189                     )
190                     it.androidxGradleWrapper.set(
191                         project.layout.projectDirectory.file(
192                             "gradle/wrapper/gradle-wrapper.properties"
193                         )
194                     )
195                     it.playgroundGradleWrapper.set(
196                         project.layout.projectDirectory.file(
197                             "playground-common/gradle/wrapper/gradle-wrapper.properties"
198                         )
199                     )
200                     it.outputFile.set(
201                         project.layout.buildDirectory.file("playgroundPropertiesValidation.out")
202                     )
203                 }
204             } else {
205                 null
206             }
207         }
208     }
209 }
210