Skip to content

Commit

Permalink
WIP dependencies updates proof of concept
Browse files Browse the repository at this point in the history
  • Loading branch information
LouisCAD committed Oct 13, 2019
1 parent dd471e7 commit 9f4a85e
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 25 deletions.
185 changes: 181 additions & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,191 @@

@file:Suppress("SpellCheckingInspection")

import Build_gradle.ArtifactGroupNaming.*

// Top-level build file where you can add configuration options common to all sub-projects/modules.

task<Delete>("clean") {
delete(rootProject.buildDir)
}

val versionPlaceholder = "_"

allprojects {
repositories {
setupForProject()
repositories { setupForProject() }
configurations.all { setupVersionPlaceholdersResolving() }
//TODO: Replace code below by pluginManagement or something else?
// (it is incorrect, because it runs once classpath configuration is already resolved)
//buildscript.configurations.all { setupVersionPlaceholdersResolving() }
}

tasks.register("checkDependenciesUpdates") {
doFirst {
val allConfigurations: Set<Configuration> = allprojects.flatMap {
it.configurations + it.buildscript.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 improve performance.
val dependenciesToUpdate: Sequence<Pair<Dependency, String>> = allDependencies.mapNotNull { dependency ->
val latestVersion = getLatestDependencyVersion(dependency) ?: return@mapNotNull null
val usedVersion = dependency.version
if (usedVersion == latestVersion) null else dependency to latestVersion
}
//TODO: Write updates to gradle.properties without overwriting unrelated properties, comments and structure.
dependenciesToUpdate.forEach { (dependency: Dependency, latestVersion: String) ->
println("Dependency ${dependency.group}:${dependency.name}:${dependency.version} -> $latestVersion")
}
}
}

task<Delete>("clean") {
delete(rootProject.buildDir)
//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?
fun isVersionStable(version: String): Boolean {
//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)
}

fun Project.getLatestDependencyVersion(dependency: Dependency): String? {
val tmpDependencyUpdateConfiguration = configurations.create("getLatestVersion") {
dependencies.add(dependency)
resolutionStrategy.componentSelection.all {
if (isVersionStable(candidate.version).not()) reject("Unstable version")
}
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)
}
}


fun Configuration.setupVersionPlaceholdersResolving() {
resolutionStrategy.eachDependency {
if (requested.version != versionPlaceholder) return@eachDependency
useVersion(requested.getVersionFromProperties())
}
}

fun ModuleVersionSelector.getVersionFromProperties(): String {
val moduleIdentifier: ModuleIdentifier = try {
@Suppress("UnstableApiUsage")
module
} catch (e: Throwable) { // Guard against possible API changes.
println(e)
object : ModuleIdentifier {
override fun getGroup(): String = this@getVersionFromProperties.group
override fun getName(): String = this@getVersionFromProperties.name
}
}
val propertyName = getVersionPropertyName(moduleIdentifier)
val version = property(propertyName = propertyName)
return version as String
}

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 -> "$group..$name"
}
return "version.$versionKey"
}

fun ModuleIdentifier.findArtifactGroupingRule(): ArtifactGroupingRule? {
if (forceFullyQualifiedName(this)) return null
val fullArtifactName = "$group:$name"
return artifactsGroupingRules.find { fullArtifactName.startsWith(it.artifactNamesStartingWith) }
}

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
}

val artifactsGroupingRules: List<ArtifactGroupingRule> = 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()

/**
* We assume each part of the "group" is dot separated (`.`), and each part of the name is dash separated (`-`).
*/
sealed class ArtifactGroupNaming {
object GroupOnly : ArtifactGroupNaming()
object GroupLastPart : ArtifactGroupNaming()
object GroupFirstTwoParts : ArtifactGroupNaming()
object GroupFirstThreeParts : ArtifactGroupNaming()
object GroupAndNameFirstPart : ArtifactGroupNaming()
object GroupLastPartAndNameSecondPart : ArtifactGroupNaming()
object GroupFirstPartAndNameTwoFirstParts : ArtifactGroupNaming()
}

class ArtifactGroupingRule(
val artifactNamesStartingWith: String,
val groupNaming: ArtifactGroupNaming
) {
init {
require(artifactNamesStartingWith.count { it == ':' } <= 1)
}
}
42 changes: 21 additions & 21 deletions buildSrc/src/main/kotlin/dependencies/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,25 @@ object Versions {
@Suppress("unused")
object Libs {
const val junit = "junit:junit:4.12"
const val roboElectric = "org.robolectric:robolectric:4.3"
const val roboElectric = "org.robolectric:robolectric:_"
const val timber = "com.jakewharton.timber:timber:4.7.1"
const val stetho = "com.facebook.stetho:stetho:1.5.0"
const val stetho = "com.facebook.stetho:stetho:_"

val kotlin = Kotlin
val kotlinX = KotlinX
val androidX = AndroidX
val google = Google

object Kotlin {
const val stdlibJdk7 = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${Versions.kotlin}"
const val testJunit = "org.jetbrains.kotlin:kotlin-test-junit:${Versions.kotlin}"
const val stdlibJdk7 = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:_"
const val testJunit = "org.jetbrains.kotlin:kotlin-test-junit:_"
}

object KotlinX {
val coroutines = Coroutines

object Coroutines {
private const val version = "1.3.1"
private const val version = "_"
private const val artifactPrefix = "org.jetbrains.kotlinx:kotlinx-coroutines"
const val core = "$artifactPrefix-core:$version"
const val coreCommon = "$artifactPrefix-core-common:$version"
Expand All @@ -47,33 +47,33 @@ object Libs {

object AndroidX {
private object Versions {
const val core = "1.0.1"
const val core = "_"
const val multidex = "2.0.0"
const val palette = "1.0.0"
const val preference = "1.0.0"
const val recyclerView = "1.0.0"
const val recyclerView = "_"
const val sqlite = "2.0.0"
const val vectorDrawable = "1.0.0"
const val leanback = "1.0.0"
const val emoji = "1.0.0"
const val constraintLayout = "1.1.3"
const val constraintLayout = "_"
const val collection = "1.0.0"
}
private val versions = Versions
const val annotation = "androidx.annotation:annotation:1.0.0"
const val appCompat = "androidx.appcompat:appcompat:1.0.2"
const val annotation = "androidx.annotation:annotation:_"
const val appCompat = "androidx.appcompat:appcompat:_"
const val asyncLayoutInflater = "androidx.asynclayoutinflater:asynclayoutinflater:1.0.0"
const val browser = "androidx.browser:browser:1.0.0"
const val car = "androidx.car:car:1.0.0-alpha5"
const val cardView = "androidx.cardview:cardview:1.0.0"
const val cardView = "androidx.cardview:cardview:_"
const val collection = "androidx.collection:collection:${versions.collection}"
const val collectionKtx = "androidx.collection:collection-ktx:${versions.collection}"
const val constraintLayout =
"androidx.constraintlayout:constraintlayout:${versions.constraintLayout}"
const val constraintLayoutSolver =
"androidx.constraintlayout:constraintlayout-solver:${versions.constraintLayout}"
const val contentPager = "androidx.contentpager:contentpager:1.0.0"
const val coordinatorLayout = "androidx.coordinatorlayout:coordinatorlayout:1.0.0"
const val coordinatorLayout = "androidx.coordinatorlayout:coordinatorlayout:_"
const val core = "androidx.core:core:${versions.core}"
const val coreKtx = "androidx.core:core-ktx:${versions.core}"
const val cursorAdapter = "androidx.cursoradapter:cursoradapter:1.0.0"
Expand All @@ -85,8 +85,8 @@ object Libs {
const val emojiAppCompat = "androidx.emoji:emoji-appcompat:${versions.emoji}"
const val emojiBundler = "androidx.emoji:emoji-bundled:${versions.emoji}"
const val exifInterface = "androidx.exifinterface:exifinterface:1.0.0"
const val fragment = "androidx.fragment:fragment:1.0.0"
const val fragmentKtx = "androidx.fragment:fragment-ktx:1.0.0"
const val fragment = "androidx.fragment:fragment:_"
const val fragmentKtx = "androidx.fragment:fragment-ktx:_"
const val gridLayout = "androidx.gridlayout:gridlayout:1.0.0"
const val heifWriter = "androidx.heifwriter:heifwriter:1.0.0"
const val interpolator = "androidx.interpolator:interpolator:1.0.0"
Expand Down Expand Up @@ -140,7 +140,7 @@ object Libs {
val legacy = Legacy

object Lifecycle {
private const val version = "2.0.0"
private const val version = "_"
const val common = "androidx.lifecycle:lifecycle-common:$version"
const val commonJava8 = "androidx.lifecycle:lifecycle-common-java8:$version"
const val compiler = "androidx.lifecycle:lifecycle-compiler:$version"
Expand All @@ -158,7 +158,7 @@ object Libs {
}

object Room {
private const val version = "2.0.0"
private const val version = "_"
const val common = "androidx.room:room-common:$version"
const val compiler = "androidx.room:room-compiler:$version"
const val guava = "androidx.room:room-guava:$version"
Expand Down Expand Up @@ -214,11 +214,11 @@ object Libs {

object Test {
val espresso = Espresso
private const val runnerVersion = "1.2.0"
private const val runnerVersion = "_"
private const val rulesVersion = runnerVersion
private const val monitorVersion = runnerVersion
private const val orchestratorVersion = runnerVersion
private const val coreVersion = "1.2.0"
private const val coreVersion = "_"
const val core = "androidx.test:core:$coreVersion"
const val coreKtx = "androidx.test:core-ktx:$coreVersion"
const val monitor = "androidx.test:monitor:$monitorVersion"
Expand All @@ -229,7 +229,7 @@ object Libs {
val ext = Ext

object Ext {
private const val extJunitVersion = "1.1.1"
private const val extJunitVersion = "_"
const val junit = "androidx.test.ext:junit:$extJunitVersion"
const val junitKtx = "androidx.test.ext:junit-ktx:$extJunitVersion"
const val truth = "androidx.test.ext:truth:$coreVersion"
Expand All @@ -245,7 +245,7 @@ object Libs {

object Espresso {
val idling = Idling
private const val version = "3.1.1"
private const val version = "_"
const val core = "androidx.test.espresso:espresso-core:$version"
const val contrib = "androidx.test.espresso:espresso-contrib:$version"
const val idlingResource =
Expand Down Expand Up @@ -275,7 +275,7 @@ object Libs {
}

object Google {
const val material = "com.google.android.material:material:1.0.0"
const val material = "com.google.android.material:material:_"
private const val wearOsVersion = "2.4.0"
const val wearable = "com.google.android.wearable:wearable:$wearOsVersion"
const val supportWearable = "com.google.android.support:wearable:$wearOsVersion"
Expand Down
25 changes: 25 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,28 @@ kotlin.native.ignoreDisabledTargets=true
android.namespacedRClass=true

splitties.version=3.0.0-dev-038

# Dependencies versions
version.androidx.annotation=1.1.0
version.androidx.appcompat=1.0.2
version.androidx.cardview=1.0.0
version.androidx.coordinatorlayout=1.0.0
version.androidx.core=1.0.1
version.androidx.constraintlayout=1.1.3
version.androidx.fragment=1.1.0
version.androidx.lifecycle=2.0.0
version.androidx.recyclerview=1.0.0
version.androidx.room=2.0.0
version.androidx.test=1.2.0
version.androidx.test.core=1.2.0
version.androidx.test.ext.junit=1.1.1
version.androidx.test.espresso=3.1.1

version.kotlin=1.3.50

version.kotlinx.coroutines=1.3.1

version.robolectric=4.3

version.com.facebook.stetho..stetho=1.5.0
version.com.google.android.material..material=1.0.0

0 comments on commit 9f4a85e

Please sign in to comment.