refreshVersions
refreshVersions copied to clipboard
Support rich versions (strictly & prefer)
Implementation should be quite easy, setting the version happens in a single place, where we have the withDependencies { … }
block.
However, there are some design decisions to tackle first because there are multiple ways to implement this:
- We can have a variant of the version placeholder, and we could add a corresponding function/property in the dependency notations API. However, this requires checking the other places that work with the version placeholder to ensure we don't break anything.
- We can support a special string value to be passed in the
version { … }
block. - We can support a special value to be passed in the
version { … }
block to compare via reference. - We can add parameter-less overloads of the
strictly()
andprefer()
functions, hijacking the Gradle package.
For now, I'm biased towards option 4, but maybe someone has an objection to that, or an idea for the 3 other options, or a different option mind?
I started implementing option4, but there's something I'm not super happy with.
While it reads okay(ish?) for strictly
:
dependencies {
api(Something.whatever) {
version { strictly() }
}
}
it reads less nicely when the version parameter is omitted for the prefer
function:
dependencies {
api(Something.whatever) {
version { prefer() }
}
}
Instead of an extension for MutableVersionConstraint
, we can have extensions on ExternalModuleDependency
(the receiver of the block passed to api
in the snippet above), or on Dependency?
(before casting to ExternalModuleDependency
), which is the type returned by the api
function as well as its "friends" like implementation
, testRuntimeOnly
, etc.
Here are a few examples of how these could look like…
as extensions for Dependency?
:
dependencies { // Let's call it option 5.
api(Something.whatever).strictVersion()
api(Something.whatever).preferDeclaredVersion()
api(Something.whatever).preferNoTransitiveUpgrade()
}
as extensions for ExternalModuleDependency
:
dependencies { // and this is option 6.
api(Something.whatever) { strictVersion() }
api(Something.whatever) { preferDeclaredVersion() }
api(Something.whatever) { preferNoTransitiveUpgrade() }
}
For option 1, where we would use variants of the version placeholder, I have been thinking that we could use _
for require
, as it's already the case, and use _!
for prefer
, and _!!
for strictly
. I find it intuitive, but also not very explicit at the same time, and not very discoverable, so I'm not sure about that, though we can have related functions for the DependencyNotation
type, that would make it discoverable for built-in dependency notations.
I just discovered that option 5 cannot work. However, here's a variant that can work when coupled with option 1:
dependencies { // This is option 1
api(Something.whatever.strictVersion())
api(Something.whatever.preferDeclaredVersion())
api("com.example.something:something:_") // Require (default, allows transitive upgrades)
api("com.example.something:something:_!") // Prefer (avoids upgrades if possible)
api("com.example.something:something:_!!") // Strictly
}
dependencies { // This is option 7
api(Something.whatever) {
version { strictVersion() } // Variant A
}
api(Something.whatever) {
version { strictlyDeclaredVersion() } // Variant B
}
api(Something.whatever) {
version {
preferDeclaredVersion()
reject("1.0.0")
}
}
api("com.example.something:something:_")
api("com.example.something:something:_") {
version { preferDeclaredVersion() }
}
api("com.example.something:something:_") {
version { strictVersion() } // Variant A
}
api("com.example.something:something:_") {
version { strictlyDeclaredVersion() } // Variant B
}
}
I was considering implementing option 1, but while Gradle let us do what we want with _!
as a version placeholder, when we put _!!
, it eats both exclamation marks… 😭
It turns out using _?
and _!
can work, so I'll probably end up using those.
That said, I'm concerned about how these fit in regarding version ranges, like when defining a range for require
, and a value for prefer
. What I started to work on doesn't support these use cases for now, I'll have to think about what we can do there to make for an intuitive support.