Stuck with "Cannot change dependencies of configuration after it has been included in dependency resolution."

My open source project has been stuck with this error since Gradle 3 was released. It is only triggered when I try to upload a release version to Maven Central. To reproduce:

git clone https://github.com/igniterealtime/Smack.git
cd Smack
git checkout 4.2
# Apply the patch on top of the 4.2 branch that will
# set the 'isSnapshot' configuration variable to 'false'.
curl http://geekplace.eu/box/Smack_4.2.1-rc1-fTle26PtdiqIGvL5M5jl3c9JTcGN2V.patch | git am
# Will fail with Gradle >= 3, and print an warning with Gradle < 3.
gradle uploadArchives

I have read a lot on that error, but was unfortunately not able to pin the culprit. What puzzles me is that it is only happening when ‘isSnapshot’ is set to false.

Full output of gradle uploadArchives --stacktrace --info --debug can be found at http://geekplace.eu/box/gradle-3-C3WMoThstKRajN9vfvtWLYohsZgzQA.org

I’d appreciate any help. I’ve been trying to solve this for years. Smack’s gradle build has become bigger and bigger over the time. And while it may not be a problem right now that I can only release a new Smack version to Maven Central using Gradle2, I fear that it eventually will be a problem in the near future.

I’m not an Android developer, so I didn’t look too much at the Android-specific parts of this build.

However, this task is at least part of the problem (if not all of it):

task maybeCheckForSnapshotDependencies {
        // Don't check for Snapshot dependencies if this is a snapshot.
        if (isSnapshot) return
        allprojects { project ->
                project.configurations.runtime.each {
                        if (it.toString().contains("-SNAPSHOT"))
                                throw new Exception("Release build contains snapshot dependencies: " + it)
                }
        }
}

The logic here is running during the configuration phase, not the execution phase. It is always executing immediately on build configuration, not only when the task maybeCheckForSnapshotDependencies executes.

As such, you see the behavior change depending on whether isSnapshot is true or false. When false, the each forces the runtime configuration to be resolved to be able to iterate through each File.

When you try to add project dependencies to compile with the logic in smack-android/build.gradle, the error is due to the configuration already being resolved since runtime extends from it.

With what your build is doing currently, you can probably see why this behavior was deprecated, then treated as an error. Nothing would stop you from adding a snapshot dependency in a subproject, which would then not fail as the check already occurred earlier.

The logic that make up the entire contents of your task definition would normally be wrapped in a doLast {...} to add it as the task action that executes at the appropriate time.

1 Like

As an added note, I wouldn’t recommend a guard clause in the task implementation. Instead, I would use onlyIf {...}, which is designed for this type of logic. Your task action would then contain only the work that needs to be done. For builds of snapshot versions, the task will correctly indicate that it was skipped in the modeling and output. It would look something like this:

task checkForSnapshotDependencies {
    onlyIf { !isSnapshot }
    doLast {
        allprojects { project ->
            project.configurations.runtime.each {
                if (it.toString().contains("-SNAPSHOT")) {
                    throw new GradleException("Release build contains snapshot dependencies: " + it)
                }
            }
        }
    }
}

Although, I might even go one step further and check that the resolved versions are not snapshots. It should give the same results, but in the spirit of the API, I would consider the resolved version from the metadata to be more what you care about than the file name.

1 Like

Thanks so much for your help.

Here is the resulting commit https://github.com/Flowdalic/Smack/commit/bd1615c3056e6ce84bdf2a352eee0d7a6aac8485