org.gradle.integtests.fixtures.resolve.ResolveTestFixture.groovy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.9.1 API redistribution.
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gradle.integtests.fixtures.resolve
import com.google.common.base.Joiner
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ModuleVersionIdentifier
import org.gradle.api.artifacts.ResolvedDependency
import org.gradle.api.artifacts.result.ComponentSelectionReason
import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
import org.gradle.api.tasks.TaskAction
import org.gradle.internal.classloader.ClasspathUtil
import org.gradle.test.fixtures.file.TestFile
import org.junit.Assert
/**
* A test fixture that injects a task into a build that resolves a dependency configuration and does some validation of the resulting graph, to
* ensure that the old and new dependency graphs plus the artifacts and files are as expected and well-formed.
*/
class ResolveTestFixture {
private final TestFile buildFile
private final String config
private boolean buildArtifacts = true
ResolveTestFixture(TestFile buildFile, String config = "compile") {
this.config = config
this.buildFile = buildFile
}
ResolveTestFixture withoutBuildingArtifacts() {
buildArtifacts = false;
return this;
}
/**
* Injects the appropriate stuff into the build script.
*/
void prepare() {
buildFile << """
buildscript {
dependencies.classpath files("${ClasspathUtil.getClasspathForClass(GenerateGraphTask).toURI()}")
}
allprojects {
tasks.addPlaceholderAction("checkDeps", ${GenerateGraphTask.name}) {
it.outputFile = rootProject.file("\${rootProject.buildDir}/${config}.txt")
it.configuration = configurations.$config
it.buildArtifacts = ${buildArtifacts}
}
}
"""
}
/**
* Verifies the result of executing the task injected by {@link #prepare()}. The closure delegates to a {@link GraphBuilder} instance.
*/
void expectGraph(@DelegatesTo(GraphBuilder) Closure closure) {
def graph = new GraphBuilder()
closure.resolveStrategy = Closure.DELEGATE_ONLY
closure.delegate = graph
closure.call()
if (graph.root == null) {
throw new IllegalArgumentException("No root node defined")
}
def configDetailsFile = buildFile.parentFile.file("build/${config}.txt")
def configDetails = configDetailsFile.text.readLines()
def actualRoot = findLines(configDetails, 'root').first()
def expectedRoot = "[id:${graph.root.id}][mv:${graph.root.moduleVersionId}][reason:${graph.root.reason}]".toString()
assert actualRoot == expectedRoot
def expectedFirstLevel = graph.root.deps.collect { "[${it.selected.moduleVersionId}:${it.selected.configuration}]" } as Set
def actualFirstLevel = findLines(configDetails, 'first-level')
compare("first level dependencies", actualFirstLevel, expectedFirstLevel)
actualFirstLevel = findLines(configDetails, 'first-level-filtered')
compare("filtered first level dependencies", actualFirstLevel, expectedFirstLevel)
actualFirstLevel = findLines(configDetails, 'lenient-first-level')
compare("lenient first level dependencies", actualFirstLevel, expectedFirstLevel)
actualFirstLevel = findLines(configDetails, 'lenient-first-level-filtered')
compare("lenient filtered first level dependencies", actualFirstLevel, expectedFirstLevel)
def actualConfigurations = findLines(configDetails, 'configuration') as Set
def expectedConfigurations = graph.nodesWithoutRoot.collect { "[${it.moduleVersionId}]" }
compare("configurations in graph", actualConfigurations, expectedConfigurations)
def actualComponents = findLines(configDetails, 'component')
def expectedComponents = graph.nodes.collect { "[id:${it.id}][mv:${it.moduleVersionId}][reason:${it.reason}]" }
compare("components in graph", actualComponents, expectedComponents)
def actualEdges = findLines(configDetails, 'dependency')
def expectedEdges = graph.edges.collect { "[from:${it.from.id}][${it.requested}->${it.selected.id}]" }
compare("edges in graph", actualEdges, expectedEdges)
def expectedArtifacts = graph.artifactNodes.collect { "[${it.moduleVersionId}][${it.artifactName}]" }
def actualArtifacts = findLines(configDetails, 'artifact')
compare("artifacts", actualArtifacts, expectedArtifacts)
actualArtifacts = findLines(configDetails, 'lenient-artifact')
compare("lenient artifacts", actualArtifacts, expectedArtifacts)
actualArtifacts = findLines(configDetails, 'filtered-lenient-artifact')
compare("filtered lenient artifacts", actualArtifacts, expectedArtifacts)
if (buildArtifacts) {
def expectedFiles = graph.root.files + graph.artifactNodes.collect { it.fileName }
def actualFiles = findLines(configDetails, 'file')
compare("files", actualFiles, expectedFiles)
actualFiles = findLines(configDetails, 'file-incoming')
compare("incoming.files", actualFiles, expectedFiles)
actualFiles = findLines(configDetails, 'file-artifact-incoming')
compare("incoming.artifacts", actualFiles, expectedFiles)
actualFiles = findLines(configDetails, 'file-filtered')
compare("filtered files", actualFiles, expectedFiles)
actualFiles = findLines(configDetails, 'file-collection-filtered')
compare("filtered FileCollection", actualFiles, expectedFiles)
actualFiles = findLines(configDetails, 'file-resolved-config')
compare("resolved configuration files", actualFiles, expectedFiles)
actualFiles = findLines(configDetails, 'file-resolved-config-filtered')
compare("resolved configuration filtered files", actualFiles, expectedFiles)
actualFiles = findLines(configDetails, 'file-lenient-config')
compare("lenient configuration files", actualFiles, expectedFiles)
actualFiles = findLines(configDetails, 'file-lenient-config-filtered')
compare("lenient configuration filtered files", actualFiles, expectedFiles)
// The following do not include file dependencies
expectedFiles = graph.artifactNodes.collect { it.fileName }
actualFiles = findLines(configDetails, 'file-artifact-resolved-config')
compare("resolved configuration artifact files", actualFiles, expectedFiles)
actualFiles = findLines(configDetails, 'file-artifact-lenient-config')
compare("lenient configuration artifact files", actualFiles, expectedFiles)
actualFiles = findLines(configDetails, 'file-artifact-lenient-config-filtered')
compare("filtered lenient configuration artifact files", actualFiles, expectedFiles)
}
}
List findLines(List lines, String prefix) {
return lines.findAll { it.startsWith(prefix + ":") }.collect { it.substring(prefix.length() + 1) }
}
void compare(String compType, Collection actual, Collection expected) {
def actualFormatted = Joiner.on("\n").join(new ArrayList(actual).sort())
def expectedFormatted = Joiner.on("\n").join(new ArrayList(expected).sort())
Assert.assertEquals("Result contains unexpected $compType", expectedFormatted, actualFormatted)
}
static class GraphBuilder {
private final Map nodes = new LinkedHashMap<>()
private NodeBuilder root
Collection getNodes() {
return nodes.values()
}
Collection getNodesWithoutRoot() {
def nodes = new HashSet<>()
visitDeps(root.deps, nodes, new HashSet<>())
return nodes
}
private void visitDeps(List edges, Set nodes, Set seen) {
for (EdgeBuilder edge : edges) {
def selected = edge.selected
if (seen.add(selected)) {
nodes.add(selected)
visitDeps(selected.deps, nodes, seen)
}
}
}
Set getArtifactNodes() {
Set result = new LinkedHashSet()
visitNodes(root, result)
return result.collect { it.artifacts }.flatten()
}
private void visitNodes(NodeBuilder node, Set result) {
Set nodesToVisit = []
for (EdgeBuilder edge: node.deps) {
def targetNode = edge.selected
if (result.add(targetNode)) {
nodesToVisit << targetNode
}
}
for(NodeBuilder child: nodesToVisit) {
visitNodes(child, result)
}
}
private getEdges() {
Set result = new LinkedHashSet<>()
Set seen = []
visitEdges(root, seen, result)
return result
}
private visitEdges(NodeBuilder node, Set seenNodes, Set edges) {
for (EdgeBuilder edge: node.deps) {
edges.add(edge)
if (seenNodes.add(edge.selected)) {
visitEdges(edge.selected, seenNodes, edges)
}
}
}
/**
* Defines the root node of the graph. The closure delegates to a {@link NodeBuilder} instance that represents the root node.
*/
def root(String value, @DelegatesTo(NodeBuilder) Closure cl) {
if (root != null) {
throw new IllegalStateException("Root node is already defined")
}
root = node(value, value)
cl.resolveStrategy = Closure.DELEGATE_ONLY
cl.delegate = root
cl.call()
return root
}
/**
* Defines the root node of the graph. The closure delegates to a {@link NodeBuilder} instance that represents the root node.
*
* @param projectPath The path of the project to which the graph belongs.
* @param moduleVersion The module version for this project.
*/
def root(String projectPath, String moduleVersion, @DelegatesTo(NodeBuilder) Closure cl) {
if (root != null) {
throw new IllegalStateException("Root node is already defined")
}
root = node("project $projectPath", moduleVersion)
cl.resolveStrategy = Closure.DELEGATE_ONLY
cl.delegate = root
cl.call()
return root
}
def node(Map attrs) {
def id = "${attrs.group}:${attrs.module}:${attrs.version}"
return node(id, id, attrs)
}
def node(String id, String moduleVersionId) {
def attrs
if (moduleVersionId.matches(':\\w+:')) {
def parts = moduleVersionId.split(':')
attrs = [group: null, module: parts[1], version:null]
moduleVersionId = ":${attrs.module}:unspecified"
} else if (moduleVersionId.matches('\\w+:\\w+:')) {
def parts = moduleVersionId.split(':')
attrs = [group: parts[0], module: parts[1], version: null]
moduleVersionId = "${attrs.group}:${attrs.module}:unspecified"
} else {
def parts = moduleVersionId.split(':')
assert parts.length == 3
attrs = [group: parts[0], module: parts[1], version: parts[2]]
}
return node(id, moduleVersionId, attrs)
}
def node(String id, String moduleVersion, Map attrs) {
def node = nodes[moduleVersion]
if (!node) {
node = new NodeBuilder(id, moduleVersion, attrs, this)
nodes[moduleVersion] = node
}
return node
}
}
static class EdgeBuilder {
final String requested
final NodeBuilder from
NodeBuilder selected
EdgeBuilder(NodeBuilder from, String requested, NodeBuilder selected) {
this.from = from
this.requested = requested
this.selected = selected
}
EdgeBuilder selects(Map selectedModule) {
selected = from.graph.node(selectedModule)
return this
}
}
static class ExpectedArtifact {
String group
String module
String version
String classifier
String type
String name
String getType() {
return type ?: 'jar'
}
String getName() {
return name ?: module
}
ModuleVersionIdentifier getModuleVersionId() {
return DefaultModuleVersionIdentifier.newId(group, module, version ?: 'unspecified')
}
String getArtifactName() {
return "${getName()}${classifier ? '-' + classifier : ''}.${getType()}"
}
String getFileName() {
return "${getName()}${version ? '-' + version : ''}${classifier ? '-' + classifier : ''}.${getType()}"
}
}
static class NodeBuilder {
final List deps = []
private final GraphBuilder graph
final String id
final String moduleVersionId
final String group
final String module
final String version
String configuration = "default"
private boolean implicitArtifact = true
final List files = []
private final Set artifacts = new LinkedHashSet<>()
private final Set reasons = new TreeSet()
NodeBuilder(String id, String moduleVersionId, Map attrs, GraphBuilder graph) {
this.graph = graph
this.group = attrs.group
this.module = attrs.module
this.version = attrs.version
this.moduleVersionId = moduleVersionId
this.id = id
}
Set getArtifacts() {
return artifacts.empty && implicitArtifact ? [new ExpectedArtifact(group: group, module: module, version: version)] : artifacts
}
String getReason() {
reasons.empty ? (this == graph.root ? 'root' : 'requested') : reasons.join(',')
}
private NodeBuilder addNode(String id, String moduleVersionId = id) {
def node = graph.node(id, moduleVersionId)
deps << new EdgeBuilder(this, node.id, node)
return node
}
/**
* Defines a dependency on the given external module.
*/
NodeBuilder module(String moduleVersionId) {
return addNode(moduleVersionId)
}
/**
* Defines a dependency on the given external module. The closure delegates to a {@link NodeBuilder} instance that represents the target node.
*/
NodeBuilder module(String moduleVersionId, @DelegatesTo(NodeBuilder) Closure cl) {
def node = module(moduleVersionId)
cl.resolveStrategy = Closure.DELEGATE_ONLY
cl.delegate = node
cl.call()
return node
}
/**
* Defines a dependency on the given project. The closure delegates to a {@link NodeBuilder} instance that represents the target node.
*/
NodeBuilder project(String path, String value, @DelegatesTo(NodeBuilder) Closure cl) {
def node = addNode("project $path", value)
cl.resolveStrategy = Closure.DELEGATE_ONLY
cl.delegate = node
cl.call()
return node
}
/**
* Defines a dependency from the current node to the given node.
*/
NodeBuilder edge(String requested, String selectedModuleVersionId) {
def node = graph.node(selectedModuleVersionId, selectedModuleVersionId)
deps << new EdgeBuilder(this, requested, node)
return node
}
/**
* Defines a dependency from the current node to the given node. The closure delegates to a {@link NodeBuilder} instance that represents the target node.
*/
NodeBuilder edge(String requested, String selectedModuleVersionId, @DelegatesTo(NodeBuilder) Closure cl) {
def node = edge(requested, selectedModuleVersionId)
cl.resolveStrategy = Closure.DELEGATE_ONLY
cl.delegate = node
cl.call()
return node
}
/**
* Defines a dependency from the current node to the given node. The closure delegates to a {@link NodeBuilder} instance that represents the target node.
*/
NodeBuilder edge(String requested, String id, String selectedModuleVersionId, @DelegatesTo(NodeBuilder) Closure cl) {
def node = graph.node(id, selectedModuleVersionId)
deps << new EdgeBuilder(this, requested, node)
cl.resolveStrategy = Closure.DELEGATE_ONLY
cl.delegate = node
cl.call()
return node
}
/**
* Defines a dependency of the current node.
*/
EdgeBuilder dependency(Map requested) {
def edge = new EdgeBuilder(this, "${requested.group}:${requested.module}:${requested.version}", null)
deps << edge
return edge
}
/**
* Specifies that this node has no artifacts associated with it.
*/
NodeBuilder noArtifacts() {
implicitArtifact = false
return this
}
/**
* Specifies an artifact for this node. A default is assumed when none specified
*/
NodeBuilder artifact(Map attributes) {
def artifact = new ExpectedArtifact(group: group, module: module, version: version, name: attributes.name, classifier: attributes.classifier, type: attributes.type)
artifacts << artifact
return this
}
/**
* Marks that this node was selected due to conflict resolution.
*/
NodeBuilder byConflictResolution() {
reasons << 'conflict'
this
}
/**
* Marks that this node was selected by a rule.
*/
NodeBuilder selectedByRule() {
reasons << 'selectedByRule'
this
}
/**
* Marks that this node has a forced vers.
*/
NodeBuilder forced() {
reasons << 'forced'
this
}
/**
* Marks that this node was substituted in a composite.
*/
NodeBuilder compositeSubstitute() {
reasons << 'composite build substitution'
this
}
}
}
class GenerateGraphTask extends DefaultTask {
File outputFile
Configuration configuration
boolean buildArtifacts
@TaskAction
def generateOutput() {
outputFile.parentFile.mkdirs()
outputFile.withPrintWriter { writer ->
configuration.resolvedConfiguration.firstLevelModuleDependencies.each {
writer.println("first-level:[${it.moduleGroup}:${it.moduleName}:${it.moduleVersion}:${it.configuration}]")
}
configuration.resolvedConfiguration.getFirstLevelModuleDependencies { true }.each {
writer.println("first-level-filtered:[${it.moduleGroup}:${it.moduleName}:${it.moduleVersion}:${it.configuration}]")
}
configuration.resolvedConfiguration.lenientConfiguration.firstLevelModuleDependencies.each {
writer.println("lenient-first-level:[${it.moduleGroup}:${it.moduleName}:${it.moduleVersion}:${it.configuration}]")
}
configuration.resolvedConfiguration.lenientConfiguration.getFirstLevelModuleDependencies { true }.each {
writer.println("lenient-first-level-filtered:[${it.moduleGroup}:${it.moduleName}:${it.moduleVersion}:${it.configuration}]")
}
visitNodes(configuration.resolvedConfiguration.firstLevelModuleDependencies, writer, new HashSet<>())
def root = configuration.incoming.resolutionResult.root
writer.println("root:${formatComponent(root)}")
configuration.incoming.resolutionResult.allComponents.each {
writer.println("component:${formatComponent(it)}")
}
configuration.incoming.resolutionResult.allDependencies.each {
writer.println("dependency:[from:${it.from.id}][${it.requested}->${it.selected.id}]")
}
if (buildArtifacts) {
configuration.files.each {
writer.println("file:${it.name}")
}
configuration.incoming.files.each {
writer.println("file-incoming:${it.name}")
}
configuration.incoming.artifacts.artifacts.each {
writer.println("file-artifact-incoming:${it.file.name}")
}
configuration.files { true }.each {
writer.println("file-filtered:${it.name}")
}
configuration.fileCollection { true }.each {
writer.println("file-collection-filtered:${it.name}")
}
configuration.resolvedConfiguration.files.each {
writer.println("file-resolved-config:${it.name}")
}
configuration.resolvedConfiguration.getFiles { true }.each {
writer.println("file-resolved-config-filtered:${it.name}")
}
configuration.resolvedConfiguration.resolvedArtifacts.each {
writer.println("file-artifact-resolved-config:${it.file.name}")
}
configuration.resolvedConfiguration.lenientConfiguration.files.each {
writer.println("file-lenient-config:${it.name}")
}
configuration.resolvedConfiguration.lenientConfiguration.getFiles { true }.each {
writer.println("file-lenient-config-filtered:${it.name}")
}
configuration.resolvedConfiguration.lenientConfiguration.artifacts.each {
writer.println("file-artifact-lenient-config:${it.file.name}")
}
configuration.resolvedConfiguration.lenientConfiguration.getArtifacts { true }.each {
writer.println("file-artifact-lenient-config-filtered:${it.file.name}")
}
}
configuration.resolvedConfiguration.resolvedArtifacts.each {
writer.println("artifact:[${it.moduleVersion.id}][${it.name}${it.classifier ? "-" + it.classifier : ""}.${it.extension}]")
}
configuration.resolvedConfiguration.lenientConfiguration.artifacts.each {
writer.println("lenient-artifact:[${it.moduleVersion.id}][${it.name}${it.classifier ? "-" + it.classifier : ""}.${it.extension}]")
}
configuration.resolvedConfiguration.lenientConfiguration.getArtifacts { true }.each {
writer.println("filtered-lenient-artifact:[${it.moduleVersion.id}][${it.name}${it.classifier ? "-" + it.classifier : ""}.${it.extension}]")
}
}
}
def visitNodes(Collection nodes, PrintWriter writer, Set visited) {
for (ResolvedDependency node : nodes) {
if (!visited.add(node)) {
continue
}
writer.println("configuration:[${node.moduleGroup}:${node.moduleName}:${node.moduleVersion}]")
visitNodes(node.children, writer, visited)
}
}
def formatComponent(ResolvedComponentResult result) {
return "[id:${result.id}][mv:${result.moduleVersion}][reason:${formatReason(result.selectionReason)}]"
}
def formatReason(ComponentSelectionReason reason) {
def reasons = []
if (reason.conflictResolution) {
reasons << "conflict"
}
if (reason.forced) {
reasons << "forced"
}
if (reason.selectedByRule) {
reasons << "selectedByRule"
}
return reasons.empty ? reason.description : reasons.join(',')
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy