Skip to content

Commit

Permalink
Merge pull request #33 from f4lco/develop
Browse files Browse the repository at this point in the history
v0.3.0
  • Loading branch information
f4lco authored Nov 17, 2024
2 parents 1babf64 + 709cd6f commit 10b100e
Show file tree
Hide file tree
Showing 11 changed files with 303 additions and 11 deletions.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,20 @@ BUILD SUCCESSFUL in 28s

## Changelog

### 0.3.0 (2024-11-08)
### 0.3.0 (2024-11-17)

Thanks to @Breefield the plugin now supports inclusion and exclusion filters on dependencies.
By example,

- Exclusion patterns allow to exclude Libyears from a particular framework, for example Spring, and
- Inclusion patterns allow to "narrow down" on a particular set of dependencies, which allows to answer questions such as "how many Libyears do internal artifacts from 'com.mycompany' bring in?"

If build authors use both pattern in conjunction, the plugin narrows down the dependencies to the inclusion list, and then proceeds to filter out dependencies matching the exclusion pattern(s).

Patterns allow for globbing, for example, the pattern `com.mycompany:*` matches all artifacts with group name "com.mycompany",
and `com.mycompany:logging-*` would match all artifact names starting with "logging".

### 0.2.1 (2024-11-08)

Thanks to @Breefield the plugin now writes a JSON report in `build/reports` which contains a machine-readable report of Libyears per dependency 🚀

Expand Down
1 change: 1 addition & 0 deletions libyear-gradle-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ dependencies {

testImplementation("com.squareup.okhttp3:mockwebserver:4.8.1")
testImplementation("org.mockito:mockito-core:3.7.7")
testImplementation("org.mockito.kotlin:mockito-kotlin:4.0.0")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.0")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.7.0")
testImplementation("org.assertj:assertj-core:3.18.1")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ internal class LibYearPluginTest {
.extracting { it?.outcome }.isEqualTo(TaskOutcome.FAILED)
}

@Test
fun testFilterModules() {
setUpProject("filterModules.gradle.kts")

val result = withGradleRunner("reportLibyears").build()
val output = result.output

assertThat(output)
.contains("from 1 dependencies")
.contains("org.slf4j:slf4j-api")
assertThat(output).doesNotContain("unknown.package")
}

@Test
fun testReportLibyear() {
setUpProject("valid.gradle.kts")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
plugins {
id("com.libyear.libyear-gradle-plugin")
java
}

repositories {
mavenCentral()
}

dependencies {
implementation("org.apache.commons:commons-text:1.9")
implementation("org.apache.commons:commons-collections4:4.4")
implementation("org.slf4j", "slf4j-api", "2.0.9")
implementation("org.slf4j", "slf4j-simple", "2.0.9")
}

libyear {
failOnError = false
validator = singleArtifactMustNotBeOlderThan(100.years)
excludedModules = setOf("*slf4j-simple")
includedModules = setOf("org.slf4j*")
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ open class LibYearExtension {

var configurations: List<String> = defaultConfigurations

var excludedModules: Set<String> = emptySet()
var includedModules: Set<String> = emptySet()

// DSL for build script authors

val defaultConfigurations: List<String> get() = listOf("compileClasspath")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,14 @@ class LibYearPlugin : Plugin<Project> {
val ageOracle = createOracle(project, extension)
val validator = createValidator(project, extension)
val visitor = ValidatingVisitor(project.logger, ageOracle, validator, ValidationConfig(failOnError = extension.failOnError))
DependencyTraversal.visit(resolvableDependencies.resolutionResult.root, visitor, extension.maxTransitiveDepth)
DependencyTraversal.visit(
project.logger,
resolvableDependencies.resolutionResult.root,
visitor,
extension.maxTransitiveDepth,
extension.excludedModules,
extension.includedModules
)
maybeReportFailure(project.logger, validator)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ open class LibYearReportTask : DefaultTask() {
.forEach {
val ageOracle = createOracle(project, extension)
val visitor = ReportingVisitor(project.logger, ageOracle)
DependencyTraversal.visit(it.incoming.resolutionResult.root, visitor, extension.maxTransitiveDepth)
DependencyTraversal.visit(
project.logger,
it.incoming.resolutionResult.root,
visitor,
extension.maxTransitiveDepth,
extension.excludedModules,
extension.includedModules
)
visitor.print()
visitor.saveReportToJson(project)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import org.slf4j.LoggerFactory
* Adapter selection: the algorithm prefers to select an adapter by name of the repository Gradle has
* sourced the artifact from, see [adapters]. If none is present, [defaultAdapter] is queried.
*/
class DefaultVersionOracle(
open class DefaultVersionOracle(
private val defaultAdapter: VersionInfoAdapter,
private val adapters: Map<String, VersionInfoAdapter>,
private val repositories: Map<String, ArtifactRepository>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@ import org.gradle.api.artifacts.component.ComponentIdentifier
import org.gradle.api.artifacts.result.ComponentResult
import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.logging.Logger
import org.gradle.internal.impldep.com.google.common.annotations.VisibleForTesting

class DependencyTraversal private constructor(
private val logger: Logger,
private val visitor: DependencyVisitor,
private val maxTransitiveDepth: Int?
private val maxTransitiveDepth: Int?,
excludedModules: Set<String>,
includedModules: Set<String>
) {
private val excludedPatterns = excludedModules.map { it to it.wildcardToRegex() }
private val includedPatterns = includedModules.map { it to it.wildcardToRegex() }

private val seen = mutableSetOf<ComponentIdentifier>()

Expand All @@ -24,10 +31,11 @@ class DependencyTraversal private constructor(
if (!visitor.canContinue()) return

if (dependency is ResolvedDependencyResult) {
if (maxTransitiveDepth != null && depth > maxTransitiveDepth) {
continue
val group = dependency.selected.moduleVersion?.group.toString()
val name = dependency.selected.moduleVersion?.name.toString()
if (shouldIncludeModule("$group:$name", depth)) {
nextComponents.add(dependency.selected)
}
nextComponents.add(dependency.selected)
}
}

Expand All @@ -37,12 +45,55 @@ class DependencyTraversal private constructor(
}
}

@VisibleForTesting
fun shouldIncludeModule(
module: String,
depth: Int
): Boolean {
if (maxTransitiveDepth != null && depth > maxTransitiveDepth) {
return false
}

// Inclusions supersede exclusions
val matchedInclusion = includedPatterns.firstOrNull { pattern -> pattern.second.matches(module) }
val matchedExclusion = excludedPatterns.firstOrNull { pattern -> pattern.second.matches(module) }

val matchesInclusions = includedPatterns.isEmpty() || matchedInclusion != null
val matchesExclusions = matchedExclusion != null
val shouldIncludeModule = matchesInclusions && !matchesExclusions

if (!shouldIncludeModule) {
if (matchesExclusions) {
logger.info("Excluding $module because it matches ${matchedExclusion!!.first}")
} else {
logger.info("Excluding $module because it does not match inclusion list")
}
}

return shouldIncludeModule
}

companion object {

fun visit(
logger: Logger,
root: ResolvedComponentResult,
visitor: DependencyVisitor,
maxTransitiveDepth: Int? = null
): Unit = DependencyTraversal(visitor, maxTransitiveDepth).visit(root, depth = 0)
maxTransitiveDepth: Int? = null,
excludedModules: Set<String> = emptySet(),
includedModules: Set<String> = emptySet()
): Unit = DependencyTraversal(
logger,
visitor,
maxTransitiveDepth,
excludedModules,
includedModules
).visit(root, depth = 0)

@VisibleForTesting
fun String.wildcardToRegex(): Regex {
val globsToRegex = this.replace(".", "\\.").replace("*", ".*")
return "^$globsToRegex$".toRegex(RegexOption.IGNORE_CASE)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ private data class ReportingInfo(
val latestVersion: String
)

class ReportingVisitor(
open class ReportingVisitor(
logger: Logger,
private val ageOracle: VersionOracle
) : DependencyVisitor(logger) {
Expand Down
Loading

0 comments on commit 10b100e

Please sign in to comment.