1 /* <lambda>null2 * Copyright 2021 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 18 19 import org.gradle.api.GradleException 20 import org.gradle.api.Project 21 import org.gradle.api.provider.Provider 22 import org.gradle.api.services.BuildService 23 import org.gradle.api.services.BuildServiceParameters 24 import org.tomlj.Toml 25 import org.tomlj.TomlParseResult 26 import org.tomlj.TomlTable 27 28 /** Loads Library groups and versions from a specified TOML file. */ 29 abstract class LibraryVersionsService : BuildService<LibraryVersionsService.Parameters> { 30 interface Parameters : BuildServiceParameters { 31 var tomlFileName: String 32 var tomlFileContents: Provider<String> 33 } 34 35 private val parsedTomlFile: TomlParseResult by lazy { 36 val result = Toml.parse(parameters.tomlFileContents.get()) 37 if (result.hasErrors()) { 38 val issues = 39 result.errors().joinToString(separator = "\n") { 40 "${parameters.tomlFileName}:${it.position()}: ${it.message}" 41 } 42 throw Exception("${parameters.tomlFileName} file has issues.\n$issues") 43 } 44 result 45 } 46 47 private fun getTable(key: String): TomlTable { 48 return parsedTomlFile.getTable(key) 49 ?: throw GradleException("Library versions toml file is missing [$key] table") 50 } 51 52 // map from name of constant to Version 53 val libraryVersions: Map<String, Version> by lazy { 54 val versions = getTable("versions") 55 versions.keySet().associateWith { versionName -> 56 val versionValue = versions.getString(versionName)!! 57 Version.parseOrNull(versionValue) 58 ?: throw GradleException( 59 "$versionName does not match expected format - $versionValue" 60 ) 61 } 62 } 63 64 // map of library groups keyed by their variable name in the toml file 65 val libraryGroups: Map<String, LibraryGroup> by lazy { 66 val result = mutableMapOf<String, LibraryGroup>() 67 for (association in libraryGroupAssociations) { 68 result[association.declarationName] = association.libraryGroup 69 } 70 result 71 } 72 73 // map of library groups keyed by group name 74 val libraryGroupsByGroupId: Map<String, LibraryGroup> by lazy { 75 val result = mutableMapOf<String, LibraryGroup>() 76 for (association in libraryGroupAssociations) { 77 // Check for duplicate groups 78 val groupId = association.libraryGroup.group 79 val existingAssociation = result[groupId] 80 if (existingAssociation != null) { 81 if ( 82 existingAssociation.atomicGroupVersion != null && 83 association.libraryGroup.atomicGroupVersion != null && 84 existingAssociation.group !in ALLOWED_ATOMIC_GROUP_EXCEPTIONS 85 ) { 86 throw GradleException( 87 "Multiple atomic groups defined with the same Maven group ID: $groupId" 88 ) 89 } 90 if (association.overrideIncludeInProjectPaths.isEmpty()) { 91 throw GradleException( 92 "Duplicate library group $groupId defined in " + 93 "${association.declarationName} does not set overrideInclude. " + 94 "Declarations beyond the first can only have an effect if they set " + 95 "overrideInclude" 96 ) 97 } 98 } else { 99 result[groupId] = association.libraryGroup 100 } 101 } 102 result 103 } 104 105 // map from project name to group override if applicable 106 val overrideLibraryGroupsByProjectPath: Map<String, LibraryGroup> by lazy { 107 val result = mutableMapOf<String, LibraryGroup>() 108 for (association in libraryGroupAssociations) { 109 for (overridePath in association.overrideIncludeInProjectPaths) { 110 result[overridePath] = association.libraryGroup 111 } 112 } 113 result 114 } 115 116 private val libraryGroupAssociations: List<LibraryGroupAssociation> by lazy { 117 val groups = getTable("groups") 118 119 fun readGroupVersion(groupDefinition: TomlTable, groupName: String, key: String): Version? { 120 val versionRef = groupDefinition.getString(key) ?: return null 121 if (!versionRef.startsWith(VersionReferencePrefix)) { 122 throw GradleException( 123 "Group entry $key is expected to start with $VersionReferencePrefix" 124 ) 125 } 126 // name without `versions.` 127 val atomicGroupVersionName = versionRef.removePrefix(VersionReferencePrefix) 128 return libraryVersions[atomicGroupVersionName] 129 ?: error( 130 "Group entry $groupName specifies $atomicGroupVersionName, but such version " + 131 "doesn't exist" 132 ) 133 } 134 groups.keySet().sorted().map { name -> 135 // get group name 136 val groupDefinition = groups.getTable(name)!! 137 val groupName = groupDefinition.getString("group")!! 138 139 // get group version, if any 140 val atomicGroupVersion = 141 readGroupVersion( 142 groupDefinition = groupDefinition, 143 groupName = groupName, 144 key = AtomicGroupVersion 145 ) 146 val overrideApplyToProjects = 147 (groupDefinition.getArray("overrideInclude")?.toList() ?: listOf()).map { 148 it as String 149 } 150 151 val group = LibraryGroup(groupName, atomicGroupVersion) 152 LibraryGroupAssociation(name, group, overrideApplyToProjects) 153 } 154 } 155 156 companion object { 157 internal fun registerOrGet(project: Project): Provider<LibraryVersionsService> { 158 val tomlFileName = "libraryversions.toml" 159 val toml = project.lazyReadFile(tomlFileName) 160 161 return project.gradle.sharedServices.registerIfAbsent( 162 "libraryVersionsService", 163 LibraryVersionsService::class.java 164 ) { spec -> 165 spec.parameters.tomlFileName = tomlFileName 166 spec.parameters.tomlFileContents = toml 167 } 168 } 169 } 170 } 171 172 // a LibraryGroupSpec knows how to associate a LibraryGroup with the appropriate projects 173 private data class LibraryGroupAssociation( 174 // the name of the variable to which it is assigned in the toml file 175 val declarationName: String, 176 // the group 177 val libraryGroup: LibraryGroup, 178 // the paths of any additional projects that this group should be assigned to 179 val overrideIncludeInProjectPaths: List<String> 180 ) 181 182 private const val VersionReferencePrefix = "versions." 183 private const val AtomicGroupVersion = "atomicGroupVersion" 184 185 // Maven groups that should be skipped for atomic duplication checks. Do not add further entries. 186 // TODO(b/401002936, b/401000219, b/401003097, b/401005632): Remove groups from this list 187 private val ALLOWED_ATOMIC_GROUP_EXCEPTIONS = 188 listOf( 189 "androidx.camera", 190 "androidx.compose.material3", 191 "androidx.lifecycle", 192 "androidx.tracing" 193 ) 194