Gradle cannot change dependencies of configuration - gradle

"refresh all gradle projects" in Intellij Idea gives me following error:
Error:(15, 0) Cannot change dependencies of configuration ':util:compile' after it has been included in dependency resolution.
The problem doesn't occur when I move second if statement to build.gradle of submodule project. While I found workaround I would like to understand why original solution is not correct. The line making trouble is:
"Class-Path": (configurations.compile.collect {"$dependencyDir/$it.name"}.join(" "))
Project structure:
mcv-gradle-example
|__ submodule (gradle project)
|__ util (gradle project)
|__ build.gradle
build.gradle:
allprojects {
apply plugin: 'java'
}
subprojects {
apply plugin: 'scala'
repositories {
mavenCentral()
}
def isSpecialProject = project.name in ["submodule"]
dependencies {
compile group: 'org.scala-lang', name: 'scala-library', version: '2.12.1'
if (isSpecialProject) {
compile project(':util')
}
}
if (isSpecialProject) {
def dependencyDir = "/deps"
task setupJarManifest() {
jar.manifest.attributes(
"Class-Path": (configurations.compile.collect {"$dependencyDir/$it.name"}.join(" ")))
}
jar.dependsOn setupJarManifest
}
}
Update:
I found out that embracing jar.manifest... with afterEvaluate allows having this task in main build.gradle. However I'm still not sure what was the gradle's problem.
Update 2:
It seems moving setting manifest.attributes to the execution phase solves he problem. My final solution is:
if (isSpecialProject) {
def dependencyDir = "deps"
jar {
doFirst {
manifest.attributes(
"Class-Path": (configurations.compile.collect {"$dependencyDir/$it.name"}.join(" "))
)
}
}
}
My guess is:
When submodule project uses configurations.compile in configuration phase, it's all dependencies are resolved. Then the subprojects closure is evaluated for util project. Since util is dependency of submodule it can't have new dependency added as one moment ago we used it's all dependencies. It's reasonable but still I don't know how usage of configurations.compile "marks" util's dependencies as closed.
Explanation
After some more research I finally know what happened:
Subproject submodule is evaluated first. It puts scala and subproject util into its dependencies.
Then gradle evaluates configurations.compile.collect which internally calls iterator method calling DefaultConfiguration#getFiles. To find what files are specified for this configurations it resolves graph and artifacts (locking it's dependencies modification abilities).
Then subprojects closure is evaluated for subproject util. It is trying to put scala in its dependencies but is locked because of point 2 so it throws an error.
Moving collect to afterEvaluation or doLast executes it after correct resolution of graph and artifacts. It can be called as many times as you want in execution phase.

Related

gradle custom task class makes itself uncompilable

I'm trying to write a custom Gradle task class and use it in another subproject. I'm having problems tying the build together.
In this example, I have named the subprojects "a" (for application) and "p" (for plugin, although I'm not using a plugin object but just providing a task class to the build script).
settings.gradle
include 'p'
include 'a'
p/build.gradle
apply plugin: 'java'
apply plugin: 'groovy'
apply plugin: 'maven-publish'
group 'test'
version '1.0'
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
}
}
}
dependencies {
implementation gradleApi()
implementation localGroovy()
}
p/src/main/groovy/p/MyTask.groovy
package p
import org.gradle.api.DefaultTask;
import org.gradle.api.tasks.TaskAction;
class MyTask extends DefaultTask {
#TaskAction
void run() {
System.out.println('yay!');
}
}
a/build.gradle
buildscript {
repositories {
mavenLocal()
}
dependencies {
classpath 'test:p:1.0'
}
}
group = 'test'
version = '1.0'
apply plugin: 'java'
task myTask(type: p.MyTask) {
}
The "plugin" is built by running, inside the p folder:
../gradlew clean build publishToMavenLocal
Inside the a folder:
../gradlew myTask
prints "yay!"
However, while developing, bugs happen. When I simulate a bug in MyTask:
MyTask() {
throw new RuntimeException("an error");
}
and build the plugin (in folder p):
../gradlew clean build publishToMavenLocal
it fails as expected.
Now I "fix" the bug by removing the broken constructor again, and rebuild in folder p:
../gradlew clean build publishToMavenLocal
but this command fails with the same error.
As far as I understand, the reason is that:
the broken plugin is in my local maven repo
trying to build the plugin detects the settings.gradle in the parent folder
gradle tries to configure all projects referenced from settings.gradle
this loads the broken plugin
the build fails
To verify, I comment out the include line for a in settings.gradle, and it works again. Reverting settings.gradle, and it still works, because now the "fixed" plugin is in my maven repo, and rebuilding the plugin will just overwrite it again with the working version.
The bottom line is that bugs in my custom task class (or custom plugin, or any other buildscript code) have the potential to make themself un-compilable, with the workaround being to edit or temporarily rename settings.gradle. The more complex the project gets, the more cumbersome this becomes: Renaming does not work if the plugin code itself contains multiple subprojects, and even commenting out becomes "commenting out the right lines".
What is the intended way to fix this problem?
Complex logic for a single (multi-) project is best organized in buildSrc. You can for the most part regard it as a normal sub-project but just for the build classpath instead. Plugins and tasks that you create here are automatically available for all projects in the multi-project.
If, for some reason, you rather continue to work with a local Maven repository, you could think about publishing stable releases of the plugin with a version number so it is easier to roll back.

pom.xml without dependencies using maven-publish plugin in a multi module gradle project

I've a gradle project with subprojects. Every subproject produces a jar and a pom that it's published on a repository
1) In the main project gradle file there's a subprojects section that I used to define what and where to publish:
snippet from rootproject.gradle:
subprojects {
apply plugin: 'maven-publish'
publishing {
publications {
mavenJava(org.gradle.api.publish.maven.MavenPublication) {
from components.java
}
}
repositories {
maven {
url 'file://c:/temp/repo'
}
}
}
}
2) In the gradle file of one of my subprojects, I've added some dependencies:
snippet from subproject.gradle:
dependencies {
compile group: 'my-group', name: 'my-module', version:'1.1.0'
}
If I run "gradle publish" from the rootproject it will correctly publish every subproject. However, I noticed that the dependency defined in the subproject is missing from the pom publication related to the subproject.
If I cut and paste the content of the subprojects section in each subproject, the generated pom file contains the correct dependency.
It seems that “from components.java” is not a reference to something that should be used by the publish task to produce the pom, but the task will publish exactly what components.java contains when you call the “from” method.
As a workaround, I moved the subprojects code in a method defined in the root:
rootproject.gradle
def configurePublishing(project) {
project.apply plugin: 'maven-publish'
…
}
And I called it from each subproject:
subproject.gradle
dependencies {
compile group: 'my-group', name: 'my-module', version:'1.1.0'
}
configurePublishing(project)
Another solution could be adding a switch in the subprojects section and centralize everything in the gradle file of the root project:
subprojects { subProject ->
switch(subproject.name) {
case: ‘my-subproject-with-dependencies’ {
dependencies {
compile group: 'my-group', name: 'my-module', version:'1.1.0'
}
break;
}
}
apply plugin: 'maven-publish'
}
Is it an acceptable approach? Is there a best practice to follow? Is there an easier way to do it?

Gradle copy local jar into cache

I need some help understanding why my build.gradle does not copy locally referred jar into its cache. On Windows, I expected to see jars in C:\Users\myusername .gradle and indeed other JARs are there.
I can't import this JAR in IntelliJ as it does not find it.
Side question: did anybody figure out how to debug Gradle's DSL? I set up remote debugging and IntelliJ never hits my breakpoint. It does not the have the checkbox on it so I guess IDE does not think it is code.
Few things are not making sense to me:
1. gradle dependencies --configuration compile
\--- com.google.guava:guava:23.5-jre
+--- com.google.code.findbugs:jsr305:1.3.9
....
MY_JAR_1.0.1.jar is not present.
2. gradle copyDependencies - this task finds and copies it.
task copyDependencies(type: Copy) {
from configurations.compile
into 'dependencies'
}
3. This task also finds my jar.
task listcompile << {
configurations.compile.each { File file -> println file.name }
}
Why does #1 not find it?
apply plugin: 'java'
repositories {
mavenCentral()
//flatDir {
// dirs 'customlibs'
//}
}
dependencies {
compile group: 'com.google.guava', name: 'guava', version: '23.5-jre'
compile files('customlibs/MY_JAR_1.0.1.jar')
}
task listcompile << {
configurations.compile.each { File file -> println file.name }
}
// copy all dependencies into this folder
task copyDependencies(type: Copy) {
from configurations.compile
into 'dependencies'
}
It seems that Gradle's dependencies task will only display modules that have a defined group:name:version coordinate. You are adding a file directly, with no associated identifier.
What you can do instead is add a flat directory repository and declare your custom JAR as a normal dependency, like so:
repositories {
flatDir {
dirs 'customlibs'
}
}
dependencies {
compile ':MY_JAR:1.0.1'
}
You will need to rename the JAR so that it matches the naming convention for Maven/Gradle, i.e. MY_NAME-1.0.1.jar (replacing the last underscore with a hyphen).

Gradle buildSrc and buildscript

We have a Gradle build that includes a buildSrc with some custom plugins. Those plugins apply yet other plugins. For example, our plugin applies com.android.tools.build:gradle. For annotation processing that library needs to be on Gradle's classpath during compilation. So, putting this in our main build.gradle works:
buildscript {
repositories {
google()
}
dependencies {
classpath "com.android.tools.build:gradle:$gToolsVersion"
}
}
However, that means that for a user to apply this plugin they must (1) apply our plugin and (2) add that buildscript boilerplate. It seems like that shouldn't be necessary. We can also add a project.buildscript block inside our plugin but that too seems unnecessary and, due to this bug is problematic: https://developer.android.com/studio/build/gradle-plugin-3-0-0.html?utm_source=android-studio#known_issues.
I added the com.android.tools.build:gradle dependency to buildSrc/build.gradle as a runtime dependency. It seems like that should work: I thought that tells Gradle that in order to run my plugin that library (and its dependencies) need to be on the classpath. However, gradle buildEnvironment (and the fact that our build fails) makes it clear that's not the case.
So, questions:
What's the difference between a runtime dependency specified in buildSrc/build.gradle and a classpath dependency specified in a buildscript block in a regular build.gradle?
How can I arrange things so that users can apply the plugin from buildSrc and not have to also add the buildscript block to their build.gradle?
I got a slightly different problem and found an acceptable solution that might help with for your second question: I wanted to apply the same repositories in the buildSrc/build.gradle and twice in the root build.gradle.
repositories.gradle in the project root:
repositories {
if (project.hasProperty('nexus')) {
maven {
url 'http://localhost:8081/repository/JCenter/'
}
maven {
url 'http://localhost:8081/repository/Maven_Google/'
}
} else {
jcenter()
google()
}
}
ext {
androidGradleBuildToolsDependency = 'com.android.tools.build:gradle:3.1.3'
}
buildSrc/build.gradle:
buildscript {
apply from: '../repositories.gradle'
}
allprojects {
apply from: '../repositories.gradle'
}
dependencies {
// androidGradleBuildToolsDependency is defined in repositories.gradle
implementation androidGradleBuildToolsDependency
}
Root build.gradle:
buildscript {
apply from: 'repositories.gradle'
}
allprojects {
// this line will also be executed from the build.gradles in subprojects, so the working
// directory isn't always the same, so we use the absolute path here
apply from: "${rootProject.projectDir}/repositories.gradle"
}
Note that you do not need the classpath dependency inside the buildscript block of the root build.gradle as you normally would. The implementation dependency in the repositories.gradle seems to auto apply it.
My solution probably doesn't work when the build.gradles are supplied via a dependency.
Stumbled upon this while digging into a tangential problem.
I've been successfully using buildSrc/build.gradle as the place to define dependencies that would normally belong in the root-project's buildscript classpath for a few of my projects.
You can see a working example here: https://github.com/episode6/chop/blob/develop/buildSrc/build.gradle
I used to use compile dependencie but just switched to runtimeClasspath which feels more appropriate and also works. I don't think your classpath dependencies were working because they would be on the classpath of the buildSrc project, but not compiled into or run along side it.
If you decide to go this route, you may run into the problem I was just digging into which only came up because of this approach.
When I tried this approach with the dokka plugin, I got the following error
Could not resolve all files for configuration ':detachedConfiguration1'.
> Cannot resolve external dependency org.jetbrains.dokka:dokka-fatjar:0.9.17 because no repositories are defined
I was able to workaround this by adding jcenter() to the root project's buildscript repositories: https://github.com/episode6/chop/blob/develop/build.gradle#L2

Gradle dependency to project in buildscript

Trying to get my head around if it is possible to use Tasks from other projects in Gradle. Let's say ProjectB is a project with src/main/groovy containing com.MyTask, having parent ProjectA
In build.gradle in ProjectC, also having parent ProjectA:
buildscript {
dependencies{
project(':ProjectB')
}
}
That seems to be legit, because introdusing a typo in "project(:'ProjectB')" fails hard. What also fails is introducing this line:
import com.MyTask
Is project-references not valid in buildscript closure? Also tried moving com.MyTask to buildSrc/src/main/groovy with the same amount of success.
The solution which worked for me was to make "com.MyTask" available both at configurationtime and in sources.
ProjectA(the parent) got this added to buildSrc/build.gradle's sourceSets:
sourceSets{
main{
groovy{
srcDir 'ProjectB/src/main/groovy'
}
}
}
Now ProjectC and all other projects can use MyTask. At the same time it is bundled with the final jar of ProjectB.
The issue has also been discussed thoroughly between between Adam Murdoch, Luke Daley and Steve Ebersole: http://gradle.1045684.n5.nabble.com/buildSrc-as-a-regular-project-td5677255.html
Edit: It was smarter manipulating parent buildSrc than the standalone project. That way IntelliJ is happy-go-lucky.
From Gradle documentation 15.4. Adding dependencies to a task:
Example 15.13. Adding dependency on task from another project
build.gradle
project('projectA') {
task taskX(dependsOn: ':projectB:taskY') << {
println 'taskX'
}
}
project('projectB') {
task taskY << {
println 'taskY'
}
}

Resources