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