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
18 
19 import java.io.File
20 import java.util.Locale
21 import java.util.regex.Matcher
22 import java.util.regex.Pattern
23 import org.gradle.api.Project
24 
25 /** Utility class which represents a version */
26 data class Version(val major: Int, val minor: Int, val patch: Int, val extra: String? = null) :
27     Comparable<Version>, java.io.Serializable {
28 
29     constructor(
30         versionString: String
31     ) : this(
32         Integer.parseInt(checkedMatcher(versionString).group(1)),
33         Integer.parseInt(checkedMatcher(versionString).group(2)),
34         Integer.parseInt(checkedMatcher(versionString).group(3)),
35         if (checkedMatcher(versionString).groupCount() == 4) checkedMatcher(versionString).group(4)
36         else null
37     )
38 
isPatchnull39     fun isPatch(): Boolean = patch != 0
40 
41     fun isSnapshot(): Boolean = "-SNAPSHOT" == extra
42 
43     fun isAlpha(): Boolean = extra?.lowercase(Locale.getDefault())?.startsWith("-alpha") ?: false
44 
45     fun isBeta(): Boolean = extra?.lowercase(Locale.getDefault())?.startsWith("-beta") ?: false
46 
47     fun isDev(): Boolean = extra?.lowercase(Locale.getDefault())?.startsWith("-dev") ?: false
48 
49     fun isRC(): Boolean = extra?.lowercase(Locale.getDefault())?.startsWith("-rc") ?: false
50 
51     fun isStable(): Boolean = (extra == null)
52 
53     // Returns whether the API surface is allowed to change within the current revision (see
54     // go/androidx/versioning for policy definition)
55     fun isFinalApi(): Boolean = !(isSnapshot() || isAlpha() || isDev())
56 
57     override fun compareTo(other: Version) =
58         compareValuesBy(
59             this,
60             other,
61             { it.major },
<lambda>null62             { it.minor },
<lambda>null63             { it.patch },
<lambda>null64             { it.extra == null }, // False (no extra) sorts above true (has extra)
<lambda>null65             { it.extra } // gradle uses lexicographic ordering
66         )
67 
toStringnull68     override fun toString(): String {
69         return if (extra != null) {
70             "$major.$minor.$patch$extra"
71         } else "$major.$minor.$patch"
72     }
73 
74     companion object {
75         private const val serialVersionUID = 345435634563L
76 
77         private val VERSION_FILE_REGEX = Pattern.compile("^(res-)?(.*).txt$")
78         private val VERSION_REGEX = Pattern.compile("^(\\d+)\\.(\\d+)\\.(\\d+)(-.+)?$")
79 
checkedMatchernull80         private fun checkedMatcher(versionString: String): Matcher {
81             val matcher = VERSION_REGEX.matcher(versionString)
82             if (!matcher.matches()) {
83                 throw IllegalArgumentException("Can not parse version: $versionString")
84             }
85             return matcher
86         }
87 
88         /** @return Version or null, if a name of the given file doesn't match */
parseOrNullnull89         fun parseOrNull(file: File): Version? {
90             if (!file.isFile) return null
91             return parseFilenameOrNull(file.name)
92         }
93 
94         /** @return Version or null, if a name of the given file doesn't match */
parseFilenameOrNullnull95         fun parseFilenameOrNull(filename: String): Version? {
96             val matcher = VERSION_FILE_REGEX.matcher(filename)
97             return if (matcher.matches()) parseOrNull(matcher.group(2)) else null
98         }
99 
100         /** @return Version or null, if the given string doesn't match */
parseOrNullnull101         fun parseOrNull(versionString: String): Version? {
102             val matcher = VERSION_REGEX.matcher(versionString)
103             return if (matcher.matches()) Version(versionString) else null
104         }
105 
106         /** Tells whether a version string would refer to a dependency range */
isDependencyRangenull107         fun isDependencyRange(version: String): Boolean {
108             if (
109                 (version.startsWith("[") || version.startsWith("(")) &&
110                     version.contains(",") &&
111                     (version.endsWith("]") || version.endsWith(")"))
112             ) {
113                 return true
114             }
115             if (version.endsWith("+")) {
116                 return true
117             }
118             return false
119         }
120     }
121 }
122 
Projectnull123 fun Project.isVersionSet() = project.version is Version
124 
125 fun Project.version(): Version {
126     return if (project.version is Version) {
127         project.version as Version
128     } else {
129         throw IllegalStateException("Tried to use project version for $name that was never set.")
130     }
131 }
132