Skip to content

Commit

Permalink
Improve transitive dependency resolution for modules
Browse files Browse the repository at this point in the history
  • Loading branch information
ryru committed Sep 8, 2024
1 parent e044fd2 commit 991d54b
Show file tree
Hide file tree
Showing 21 changed files with 356 additions and 612 deletions.
44 changes: 23 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,16 @@ Filter Options:
-o=<module,...> Module names in origin. Specify multiple comma-separated module names.
-d=<module,...> Module names in destination. Specify multiple comma-separated module names.
-c=<configuration,...> Configurations used in dependencies. Specify multiple comma-separated configuration names.
--transitive Also include transitive modules.
-t, --transitive If set, also include transitive module dependencies. This applies only if a module filter is active.
Display Options:
Options controlling how to output the analysed data. Display options can not be combined.
--modules Shows all modules of the project applying to the specified filters.
--configurations Displays all configurations applying to the specified filters and sorted by frequency of occurrence.
--chart-mermaid Generate the Mermaid graph chart source for the dependencies fulfilling the filter criteria.
--mermaid-graph Generate the Mermaid graph chart source for the dependencies fulfilling the filter criteria.
--chart-mermaid Generate the Mermaid graph chart source for the dependencies fulfilling the filter criteria. (deprecated)
Options:
-h, --help Show this message and exit
Expand Down Expand Up @@ -87,59 +88,60 @@ cd dependency-graph-analyser/
4. Publish the Gradle tooling API plugin and its data model to Maven local (available in the
directory `~/.m2/repository/ch/addere/dga/`)

Get an overview of this project by running `dga .``:
Get an overview of this project by running `dga .`:

```
./app/build/install/dga/bin/dga .
Analyse project "dependency-graph-analyser"
5 modules
5 dependencies (1 unique configurations)
5 dependency configurations (1 unique dependency configurations)
```

### Create a Mermaid Chart

Use `dga . --chart-mermaid` to generate a Mermaid chart of this project:

```
./app/build/install/dga/bin/dga . --chart-mermaid
./app/build/install/dga/bin/dga . --mermaid-graph
Analyse project "dependency-graph-analyser"
6 modules
6 dependencies (2 unique configurations)
5 modules
5 dependency configurations (1 unique dependency configurations)
graph TD
vd2a57d(app) -->|api| ved7802(exporter)
vd2a57d(app) -->|api| v1bc49d(importer)
vd2a57d(app) -->|implementation| va74ad8(core)
vd2a57d(app) -->|implementation| v1bc49d(importer)
v80f88a(dependency-plugin) -->|implementation| v8ebf3d(dependency-model)
ved7802(exporter) -->|api| vf8b0b9(graph)
v1bc49d(importer) -->|api| v8ebf3d(dependency-model)
v1bc49d(importer) -->|api| vf8b0b9(graph)
v1bc49d(importer) -->|implementation| va74ad8(core)
v1bc49d(importer) -->|implementation| v8ebf3d(dependency-model)
```

#### Filter Modules

Use `dga . -o app,exporter --chart-mermaid` to generate a Mermaid chart of this project only
containing the modules `app` and `exporter` in the origin:
Use `dga . -o app --mermaid-graph` to generate a Mermaid graph chart of this project only
containing the dependencies originating from the module `app`:

```
./app/build/install/dga/bin/dga . -o app,exporter --chart-mermaid
./app/build/install/dga/bin/dga . -o app --mermaid-graph
Analyse project "dependency-graph-analyser"
5 modules
5 dependencies (1 unique configurations)
5 dependency configurations (1 unique dependency configurations)
Applying filter on data results in:
3 modules
2 dependency configurations (1 unique dependency configurations)
graph TD
vd2a57d(app) -->|implementation| va74ad8(core)
vd2a57d(app) -->|implementation| v1bc49d(importer)
v80f88a(dependency-plugin) -->|implementation| v8ebf3d(dependency-model)
v1bc49d(importer) -->|implementation| va74ad8(core)
v1bc49d(importer) -->|implementation| v8ebf3d(dependency-model)
```

- Use `--transitive` to also include transitive dependencies.

## How DGA works

See [HowItWorks.md](HOWITWORKS.md)
Expand Down
8 changes: 5 additions & 3 deletions app/src/main/kotlin/ch/addere/dga/app/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import ch.addere.dga.app.configuration.OutputOptions
import ch.addere.dga.app.domain.model.CommandConfig
import ch.addere.dga.app.domain.model.FilterConfig
import ch.addere.dga.app.domain.service.DependencyCommandHandler
import ch.addere.dga.app.infrastructure.factory.coreModule
import ch.addere.dga.app.infrastructure.factory.dgaModule
import ch.addere.dga.app.infrastructure.factory.importerModule
import ch.addere.dga.app.infrastructure.factory.userInputModule
Expand Down Expand Up @@ -70,7 +71,7 @@ private class Dga : CliktCommand(help = "Analyse the module dependency graph of
optionsFilter.originModules,
optionsFilter.destinationModules,
optionsFilter.configurations,
optionsFilter.transitiveModules
optionsFilter.includeTransitiveModules
)
val argument = CommandConfig(::echo, gradleProject, filterConfig, outputOption)
val command: DependencyCommandHandler = get { parametersOf(argument) }
Expand Down Expand Up @@ -109,9 +110,9 @@ private class OptionsFilter : OptionGroup(
.convert("configuration,...") { Configuration(it) }.split(",").default(emptyList())
.help("Configurations used in dependencies. Specify multiple comma-separated configuration names.")

val transitiveModules: Boolean by option("--transitive")
val includeTransitiveModules: Boolean by option("-t", "--transitive")
.flag()
.help("Also include transitive modules.")
.help("If set, also include transitive module dependencies. This applies only if a module filter is active.")
}

fun main(args: Array<String>) {
Expand All @@ -121,6 +122,7 @@ fun main(args: Array<String>) {
modules(userInputModule)
modules(dgaModule)
modules(importerModule)
modules(coreModule)
}

dga.parse(args)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
package ch.addere.dga.app.domain.service

import ch.addere.dga.app.domain.model.FilterConfig
import ch.addere.dga.core.application.service.DependencySearchService
import ch.addere.dga.core.domain.model.Configuration
import ch.addere.dga.core.domain.model.Dependency
import ch.addere.dga.core.domain.model.FilteredConfiguration
import ch.addere.dga.core.domain.model.FilteredModules
import ch.addere.dga.core.domain.service.ConfigurationService
import ch.addere.dga.core.domain.service.DependencyRelationService
import ch.addere.dga.core.domain.service.DependencyService
import ch.addere.dga.core.domain.service.ModuleService

class FilterService(
private val dependencyService: DependencyService,
private val moduleService: ModuleService,
private val configurationService: ConfigurationService,
private val dependencyRelationService: DependencyRelationService,
private val dependencySearchService: DependencySearchService,
) {


fun allDependencies(): Set<Dependency> {
return dependencyService.filteredDependencies(
FilteredModules(false, emptyList()),
Expand All @@ -26,41 +26,58 @@ class FilterService(
)
}


fun filter(filterConfig: FilterConfig): Set<Dependency> {
val inputModules = filterConfig.modules
val inputOrigin = filterConfig.originModules
val inputDestination = filterConfig.destinationModules
val inputConfigurations = filterConfig.configurations
val requestedModules = filterConfig.modules
val requestedOriginModules = filterConfig.originModules
val requestedDestinationModules = filterConfig.destinationModules
val requestedConfigurations = filterConfig.configurations

val filteredModules = inputModules.flatMap(moduleService::resolvePartialModuleName).toList()
val filteredOrigin = inputOrigin.flatMap(moduleService::resolvePartialModuleName).toList()
val filteredDestination =
inputDestination.flatMap(moduleService::resolvePartialModuleName).toList()
val filteredConfigurations =
inputConfigurations.flatMap(configurationService::resolvePartialConfigurationName)
val requestedCanonicalModules =
requestedModules.flatMap(moduleService::resolvePartialModuleName).toList()
val requestedCanonicalOriginModules =
requestedOriginModules.flatMap(moduleService::resolvePartialModuleName).toList()
val requestedCanonicalDestinationModules =
requestedDestinationModules.flatMap(moduleService::resolvePartialModuleName).toList()
val requestedCanonicalConfigurations: List<Configuration> =
requestedConfigurations.flatMap(configurationService::resolvePartialConfigurationName)
.toList()

var filteredDependencies: Set<Dependency> =
dependencyService.filteredDependencies(
FilteredModules(inputModules.isNotEmpty(), filteredModules),
FilteredModules(inputOrigin.isNotEmpty(), filteredOrigin),
FilteredModules(inputDestination.isNotEmpty(), filteredDestination),
FilteredConfiguration(inputConfigurations.isNotEmpty(), filteredConfigurations)
FilteredModules(requestedModules.isNotEmpty(), requestedCanonicalModules),
FilteredModules(
requestedOriginModules.isNotEmpty(),
requestedCanonicalOriginModules
),
FilteredModules(
requestedDestinationModules.isNotEmpty(),
requestedCanonicalDestinationModules
),
FilteredConfiguration(
requestedConfigurations.isNotEmpty(),
requestedCanonicalConfigurations
)
)

if (filterConfig.includeTransitiveDependencies) {
val configurationSet = filteredDependencies.map { it.configuration }.toSet()
filteredDependencies = filteredDependencies.flatMap { dependency ->
dependencyRelationService.allDependenciesOf(
dependency.origin,
configurationSet
) union
dependencyRelationService.allDependenciesOf(
dependency.destination,
configurationSet
)
// val transitiveDependencies: Set<Dependency> = filteredDependencies
// .flatMap { setOf(it.origin, it.destination) }
// .toSet()
// .flatMap {
// dependencyRelationService.allDependenciesOf(
// it,
// requestedCanonicalConfigurations
// )
// }
// .toSet()
val transitiveDependencies: Set<Dependency> = requestedCanonicalModules.flatMap {
dependencySearchService.findAllDependenciesUsedByModule(
it,
requestedCanonicalConfigurations
)
}.toSet()

filteredDependencies = filteredDependencies union transitiveDependencies
}

return filteredDependencies
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ch.addere.dga.app.infrastructure.factory

import ch.addere.dga.core.application.service.DependencySearchService
import ch.addere.dga.core.application.service.DependencySearchServiceImpl
import org.koin.core.module.dsl.bind
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module

val coreModule = module {
singleOf(::DependencySearchServiceImpl) { bind<DependencySearchService>() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ import ch.addere.dga.app.domain.service.printer.ConsolePrinter
import ch.addere.dga.app.domain.service.printer.DependencyPrinter
import ch.addere.dga.app.domain.service.printer.MermaidPrinter
import ch.addere.dga.app.domain.service.printer.ModulePrinter
import ch.addere.dga.core.domain.DependencyRepository
import ch.addere.dga.core.domain.DependencyRepositoryImpl
import ch.addere.dga.core.domain.model.graph.ModuleDependencyDag
import ch.addere.dga.core.domain.service.ConfigurationService
import ch.addere.dga.core.domain.service.ConfigurationServiceImpl
import ch.addere.dga.core.domain.service.DependencyRelationService
import ch.addere.dga.core.domain.service.DependencyRelationServiceImpl
import ch.addere.dga.core.domain.service.DependencyRepository
import ch.addere.dga.core.domain.service.DependencyService
import ch.addere.dga.core.domain.service.DependencyServiceImpl
import ch.addere.dga.core.domain.service.ModuleRepository
Expand All @@ -29,8 +28,7 @@ val dgaModule = module {
singleOf(::ConsolePrinter)
singleOf(::DependencyCommandHandler)
singleOf(::DependencyPrinter)
singleOf(::DependencyRelationServiceImpl) { bind<DependencyRelationService>() }
singleOf(::DependencyRepository)
singleOf(::DependencyRepositoryImpl) { bind<DependencyRepository>() }
singleOf(::DependencyServiceImpl) { bind<DependencyService>() }
singleOf(::MermaidPrinter)
singleOf(::ModuleDependencyDag)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ repositories {

dependencies {
testImplementation("com.willowtreeapps.assertk:assertk-jvm:0.26.1")
testImplementation("io.kotest:kotest-assertions-core:5.9.1")
testImplementation("io.kotest:kotest-runner-junit5:5.9.1")
testImplementation("io.mockk:mockk:1.13.12")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.0.0")
}

Expand All @@ -19,6 +22,10 @@ testing {
}
}

tasks.withType<Test>().configureEach {
useJUnitPlatform()
}

kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(21))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package ch.addere.dga.core.application.service

import ch.addere.dga.core.domain.model.Configuration
import ch.addere.dga.core.domain.model.Dependency
import ch.addere.dga.core.domain.model.Module

interface DependencySearchService {

/**
* Returns all dependencies that have modules requiring [module].
*/
fun findAllDependenciesDependingOnModule(
module: Module,
withConfigurations: Collection<Configuration>
): Set<Dependency>

/**
* Returns all dependencies which are required in order [module] can work.
*/
fun findAllDependenciesUsedByModule(
module: Module,
withConfigurations: Collection<Configuration>
): Set<Dependency>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package ch.addere.dga.core.application.service

import ch.addere.dga.core.domain.DependencyRepository
import ch.addere.dga.core.domain.model.Configuration
import ch.addere.dga.core.domain.model.Dependency
import ch.addere.dga.core.domain.model.Module

class DependencySearchServiceImpl(private val dependencyRepository: DependencyRepository) :
DependencySearchService {

override fun findAllDependenciesDependingOnModule(
module: Module,
withConfigurations: Collection<Configuration>
): Set<Dependency> {
return dependenciesWithDestination(module, withConfigurations)
}

override fun findAllDependenciesUsedByModule(
module: Module,
withConfigurations: Collection<Configuration>
): Set<Dependency> {
return dependenciesWithOrigin(module, withConfigurations)
}

private fun dependenciesWithDestination(
destination: Module,
withConfigurations: Collection<Configuration>
): Set<Dependency> {
val allDependenciesWithSameDestination: Set<Dependency> =
dependencyRepository.getAllDependencies()
.filter { dependency ->
dependency.destination == destination && withConfigurations.contains(
dependency.configuration
)
}
.toSet()

return if (allDependenciesWithSameDestination.isEmpty()) {
emptySet()
} else {
allDependenciesWithSameDestination
.flatMap { dependenciesWithDestination(it.origin, withConfigurations) }
.toSet() union allDependenciesWithSameDestination
}
}

private fun dependenciesWithOrigin(
origin: Module,
withConfigurations: Collection<Configuration>
): Set<Dependency> {
println("call with origin = $origin")
val allDependenciesWithSameOrigin: Set<Dependency> =
dependencyRepository.getAllDependencies()
.filter { dependency ->
dependency.origin == origin && withConfigurations.contains(
dependency.configuration
)
}
.toSet()
println(allDependenciesWithSameOrigin.joinToString(", "))

return if (allDependenciesWithSameOrigin.isEmpty()) {
emptySet()
} else {
allDependenciesWithSameOrigin
.flatMap { dependenciesWithOrigin(it.destination, withConfigurations) }
.toSet() union allDependenciesWithSameOrigin
}
}
}
Loading

0 comments on commit 991d54b

Please sign in to comment.