/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see .
*/
package com.itsaky.androidide.utils
import com.itsaky.androidide.buildinfo.BuildInfo
import java.util.Objects
/**
* Represents a version of the Android Gradle Plugin
*
* Designed for plugin authors to compare the currently running plugin version, so implements
* comparable.
*
* For example `AndroidPluginVersion(7, 0)` refers to Android Gradle Plugin version 7.0.0,
* and `AndroidPluginVersion(7, 0, 1)` refers to Android Gradle Plugin version 7.0.1.
*
* The internal representation is normalized, so that `AndroidPluginVersion(7, 0)` equals
* `AndroidPluginVersion(7, 0, 0)`
*
* Also supports preview versions through methods [alpha], [beta] and [rc] that return the more
* specific version. For example `AndroidPluginVersion(7, 0).alpha(5)` refers to Android Gradle
* Plugin version 7.0.0-alpha05. This is for use when developing using incubating APIs that have
* changed between preview releases of the Android Gradle Plugin. Once those APIs are stable in a
* stable version of Android Gradle Plugin, it's recommended to drop support for the preview versions.
* For example, if a new API was introduced in 7.0.0-alpha05, you can test for that using
*
* ```if (androidComponents.pluginVersion >= AndroidPluginVersion(7.0).alpha(5)) { ... }```
*
* If that API is marked as stable in 7.0.0, drop support for the preview versions before it by
* updating your condition to:
*
* ```if (androidComponents.pluginVersion >= AndroidPluginVersion(7.0)) { ... }```
*/
class AndroidPluginVersion private constructor(
/**
* The major version.
*
* e.g. 7 for Android Gradle Plugin Version 7.0.1
*/
val major: Int,
/**
* The minor version.
*
* e.g. 0 for Android Gradle Plugin Version 7.0.1
*/
val minor: Int,
/**
* The micro, or patch version.
*
* e.g. 1 for Android Gradle Plugin Version 7.0.1
*/
val micro: Int,
private val _previewType: PreviewType,
/**
* The preview version.
*
* e.g. 5 for Android Gradle Plugin Version 7.0.0-alpha05
*/
val preview: Int,
) : Comparable {
init {
require(
major >= 0 && minor >= 0 && micro >= 0) { "Versions of the Android Gradle Plugin must not be negative" }
}
/**
* The type of preview version.
*
* Null in the case of a stable version. One of 'alpha', 'beta', 'rc', 'dev' for preview versions.
*
* e.g. 'alpha' for Android Gradle Plugin Version 7.0.0-alpha05
*/
val previewType: String? get() = _previewType.publicName
/**
* Create an AndroidPluginVersion with the given major and minor version.
*
* For example `AndroidPluginVersion(7, 0)` refers to Android Gradle Plugin version 7.0.0.
*/
constructor(major: Int, minor: Int) : this(major, minor, 0, PreviewType.FINAL, 0)
/**
* Create an AndroidPluginVersion with the given major, minor and micro version.
*
* For example `AndroidPluginVersion(7, 0, 1)` refers to Android Gradle Plugin version 7.0.1.
*/
constructor(major: Int, minor: Int, micro: Int) : this(major, minor, micro, PreviewType.FINAL, 0)
private enum class PreviewType(val publicName: String?) {
ALPHA("alpha"),
BETA("beta"),
RC("rc"),
DEV("dev"),
FINAL(null);
companion object {
@JvmStatic
fun find(publicName: String?): PreviewType {
if (publicName.isNullOrBlank()) {
return FINAL
}
return when (publicName) {
ALPHA.publicName -> ALPHA
BETA.publicName -> BETA
RC.publicName -> RC
DEV.publicName -> DEV
else -> throw IllegalArgumentException("Unknown preview type '$publicName'")
}
}
}
}
/**
* From a stable [AndroidPluginVersion] returns an alpha version.
*
* For example `AndroidPluginVersion(7, 0).alpha(5)` refers to Android Gradle Plugin version 7.0.0-alpha05.
*/
fun alpha(alpha: Int): AndroidPluginVersion {
require(
_previewType == PreviewType.FINAL) { "alpha(int) only expected to be called on final versions" }
require(alpha >= 1) { "Alpha version must be at least 1" }
return AndroidPluginVersion(major, minor, micro, PreviewType.ALPHA, preview = alpha)
}
/**
* From a stable [AndroidPluginVersion] returns a beta version.
*
* For example `AndroidPluginVersion(7, 0).beta(2)` refers to Android Gradle Plugin version 7.0.0-beta02.
*/
fun beta(beta: Int): AndroidPluginVersion {
require(
_previewType == PreviewType.FINAL) { "beta(int) only expected to be called on final versions" }
require(beta >= 1) { "Beta version must be at least 1" }
return AndroidPluginVersion(major, minor, micro, PreviewType.BETA, preview = beta)
}
/**
* From a stable [AndroidPluginVersion] returns a release candidate version.
*
* For example `AndroidPluginVersion(7, 0).rc(1)` refers to Android Gradle Plugin version 7.0.0-rc01.
*/
fun rc(rc: Int): AndroidPluginVersion {
require(
_previewType == PreviewType.FINAL) { "rc(int) only expected to be called on final versions" }
require(rc >= 1) { "Release candidate version must be at least 1" }
return AndroidPluginVersion(major, minor, micro, PreviewType.RC, preview = rc)
}
/**
* From a stable [AndroidPluginVersion] specify an internal development version.
*
* `-dev` versions are never publicly released, but this can be useful if you are
* building the Android Gradle Plugin from source.
*
* For example `AndroidPluginVersion(7, 0).dev()` refers to Android Gradle Plugin version 7.0.0-dev.
*/
fun dev(): AndroidPluginVersion {
return AndroidPluginVersion(major, minor, micro, PreviewType.DEV, preview = 0)
}
/**
* Creates a new [AndroidPluginVersion] from this version with the given values updated.
*/
fun update(
major: Int = this.major,
minor: Int = this.minor,
micro: Int = this.micro,
previewType: String? = this.previewType,
preview: Int = this.preview
): AndroidPluginVersion {
return AndroidPluginVersion(major, minor, micro, PreviewType.find(previewType), preview)
}
override fun compareTo(other: AndroidPluginVersion): Int {
return comparator.compare(this, other)
}
override fun equals(other: Any?): Boolean {
return other is AndroidPluginVersion &&
major == other.major &&
minor == other.minor &&
micro == other.micro &&
_previewType == other._previewType &&
preview == other.preview
}
override fun hashCode(): Int {
return Objects.hash(major, minor, micro, _previewType, preview)
}
override fun toString(): String =
"Android Gradle Plugin version ${toStringSimple()}"
fun toStringSimple(): String = "$major.$minor.$micro" +
(if (previewType != null) "-$previewType" else "") +
(if (preview > 0) if(preview < 10) "0$preview" else preview else "")
companion object {
private val comparator: Comparator =
Comparator.comparingInt { it.major }
.thenComparingInt { it.minor }
.thenComparingInt { it.micro }
.thenComparingInt { it._previewType.ordinal }
.thenComparingInt { it.preview }
/**
* The minimum version of Android Gradle Plugin that is supported by AndroidIDE>
*/
@JvmField
val MINIMUM_SUPPORTED = parse(BuildInfo.AGP_VERSION_MININUM)
/**
* The version of Android Gradle Plugin that has been known to work with AndroidIDE>
*/
@JvmField
val LATEST_TESTED = parse(BuildInfo.AGP_VERSION_LATEST)
/**
* Parses the given AGP version and returns the [AndroidPluginVersion] representing the same.
*/
@JvmStatic
fun parse(version: String): AndroidPluginVersion {
require(version.isNotBlank()) {
"Invalid input '$version'"
}
val regex = """^(\d+)(?:\.(\d+)(?:\.(\d+)(?:-([a-z]+)(\d+)?)?)?)?${'$'}""".toRegex()
val match = checkNotNull(regex.find(version)) {
"Failed to match input '$version'"
}
return match.groupValues.run {
val int = fun(index: Int, default: Int): Int =
if (lastIndex >= index) this[index].let { if (it.isBlank()) 0 else it.toInt() } else default
val str = fun(index: Int, default: String) =
if (lastIndex >= index) this[index] else default
val major = this[1].toInt()
val minor = int(2, 0)
val micro = int(3, 0)
val previewType = str(4, "")
val preview = int(5, 0)
return@run AndroidPluginVersion(major, minor, micro, PreviewType.find(previewType), preview)
}
}
}
}