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 2017 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.
*/
//file:noinspection GrMethodMayBeStatic
package org.gradle.integtests.fixtures.resolve
import com.google.common.base.Joiner
import groovy.transform.Canonical
import org.gradle.api.artifacts.ModuleVersionIdentifier
import org.gradle.api.artifacts.result.ComponentSelectionCause
import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
import org.gradle.internal.classloader.ClasspathUtil
import org.gradle.test.fixtures.file.TestFile
import org.junit.ComparisonFailure
/**
* A test fixture that injects a "checkDeps" 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 static final START_MARKER = "// RESOLVE_TEST_FIXTURE_START"
private static final END_MARKER = "// RESOLVE_TEST_FIXTURE_END"
final TestFile buildFile
String config
private String defaultConfig = "default"
private boolean buildArtifacts = true
ResolveTestFixture(TestFile buildFile, String config = "runtimeClasspath") {
this.config = config
this.buildFile = buildFile
}
ResolveTestFixture withoutBuildingArtifacts() {
buildArtifacts = false
return this
}
ResolveTestFixture expectDefaultConfiguration(String config) {
defaultConfig = config
return this
}
/**
* Creates a 'checkDeps' task that resolves the given configuration.
*/
void prepare(String configToCheck) {
prepare {
config(configToCheck, "checkDeps")
}
}
/**
* Injects the appropriate stuff into the build script. By default, creates a 'checkDeps' task that resolves the configuration provided in the constructor.
*/
void prepare(@DelegatesTo(CheckTaskBuilder) Closure closure = {}) {
def builder = new CheckTaskBuilder()
closure.delegate = builder
closure.run()
if (builder.configs.isEmpty()) {
builder.config(config, "checkDeps")
}
def existingScript = buildFile.exists() ? buildFile.text : ""
def start = existingScript.indexOf(START_MARKER)
def end = existingScript.indexOf(END_MARKER) + END_MARKER.length()
if (start >= 0) {
existingScript = existingScript.substring(0, start) + existingScript.substring(end, existingScript.length())
}
def buildScriptBlock = """
buildscript {
dependencies.classpath files("${ClasspathUtil.getClasspathForClass(GenerateGraphTask).toURI()}")
}
"""
String generatedContent = ""
builder.configs.forEach { config, taskName ->
generatedContent += """
allprojects {
tasks.register("${taskName}", ${GenerateGraphTask.name}) {
it.outputFile = rootProject.file("\${rootProject.buildDir}/last-graph.txt")
it.rootComponent = configurations.${config}.incoming.resolutionResult.rootComponent
it.files.from(configurations.${config})
it.incomingFiles = configurations.${config}.incoming.files
it.incomingArtifacts = configurations.${config}.incoming.artifacts
it.artifactViewFiles = configurations.${config}.incoming.artifactView { }.files
it.artifactViewArtifacts = configurations.${config}.incoming.artifactView { }.artifacts
it.lenientArtifactViewFiles = configurations.${config}.incoming.artifactView { it.lenient = true }.files
it.lenientArtifactViewArtifacts = configurations.${config}.incoming.artifactView { it.lenient = true }.artifacts
it.buildArtifacts = ${buildArtifacts}
${registerInputs(config)}
}
}
"""
}
buildFile.text = """
$buildScriptBlock
$existingScript
$START_MARKER
$generatedContent
$END_MARKER
"""
}
/**
* We only want to add give configuration as a task input if we're building artifacts.
*
* Some tests, such as {@link CompositeBuildDependencyCycleIntegrationTest}, have a dependency cycle between two projects, and if we add this input, then
* the build will fail because of a circular task dependency. These tests are set up to NOT build artifacts, and thus avoid this problem.
*
* @param config the configuration to add as an input
*/
@SuppressWarnings('GroovyDocCheck')
private String registerInputs(Object config) {
return buildArtifacts ? "it.inputs.files configurations." + config : ""
}
def getResultFile() {
buildFile.parentFile.file("build/last-graph.txt")
}
/**
* Verifies the result of executing the {@link GenerateGraphTask} injected by {@link #prepare()}.
*
* That task writes information about the graph, files and artifacts to a flat file accessible here via {@link #getResultFile()}.
* This method reads that file (the actual result) and compares it to the expected result - the graph info provided to this fixture via
* the DSL supplied as an argument.
*
* @param closure a closure containing DSL that configures the expected graph
*/
void expectGraph(@DelegatesTo(GraphBuilder) Closure closure) {
def graph = new GraphBuilder()
closure.resolveStrategy = Closure.DELEGATE_ONLY
closure.delegate = graph
closure.call()
def root = graph.root
if (root == null) {
throw new IllegalArgumentException("No root node defined")
}
def configDetailsFile = getResultFile()
def configDetails = configDetailsFile.text.readLines()
def actualRoot = findLines(configDetails, 'root').first()
def expectedRoot = "[${root.type}][id:${root.id}][mv:${root.moduleVersionId}][reason:${root.reason}]".toString()
assert actualRoot.startsWith(expectedRoot)
def actualComponents = findLines(configDetails, 'component')
def expectedComponents = graph.nodes.collect { baseNode ->
def variants = baseNode.variants
new ParsedNode(type: baseNode.type,
id: baseNode.id,
module: baseNode.moduleVersionId,
reasons: baseNode.allReasons,
variants: variants,
ignoreReasons: baseNode.ignoreReasons,
ignoreReasonPrefixes: baseNode.ignoreReasonPrefixes)
}
compareNodes("components in graph", parseNodes(actualComponents), expectedComponents)
def actualEdges = findLines(configDetails, 'dependency')
def expectedEdges = graph.edges.collect { "${it.constraint ? '[constraint]' : ''}[from:${it.from.id}][${it.requested}->${it.selected.id}]" }
compare("edges in graph", actualEdges, expectedEdges)
def expectedFiles = root.files + graph.artifactNodes.collect { it.fileName }
def expectedArtifacts = graph.artifactNodes.collect { "${it.fileName} (${it.componentId})" } + graph.files as List
def actualArtifacts = findLines(configDetails, 'incoming-artifact-artifact')
compare("incoming.artifacts.artifacts", actualArtifacts, expectedArtifacts)
if (buildArtifacts) {
def actualFiles = findLines(configDetails, 'file-file')
compare("files", actualFiles, expectedFiles)
actualFiles = findLines(configDetails, 'file-filtered')
compare("files (filtered)", actualFiles, expectedFiles)
actualFiles = findLines(configDetails, 'incoming-file')
compare("incoming.files", actualFiles, expectedFiles)
actualArtifacts = findLines(configDetails, 'incoming-artifact')
compare("incoming.artifacts", actualArtifacts, expectedArtifacts)
actualArtifacts = findLines(configDetails, 'incoming-resolved-artifact')
compare("incoming.resolvedArtifacts", actualArtifacts, expectedArtifacts)
actualFiles = findLines(configDetails, 'incoming-artifact-file')
compare("incoming.artifacts.artifactFiles", actualFiles, expectedFiles)
actualFiles = findLines(configDetails, 'artifact-view-file')
compare("artifactView.files", actualFiles, expectedFiles)
actualArtifacts = findLines(configDetails, 'artifact-view-artifact')
compare("artifactView.artifacts", actualArtifacts, expectedArtifacts)
actualFiles = findLines(configDetails, 'artifact-view-file-file')
compare("artifactView.files.files", actualFiles, expectedFiles)
actualArtifacts = findLines(configDetails, 'artifact-view-artifact-artifact')
compare("artifactView.artifacts.artifacts", actualArtifacts, expectedArtifacts)
actualArtifacts = findLines(configDetails, 'artifact-view-resolved-artifact')
compare("artifactView.resolvedArtifacts", actualArtifacts, expectedArtifacts)
actualFiles = findLines(configDetails, 'artifact-view-artifact-file')
compare("artifactView.artifacts.artifactFiles", actualFiles, expectedFiles)
actualFiles = findLines(configDetails, 'lenient-artifact-view-file')
compare("artifactView.files (lenient)", actualFiles, expectedFiles)
actualArtifacts = findLines(configDetails, 'lenient-artifact-view-artifact')
compare("artifactView.artifacts (lenient)", actualArtifacts, expectedArtifacts)
actualFiles = findLines(configDetails, 'lenient-artifact-view-file-file')
compare("artifactView.files.files (lenient)", actualFiles, expectedFiles)
actualArtifacts = findLines(configDetails, 'lenient-artifact-view-artifact-artifact')
compare("artifactView.artifacts.artifacts (lenient)", actualArtifacts, expectedArtifacts)
actualArtifacts = findLines(configDetails, 'lenient-artifact-view-resolved-artifact')
compare("artifactView.resolvedArtifacts (lenient)", actualArtifacts, expectedArtifacts)
actualFiles = findLines(configDetails, 'lenient-artifact-view-artifact-file')
compare("artifactView.artifacts.artifactFiles (lenient)", actualFiles, expectedFiles)
}
}
List findLines(List lines, String prefix) {
return lines.findAll { it.startsWith(prefix + ":") }.collect { it.substring(prefix.length() + 1) }
}
List parseNodes(List nodes) {
nodes.collect { parseNode(it) }
}
ParsedNode parseNode(String line) {
int start = 1
// we look for ][ instead of just ], because of that one test that checks that we can have random characters in id
// see IvyDynamicRevisionRemoteResolveIntegrationTest. uses latest version from version range with punctuation characters
int idx = line.indexOf('][')
if (idx < 0) {
throw new IllegalArgumentException("Missing type in '$line'")
}
String type = line.substring(start, idx)
start = idx + 5
idx = line.indexOf('][', start)
if (idx < 0) {
throw new IllegalArgumentException("Missing id in '$line'")
}
String id = line.substring(start, idx) // [id:
start = idx + 5
idx = line.indexOf('][', start)
if (idx < 0) {
throw new IllegalArgumentException("Missing module in '$line'")
}
String module = line.substring(start, idx) // [mv:
start = idx + 9
idx = line.indexOf(']', start) // [reason:
if (idx < 0) {
throw new IllegalArgumentException("Missing reasons in '$line'")
}
List reasons = line.substring(start, idx).split('!!') as List
Set variants = []
start = idx + 15
while (start < line.length()) {
idx = line.indexOf(' attributes:', start) // [variant name:
String variant = line.substring(start, idx)
start = idx + 12
idx = line.indexOf('@@', start)
if (idx < 0) {
idx = line.indexOf(']', start) // attributes:
}
Map attributes = line.substring(start, idx)
.split(',') // attributes are separated by commas
.findAll() // only keep non empty entries (thank you, split!)
.collectEntries { it.split('=') as List }
start = idx + 15 // '@@'
variants << new Variant(name: variant, attributes: attributes)
}
new ParsedNode(type: type, id: id, module: module, reasons: reasons, variants: variants)
}
static class ParsedNode {
String type
String id
String module
Set reasons
boolean ignoreRequested
Set ignoreReasons
Set ignoreReasonPrefixes
Set variants = []
boolean diff(ParsedNode actual, StringBuilder sb) {
List errors = []
if (type != actual.type) {
errors << "Expected type '$type' but was: $actual.type"
}
if (id != actual.id) {
errors << "Expected ID '$id' but was: $actual.id"
}
if (this.module != actual.module) {
errors << "Expected module '${this.module}' but was: $actual.module"
}
def actualReasons = actual.reasons.findAll {
if (it == "requested" && ignoreRequested) {
false
} else if (ignoreReasons.contains(it)) {
false
} else if (ignoreReasonPrefixes.any { prefix -> it.startsWith(prefix) }) {
false
} else {
true
}
}.toSet()
if (actualReasons != reasons) {
errors << "Expected reasons ${reasons} but was: ${actual.reasons}"
}
this.variants.each { variant ->
def actualVariant = actual.variants.find { it.name == variant.name }
if (!actualVariant) {
errors << "Expected variant name $variant, but wasn't found in: $actual.variants.name"
} else {
if (variant.attributes != actualVariant.attributes) {
errors << "On variant $variant.name, expected attributes $variant.attributes, but was: $actualVariant.attributes"
}
}
}
if (errors) {
sb.append("On component $id:\n")
errors.each {
sb.append(" - ").append(it).append("\n")
}
return true
}
return false
}
String toString() {
"id: $id, module: ${this.module}, reasons: ${reasons}${this.variants}"
}
}
static void compareNodes(String compType, Collection actual, Collection expected) {
def actualSorted = actual.sort { it.id }
def expectedSorted = expected.sort { it.id }
StringBuilder errors = new StringBuilder()
StringBuilder matched = new StringBuilder()
expectedSorted.each { node ->
def actualNode = actualSorted.find { it.id == node.id }
if (!actualNode) {
errors.append("Expected to find node ${node.id} but wasn't present in result\n")
} else if (!node.diff(actualNode, errors)) {
matched.append(" - $node\n")
}
}
actualSorted.each { node ->
if (!expectedSorted.find { it.id == node.id }) {
errors.append("Found unexpected node $node")
}
}
if (errors.length() > 0) {
throw new AssertionError("Result contains unexpected $compType\n${errors}\nMatched $compType:\n${matched}")
}
}
void compare(String compType, Collection actual, Collection expected) {
def actualSorted = new ArrayList(actual).sort()
def expectedSorted = new ArrayList(expected).sort()
boolean equals = actual.size() == expectedSorted.size()
if (equals) {
for (int i = 0; i < actual.size(); i++) {
equals &= actualSorted.get(i).startsWith(expectedSorted.get(i))
}
}
if (!equals) {
def actualFormatted = Joiner.on("\n").join(actualSorted)
def expectedFormatted = Joiner.on("\n").join(expectedSorted)
throw new ComparisonFailure("Result contains unexpected $compType", expectedFormatted, actualFormatted);
}
}
static class GraphBuilder {
private final Map nodes = [:]
private NodeBuilder root
final Set virtualConfigurations = []
Collection getNodes() {
return nodes.values()
}
Collection getNodesWithoutRoot() {
def nodes = new HashSet<>()
visitDeps(this.root.deps, nodes, new HashSet<>())
return nodes
}
void virtualConfiguration(String id) {
virtualConfigurations << id
}
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(this.root, result)
return result.collect { it.artifacts }.flatten()
}
Set getFiles() {
Set result = new LinkedHashSet()
result.add(this.root)
visitNodes(this.root, result)
return result.collect { node -> node.files }.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(this.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.
*
* @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 (this.root != null) {
throw new IllegalStateException("Root node is already defined")
}
this.root = projectNode(projectPath, moduleVersion)
cl.resolveStrategy = Closure.DELEGATE_ONLY
cl.delegate = this.root
cl.call()
return this.root
}
private NodeBuilder projectNode(String projectIdentityPath, String moduleVersion) {
return node("project:$projectIdentityPath", "project $projectIdentityPath", moduleVersion)
}
private NodeBuilder moduleNode(String moduleVersionId) {
def parts = moduleVersionId.split(':')
// the supplied moduleVersionId may contain additional attributes
assert parts.length >= 3
def group = parts[0]
def module = parts[1]
def version = parts[2]
def actualMVI = "${group}:${module}:${version}"
return node("module:${actualMVI},${group}:${module}", actualMVI, moduleVersionId)
}
NodeBuilder module(Map attrs) {
def group = attrs.group
def module = attrs.module
def version = attrs.version
def moduleVersionId = "$group:$module:$version"
return node("module:$moduleVersionId,$group:$module", moduleVersionId, moduleVersionId, attrs)
}
NodeBuilder node(String type, 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(':')
if (parts.length == 3) {
attrs = [group: parts[0], module: parts[1], version: parts[2]]
} else {
assert parts.length == 4
attrs = [group: parts[0], module: parts[1], version: parts[2], configuration: parts[3]]
id = "${attrs.group}:${attrs.module}:${attrs.version}"
moduleVersionId = id
}
}
return node(type, id, moduleVersionId, attrs)
}
NodeBuilder node(String type, String id, String moduleVersion, Map attrs) {
def node = nodes[moduleVersion]
if (!node) {
node = new NodeBuilder(type, id, moduleVersion, attrs, this)
nodes[moduleVersion] = node
}
if (attrs.configuration) {
node.configuration(attrs.configuration)
}
return node
}
}
static class EdgeBuilder {
final String requested
final NodeBuilder from
NodeBuilder selected
boolean constraint
EdgeBuilder(NodeBuilder from, String requested, NodeBuilder selected) {
this.from = from
this.requested = requested
this.selected = selected
}
EdgeBuilder selects(Map selectedModule) {
selected = from.graph.module(selectedModule)
return this
}
}
static class ExpectedArtifact {
String componentId
String group
String module
String moduleVersion
String version
String classifier
String type
String extension
String name
String fileName
String legacyName
ModuleVersionIdentifier getModuleVersionId() {
String effectiveVersion = moduleVersion ? moduleVersion : 'unspecified'
return DefaultModuleVersionIdentifier.newId(this.group, this.module, effectiveVersion)
}
String getLegacyArtifactName() {
def effectiveName = legacyName != null ? legacyName : nameComponent
def effectiveType = type != null ? type : 'jar'
def effectiveExt = extension != null ? extension : effectiveType
return "${effectiveName}:${classifier}:${effectiveExt}:${effectiveType}"
}
String getFileName() {
if (fileName) {
return fileName
}
return "${nameComponent}${versionComponent}${classifierComponent}${extensionComponent}"
}
String getNameComponent() {
return name ?: this.module
}
private String getVersionComponent() {
if (version == "") {
return ""
} else if (version != null) {
return "-${version}"
} else if (moduleVersion == "") {
return ""
} else if (moduleVersion != null) {
return "-${moduleVersion}"
} else {
return ""
}
}
private String getExtensionComponent() {
if (extension == "") {
return ""
} else if (extension != null) {
return ".$extension"
} else if (type == "") {
return ""
} else if (type != null) {
return ".$type"
} else {
return ".jar"
}
}
private String getClassifierComponent() {
if (classifier) {
return "-$classifier"
} else {
return ""
}
}
}
static class CheckTaskBuilder {
final Map configs = [:]
void config(String config) {
configs.put(config, "check${config.capitalize()}")
}
void config(String config, String taskName) {
configs.put(config, taskName)
}
}
@Canonical
static class Variant {
String name
Map attributes
String toString() {
"variant $name, variant attributes $attributes"
}
}
static class NodeBuilder {
final List deps = []
private final GraphBuilder graph
final String type
final String id
final String moduleVersionId
final String group
final String module
final String version
final Set configurations = []
Set firstLevelConfigurations
private boolean implicitArtifact = true
final List files = []
private final Set artifacts = new LinkedHashSet<>()
private final Set reasons = new TreeSet()
private boolean ignoreRequested
private final Set ignoreReasons = new HashSet<>()
private final Set ignoreReasonPrefixes = new HashSet<>()
Set variants = []
boolean checkVariant
NodeBuilder(String type, 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
this.type = type
if (attrs.variantName) {
variant(attrs.variantName, attrs.variantAttributes)
}
reasons.add('requested')
}
Set getArtifacts() {
return artifacts.empty && implicitArtifact ? [new ExpectedArtifact(componentId: id, group: this.group, module: this.module, moduleVersion: this.version)] : artifacts
}
String getReason() {
allReasons.join('!!')
}
Set getAllReasons() {
if (this == graph.root) {
reasons.remove('requested')
reasons.add('root')
}
if (ignoreRequested) {
reasons.remove('requested')
}
return reasons
}
private NodeBuilder addNode(NodeBuilder node) {
deps << new EdgeBuilder(this, node.id, node)
return node
}
/**
* Defines a dependency on the given external module.
*/
NodeBuilder module(String moduleVersionId) {
return addNode(graph.moduleNode(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 = addNode(graph.moduleNode(moduleVersionId))
applyTo(node, cl)
return node
}
/**
* Defines a dependency on a unique snapshot module.
*/
NodeBuilder snapshot(String moduleVersionId, String timestamp, String requestedVersion = null) {
def id = moduleVersionId + ":" + timestamp
def parts = moduleVersionId.split(':')
assert parts.length == 3
def (group, name, version) = parts
def attrs = [group: group, module: name, version: version]
def node = graph.node("module:$moduleVersionId,$group:$name", id, moduleVersionId, attrs)
deps << new EdgeBuilder(this, requestedVersion ? "${group}:${name}:${requestedVersion}" : moduleVersionId, node)
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 projectIdentityPath, String moduleVersion, @DelegatesTo(NodeBuilder) Closure cl = {}) {
def node = addNode(graph.projectNode(projectIdentityPath, moduleVersion))
applyTo(node, cl)
return node
}
/**
* Defines a link between nodes created through a dependency constraint.
*/
NodeBuilder constraint(String requested, String selectedModuleVersionId = requested, @DelegatesTo(NodeBuilder) Closure cl = {}) {
def node = graph.moduleNode(selectedModuleVersionId)
def edge = new EdgeBuilder(this, requested, node)
edge.constraint = true
deps << edge
applyTo(node, cl)
return node
}
/**
* Adds a constraint that selects the given project.
*/
NodeBuilder constraint(String requested, String selectedProjectIdentityPath, String selectedModuleVersionId, @DelegatesTo(NodeBuilder) Closure cl = {}) {
def node = graph.projectNode(selectedProjectIdentityPath, selectedModuleVersionId)
def edge = new EdgeBuilder(this, requested, node)
edge.constraint = true
deps << edge
applyTo(node, cl)
return node
}
/**
* Defines a dependency from the current node to the given module. 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 = graph.moduleNode(selectedModuleVersionId)
deps << new EdgeBuilder(this, requested, node)
applyTo(node, cl)
return node
}
/**
* Defines a dependency from the current node to the given project. The closure delegates to a {@link NodeBuilder} instance that represents the target node.
*/
NodeBuilder edge(String requested, String selectedProjectIdentityPath, String selectedModuleVersionId, @DelegatesTo(NodeBuilder) Closure cl = {}) {
def node = graph.projectNode(selectedProjectIdentityPath, selectedModuleVersionId)
deps << new EdgeBuilder(this, requested, node)
applyTo(node, cl)
return node
}
private static void applyTo(NodeBuilder node, Closure cl) {
cl.resolveStrategy = Closure.DELEGATE_ONLY
cl.delegate = node
cl.call()
}
/**
* 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(
componentId: id,
group: this.group,
module: this.module,
moduleVersion: this.version,
version: attributes.version,
name: attributes.name,
classifier: attributes.classifier,
type: attributes.type,
extension: attributes.extension, // defaults to the type, empty string means no extension
fileName: attributes.fileName, // overrides the expected file name, defaults to (name)-(version)-(classifier).(type)
legacyName: attributes.legacyName
)
artifacts << artifact
return this
}
/**
* Marks that this node was selected due to conflict resolution.
*/
NodeBuilder byConflictResolution() {
reasons << 'conflict resolution'
this
}
/**
* Marks that this node was selected due to conflict resolution.
*/
NodeBuilder byConflictResolution(String message) {
reasons << "${ComponentSelectionCause.CONFLICT_RESOLUTION.defaultReason}: $message".toString()
this
}
/**
* Marks that this node was selected by a rule.
*/
NodeBuilder selectedByRule() {
reasons << 'selected by rule'
this
}
/**
* Marks that this node was selected by a rule.
*/
NodeBuilder selectedByRule(String message) {
reasons << "${ComponentSelectionCause.SELECTED_BY_RULE.defaultReason}: $message".toString()
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
}
/**
* Marks that this node was selected by the given reason
*/
NodeBuilder byReason(String reason) {
reasons << reason
this
}
NodeBuilder notRequested() {
reasons.remove('requested')
this
}
NodeBuilder maybeRequested() {
ignoreRequested = true
ignoreReasons.add('requested')
this
}
NodeBuilder maybeByConflictResolution() {
ignoreReasonPrefixes.add("conflict resolution")
this
}
NodeBuilder maybeByConstraint() {
ignoreReasonPrefixes.add("constraint")
this
}
NodeBuilder maybeSelectedByRule() {
ignoreReasonPrefixes.add("selected by rule")
this
}
NodeBuilder maybeByReason(String reason) {
ignoreReasons.add(reason)
this
}
NodeBuilder byConstraint(String reason = null) {
if (reason == null) {
reasons << ComponentSelectionCause.CONSTRAINT.defaultReason
} else {
reasons << "${ComponentSelectionCause.CONSTRAINT.defaultReason}: $reason".toString()
}
this
}
NodeBuilder byAncestor() {
byReason(ComponentSelectionCause.BY_ANCESTOR.defaultReason)
}
NodeBuilder byConsistentResolution(String source) {
byConstraint("version resolved in configuration ':$source' by consistent resolution")
}
/**
* Marks that this node was selected by the given reason
*/
NodeBuilder byReasons(List reasons) {
this.reasons.addAll(reasons)
this
}
NodeBuilder variant(String name, Map attributes = [:]) {
configuration(name)
checkVariant = true
String variantName = name
Map stringAttributes = attributes.collectEntries { entry ->
[entry.key, entry.value instanceof Closure ? entry.value.call() : entry.value.toString()]
}
this.variants << new Variant(name: variantName, attributes: stringAttributes)
this
}
void setConfiguration(String configuration) {
configurations.clear()
configurations.add(configuration)
}
void configuration(String configuration) {
configurations << configuration
}
void setFirstLevelConfigurations(Collection firstLevelConfigurations) {
this.firstLevelConfigurations = firstLevelConfigurations as Set
}
Set getFirstLevelConfigurations() {
firstLevelConfigurations == null ? configurations : firstLevelConfigurations
}
}
/**
* Enables Maven derived variants, as if the Java plugin was applied
*/
void addDefaultVariantDerivationStrategy() {
buildFile << """
allprojects { dependencies.components.variantDerivationStrategy = new org.gradle.internal.component.external.model.JavaEcosystemVariantDerivationStrategy() }
"""
}
void addJavaEcosystem() {
buildFile << """
allprojects {
apply plugin: 'org.gradle.jvm-ecosystem'
}
"""
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy