Given a project specified with a SNAPSHOT version, building it fails if it transitively finds another snapshot of itself in the dependency graph


#1

Consider the following scenario: Let p be a Gradle plugin that compile-depends on a library l; hence, in p’s build.gradle we have something like (pseudocode) dependencies { compile 'l' }. In addition, suppose l’s build applies p; hence, in l’s build.gradle we have something like (again, pseudocode) apply plugin: 'p'.

Suppose we want to build l. This appears to be no problem as long as the version of p that is actually used to build l depends on another version of l that we are currently building. As I have observed today in a real practical scenario (not contrived!), we will run into problems if we want to build a SNAPSHOT version of l and apply a version of p that depends on the same snapshot version. Obviously, building l is going to produce a new snapshot which cannot be that snapshot that p uses at runtime. That appears to be a completely justified scenario that Gradle should support. However, when I tried to execute a build for l today with precisely that scenario I got the following error (names replaced for privacy reasons):

A problem occurred configuring project ':l'.
> Could not resolve all artifacts for configuration ':l:classpath'.
   > Could not resolve group:l:4.0.0-SNAPSHOT.
     Required by:
         project :l > group:p:1.1.0-SNAPSHOT:20180324.182140-105
      > Group:p:1.1.0-SNAPSHOT:20180324.182140-105 declares a dependency from configuration 'default' to configuration 'runtime' which is not declared in the descriptor for project :l.

It seems that Gradle is somehow excluding or removing the dependency on l because it has the same group Id, artifact Id, and version (4.0.0-SNAPSHOT) of the project we are currently building (which is l).

I think Gradle should simply accept that provided that a) it is a snapshot version and b) it finds a snapshot in some repository, because it can obviously be only an older one.

What do you think?


#2

Bump. Hoping that someone will shed a light on that.


(Dimitar Dimitrov) #3

This is not supported and I doubt it will be.

I also fail to see why this is a valid use case, perhaps you may care to provide more details?

In particular if your build depends on a random old build, how would you recover if somebody pushes a completely broken snapshot that breaks compilation?


#4

Let me expand. We are developing the library l and plugin p I was referring to above. As such, we control them in any way; which is not completely irrelevant to what follows, especially when it comes to how to recover from broken builds. l is a core utility library used by almost all of our projects, in particular, also by our plugin p. Similarly, p is a utility Gradle plugin applied to almost all of our builds, in particular, we want to apply it for building l. So far, this is nothing special. We can often find these kind of circular relationships in practice; take as an example a self-hosted compiler infrastructure.

As a matter of rather long development cycles between release versions, we want to use snpashots of both l and p as they are in a state where they can be used already, except they are not yet at the state where they are ready to get released. To me, this is comparable to a self-hosted compiler infrastructure where you want to use a compiler c to build a new (pre-release) version c’ of that compiler.

We would not run into this problem just by redefining the versioning scheme of a snapshot. Imagine we would define a snapshot version to include a sequence number. So instead of 4.0.0-SNAPSHOT we could have 4.0.0-m1, 4.0.0-m2, … (i.e., like a build number incremented on each build). That would solve the problem. It’s just more cumbersome because we would loose the “magic” of -SNAPSHOT dependencies that are automatically resolved to the latest version available, by the need to regularly upgrade the versions in our build scripts (probably that could be scripted away in some (hacky) way).

That brings me to repeat in a slight reformulated way what I have state in the first post of this thread:
Given a Gradle project p that has group g, artifact Id a and version v-SNAPSHOT, if the dependency graph of a build of p transitively includes an artifact identified as g:a:v-SNAPSHOT, Gradle should accept that rather than fail.

I don’t agree. Depending on a snapshot is not like depending on a random build. It’s depending on the latest build. If that latest build breaks another build then, well, if you control both, you fix the former and then repeat the latter. No?


(Dimitar Dimitrov) #5

This is a messy situation.

I have a project like that and in my case, both the lib and the plugin are in separate projects. I use explicit versions for both. If I need to test something unreleased, I manually change the dependency to be snapshot with all consequences that would entail. At some point I was using “+” (latest version), but that was too unstable.

Composite builds can help you if you want to test newer library with the plugin, but if you try the other way around it works only if you use the buildscript.dependencies { ... } way of applying plugins (doesn’t work with the plugins { ... } block).


#6

I have updated this thread’s title to be more easily found in case some Gradle user experiences the same problem in the future.