Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ class ProjectGuardPlugin : Plugin<Project> {
group = "other"
description = "Generates a JSON containing the dependencies of this module."
projectPath.set(project.path)
dependencies.set(graphBuilder.buildFromProject(project))
dependencyGraph.set(graphBuilder.buildFromProject(project))
outputFile.set(
project.layout.buildDirectory.file(dependenciesFilePath)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@ package com.rubensousa.projectguard.plugin.internal

internal object DependencyConfiguration {

const val COMPILE = "compileClasspath"
const val TEST = "testCompileClasspath"
const val TEST_FIXTURE = "testFixturesCompileClasspath"

private val supportedConfigurations = mutableSetOf(
"androidTestUtil",
"compileClasspath",
"testCompileClasspath",
"testFixturesCompileClasspath",
COMPILE,
TEST,
TEST_FIXTURE,
)

fun isConfigurationSupported(configurationId: String): Boolean {
Expand All @@ -38,4 +42,8 @@ internal object DependencyConfiguration {
&& !lowerCaseConfiguration.contains("metadata")
}

fun isTestConfiguration(configurationId: String): Boolean {
return configurationId.lowercase().contains("test")
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,85 +18,145 @@ package com.rubensousa.projectguard.plugin.internal

import java.io.Serializable

internal class DependencyGraph(
val configurationId: String,
val nodes: MutableMap<String, MutableSet<String>> = mutableMapOf(),
) : Serializable {
internal class DependencyGraph : Serializable {

private val configurations = mutableMapOf<String, Configuration>()
private val libraries = mutableSetOf<String>()

fun addInternalDependency(module: String, dependency: String) {
addDependency(module, dependency)
}
fun getConfigurations() = configurations.values.toList()

fun addExternalDependency(module: String, dependency: String) {
addDependency(module, dependency)
libraries.add(dependency)
fun addInternalDependency(
module: String,
dependency: String,
configurationId: String = DependencyConfiguration.COMPILE,
) {
addDependency(
module = module,
dependency = dependency,
configurationId = configurationId
)
}

private fun addDependency(module: String, dependency: String) {
val existingDependencies = nodes.getOrPut(module) { mutableSetOf() }
existingDependencies.add(dependency)
fun addExternalDependency(
module: String,
dependency: String,
configurationId: String = DependencyConfiguration.COMPILE,
) {
addDependency(
module = module,
dependency = dependency,
configurationId = configurationId
)
libraries.add(dependency)
}

fun isExternalLibrary(dependency: String): Boolean {
return libraries.contains(dependency)
}

fun getDependencies(module: String): Set<String> {
return nodes[module] ?: emptySet()
}

fun getAllDependencies(module: String): List<Dependency> {
/**
* Until https://github.com/rubensousa/ProjectGuard/issues/3 is resolved,
* exclude transitive dependency traversals for test configurations
*/
if (configurationId.contains("test")) {
return getDependencies(module).map {
DirectDependency(it)
}
}
fun getDependencies(module: String): List<Dependency> {
val visitedDependencies = mutableSetOf<String>()
val paths = mutableMapOf<String, Dependency>()
val stack = ArrayDeque<TraversalState>()
stack.addAll(getDependencies(module).map { dependency ->
TraversalState(dependency, emptyList())
})
while (stack.isNotEmpty()) {
val currentModule = stack.removeFirst()
val currentDependency = currentModule.dependency
val queue = ArrayDeque<TraversalState>()
configurations.values.forEach { configuration ->
configuration.getDependencies(module).forEach { dependency ->
queue.addFirst(
TraversalState(
configurationId = configuration.id,
dependency = dependency,
path = emptyList()
)
)
}
}
while (queue.isNotEmpty()) {
val currentTraversal = queue.removeFirst()
val currentDependency = currentTraversal.dependency
if (visitedDependencies.contains(currentDependency)) {
continue
}
paths[currentDependency] = if (currentModule.path.isEmpty()) {
paths[currentDependency] = if (currentTraversal.path.isEmpty()) {
DirectDependency(currentDependency)
} else {
TransitiveDependency(
currentDependency,
currentModule.path + currentDependency
currentTraversal.path + currentDependency
)
}
visitedDependencies.add(currentDependency)
getDependencies(currentDependency).forEach { nextDependency ->
stack.addFirst(
TraversalState(
nextDependency,
currentModule.path + currentDependency
)
)
configurations.values.forEach { configuration ->
// Search only for non-test configurations as they're not considered transitive at this point
if (!DependencyConfiguration.isTestConfiguration(configuration.id)) {
configuration.getDependencies(currentDependency).forEach { nextDependency ->
queue.addFirst(
TraversalState(
configurationId = configuration.id,
dependency = nextDependency,
path = currentTraversal.path + currentDependency
)
)
}
}
}
}
return paths.values.toList()
return paths.values.sortedBy { it.id }
}

override fun toString(): String {
return "DependencyGraph(configurationId='$configurationId', nodes=$nodes)"
private fun addDependency(
module: String,
dependency: String,
configurationId: String,
) {
val configuration = configurations.getOrPut(configurationId) {
Configuration(configurationId)
}
configuration.add(module = module, dependency = dependency)
}

private data class TraversalState(
val configurationId: String,
val dependency: String,
val path: List<String>,
)

class Configuration(val id: String) : Serializable {

private val nodes = mutableMapOf<String, MutableSet<String>>()

fun add(module: String, dependency: String) {
val existingDependencies = nodes.getOrPut(module) { mutableSetOf() }
existingDependencies.add(dependency)
}

fun getDependencies(module: String): Set<String> {
return nodes[module] ?: emptySet()
}

override fun equals(other: Any?): Boolean {
return other is Configuration
&& other.id == this.id
&& other.nodes == this.nodes
}

override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + nodes.hashCode()
return result
}

}

override fun equals(other: Any?): Boolean {
return other is DependencyGraph
&& other.libraries == this.libraries
&& other.configurations == this.configurations
}

override fun hashCode(): Int {
var result = configurations.hashCode()
result = 31 * result + libraries.hashCode()
return result
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -23,38 +23,35 @@ import org.gradle.api.artifacts.ProjectDependency

internal class DependencyGraphBuilder {

fun buildFromDump(projectDump: DependencyGraphDump): List<DependencyGraph> {
val graphs = mutableMapOf<String, DependencyGraph>()
fun buildFromDump(projectDump: DependencyGraphDump): DependencyGraph {
val graph = DependencyGraph()
projectDump.modules.forEach { report ->
report.configurations.forEach { configuration ->
val graph = graphs.getOrPut(configuration.id) {
DependencyGraph(configurationId = configuration.id)
}
configuration.dependencies.forEach { dependency ->
if (dependency.isLibrary) {
graph.addExternalDependency(
configurationId = configuration.id,
module = report.module,
dependency = dependency.id,
)
} else {
graph.addInternalDependency(
configurationId = configuration.id,
module = report.module,
dependency = dependency.id,
)
}
}
}
}
return graphs.values.toList()
return graph
}

fun buildFromProject(project: Project): List<DependencyGraph> {
return project.configurations
fun buildFromProject(project: Project): DependencyGraph {
val graph = DependencyGraph()
project.configurations
.filter { config -> config.isCanBeResolved && DependencyConfiguration.isConfigurationSupported(config.name) }
.map { config ->
val graph = DependencyGraph(
configurationId = config.name,
)
.forEach { config ->
val moduleId = project.path
config.incoming.dependencies
.forEach { dependency ->
Expand All @@ -63,7 +60,8 @@ internal class DependencyGraphBuilder {
if (dependency.path != moduleId) {
graph.addInternalDependency(
module = moduleId,
dependency = dependency.path
dependency = dependency.path,
configurationId = config.name
)
}
}
Expand All @@ -72,11 +70,12 @@ internal class DependencyGraphBuilder {
graph.addExternalDependency(
module = moduleId,
dependency = "${dependency.group}:${dependency.name}",
configurationId = config.name
)
}
}
}
graph
}
return graph
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,22 @@

package com.rubensousa.projectguard.plugin.internal

internal class DependencyRestrictionFinder {
internal class DependencyRestrictionFinder(
private val graph: DependencyGraph,
) {

fun find(
moduleId: String,
graph: DependencyGraph,
spec: ProjectGuardSpec,
): List<DependencyRestriction> {
return find(
moduleId = moduleId,
graphs = listOf(graph),
spec = spec
)
}

fun find(
moduleId: String,
graphs: List<DependencyGraph>,
spec: ProjectGuardSpec,
): List<DependencyRestriction> {
val restrictions = mutableListOf<DependencyRestriction>()
graphs.forEach { graph ->
graph.getAllDependencies(moduleId).forEach { dependency ->
fillRestrictions(
restrictions = restrictions,
moduleId = moduleId,
dependency = dependency,
spec = spec,
)
}
graph.getDependencies(moduleId).forEach { dependency ->
fillRestrictions(
restrictions = restrictions,
moduleId = moduleId,
dependency = dependency,
spec = spec,
)
}
// We might find multiple restrictions to the same dependency, just filter them out
return filterRestrictions(moduleId, restrictions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,10 @@ internal class CheckExecutor(
val htmlReportGenerator = HtmlReportGenerator()
htmlReportGenerator.generate(report, reportDir)
if (fatalMatches.isNotEmpty()) {
throw VerificationException("Found ${fatalMatches.size} fatal match(es). See report at file:///$reportFilePath")
throw VerificationException(
"${fatalMatches.take(10).joinToString("\n\n") { it.getDescription() }} \n " +
"Found ${fatalMatches.size} fatal match(es). See full report at file:///$reportFilePath"
)
} else {
println("No fatal matches found. See report at file:///$reportFilePath")
}
Expand Down
Loading