1 /* 2 * Copyright 2018 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.gmaven 18 19 import androidx.build.Version 20 import groovy.util.XmlSlurper 21 import groovy.util.slurpersupport.Node 22 import groovy.util.slurpersupport.NodeChild 23 import org.gradle.api.GradleException 24 import org.gradle.api.logging.Logger 25 import java.io.FileNotFoundException 26 import java.io.IOException 27 28 /** 29 * Queries maven.google.com to get the version numbers for each artifact. 30 * Due to the structure of maven.google.com, a new query is necessary for each group. 31 * 32 * @param logger Logger of the root project. No reason to create multiple instances of this. 33 */ 34 class GMavenVersionChecker(private val logger: Logger) { 35 private val versionCache: MutableMap<String, GroupVersionData> = HashMap() 36 37 /** 38 * Checks whether the given artifact is already on maven.google.com. 39 * 40 * @param group The project group on maven 41 * @param artifactName The artifact name on maven 42 * @param version The version on maven 43 * @return true if the artifact is already on maven.google.com 44 */ isReleasednull45 fun isReleased(group: String, artifactName: String, version: String): Boolean { 46 return getVersions(group, artifactName)?.contains(Version(version)) ?: false 47 } 48 49 /** 50 * Return the available versions on maven.google.com for a given artifact 51 * 52 * @param group The group id of the artifact 53 * @param artifactName The name of the artifact 54 * @return The set of versions that are available on maven.google.com. Null if artifact is not 55 * available. 56 */ getVersionsnull57 private fun getVersions(group: String, artifactName: String): Set<Version>? { 58 val groupData = getVersionData(group) 59 return groupData?.artifacts?.get(artifactName)?.versions 60 } 61 62 /** 63 * Returns the version data for each artifact in a given group. 64 * <p> 65 * If data is not cached, this will make a web request to get it. 66 * 67 * @param group The group to query 68 * @return A data class which has the versions for each artifact 69 */ getVersionDatanull70 private fun getVersionData(group: String): GroupVersionData? { 71 return versionCache.getOrMaybePut(group) { 72 fetchGroup(group, DEFAULT_RETRY_LIMIT) 73 } 74 } 75 76 /** 77 * Fetches the group version information from maven.google.com 78 * 79 * @param group The group name to fetch 80 * @param retryCount Number of times we'll retry before failing 81 * @return GroupVersionData that has the data or null if it is a new item. 82 */ fetchGroupnull83 private fun fetchGroup(group: String, retryCount: Int): GroupVersionData? { 84 val url = buildGroupUrl(group) 85 for (run in 0..retryCount) { 86 logger.info("fetching maven XML from $url") 87 try { 88 val parsedXml = XmlSlurper(false, false).parse(url) as NodeChild 89 return GroupVersionData.from(parsedXml) 90 } catch (ignored: FileNotFoundException) { 91 logger.info("could not find version data for $group, seems like a new file") 92 return null 93 } catch (ioException: IOException) { 94 logger.warn("failed to fetch the maven info, retrying in 2 seconds. " + 95 "Run $run of $retryCount") 96 Thread.sleep(RETRY_DELAY) 97 } 98 } 99 throw GradleException("Could not access maven.google.com") 100 } 101 102 companion object { 103 /** 104 * Creates the URL which has the XML file that describes the available versions for each 105 * artifact in that group 106 * 107 * @param group Maven group name 108 * @return The URL of the XML file 109 */ buildGroupUrlnull110 private fun buildGroupUrl(group: String) = 111 "$BASE${group.replace(".","/")}/$GROUP_FILE" 112 } 113 } 114 115 private fun <K, V> MutableMap<K, V>.getOrMaybePut(key: K, defaultValue: () -> V?): V? { 116 val value = get(key) 117 return if (value == null) { 118 val answer = defaultValue() 119 if (answer != null) put(key, answer) 120 answer 121 } else { 122 value 123 } 124 } 125 126 /** 127 * Data class that holds the artifacts of a single maven group. 128 * 129 * @param name Maven group name 130 * @param artifacts Map of artifact versions keyed by artifact name 131 */ 132 private data class GroupVersionData( 133 val name: String, 134 val artifacts: Map<String, ArtifactVersionData> 135 ) { 136 companion object { 137 /** 138 * Constructs an instance from the given node. 139 * 140 * @param xml The information node fetched from {@code GROUP_FILE} 141 */ fromnull142 fun from(xml: NodeChild): GroupVersionData { 143 /* 144 * sample input: 145 * <android.arch.core> 146 * <runtime versions="1.0.0-alpha4,1.0.0-alpha5,1.0.0-alpha6,1.0.0-alpha7"/> 147 * <common versions="1.0.0-alpha4,1.0.0-alpha5,1.0.0-alpha6,1.0.0-alpha7"/> 148 * </android.arch.core> 149 */ 150 val name = xml.name() 151 val artifacts: MutableMap<String, ArtifactVersionData> = HashMap() 152 153 xml.childNodes().forEach { 154 val node = it as Node 155 val versions = (node.attributes()["versions"] as String).split(",").map { 156 if (it == "0.1" || it == "0.2" || it == "0.3") { 157 // androidx.core:core-ktx shipped versions 0.1, 0.2, and 0.3 which do not 158 // comply with our versioning scheme. 159 Version(it + ".0") 160 } else { 161 Version(it) 162 } 163 }.toSet() 164 artifacts.put(it.name(), ArtifactVersionData(it.name(), versions)) 165 } 166 return GroupVersionData(name, artifacts) 167 } 168 } 169 } 170 171 /** 172 * Data class that holds the version information about a single artifact 173 * 174 * @param name Name of the maven artifact 175 * @param versions set of version codes that are already on maven.google.com 176 */ 177 private data class ArtifactVersionData(val name: String, val versions: Set<Version>) 178 179 // wait 2 seconds before retrying if fetch fails 180 private const val RETRY_DELAY: Long = 2000 // ms 181 182 // number of times we'll try to reach maven.google.com before failing 183 private const val DEFAULT_RETRY_LIMIT = 20 184 185 private const val BASE = "https://dl.google.com/dl/android/maven2/" 186 private const val GROUP_FILE = "group-index.xml"