diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ff08dac8..3e16fe3ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Unreleased +# v0.8.x + +The long-term plan for v1.0 is to focus on `:refreshVersions` instead of `:buildSrcVersions` + +See https://github.com/jmfayard/buildSrcVersions/issues/104 + +Starting from release 0.8.0 the plugin is now called and contain only the task `refreshVersions` + +```groovy +plugins { + id("de.fayard.refreshVersions").version("0.8.x") // or newer +} +``` + +We are not quite ready yet to extract the useful parts of `buildSrcVersions` to another plugin, +so if you need the features from buildSrcVersions, stay with this for now: + +```groovy +plugins { + id("de.fayard.buildSrcVersions").version("0.7.0") +} +``` + + + # 0.7.0 The plugin will in the future focus on `:refreshVersions`. diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index b164adffc..006d1fd31 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -50,6 +50,7 @@ pluginBundle { dependencies { testImplementation("io.kotlintest:kotlintest-runner-junit5:3.1.9") + implementation(gradleKotlinDsl()) // SYNC WITH plugin/src/main/kotlin/de/fayard/internal/PluginConfig.kt implementation("com.github.ben-manes:gradle-versions-plugin:0.25.0") @@ -61,6 +62,7 @@ dependencies { tasks.withType { kotlinOptions.jvmTarget = "1.8" + kotlinOptions.freeCompilerArgs = listOf("-Xinline-classes") } tasks.withType { diff --git a/plugin/src/main/kotlin/de/fayard/RefreshVersionsExtension.kt b/plugin/src/main/kotlin/de/fayard/RefreshVersionsExtension.kt index 447bd4817..6ec3efd69 100644 --- a/plugin/src/main/kotlin/de/fayard/RefreshVersionsExtension.kt +++ b/plugin/src/main/kotlin/de/fayard/RefreshVersionsExtension.kt @@ -47,7 +47,10 @@ interface RefreshVersionsExtension { ***/ fun useFqdnFor(vararg dependencyName: String) + // TODO: remove var alignVersionsForGroups: MutableList + // TODO: do something with it + var versionsMapping: MutableMap /** * See [versionsOnlyMode] diff --git a/plugin/src/main/kotlin/de/fayard/RefreshVersionsPlugin.kt b/plugin/src/main/kotlin/de/fayard/RefreshVersionsPlugin.kt index 864c20f67..812b9e57d 100644 --- a/plugin/src/main/kotlin/de/fayard/RefreshVersionsPlugin.kt +++ b/plugin/src/main/kotlin/de/fayard/RefreshVersionsPlugin.kt @@ -5,6 +5,11 @@ import de.fayard.internal.PluginConfig import de.fayard.internal.PluginConfig.isNonStable import de.fayard.internal.PluginsSetup import de.fayard.internal.RefreshVersionsExtensionImpl +import de.fayard.versions.RefreshVersionsPropertiesExtension +import de.fayard.versions.RefreshVersionsPropertiesTask +import de.fayard.versions.extensions.registerOrCreate +import de.fayard.versions.getVersionProperties +import de.fayard.versions.setupVersionPlaceholdersResolving import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.artifacts.ModuleVersionSelector @@ -13,50 +18,74 @@ import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.create import java.util.Properties - open class RefreshVersionsPlugin : Plugin { + /** + * Overwrite the default by adding the following line to gradle.properties: + * + * ``` + * refreshVersions.useExperimentalUpdater=true + * ``` + * **/ + internal val Project.useExperimentalUpdater: Boolean + get() = findProperty(PluginConfig.USE_EXPERIMENTAL_UPDATER) == "true" + override fun apply(project: Project) { - check(project == project.rootProject) { "ERROR: plugins de.fayard.refreshVersions must be applied to the root build.gradle(.kts)" } - project.apply(plugin = PluginConfig.GRADLE_VERSIONS_PLUGIN_ID) - project.configure() - project.useVersionsFromProperties() + check(project == project.rootProject) { + "ERROR: plugins de.fayard.refreshVersions must be applied to the root build.gradle(.kts)" + } + + if (project.useExperimentalUpdater) { + project.configureExperimentalUpdater() + val properties: Map = project.getVersionProperties() + project.allprojects { configurations.all { setupVersionPlaceholdersResolving(properties) } } + } else { + project.apply(plugin = PluginConfig.GRADLE_VERSIONS_PLUGIN_ID) + project.configure() + project.useVersionsFromProperties() + } + } + + private fun Project.configureExperimentalUpdater() { + extensions.create(name = PluginConfig.EXTENSION_NAME) + tasks.registerOrCreate(name = PluginConfig.REFRESH_VERSIONS) { + group = "Help" + description = "Search for new dependencies versions and update versions.properties" + } } - fun Project.configure() = with(PluginConfig) { + private fun Project.configure() = with(PluginConfig) { PluginsSetup.copyPluginsGradleKtsIfNeeded(project) extensions.create(RefreshVersionsExtension::class, EXTENSION_NAME, RefreshVersionsExtensionImpl::class) + @Suppress("LiftReturnOrAssignment") if (supportsTaskAvoidance()) { val provider: TaskProvider = when { - tasks.findByPath(DEPENDENCY_UPDATES_PATH) == null -> tasks.register(DEPENDENCY_UPDATES_PATH, DependencyUpdatesTask::class.java) + tasks.findByPath(DEPENDENCY_UPDATES_PATH) == null -> tasks.register( + DEPENDENCY_UPDATES_PATH, + DependencyUpdatesTask::class.java + ) else -> tasks.named(DEPENDENCY_UPDATES, DependencyUpdatesTask::class.java) } - configureGradleVersions = { operation -> provider.configure(operation) } - configureGradleVersions(DependencyUpdatesTask::configureBenManesVersions) - - tasks.register(REFRESH_VERSIONS, RefreshVersionsTask::class.java, RefreshVersionsTask::configureRefreshVersions) - } else { val dependencyUpdatesTask = tasks.maybeCreate(DEPENDENCY_UPDATES, DependencyUpdatesTask::class.java) configureGradleVersions = { operation -> dependencyUpdatesTask.operation() } - configureGradleVersions(DependencyUpdatesTask::configureBenManesVersions) - - tasks.create(REFRESH_VERSIONS, RefreshVersionsTask::class, RefreshVersionsTask::configureRefreshVersions) } + configureGradleVersions(DependencyUpdatesTask::configureBenManesVersions) + tasks.registerOrCreate(name = REFRESH_VERSIONS, action = RefreshVersionsTask::configureRefreshVersions) } } -fun Project.useVersionsFromProperties() { +private fun Project.useVersionsFromProperties() { @Suppress("UNCHECKED_CAST") val properties: Map = Properties().apply { - val propertiesFile = listOf("versions.properties", "gradle.properties").firstOrNull { project.file(it).canRead() } ?: return + val propertiesFile = + listOf("versions.properties", "gradle.properties").firstOrNull { project.file(it).canRead() } ?: return load(project.file(propertiesFile).reader()) } as Map - val resolutionStrategyConfig = project.findProperty("resolutionStrategyConfig") as? String if (resolutionStrategyConfig == "false") return allprojects { @@ -80,13 +109,13 @@ fun Project.useVersionsFromProperties() { } -fun DependencyUpdatesTask.configureBenManesVersions() { +private fun DependencyUpdatesTask.configureBenManesVersions() { rejectVersionIf { isNonStable(candidate.version) } checkForGradleUpdate = true outputFormatter = "json" } -fun RefreshVersionsTask.configureRefreshVersions() { +private fun RefreshVersionsTask.configureRefreshVersions() { group = "Help" description = "Search for available dependencies updates and update gradle.properties" dependsOn(PluginConfig.DEPENDENCY_UPDATES_PATH) diff --git a/plugin/src/main/kotlin/de/fayard/RefreshVersionsTask.kt b/plugin/src/main/kotlin/de/fayard/RefreshVersionsTask.kt index 3ed4b6eb1..2845c743b 100644 --- a/plugin/src/main/kotlin/de/fayard/RefreshVersionsTask.kt +++ b/plugin/src/main/kotlin/de/fayard/RefreshVersionsTask.kt @@ -31,12 +31,12 @@ open class RefreshVersionsTask : DefaultTask() { @TaskAction fun taskActionGradleProperties() { val extension: RefreshVersionsExtensionImpl = extension() - val updateGradleProperties = UpdateProperties(extension) + val updateGradleProperties = UpdateProperties() val specialDependencies = listOf(PluginConfig.gradleVersionsPlugin, PluginConfig.gradleRefreshVersions, PluginConfig.gradleLatestVersion(dependencyGraph)) - val dependencies = (unsortedParsedDependencies + specialDependencies) + val dependencies: List = (unsortedParsedDependencies + specialDependencies) .sortedBeautifullyBy(extension.orderBy) { it.versionProperty } .distinctBy { it.versionProperty } @@ -51,7 +51,7 @@ open class RefreshVersionsTask : DefaultTask() { val message = with(PluginConfig) { """ - |Running plugins.id("$PLUGIN_ID").version("$PLUGIN_VERSION") with useRefreshVersions=${useRefreshVersions} and extension: $extension + |Running plugins.id("$PLUGIN_ID").version("$PLUGIN_VERSION") with configuration: $extension |See documentation at $issue53PluginConfiguration | """.trimMargin() @@ -76,7 +76,7 @@ open class RefreshVersionsTask : DefaultTask() { val projectExtension = project.extensions.getByType() as RefreshVersionsExtensionImpl this._extension = projectExtension.defensiveCopy() action.execute(this._extension) - PluginConfig.useRefreshVersions = project.hasProperty("plugin.de.fayard.buildSrcVersions") || project.hasProperty("plugin.de.refreshVersions") + // TODO: use DEFAULT_MAPPING instead of ALIGN_VERSION_GROUPS PluginConfig.ALIGN_VERSION_GROUPS.clear() PluginConfig.ALIGN_VERSION_GROUPS.addAll(_extension.alignVersionsForGroups) } diff --git a/plugin/src/main/kotlin/de/fayard/internal/DependencyGraph.kt b/plugin/src/main/kotlin/de/fayard/internal/DependencyGraph.kt index f321456e0..4a50cd604 100644 --- a/plugin/src/main/kotlin/de/fayard/internal/DependencyGraph.kt +++ b/plugin/src/main/kotlin/de/fayard/internal/DependencyGraph.kt @@ -45,6 +45,7 @@ data class Dependency( companion object { fun virtualGroup(dependency: Dependency, withVersion: Boolean = false): String? { + // TODO: use DEFAULT_MAPPING instead of ALIGN_VERSION_GROUPS val virtualGroup = PluginConfig.ALIGN_VERSION_GROUPS.firstOrNull { "${dependency.group}.${dependency.module}".startsWith(it) } return when { virtualGroup == null -> null diff --git a/plugin/src/main/kotlin/de/fayard/internal/PluginConfig.kt b/plugin/src/main/kotlin/de/fayard/internal/PluginConfig.kt index 346a08760..729589858 100644 --- a/plugin/src/main/kotlin/de/fayard/internal/PluginConfig.kt +++ b/plugin/src/main/kotlin/de/fayard/internal/PluginConfig.kt @@ -13,17 +13,18 @@ import java.io.File @Suppress("unused") object PluginConfig { - - const val PLUGIN_ID = "de.fayard.refreshVersions" const val PLUGIN_VERSION = "0.8.2" // plugin.de.fayard.refreshVersions const val GRADLE_VERSIONS_PLUGIN_ID = "com.github.ben-manes.versions" const val GRADLE_VERSIONS_PLUGIN_VERSION = "0.25.0" // Sync with plugin/build.gradle.kts const val DEPENDENCY_UPDATES = "dependencyUpdates" + const val REFRESH_VERSIONS_UPDATES_PATH = "refreshVersionsUpdates" // TODO: think about a better name + const val USE_EXPERIMENTAL_UPDATER = "refreshVersions.useExperimentalUpdater" // TODO: think about a better name const val DEPENDENCY_UPDATES_PATH = ":$DEPENDENCY_UPDATES" const val REFRESH_VERSIONS = "refreshVersions" const val EXTENSION_NAME = REFRESH_VERSIONS const val DEFAULT_PROPERTIES_FILE = "versions.properties" + const val AVAILABLE_DEPENDENCIES_FILE = "build/dependencyUpdates/refreshVersions.txt" /** There is no standard on how to name stable and unstable versions * This version is a good starting point but you can define you rown @@ -54,20 +55,25 @@ object PluginConfig { "version.$module" ) - /** - * We want to treat all "org.getbrains.kotlinx:kotlinx-coroutines-*" as if they were a maven group - * with one common version, but different from org.jetbrains.kotlinx:kotlinx-serialization* - * For now this list is not part of the public API but feel free to add feedback that you need it. - * Add your use case here https://github.com/jmfayard/buildSrcVersions/issues/102 - ***/ - val ALIGN_VERSION_GROUPS: MutableList = mutableListOf( - "org.jetbrains.kotlinx.kotlinx-coroutines", - "org.jetbrains.kotlinx.kotlinx-serialization", - "com.louiscad.splitties:splitties", - "com.squareup.retrofit2" + // TODO: replace ALIGN_VERSION_GROUPS by DEFAULT_MAPPING + val ALIGN_VERSION_GROUPS: MutableList + get() = DEFAULT_MAPPING.values.toMutableList() + + val DEFAULT_MAPPING: Map = mapOf( + "org.jetbrains.kotlinx..kotlinx-coroutines" to "org.jetbrains.kotlinx.kotlinx-coroutines", + "org.jetbrains.kotlinx..kotlinx-serialization" to "org.jetbrains.kotlinx.kotlinx-serialization", + "com.louiscad.splitties..splitties" to "com.louiscad.splitties:splitties", + "com.squareup.retrofit2" to "com.squareup.retrofit2" + /** + * TODO: improve DEFAULT_MAPPING according to + * https://github.com/LouisCAD/Splitties/blob/9f4a85e7ecc612d290fd6e4615b3472c05aece51/build.gradle.kts#L151-L167 + * for example: + "retrofit2" to "com.squareup.retrofit2", + "kotlin" to "org.jetbrains.kotlin:kotlin", + "moshi" to "com.squareup.moshi" + **/ ) - @JvmStatic fun versionPropertyFor(d: Dependency): String = when (d.mode) { MODULE -> d.name @@ -144,6 +150,8 @@ object PluginConfig { internal val extensionAdapter: JsonAdapter by moshiAdapter() + internal val dependencyAdapter: JsonAdapter by moshiAdapter() + fun readGraphFromJsonFile(jsonInput: File): DependencyGraph { return dependencyGraphAdapter.fromJson(jsonInput.source().buffer())!! } @@ -229,8 +237,6 @@ object PluginConfig { return (configured + byDefault + ambiguities + depsFromGroups - groups).distinct().sorted() } - var useRefreshVersions: Boolean = false - lateinit var configureGradleVersions: (DependencyUpdatesTask.() -> Unit) -> Unit } diff --git a/plugin/src/main/kotlin/de/fayard/internal/PluginsSetup.kt b/plugin/src/main/kotlin/de/fayard/internal/PluginsSetup.kt index 1babcbacb..4e271122a 100644 --- a/plugin/src/main/kotlin/de/fayard/internal/PluginsSetup.kt +++ b/plugin/src/main/kotlin/de/fayard/internal/PluginsSetup.kt @@ -12,7 +12,7 @@ object PluginsSetup { apply(from = "gradle/plugins.gradle.kts") """ - fun pluginFileContent(): String { + private fun pluginFileContent(): String { return this::class.java.getResourceAsStream("/$PLUGIN_GRADLE_KTS.txt").reader().readText() } diff --git a/plugin/src/main/kotlin/de/fayard/internal/RefreshVersionsExtensionImpl.kt b/plugin/src/main/kotlin/de/fayard/internal/RefreshVersionsExtensionImpl.kt index f897f3fb5..8beb72697 100644 --- a/plugin/src/main/kotlin/de/fayard/internal/RefreshVersionsExtensionImpl.kt +++ b/plugin/src/main/kotlin/de/fayard/internal/RefreshVersionsExtensionImpl.kt @@ -9,12 +9,17 @@ internal open class RefreshVersionsExtensionImpl( var useFqqnFor: List = emptyList(), var alwaysUpdateVersions: Boolean = false, override var orderBy: OrderBy = OrderBy.GROUP_AND_LENGTH, - override var alignVersionsForGroups: MutableList = PluginConfig.ALIGN_VERSION_GROUPS + // TODO: use DEFAULT_MAPPING instead of ALIGN_VERSION_GROUPS + override var versionsMapping: MutableMap = PluginConfig.DEFAULT_MAPPING.toMutableMap() ) : RefreshVersionsExtension, java.io.Serializable { + override var alignVersionsForGroups: MutableList + get() = versionsMapping.values.toMutableList() + set(value) = println("WARNING: alignVersionsForGroups is deprecated, use instead versionsMapping") + // Necessary because of https://github.com/jmfayard/buildSrcVersions/issues/92 fun defensiveCopy(): RefreshVersionsExtensionImpl = RefreshVersionsExtensionImpl( - propertiesFile, useFqqnFor, alwaysUpdateVersions, orderBy, alignVersionsForGroups + propertiesFile, useFqqnFor, alwaysUpdateVersions, orderBy, versionsMapping ) override fun alwaysUpdateVersions() { @@ -25,6 +30,7 @@ internal open class RefreshVersionsExtensionImpl( override fun toString(): String = PluginConfig.extensionAdapter.toJson(this) override fun rejectVersionIf(filter: ComponentFilter) { + // TODO: use the filter in RefreshVersionsTask (PluginConfig.configureGradleVersions) { this.rejectVersionIf(filter) } diff --git a/plugin/src/main/kotlin/de/fayard/internal/UpdateProperties.kt b/plugin/src/main/kotlin/de/fayard/internal/UpdateProperties.kt index 3d2a983b1..deed43935 100644 --- a/plugin/src/main/kotlin/de/fayard/internal/UpdateProperties.kt +++ b/plugin/src/main/kotlin/de/fayard/internal/UpdateProperties.kt @@ -1,11 +1,8 @@ package de.fayard.internal -import de.fayard.RefreshVersionsExtension import java.io.File -data class UpdateProperties( - val extension: RefreshVersionsExtension -) { +class UpdateProperties { fun generateVersionProperties(file: File, dependencies: List) = with(UpdateVersionsOnly) { PluginConfig.isAndroidProject = dependencies.any { it.group.contains("android") } @@ -24,7 +21,7 @@ data class UpdateProperties( ) } - fun String.wasGeneratedByPlugin(): Boolean = when { + private fun String.wasGeneratedByPlugin(): Boolean = when { startsWith("module.kotlin") -> true startsWith("module.android") -> true startsWith("version.") -> true @@ -34,7 +31,7 @@ data class UpdateProperties( else -> false } - fun updateGradleProperties(file: File, newLines: List, removeIf: (String) -> Boolean) { + private fun updateGradleProperties(file: File, newLines: List, removeIf: (String) -> Boolean) { if (!file.exists()) file.createNewFile() val existingLines = file.readLines().filterNot { line -> removeIf(line) } diff --git a/plugin/src/main/kotlin/de/fayard/versions/ArtifactGrouping.kt b/plugin/src/main/kotlin/de/fayard/versions/ArtifactGrouping.kt new file mode 100644 index 000000000..26feb3473 --- /dev/null +++ b/plugin/src/main/kotlin/de/fayard/versions/ArtifactGrouping.kt @@ -0,0 +1,25 @@ +package de.fayard.versions + +import org.gradle.api.Incubating + +/** + * We assume each part of the "group" is dot separated (`.`), and each part of the name is dash separated (`-`). + */ +internal enum class ArtifactGroupNaming { + GroupOnly, + GroupLastPart, + GroupFirstTwoParts, + GroupFirstThreeParts, + GroupAndNameFirstPart, + GroupLastPartAndNameSecondPart, + GroupFirstPartAndNameTwoFirstParts +} + +internal class ArtifactGroupingRule( + val artifactNamesStartingWith: String, + val groupNaming: ArtifactGroupNaming +) { + init { + require(artifactNamesStartingWith.count { it == ':' } <= 1) + } +} diff --git a/plugin/src/main/kotlin/de/fayard/versions/ComponentSelectionData.kt b/plugin/src/main/kotlin/de/fayard/versions/ComponentSelectionData.kt new file mode 100644 index 000000000..f6d288e6f --- /dev/null +++ b/plugin/src/main/kotlin/de/fayard/versions/ComponentSelectionData.kt @@ -0,0 +1,10 @@ +package de.fayard.versions + +import org.gradle.api.Incubating +import org.gradle.api.artifacts.component.ModuleComponentIdentifier + +@Incubating +data class ComponentSelectionData( + val currentVersion: String, + val candidate: ModuleComponentIdentifier +) diff --git a/plugin/src/main/kotlin/de/fayard/versions/ComponentSelectionDataExtensions.kt b/plugin/src/main/kotlin/de/fayard/versions/ComponentSelectionDataExtensions.kt new file mode 100644 index 000000000..dede20a8d --- /dev/null +++ b/plugin/src/main/kotlin/de/fayard/versions/ComponentSelectionDataExtensions.kt @@ -0,0 +1,49 @@ +package de.fayard.versions + +import org.gradle.api.Incubating + +fun ComponentSelectionData.candidateIsLessStableThanCurrent(): Boolean { + return candidateVersion.stabilityLevel().isAtLeastAsStableAs(currentlyUsedVersion.stabilityLevel()).not() +} + +@Incubating +fun ComponentSelectionData.currentStabilityLevel(): StabilityLevel = currentlyUsedVersion.stabilityLevel() + +@Incubating +fun ComponentSelectionData.candidateStabilityLevel(): StabilityLevel = candidateVersion.stabilityLevel() + + +private val ComponentSelectionData.candidateVersion: Version inline get() = Version(candidate.version) +private val ComponentSelectionData.currentlyUsedVersion: Version inline get() = Version(currentVersion) + +private inline class Version(val value: String) + +private fun Version.stabilityLevel(): StabilityLevel = when { + isStable() -> StabilityLevel.Stable + "rc" in value -> StabilityLevel.ReleaseCandidate + isMilestone() -> StabilityLevel.Milestone + "eap" in value -> StabilityLevel.EarlyAccessProgram + "beta" in value -> StabilityLevel.Beta + "alpha" in value -> StabilityLevel.Alpha + "dev" in value -> StabilityLevel.Development + else -> StabilityLevel.Unknown +} + +//TODO: Allow to get if release stability level between Stable, RC, M(ilestone), eap, beta, alpha, dev and unknown, +// then allow setting default level and per group/artifact exceptions. Maybe through comments in gradle.properties? +private fun Version.isStable(): Boolean { + val version = value + //TODO: cache list and regex for improved efficiency. + val hasStableKeyword = listOf("RELEASE", "FINAL", "GA").any { version.toUpperCase().contains(it) } + val regex = "^[0-9,.v-]+$".toRegex() + return hasStableKeyword || regex.matches(version) +} + +private fun Version.isMilestone(): Boolean { + val version = value + return when (val indexOfM = version.indexOfLast { it == 'M' }) { + -1 -> false + version.lastIndex -> false + else -> version.substring(startIndex = indexOfM + 1).all { it.isDigit() } + } +} diff --git a/plugin/src/main/kotlin/de/fayard/versions/PluginsManagementSetup.kt b/plugin/src/main/kotlin/de/fayard/versions/PluginsManagementSetup.kt new file mode 100644 index 000000000..16177b1b3 --- /dev/null +++ b/plugin/src/main/kotlin/de/fayard/versions/PluginsManagementSetup.kt @@ -0,0 +1,52 @@ +package de.fayard.versions + +import de.fayard.internal.PluginConfig +import org.gradle.kotlin.dsl.SettingsScriptApi +import org.gradle.kotlin.dsl.extra +import org.gradle.kotlin.dsl.provideDelegate +import java.util.Properties + +/** + * Sets up a resolution strategy for the plugins that does the following: + * For each plugin, tries to find the corresponding version declared in `versions.properties`, and uses it. + * + * The expected property key is the id of the plugin, prefixed with `plugin.` (e.g. `plugin.io.fabric`), excepted for + * the Kotlin and Android gradle plugins where `version.kotlin` and `plugin.android` are used. + * + * Note that the properties can alias to another one from `version.properties` (hard limit is 5 property redirects), + * you just need to put the key of the property you want to use (e.g. `plugin.some-plugin=plugin.android`). + * + * This function also sets up the module for the Android and Fabric (Crashlytics) Gradle plugins, so you can avoid the + * buildscript classpath configuration boilerplate. + */ +fun SettingsScriptApi.setupVersionPlaceholdersResolving() { + pluginManagement { + val resolutionStrategyConfig: String? by extra // Allows disabling the resolutionStrategy if ever needed. + val versionProperties = file("versions.properties") + if (resolutionStrategyConfig == "false" || versionProperties.canRead().not()) return@pluginManagement + + @Suppress("UNCHECKED_CAST") + val properties: Map = Properties().apply { + load(versionProperties.reader()) + } as Map + resolutionStrategy.eachPlugin { + val pluginId = requested.id.id + if (pluginId == "de.fayard.refreshVersions") { + useVersion(PluginConfig.PLUGIN_VERSION) + return@eachPlugin + } + val pluginNamespace = requested.id.namespace ?: "" + val versionKey = when { + pluginNamespace.startsWith("org.jetbrains.kotlin") -> "version.kotlin" + pluginNamespace.startsWith("com.android") -> "plugin.android" + else -> "plugin.$pluginId" + } + val version = resolveVersion(properties, versionKey) ?: return@eachPlugin + when { + pluginNamespace.startsWith("com.android") -> useModule("com.android.tools.build:gradle:$version") + pluginId == "io.fabric" -> useModule("io.fabric.tools:gradle:$version") + else -> useVersion(version) + } + } + } +} diff --git a/plugin/src/main/kotlin/de/fayard/versions/RefreshVersionsPropertiesExtension.kt b/plugin/src/main/kotlin/de/fayard/versions/RefreshVersionsPropertiesExtension.kt new file mode 100644 index 000000000..9bc41d126 --- /dev/null +++ b/plugin/src/main/kotlin/de/fayard/versions/RefreshVersionsPropertiesExtension.kt @@ -0,0 +1,72 @@ +package de.fayard.versions + +import org.gradle.api.Incubating + +@Suppress("MemberVisibilityCanBePrivate") +open class RefreshVersionsPropertiesExtension { + + /** + * If the passed [predicate] returns `true`, candidate version will be rejected. + * + * This predicate is mutually exclusive with [acceptVersionOnlyIf], you should use only one (the last one wins). + * + * Default: + * + * ```kotlin + * rejectVersionIf { + * candidateIsLessStableThanCurrent() + * } + * ``` + * + * **Usage example:** + * ```kotlin + * rejectVersionIf { + * candidateStabilityLevel() isLessStableThan StabilityLevel.Milestone + * } + * ``` + */ + fun rejectVersionIf(predicate: ComponentSelectionData.() -> Boolean) { + rejectVersionsPredicate = predicate + acceptVersionsPredicate = null + } + + /** + * Candidate version will be **rejected** unless the passed [predicate] returns `true`. + * + * This predicate is mutually exclusive with [rejectVersionIf], you should use only one (the last one wins). + * + * **Usage example:** + * ```kotlin + * acceptVersionOnlyIf { + * candidateStabilityLevel() isAtLeastAsStableAs when (candidate.group) { + * "org.jetbrains.kotlinx" -> when (candidate.module) { + * "kotlinx-coroutines" -> StabilityLevel.Alpha + * else -> StabilityLevel.Beta + * } + * else -> StabilityLevel.ReleaseCandidate + * } + * } + * ``` + */ + @Incubating + fun acceptVersionOnlyIf(predicate: ComponentSelectionData.() -> Boolean) { + acceptVersionsPredicate = predicate + rejectVersionsPredicate = null + } + + @Incubating + fun alwaysUpdateStraightAway() { + alwaysUpdateStraightAway = true + } + + internal var alwaysUpdateStraightAway = false + private set + + internal var rejectVersionsPredicate: (ComponentSelectionData.() -> Boolean)? = { + candidateIsLessStableThanCurrent() + } + private set + + internal var acceptVersionsPredicate: (ComponentSelectionData.() -> Boolean)? = null + private set +} diff --git a/plugin/src/main/kotlin/de/fayard/versions/RefreshVersionsPropertiesTask.kt b/plugin/src/main/kotlin/de/fayard/versions/RefreshVersionsPropertiesTask.kt new file mode 100644 index 000000000..14be20ea0 --- /dev/null +++ b/plugin/src/main/kotlin/de/fayard/versions/RefreshVersionsPropertiesTask.kt @@ -0,0 +1,110 @@ +package de.fayard.versions + +import de.fayard.versions.extensions.isGradlePlugin +import de.fayard.versions.extensions.moduleIdentifier +import org.gradle.api.DefaultTask +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.Dependency +import org.gradle.api.specs.Specs +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.options.Option +import org.gradle.kotlin.dsl.getByType + +open class RefreshVersionsPropertiesTask : DefaultTask() { + + @Suppress("UnstableApiUsage") + @Option(description = "Update all versions, I will check git diff afterwards") + @Optional + var update: Boolean = false + + @TaskAction + fun taskActionRefreshVersions() { + + val allConfigurations: Set = project.allprojects.flatMap { + it.buildscript.configurations + it.configurations + }.toSet() + val allDependencies = allConfigurations.asSequence() + .flatMap { it.allDependencies.asSequence() } + .distinctBy { it.group + it.name } + + //TODO: Filter using known grouping strategies to only use the main artifact to resolve latest version, this + // will reduce the number of repositories lookups, improving performance. + + val initialRepositories = project.rootProject.repositories + project.allprojects.flatMap { it.buildscript.repositories + it.repositories }.toSet().let { allRepositories -> + project.rootProject.repositories.addAll(allRepositories) + } + try { + val extension = project.rootProject.extensions.getByType() + + val versionProperties: Map = project.getVersionProperties() + val dependenciesWithUpdate: List> = allDependencies.mapNotNull { dependency -> + + println("Dependency ${dependency.group}:${dependency.name}:${dependency.version}") + + val usedVersion = dependency.version.takeIf { + dependency.isManageableVersion(versionProperties) + } ?: return@mapNotNull null //TODO: Keep aside to report hardcoded versions and version ranges, + //todo... see this issue: https://github.com/jmfayard/buildSrcVersions/issues/126 + + val latestVersion = project.rootProject.getLatestDependencyVersion(extension, dependency) + + return@mapNotNull dependency to (if (usedVersion == latestVersion) null else latestVersion) + }.toList() + project.rootProject.updateVersionsProperties(dependenciesWithUpdate) + } finally { + project.rootProject.repositories.let { + it.clear() + it.addAll(initialRepositories) + } + } + } + + private fun Dependency.isManageableVersion(versionProperties: Map): Boolean { + return when { + version == versionPlaceholder -> true + moduleIdentifier?.isGradlePlugin == true -> { + val versionFromProperty = versionProperties[moduleIdentifier!!.getVersionPropertyName()] + ?: return false + versionFromProperty.isAVersionAlias().not() + } + else -> false + } + } +} + +private fun Project.getLatestDependencyVersion( + extension: RefreshVersionsPropertiesExtension, + dependency: Dependency +): String? { + val tmpDependencyUpdateConfiguration = configurations.create("getLatestVersion") { + dependencies.add(dependency) + resolutionStrategy.componentSelection.all { + val componentSelectionData = ComponentSelectionData( + currentVersion = dependency.version ?: "", + candidate = candidate + ) + extension.rejectVersionsPredicate?.let { rejectPredicate -> + if (rejectPredicate(componentSelectionData)) { + reject("Rejected in rejectVersionsIf { ... }") + } + } + extension.acceptVersionsPredicate?.let { acceptPredicate -> + if (acceptPredicate(componentSelectionData).not()) { + reject("Not accepted in acceptVersionOnlyIf { ... }") + } + } + } + resolutionStrategy.eachDependency { + if (requested.version != null) useVersion("+") + } + } + try { + val lenientConfiguration = tmpDependencyUpdateConfiguration.resolvedConfiguration.lenientConfiguration + return lenientConfiguration.getFirstLevelModuleDependencies(Specs.SATISFIES_ALL).singleOrNull()?.moduleVersion + } finally { + configurations.remove(tmpDependencyUpdateConfiguration) + } +} diff --git a/plugin/src/main/kotlin/de/fayard/versions/StabilityLevel.kt b/plugin/src/main/kotlin/de/fayard/versions/StabilityLevel.kt new file mode 100644 index 000000000..9556af6c6 --- /dev/null +++ b/plugin/src/main/kotlin/de/fayard/versions/StabilityLevel.kt @@ -0,0 +1,18 @@ +package de.fayard.versions + +import org.gradle.api.Incubating + +@Incubating +enum class StabilityLevel { + Stable, + ReleaseCandidate, + Milestone, + EarlyAccessProgram, + Beta, + Alpha, + Development, + Unknown; + + infix fun isLessStableThan(other: StabilityLevel): Boolean = ordinal > other.ordinal + infix fun isAtLeastAsStableAs(other: StabilityLevel): Boolean = isLessStableThan(other).not() +} diff --git a/plugin/src/main/kotlin/de/fayard/versions/VersionsPlaceholdersReplacement.kt b/plugin/src/main/kotlin/de/fayard/versions/VersionsPlaceholdersReplacement.kt new file mode 100644 index 000000000..9b8e5ec3c --- /dev/null +++ b/plugin/src/main/kotlin/de/fayard/versions/VersionsPlaceholdersReplacement.kt @@ -0,0 +1,166 @@ +package de.fayard.versions + +import de.fayard.versions.ArtifactGroupNaming.* +import de.fayard.versions.extensions.isGradlePlugin +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.ModuleIdentifier +import org.gradle.api.artifacts.ModuleVersionSelector +import java.util.Properties + +internal const val versionPlaceholder = "_" + +internal fun Configuration.setupVersionPlaceholdersResolving(properties: Map) { + resolutionStrategy.eachDependency { + if (requested.version != versionPlaceholder) return@eachDependency + useVersion(requested.getVersionFromProperties(properties)) + } +} + +internal fun ModuleIdentifier.getVersionPropertyName(): String { + //TODO: Reconsider the TODO below because we don't care about settings.gradle(.kts) buildscript for plugins since + // it can alias to any version property. + + //TODO: Allow customizing the artifact grouping rules, including resetting the default ones. + // What about the plugins? Should we use a custom text-based file format to allow early configuration? + // If we go down that road, what about invalidation? Also, would that invalidate the whole build or can we do + // better? Or would we have to hack to have the needed invalidation to happen? + return getVersionPropertyName(this) +} + +internal fun Project.getVersionProperties( + includeProjectProperties: Boolean = true +): Map { + return mutableMapOf().also { map -> + // Read from versions.properties + Properties().also { + it.load(file("versions.properties").reader()) + }.forEach { (k, v) -> if (k is String && v is String) map[k] = v } + // Overwrite with relevant project properties + if (includeProjectProperties) properties.forEach { (k, v) -> + if (v is String) { + if (v.startsWith("version.") || v.startsWith("plugin.")) { + map[k] = v + } + } + } + } +} + +internal tailrec fun resolveVersion(properties: Map, key: String, redirects: Int = 0): String? { + if (redirects > 5) error("Up to five redirects are allowed, for readability. You should only need one.") + val value = properties[key] ?: return null + return if (value.isAVersionAlias()) resolveVersion(properties, key, redirects + 1) else value +} + +/** + * Expects the value of a version property (values of the map returned by [getVersionProperties]). + */ +internal fun String.isAVersionAlias(): Boolean = startsWith("version.") + +private fun ModuleVersionSelector.getVersionFromProperties(properties: Map): String { + val propertyName = moduleIdentifier.getVersionPropertyName() + return resolveVersion(properties, propertyName) + ?: error("Property with key $propertyName wasn't found in the versions.properties file") +} + +private val ModuleVersionSelector.moduleIdentifier: ModuleIdentifier + get() = try { + @Suppress("UnstableApiUsage") + module + } catch (e: Throwable) { // Guard against possible API changes. + println(e) + object : ModuleIdentifier { + override fun getGroup(): String = this@moduleIdentifier.group + override fun getName(): String = this@moduleIdentifier.name + } + } + +@JvmName("_getVersionPropertyName") +private fun getVersionPropertyName(moduleIdentifier: ModuleIdentifier): String { + val group = moduleIdentifier.group + val name = moduleIdentifier.name + val versionKey: String = when (moduleIdentifier.findArtifactGroupingRule()?.groupNaming) { + GroupOnly -> group + GroupLastPart -> group.substringAfterLast('.') + GroupFirstTwoParts -> { + val groupFirstPart = group.substringBefore('.') + val groupSecondPart = group.substringAfter('.').substringBefore('.') + "$groupFirstPart.$groupSecondPart" + } + GroupFirstThreeParts -> { + val groupFirstPart = group.substringBefore('.') + val groupSecondPart = group.substringAfter('.').substringBefore('.') + val groupThirdPart = group.substringAfter('.').substringAfter('.').substringBefore('.') + "$groupFirstPart.$groupSecondPart.$groupThirdPart" + } + GroupAndNameFirstPart -> "$group.${name.substringBefore('-')}" + GroupLastPartAndNameSecondPart -> { + val groupLastPart = group.substringAfterLast('.') + val nameSecondPart = name.substringAfter('-').substringBefore('-') + "$groupLastPart.$nameSecondPart" + } + GroupFirstPartAndNameTwoFirstParts -> { + val groupFirstPart = group.substringBefore('.') + val nameFirstPart = name.substringBefore('-') + val nameSecondPart = name.substringAfter('-').substringBefore('-') + "$groupFirstPart.$nameFirstPart-$nameSecondPart" + } + null -> when { + moduleIdentifier.isGradlePlugin -> { + val pluginId = name.substringBeforeLast(".gradle.plugin") + return when { + pluginId.startsWith("org.jetbrains.kotlin") -> "version.kotlin" + pluginId.startsWith("com.android") -> "plugin.android" + else -> "plugin.$pluginId" + } + } + else -> "$group..$name" + } + } + return "version.$versionKey" +} + +private fun ModuleIdentifier.findArtifactGroupingRule(): ArtifactGroupingRule? { + if (forceFullyQualifiedName(this)) return null + val fullArtifactName = "$group:$name" + //TODO: Make the rules user-editable + return artifactsGroupingRules.find { fullArtifactName.startsWith(it.artifactNamesStartingWith) } +} + +private fun forceFullyQualifiedName(moduleIdentifier: ModuleIdentifier): Boolean { + val group = moduleIdentifier.group + val name = moduleIdentifier.name + if (group.startsWith("androidx.") && group != "androidx.legacy") { + val indexOfV = name.indexOf("-v") + if (indexOfV != -1 && + indexOfV < name.lastIndex && + name.substring(indexOfV + 1, name.lastIndex).all { it.isDigit() } + ) return true // AndroidX artifacts ending in "v18" or other "v${someApiLevel}" have standalone version number. + } + return false +} + +@Suppress("SpellCheckingInspection") +private val artifactsGroupingRules: List = sequenceOf( + "org.jetbrains.kotlin:kotlin" to GroupLastPart, + "org.jetbrains.kotlinx:kotlinx" to GroupLastPartAndNameSecondPart, + "androidx." to GroupOnly, + "androidx.media:media-widget" to GroupFirstPartAndNameTwoFirstParts, + "androidx.test:core" to GroupAndNameFirstPart, // Rest of androidx.test share the same version. + "androidx.test.ext:junit" to GroupAndNameFirstPart, + "androidx.test.ext:truth" to GroupFirstTwoParts, // Same version as the rest of androidx.test. + "androidx.test.services" to GroupFirstTwoParts, // Same version as the rest of androidx.test. + "androidx.test.espresso.idling" to GroupFirstThreeParts, // Same version as other androidx.test.espresso artifacts. + "com.louiscad.splitties:splitties" to GroupLastPart, + "com.squareup.retrofit2" to GroupLastPart, + "com.squareup.okhttp3" to GroupLastPart, + "com.squareup.moshi" to GroupLastPart, + "com.squareup.sqldelight" to GroupLastPart, + "org.robolectric" to GroupLastPart +).map { (artifactNamesStartingWith, groupNaming) -> + ArtifactGroupingRule( + artifactNamesStartingWith = artifactNamesStartingWith, + groupNaming = groupNaming + ) +}.sortedByDescending { it.artifactNamesStartingWith.length }.toList() diff --git a/plugin/src/main/kotlin/de/fayard/versions/VersionsPropertiesWriting.kt b/plugin/src/main/kotlin/de/fayard/versions/VersionsPropertiesWriting.kt new file mode 100644 index 000000000..375549f68 --- /dev/null +++ b/plugin/src/main/kotlin/de/fayard/versions/VersionsPropertiesWriting.kt @@ -0,0 +1,66 @@ +package de.fayard.versions + +import de.fayard.versions.extensions.moduleIdentifier +import org.gradle.api.Project +import org.gradle.api.artifacts.Dependency + +internal fun Project.updateVersionsProperties(dependenciesWithUpdate: List>) { + val file = file("versions.properties") + if (file.exists().not()) file.createNewFile() + + val properties: Map = getVersionProperties(includeProjectProperties = false) + + val newFileContent = buildString { + appendln(fileHeader) + appendln() + //TODO: Keep comments from user (ours begin with ##, while user's begin with a single #), + // we need to find a solution to keep the order/position. + val versionsWithUpdatesIfAvailable: List = dependenciesWithUpdate + .mapNotNull { (dependency, lastVersionOrNull) -> + dependency.moduleIdentifier?.getVersionPropertyName()?.let { + val currentVersion = properties[it]?.takeUnless { version -> version.isAVersionAlias()} + ?: return@mapNotNull null + VersionWithUpdateIfAvailable( + key = it, + currentVersion = currentVersion, + availableUpdateVersion = lastVersionOrNull + ) + } + } + .distinctBy { it.key } + val versionAliases: List = properties.mapNotNull { (k, v) -> + v.takeIf { version -> version.isAVersionAlias() }?.let { + VersionWithUpdateIfAvailable( + key = k, + currentVersion = v, + availableUpdateVersion = null + ) + } + } + (versionsWithUpdatesIfAvailable + versionAliases) + .sortedBy { it.key } + .forEach { + val paddedKey = it.key.padStart(available.length + 2) + val currentVersionLine = "${paddedKey}=${it.currentVersion}" + appendln(currentVersionLine) + it.availableUpdateVersion?.let { newVersion -> + append("##"); append(available.padStart(it.key.length - 2)) + append('='); appendln(newVersion) + } + } + } + file.writeText(newFileContent) +} + +private const val available = "# available" +private val fileHeader = """ + |## Dependencies and Plugin versions with their available updates + |## Generated by ${'$'} ./gradlew refreshVersions + |## Please, don't put extra comments in that file yet, keeping them is not supported yet. +""".trimMargin() + +private class VersionWithUpdateIfAvailable( + val key: String, + val currentVersion: String, + val availableUpdateVersion: String? +) diff --git a/plugin/src/main/kotlin/de/fayard/versions/extensions/Dependency.kt b/plugin/src/main/kotlin/de/fayard/versions/extensions/Dependency.kt new file mode 100644 index 000000000..d034f335e --- /dev/null +++ b/plugin/src/main/kotlin/de/fayard/versions/extensions/Dependency.kt @@ -0,0 +1,14 @@ +package de.fayard.versions.extensions + +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.ModuleIdentifier + +internal val Dependency.moduleIdentifier: ModuleIdentifier? + get() { + val group = group ?: return null + val name = name ?: return null + return object : ModuleIdentifier { + override fun getGroup(): String = group + override fun getName(): String = name + } + } diff --git a/plugin/src/main/kotlin/de/fayard/versions/extensions/GradleCompat.kt b/plugin/src/main/kotlin/de/fayard/versions/extensions/GradleCompat.kt new file mode 100644 index 000000000..490927d8f --- /dev/null +++ b/plugin/src/main/kotlin/de/fayard/versions/extensions/GradleCompat.kt @@ -0,0 +1,16 @@ +package de.fayard.versions.extensions + +import org.gradle.api.Action +import org.gradle.api.Task +import org.gradle.api.tasks.TaskContainer +import org.gradle.util.GradleVersion + +internal inline fun TaskContainer.registerOrCreate(name: String) { + if (supportsTaskRegistration) register(name, T::class.java) else create(name, T::class.java) +} + +internal inline fun TaskContainer.registerOrCreate(name: String, action: Action) { + if (supportsTaskRegistration) register(name, T::class.java, action) else create(name, T::class.java, action) +} + +private val supportsTaskRegistration = GradleVersion.current() >= GradleVersion.version("4.9") diff --git a/plugin/src/main/kotlin/de/fayard/versions/extensions/ModuleIdentifier.kt b/plugin/src/main/kotlin/de/fayard/versions/extensions/ModuleIdentifier.kt new file mode 100644 index 000000000..ef563ec2b --- /dev/null +++ b/plugin/src/main/kotlin/de/fayard/versions/extensions/ModuleIdentifier.kt @@ -0,0 +1,6 @@ +package de.fayard.versions.extensions + +import org.gradle.api.artifacts.ModuleIdentifier + +internal val ModuleIdentifier.isGradlePlugin: Boolean + get() = name.endsWith(".gradle.plugin") diff --git a/plugin/src/test/kotlin/de/fayard/EditorConfigTest.kt b/plugin/src/test/kotlin/de/fayard/EditorConfigTest.kt index 6245549ac..c808c1c4d 100644 --- a/plugin/src/test/kotlin/de/fayard/EditorConfigTest.kt +++ b/plugin/src/test/kotlin/de/fayard/EditorConfigTest.kt @@ -13,8 +13,6 @@ import io.kotlintest.tables.row import io.kotlintest.tables.table import java.io.File - - class EditorConfigTest: FreeSpec({ val testFolder = File("src/test/resources").absoluteFile diff --git a/plugin/src/test/kotlin/de/fayard/GradlePropertiesTest.kt b/plugin/src/test/kotlin/de/fayard/GradlePropertiesTest.kt index cb8bed542..42bbdfde7 100644 --- a/plugin/src/test/kotlin/de/fayard/GradlePropertiesTest.kt +++ b/plugin/src/test/kotlin/de/fayard/GradlePropertiesTest.kt @@ -14,6 +14,7 @@ class GradlePropertiesTest : FreeSpec({ } + //TODO: update the tests for the new config "versionsMapping" "Versions Properties" - { with(UpdateVersionsOnly) { diff --git a/plugin/src/test/kotlin/de/fayard/internal/NonRegression.kt b/plugin/src/test/kotlin/de/fayard/internal/NonRegression.kt index d855ba288..b4cd64611 100644 --- a/plugin/src/test/kotlin/de/fayard/internal/NonRegression.kt +++ b/plugin/src/test/kotlin/de/fayard/internal/NonRegression.kt @@ -8,6 +8,9 @@ import io.kotlintest.shouldBe import io.kotlintest.specs.FreeSpec import java.io.File +// TODO: transform plugin/src/test/resources/reports from JSON to the new file format +// TODO: mmake sure the unit tests still pass + class NonRegression : FreeSpec({ val reportsFolder = testResourceFile("reports") @@ -46,7 +49,7 @@ class NonRegression : FreeSpec({ val dependencies = (dependencyGraph.map { it.copy(available = null) }) .sortedBeautifullyBy(OrderBy.GROUP_AND_LENGTH) { it.versionProperty } .distinctBy { it.versionProperty } - UpdateProperties(extension).generateVersionProperties(received, dependencies) + UpdateProperties().generateVersionProperties(received, dependencies) if (propertiesFile.exists()) { val receivedIdentifiers = received.readLines().map { it.substringBefore("=", "") }.filter { it.startsWith("#") || it.isBlank() } diff --git a/sample-groovy/versions.properties b/sample-groovy/versions.properties index d9a4019a5..70693fe6a 100644 --- a/sample-groovy/versions.properties +++ b/sample-groovy/versions.properties @@ -3,8 +3,13 @@ # You can edit the rest of the file, it will be kept intact # See https://github.com/jmfayard/buildSrcVersions/issues/77 module.kotlin=1.3.50 +module.android=3.5.0 plugin.com.github.ben-manes.versions=0.25.0 plugin.de.fayard.refreshVersions=0.8.2 plugin.com.gradle.build-scan=2.4.2 +version.androidx.annotation..annotation=1.1.0 +version.org.jetbrains..annotation=17.0.0 +version.com.google.inject..guice=2.0 version.gradleLatestVersion=5.6.2 -# # available=5.6.3 \ No newline at end of file +# # available=5.6.4 +version.guava=15.0 \ No newline at end of file diff --git a/sample-kotlin/build.gradle.kts b/sample-kotlin/build.gradle.kts index 62680be4d..34316d650 100644 --- a/sample-kotlin/build.gradle.kts +++ b/sample-kotlin/build.gradle.kts @@ -1,24 +1,22 @@ import com.louiscad.splitties.AndroidX import com.louiscad.splitties.KotlinX import com.louiscad.splitties.Testing +import de.fayard.versions.StabilityLevel +import de.fayard.versions.candidateStabilityLevel import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id("de.fayard.refreshVersions") - id("com.louiscad.splitties") - kotlin("jvm") + id("com.louiscad.splitties") version "0.1.3" + kotlin("jvm") version "1.3.50" `build-scan` } group = "de.fayard" refreshVersions { - // See configuration options at https://github.com/jmfayard/buildSrcVersions/issues/53 - propertiesFile = "versions.properties" - alwaysUpdateVersions() - useFqdnFor("guice", "mongo-java-driver") rejectVersionIf { - isNonStable(candidate.version) && isNonStable(currentVersion).not() + candidateStabilityLevel() isLessStableThan StabilityLevel.Stable } } @@ -30,6 +28,8 @@ buildScan { repositories { maven("../plugin/src/test/resources/maven") mavenCentral() + google() + jcenter() } fun DependencyHandler.implementations(deps: List) = @@ -42,7 +42,7 @@ dependencies { implementations(listOf(AndroidX.browser, AndroidX.cardView)) testImplementations(listOf(KotlinX.coroutines.core, KotlinX.coroutines.coreCommon)) testImplementations(listOf(Testing.kotestRunner, Testing.kotestExtensions)) - implementation("com.google.guava:guava:15.0") + implementation("com.google.guava:guava:_") implementation("com.google.inject:guice:2.0") implementation("com.squareup.okhttp3:okhttp:3.10.0") implementation("com.squareup.okhttp3:okhttp-urlconnection:3.10.0") diff --git a/sample-kotlin/gradle.properties b/sample-kotlin/gradle.properties index f2f98cf39..3d751208a 100644 --- a/sample-kotlin/gradle.properties +++ b/sample-kotlin/gradle.properties @@ -1,5 +1 @@ -# The plugin https://github.com/jmfayard/buildSrcVersions -# allows to overwrite plugin and dependencies versions from the values inside 'gradle.properties' -# Set to 'verbose' to understand how dependency and plugin versions are overwritten -# Set to 'false' to disable this feature -resolutionStrategyConfig=verbose +refreshVersions.useExperimentalUpdater=true diff --git a/sample-kotlin/gradle/plugins.gradle.kts b/sample-kotlin/gradle/plugins.gradle.kts deleted file mode 100644 index 8a0255c67..000000000 --- a/sample-kotlin/gradle/plugins.gradle.kts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * File generated by $ ./gradlew refreshVersions - * - * Gradle has replaced the buildscript { ... } block by a better alternative that looks like this - * -``` -plugins { -id("com.android.application") -id("com.louiscad.splitties") -id("org.jetbrains.kotlin.android") -id("org.jetbrains.kotlin.kapt") -id("kotlin-android-extensions") -} -``` - * This boilerplate does two things: - * - * 1. it configures the plugin versions using the file `versions.properties` generated by - * $ ./gradlew refreshVersions - * - * 2. it fixes the bug of the Android Gradle Plugin that doesn't publish the required metadata - * Please see and upvote the issue: - * https://issuetracker.google.com/issues/64551265 - * - * Include this file like this: - * - -```kotlin -// settings.gradle.kts -pluginManagement { -repositories { -google() -gradlePluginPortal() -} -} -apply(from = "plugins.gradle.kts") -// rootProject.name = xxx -// include(":app") -``` - - ***/ -@Suppress("CAST_NEVER_SUCCEEDS") -(this as Settings).pluginManagement { - - /** - * This `resolutionStrategy` allows plugin versions to be configured from - * `versions.properties - * The convention is simply - * plugin.$PLUGINID=$PLUGIN_VERSION - * To check what happen, you can set the property - * resolutionStrategyConfig=verbose - **/ - val resolutionStrategyConfig: String? by extra - val versionProperties = file("../versions.properties") - if (resolutionStrategyConfig == "false" || versionProperties.canRead().not()) return@pluginManagement - val androidPluginIds = listOf("com.android.application", "com.android.library") - val kotlinPluginIds = listOf("org.jetbrains.kotlin.android", "org.jetbrains.kotlin.kapt", "kotlin-android-extensions") - @Suppress("UNCHECKED_CAST") - val properties: Map = java.util.Properties().apply { - load(versionProperties.reader()) - } as Map - resolutionStrategy.eachPlugin { - val pluginId = requested.id.id - val version = properties["plugin.$pluginId"] - val message = when { - pluginId in kotlinPluginIds -> { - val module = "org.jetbrains.kotlin:kotlin-gradle-plugin:${properties["module.kotlin"]}" - useModule(module) - "ResolutionStrategy used module=$module for plugin=$pluginId" - } - pluginId in androidPluginIds -> { - val module = "com.android.tools.build:gradle:${properties["module.android"]}" - useModule(module) - "ResolutionStrategy used module=$module for plugin=$pluginId" - } - version != null -> { - useVersion(version) - "ResolutionStrategy used version=$version for plugin=$pluginId" - } - else -> "ResolutionStrategy did not find a version for $pluginId" - } - if (resolutionStrategyConfig == "verbose") println(message) - } -} diff --git a/sample-kotlin/settings.gradle.kts b/sample-kotlin/settings.gradle.kts index d811092fa..567ec5044 100644 --- a/sample-kotlin/settings.gradle.kts +++ b/sample-kotlin/settings.gradle.kts @@ -1,10 +1,25 @@ +import de.fayard.versions.setupVersionPlaceholdersResolving + +buildscript { + repositories { + mavenLocal() // Only necessary for testing + gradlePluginPortal() + mavenCentral() + } + // Didn't find a way to use classpath from composite build, so we are hardcoding a + // version expected to be released in one of the configured repositories. + dependencies.classpath("de.fayard:plugin:0.8.2") +} + pluginManagement { repositories { gradlePluginPortal() mavenCentral() } } -apply(from = "gradle/plugins.gradle.kts") + +setupVersionPlaceholdersResolving() + rootProject.name = "sample-kotlin" includeBuild("../plugin") diff --git a/sample-kotlin/versions.properties b/sample-kotlin/versions.properties index 2014858d9..c1701224a 100644 --- a/sample-kotlin/versions.properties +++ b/sample-kotlin/versions.properties @@ -1,22 +1,8 @@ -# Dependencies and Plugin versions with their available updates -# Generated by $ ./gradlew refreshVersions -# You can edit the rest of the file, it will be kept intact -# See https://github.com/jmfayard/buildSrcVersions/issues/77 -module.kotlin=1.3.50 -module.android=3.5.0 -plugin.com.github.ben-manes.versions=0.25.0 -plugin.de.fayard.refreshVersions=0.8.2 -plugin.org.jetbrains.kotlin.jvm=1.3.50 +## Dependencies and Plugin versions with their available updates +## Generated by $ ./gradlew refreshVersions +## Please, don't put extra comments in that file yet, keeping them is not supported yet. + plugin.com.louiscad.splitties=0.1.3 -plugin.com.gradle.build-scan=3.0 -version.org.jetbrains.kotlinx=1.3.2 -version.com.squareup.okhttp3=4.2.0 -version.org.jetbrains.kotlin=1.3.50 -version.io.kotestArtifact=+ -version.org.mongodb..mongo-java-driver=3.11.0 -version.androidx.cardview..cardview=+ -version.androidx.browser..browser=+ -version.com.google.inject..guice=2.0 -version.gradleLatestVersion=5.6.2 -# # available=5.6.3 -version.guava=15.0 \ No newline at end of file +version.com.google.guava..guava=15.0 +## # available=23.0 +version.kotlin=1.3.50