Skip to content

Commit

Permalink
Rework: dependency format check
Browse files Browse the repository at this point in the history
  • Loading branch information
jjohannes committed Dec 1, 2023
1 parent e005469 commit 53eb79a
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ tasks.register("check") {
group = mainBuildGroup
description = "Runs all checks and produces test summary and code coverage reports"
dependsOn(subprojects.map { ":${it.name}:$name" })
dependsOn(gradle.includedBuild("platform").task(":check"))
doLast {
println("Unit test summary: app/build/reports/tests/unit-test/aggregated-results/index.html")
println("Unit test code coverage: app/build/reports/jacoco/testCodeCoverageReport/html/index.html")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import org.example.dependencyanalysis.DependencyFormatCheck
import org.example.dependencyanalysis.DependencyVersionUpgradesCheck

plugins {
id("java-platform")
id("org.example.dependency-analysis")
}

val checkDependencyFormatting = tasks.register<DependencyFormatCheck>("checkDependencyFormatting") {
group = LifecycleBasePlugin.VERIFICATION_GROUP

buildFilePath.set(project.buildFile.absolutePath)
shouldNotHaveVersions.set(false)
declaredDependencies.put("api", provider { configurations.api.get().dependencies.map { d -> d.toDeclaredString() } })
declaredDependencies.put("runtime", provider { configurations.runtime.get().dependencies.map { d -> d.toDeclaredString() } })
declaredConstraints.put("api", provider { configurations.api.get().dependencyConstraints.map { d -> d.toDeclaredString() } })
declaredConstraints.put("runtime", provider { configurations.runtime.get().dependencyConstraints.map { d -> d.toDeclaredString() } })
}

// Install a task that checks if new versions are available for what is declared in the platform
Expand All @@ -14,3 +25,10 @@ tasks.register<DependencyVersionUpgradesCheck>("checkForDependencyVersionUpgrade
apiDependencies.set(configurations.api.get().dependencies.map { "${it.group}:${it.name}:${it.version}" })
apiDependencyConstraints.set(configurations.api.get().dependencyConstraints.map { "${it.group}:${it.name}:${it.version}" })
}

tasks.check {
dependsOn(checkDependencyFormatting)
}

fun Dependency.toDeclaredString() = "$group:$name:$version"
fun DependencyConstraint.toDeclaredString() = "$group:$name:$version"
Original file line number Diff line number Diff line change
@@ -1,49 +1,40 @@
import com.autonomousapps.DependencyAnalysisSubExtension
import org.example.dependencyanalysis.DependencyFormatCheck
import org.example.dependencyanalysis.DependencyScopeCheck
import org.example.dependencyanalysis.configurationPrefixesToSkip

plugins {
id("base")
id("org.example.dependency-analysis")
id("java")
}

// Check that dependencies are always declared without version.
configurations.all {
if (configurationPrefixesToSkip.any { name.startsWith(it) }) {
return@all
}
val checkDependencyFormatting = tasks.register<DependencyFormatCheck>("checkDependencyFormatting") {
group = LifecycleBasePlugin.VERIFICATION_GROUP

val configuration = this
withDependencies {
forEach { dependency ->
if (dependency is ExternalModuleDependency && !dependency.version.isNullOrEmpty()) {
throw RuntimeException("""
${project.name}/build.gradle.kts
Dependencies with versions are not allowed. Please declare the dependency as follows:
${configuration.name}("${dependency.group}:${dependency.name}")
All versions must be declared in 'gradle/platform'.
If the version is not yet defined there, add the following to 'gradle/build.gradle.kts':
api("${dependency.group}:${dependency.name}:${dependency.version}")
""".trimIndent())
}
}
buildFilePath.set(project.buildFile.absolutePath)
shouldNotHaveVersions.set(true)
sourceSets.all {
declaredDependencies.put(implementationConfigurationName, provider { configurations.getByName(implementationConfigurationName).dependencies.map { d -> d.toDeclaredString() } })
declaredDependencies.put(runtimeOnlyConfigurationName, provider { configurations.getByName(runtimeOnlyConfigurationName).dependencies.map { d -> d.toDeclaredString() } })
declaredDependencies.put(compileOnlyConfigurationName, provider { configurations.getByName(compileOnlyConfigurationName).dependencies.map { d -> d.toDeclaredString() } })
declaredDependencies.put(apiConfigurationName, provider { configurations.findByName(apiConfigurationName)?.dependencies?.map { d -> d.toDeclaredString() } ?: emptyList() })
declaredDependencies.put(compileOnlyApiConfigurationName, provider { configurations.findByName(compileOnlyApiConfigurationName)?.dependencies?.map { d -> d.toDeclaredString() } ?: emptyList() })
}
}

// Configure a 'checkDependencyScopes' tasks that uses the 'com.autonomousapps.dependency-analysis' plugin.
// To find unused dependencies and check 'api' vs. 'implementation' scopes.
val dependencyScopesCheck = tasks.register<DependencyScopeCheck>("checkDependencyScopes") {
val checkDependencyScopes = tasks.register<DependencyScopeCheck>("checkDependencyScopes") {
group = LifecycleBasePlugin.VERIFICATION_GROUP
shouldRunAfter(checkDependencyFormatting)
}

tasks.check {
dependsOn(dependencyScopesCheck)
dependsOn(checkDependencyFormatting)
dependsOn(checkDependencyScopes)
}

plugins.withId("com.autonomousapps.dependency-analysis") {
extensions.getByType<DependencyAnalysisSubExtension>().registerPostProcessingTask(dependencyScopesCheck)
extensions.getByType<DependencyAnalysisSubExtension>().registerPostProcessingTask(checkDependencyScopes)
}

fun Dependency.toDeclaredString() = when(this) {
is ProjectDependency -> ":$name"
else -> "$group:$name${if (version == null) "" else ":$version"}"
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,10 @@ tasks.register("checkDependencyScopes") {
description = "Check all dependency scopes (api vs implementation) and find unused dependencies"
dependsOn(subprojects.map { "${it.path}:checkDependencyScopes"})
}

tasks.register("checkDependencyFormatting") {
group = LifecycleBasePlugin.VERIFICATION_GROUP
description = "Check format of all dependency declarations"
dependsOn(subprojects.map { "${it.path}:checkDependencyFormatting"})
dependsOn(gradle.includedBuild("platform").task(":checkDependencyFormatting"))
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package org.example.dependencyanalysis

import org.gradle.api.DefaultTask
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction

/**
* Check that 'dependencies' are defined in alphabetical order and without version.
*/
abstract class DependencyFormatCheck : DefaultTask() {

@get:Input
abstract val buildFilePath : Property<String>

@get:Input
abstract val declaredDependencies : MapProperty<String, List<String>> // Map of 'scope' to 'coordinates'

@get:Input
abstract val declaredConstraints : MapProperty<String, List<String>> // Map of 'scope' to 'coordinates'

@get:Input
abstract val shouldNotHaveVersions : Property<Boolean>

@TaskAction
fun check() {
declaredDependencies.get().forEach { (scope, dependencies) ->
if (shouldNotHaveVersions.get()) {
dependencies.forEach { coordinates ->
if (coordinates.count { it == ':' } == 2 && !coordinates.startsWith("org.jetbrains.kotlin:kotlin-stdlib:")) {
throw RuntimeException("""
${buildFilePath.get()}
Dependencies with versions are not allowed. Please declare the dependency as follows:
${scope}("${coordinates.substring(0, coordinates.lastIndexOf(':'))}")
All versions must be declared in 'gradle/platform'.
If the version is not yet defined there, add the following to 'gradle/platform/build.gradle.kts':
api("$coordinates")
""".trimIndent())
}
}
}

val declaredInBuildFile = dependencies.filter {
// Ignore dependencies that are defined in our plugins
it !in listOf(
"org.example.product:platform",
"org.slf4j:slf4j-simple",
"org.junit.jupiter:junit-jupiter-engine",
"org.junit.jupiter:junit-jupiter")
}
val sortedProject = declaredInBuildFile.filter { it.startsWith(":") }.sorted()
val sortedExternal = declaredInBuildFile.filter { !it.startsWith(":") }.sorted()
if (declaredInBuildFile != sortedProject + sortedExternal) {
throw RuntimeException("""
${buildFilePath.get()}
$scope dependencies are not declared in alphabetical order. Please use this order:
${sortedProject.joinToString("\n ") {"${scope}(project(\"${it}\"))"}}
${sortedExternal.joinToString("\n ") {"${scope}(\"${it}\")"}}
""".trimIndent())
}
}

declaredConstraints.get().forEach { (scope, constraints) ->
val sortedConstraints = constraints.sorted()
if (constraints != sortedConstraints) {
throw RuntimeException("""
${buildFilePath.get()}
$scope dependency constraints are not declared in alphabetical order. Please use this order:
${sortedConstraints.joinToString("\n ") {"${scope}(\"${it}\")"}}
""".trimIndent())
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import com.autonomousapps.model.Advice
import com.autonomousapps.model.ProjectCoordinates
import org.gradle.api.tasks.TaskAction

/**
* Task that uses the 'com.autonomousapps.dependency-analysis' plugin to find unused dependencies and check
* 'api' vs. 'implementation' scopes.
*/
abstract class DependencyScopeCheck : AbstractPostProcessingTask() {

@TaskAction
Expand Down

This file was deleted.

0 comments on commit 53eb79a

Please sign in to comment.