Gradle cannot change dependencies of configuration

Viewed 3606

"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:

  1. Subproject submodule is evaluated first. It puts scala and subproject util into its dependencies.
  2. 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).
  3. 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.

0 Answers
Related