How to add a dependency constraint to a grade project from a custom gradle plugin? - gradle

How to add a dependency constraint to a project from a custom gradle plugin?
I want to create a gradle plugin that will constrain transitive dependencies in the project the plugin is applied to. Ideally, I want this plugin to follow the following rules:
Only try and apply a constraint to a project if the it exists in the project. E.g. only apply a constraint on dependency X if dependency X is pulled into the project.
Apply the constraints automatically on application of the plugin, I don't want to create/run an extra task or anything like that.
(Bonus) Don't apply the constraints to primary dependencies, only transitive dependencies.
Here's what I have so far.
class ConstraintPlugin implements Plugin<Project> {
private Map<String, String> constraintMap = [
"jackson-core": "2.13.2",
"logback-classic": "2.3.9"
]
#Override
void apply(Project project) {
project.configurations.each {config ->
config.resolvedConfiguration.resolvedArtifacts.each {dep ->
if (constraintMap.containsKey(dep.name)) {
ModuleVersionIdentifier id = dep.moduleVersion.id
String constraintVersion = constraintMap.get(id.name)
DependencyConstraint dc = new DefaultDependencyConstraint(id.group, id.name, constraintVersion)
config.dependencyConstraints.add(dc)
}
}
}
I'm using a map instantiated within the constraint plugin class with the names of the dependencies I want to constrain along with the versions to constrain to in the project this plugin is applied to. Here I want to constrain the jackson-core and logback-classic dependencies.
My first thought was to run through each configuration and see if that dependency name is present within that configuration. If so, create a new DependencyConstraint and add it to the configuration. However this is not working when I apply the plugin to another project.
Also note I am not disregarding primary dependencies quite yet, this feature is more like a "nice to have" at this point, I haven't figured out how to only look at transitive dependencies within a given configuration.
I think my issue lies with how I am applying the new DependencyConstraint programmatically but I can't seem to find a way to add a constraint that works. The only other way I've tried is project.dependencies.constraints.create(dc) which also doesn't work.

Related

Can not add gradle DuplicatesStrategy INHERIT

In my project with gradle 7.5.1 there was a error for the following task
processResources {
with copySpec {
from "src/main/resources/"
}
}
Here is the error message. I have application.properties, application-dev.properties, application-test.properties files in my resources folder.
Entry application-dev.properties is a duplicate but no duplicate handling strategy has been set.
So I was trying to add DuplicatesStrategy like bellow
allprojects {
tasks.withType(Copy).all {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
}
After adding this the project runs but now I need another strategy INHERIT. If I replace DuplicatesStrategy.INCLUDE with DuplicatesStrategy.INHERIT I am getting the same error again
Entry application-dev.properties is a duplicate but no duplicate handling strategy has been set
I checked(see implementation) the DuplicatesStrategy enum and INHERIT is a valid value.
package org.gradle.api.file;
public enum DuplicatesStrategy {
INCLUDE,
EXCLUDE,
WARN,
FAIL,
INHERIT;
private DuplicatesStrategy() {
}
}
How do I add duplicate strategy properly?
As I could not find the problem I decided to go with the strategy INCLUDE which is same. Duplicate Strategy has the following documentation
EXCLUDE Do not allow duplicates by ignoring subsequent items to be created at the same path.
FAIL Throw a DuplicateFileCopyingException when subsequent items are to be created at the same path.
INCLUDE Do not attempt to prevent duplicates.
INHERIT The default strategy, which is to inherit the strategy from the parent copy spec, if any, or INCLUDE if the copy spec has no parent.
WARN Do not attempt to prevent duplicates, but log a warning message when multiple items are to be created at the same path.
As my copySpec does not have any parent, I expected INCLUDE will be automatically used. But as it is not happening I decided to go for the INCLUDE instead of INHERIT.

How to add dependencies (and repositories) dynamically in a custom Gradle plugin task using Kotlin dsl

The title basically says it all.
I am trying to create a plugin that can be configured and depending on the configuration, the task provided by the plugin adds compileOnly or implementation deppendencies to the project.
The resources on writing custom Gradle plugins are abhorrent (especially in Kotlin instead of Groovy) and I can't figure out how to do this myself.
This is where I'm at with my custom plugin code:
class SpigotVersioner: Plugin<Project> {
override fun apply(project: Project) {
println("Latest spigot version: ${WebScraper.getLatestVersion()}")
val extension = project.extensions.create("spigot", SpigotExtension::class.java)
extension.apiVersion.set("latest")
extension.bukkitVersion.set("latest")
project.task("compileSpigotAPI") {
it.group = "spigot"
it.description = "Adds the spigot api implementation to the project."
it.doLast {
val apiVersion = extension.apiVersion.get()
val dependency = deriveDependencyStr(apiVersion)
//DOESN'T WORK!
project.dependencies {
compileOnly(dependency)
}
//WHAT ARE THESE PARAMETERS SUPPOSED TO BE?
project.dependencies.add(configurationName: String, dependencyNotation: Any)
}
}
}
}
This is supposed to mimic something like
dependencies {
compileOnly 'my.derived.dependency.str:apiVersion:xy'
}
only the dependency being added is supposed to be configurable via an extension.
If possible, I'd like to extend this to also add the appropriate repository as well but the dependency issue is more important.
Bit of an old question now, but I too struggled with this so hopefully this answer is of use to someone.
//WHAT ARE THESE PARAMETERS SUPPOSED TO BE?
project.dependencies.add(configurationName: String, dependencyNotation: Any)
The configurationName is the configuration that you wish to add the dependency to e.g. implementation, testImplementation or api etc.
The dependencyNotation can be any of the following:
String Notation: Simply a String written using Gradle dependency notation e.g. com.mycompany:my-awesome-dependency:1.2.3. There are ways to also specify things like strictness when using these 'simple' declarations, this is somewhat documented here.
Map Notation: This is where you pass a Map<String, String> containing key-value pairs representing the dependency. The documentation on this is either non existent or elusive, but for example: "group": "com.mycompany", "name": "my-awesome-dependency", "version": "1.2.3".
Dependency Interface: This is where you pass in an object that implements one of the Dependency interfaces that the Gradle API provides. The most basic being org.gradle.api.artifacts.Dependency. The main issue with this method is again that the documentation is either elusive or non-existent. I cannot see a way to have Gradle create one of these objects (or see any pre implemented classes in the public API). You could always just implement the interface but there are some methods on there like contentEquals and copy() which seem overkill to implement.
My recommendation if it suits your use case would be to use the first option above.

How can I decompose a Gradle build into multiple files?

I'm writing a Gradle plugin which contains a collection of multiple chunks of independent configuration which will be applied to any project applying the plugin.
I want to keep the fragments very separate to discourage other people from adding unrelated logic to an existing place, and to improve visibility of what the plugin is actually configuring.
So I thought I could do this:
class CommonChecksPlugin implements Plugin<Project> {
#Override
void apply(Project project) {
project.apply plugin: 'base'
def resolveFile = { filename ->
new URL(buildscript.sourceURI.toURL(), filename)
}
project.apply from: resolveFile('configuration1.gradle')
project.apply from: resolveFile('configuration2.gradle')
}
}
Example configurationN.gradle:
task 'checkSomething', type: CheckSomething
Problem is, this Java class CheckSomething cannot be resolved.
Is there a sensible way to do this other than just giving up and moving all the sub-scripts in as full Groovy classes? I'm reluctant to move them to classes, because I want to apply the same checks to the plugin project itself, and it seems difficult to apply them if they require compilation.
The applied script has a different classloader to the plugin, it doesn't inherit the buildscript classloader so therefore the task isn't on the classpath. You can do the following:
project.ext.CheckSomething = CheckSomething
project.apply from: resolveFile('configuration1.gradle')
See https://discuss.gradle.org/t/buildscript-configurations-classpath-not-available-in-apply-from-scripts/1391

Find all transitive artifacts with used classes

I am using jqassistant to create a transitive dependency list of artifacts. My aim is to base this on the type dependencies because I want to find architecture deficits by comparing the Maven transitive dependency list with the one given by type dependencies (all artifacts of which at least one type is used in any transitive form).
Unfortunately, the following query only gave back the original artifact basis-vs-bo. The query was applied to a database that I created by scanning a Maven repository.
What did I miss?
MATCH (basis:Artifact{name:"basis-vs-bo",version:"4.0.0"})
WHERE NOT( HAS (basis.classifier))
WITH basis
MATCH (basis) -[:CONTAINS]-> (t:Type) -[:DEPENDS_ON*]-> (s:Type) <-[:CONTAINS]- (a:Artifact)
RETURN DISTINCT a
The Maven repository scanner treats all artifacts in isolation, i.e. dependencies between them are not evaluated while scanning classes.
This is the same behavior as scanning several JAR files from a directory, so dependencies between classes (method invocations, inheritance hierarchies, etc.) are only resolved if the involved classes are located within the same artifact.
As the dependencies between the artifacts are known in the case of the Maven repository it should be possible to apply queries which resolve these dependencies similar to the "classpath:*" concepts but this needs to be evaluated.
Update
The following queries should help getting class based dependencies (DEPENDS_ON) between classes in different artifacts:
Build DEPENDS_ON relations between artifacts based on declared dependencies in POMs:
MATCH
(r:Repository)-[:CONTAINS_POM]->(p1:Pom)-[:DESCRIBES]->(a1:Java:Archive),
(r:Repository)-[:CONTAINS_POM]->(p2:Pom)-[:DESCRIBES]->(a2:Java:Archive),
(p1)-[:DECLARES_DEPENDENCY]->(a2)
MERGE
(a1)-[:DEPENDS_ON]->(a2)
RETURN
a1.fqn, collect(a2.fqn)
Resolve types required by an artifact to those contained in an artifact which is a dependency, a relation RESOLVES_TO is created:
MATCH
(a1:Artifact)-[:DEPENDS_ON]->(a2:Artifact),
(a1)-[:REQUIRES]->(requiredType:Type),
(a2)-[:CONTAINS]->(containedType:Type)
WHERE
requiredType.fqn = containedType.fqn
MERGE
(requiredType)-[:RESOLVES_TO]->(containedType)
RETURN
a1.fqn, a2.fqn, collect(requiredType.fqn)
Propagate DEPENDS_ON relations between types based on the RESOLVES_TO relation:
MATCH
(a1:Artifact)-[:REQUIRES]->(requiredType:Type),
(a1)-[:CONTAINS]->(type:Type)-[:DEPENDS_ON]->(requiredType),
(a2:Artifact)-[:CONTAINS]->(resolvedType:Type),
(requiredType)-[:RESOLVES_TO]->(resolvedType:Type)
MERGE
(type)-[dependsOn:DEPENDS_ON]->(resolvedType)
SET
dependsOn.resolved=true
RETURN
a1.fqn, type.fqn, a2.fqn, collect(resolvedType.fqn)
Does this work for you?

Maven plugin: Passing all available properties to an Interpolator

In a Maven plugin I would like to use all properties usually available to the pom inside an Interpolator through a ValueSource.
I can access properties defined inside the pom, system properties and environment variable properties using one of MavenProject#getProperties(), MavenSession#getProperties() or MavenProject#getModel()#getProperties. This works fine with code like that:
import org.codehaus.plexus.interpolation.PropertiesBasedValueSource;
ValueSource valueSource = new PropertiesBasedValueSource( project.getModel()
.getProperties() )
However, the standard project properties like project.version or project.build.directory are not part of any of those Properties instances. How can I get all of them at once in a properties instance?
My plugin runs in the generate-sources phase using Maven 2.2.1 or 3.0.3.
Answer to my own question :-):
I want to pass all properties available normally in filters. While I could set each and every property available as a parameter to my plugin, that solution would require to modify the plugin source code and recompile it whenever new properties get added tomaven. Also, the source code of the mojo would grow to a high number of lines.
The project.* properties are not actually properties available through the Project#getProperties method when used in filters. Instead they are mapped directly to the getter methods of the project object itself. The class doing that is PrefixedObjectValueSource in the same interpolation package as PropertiesBasedValueSource. It will map each getter method (bean property) to a property, prefixing it with a defined string, and recursively exposing the bean properties of each property.
import org.codehaus.plexus.interpolation.PrefixedObjectValueSource;
import org.codehaus.plexus.interpolation.PropertiesBasedValueSource;
ValueSource allProperties = new PropertiesBasedValueSource( getProject().getModel()
.getProperties() );
ValueSource projectProperties =
new PrefixedObjectValueSource( "project.", getProject() ) );

Resources