Hello I wonder if there is a smarter way to handle...
# community-support
t
Hello I wonder if there is a smarter way to handle my situation with incompatible dependency versions. πŸ€” Assume that we have a project structure like this:
Copy code
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                     
β”‚Applicationβ”‚                     
β””β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”˜                     
β”Œβ–½β”€β”€β”€β”€β”€β”€β”β”Œβ–½β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚Featureβ”‚β”‚External Library B (v4)β”‚
β””β”¬β”€β”€β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”Œβ–½β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”              
β”‚External Library Aβ”‚              
β””β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              
β”Œβ–½β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         
β”‚External Library B (v5)β”‚         
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β€’ We can modify
Application
and `Feature`; we CANNOT modify external libraries
A
and
B
. β€’
Application
requires version 4 of library
B
to run, and crashes when
B
is updated to version 5. β€’ External Library
A
declared version 5 of
B
as dependency, but actually works perfectly fine with version 4 of
B
. Problem is that the
Application
crashes due to library
B
being "automatically upgraded" (resolved) to version 5. My solution to this is to add a
resolutionStrategy
block to force the use of version 4 of
B
through all its transitive dependencies:
Copy code
configurations.all{
    resolutionStrategy {
        force("test.library:b:4.0.0")
    }
}
Trouble is, the above
resolutionStrategy
block only works when it is put in the build script of the top level project (i.e.
Application
in this case); it does not work when put in the build script of
Feature
. As such, we have to instruct all users of
Feature
to add the
resolutionStrategy
block above. While this works it is clearly not a very nice solution. Is there a way to handle this such that the users of
Feature
don't have to perform the version resolution? (like perhaps, a way to resolve the version on
Feature
project, such that top level projects don't have to handle it?)
v
Resolution strategy force is a very heavy and low level hammer that you should seldomly use. Define a strict version constraints on Feature to downgrade B, that should work as intended I think.
πŸ‘€ 1
b
Agree with @Vampire. However if you can modify
Application
, why don't you update the code to work with B v5? It's just a question of time until you will find yourself in the same situation again, when Lib A stops working with Bv4.
πŸ‘ 1
πŸ‘† 1
t
@Vampire Unfortunately the
constraints
block don't seem to exist for Kotlin Multiplatform's plugin's
dependencies
, and doing it separately out of the block don't work neither:
Copy code
dependencies {
    //<http://docs.gradle.org/8.5/userguide/single_versions.html#sec:declaring_without_version>
    commonMainImplementation("test.library:b")
    constraints {
        commonMainImplementation("test.library:b:4.0.0")
    }
}
@Benedikt Ritter Yeah updating
B
to v5 is a good solution, but for my case I am actually developing
Feature
for my coworker who maintain
Application
. I want to make using my
Feature
as easy as possible for my coworker.
b
I see. I don't have experience with Kotlin MPP. So can't tell how to do it the right way.
v
You probably need
commonMainApi
instead so the constraint propagates to the consumer? πŸ€·β€β™‚οΈ
πŸ‘€ 1
But actually whatever you do in Feature will be wrong!
t
Oh hey I got it working by declaring a dependency on
Feature
with strict versioning syntax on
B
(even though
Feature
doesn't use
B
directly)
Copy code
val androidMain by getting {
    dependencies {
        //...
        implementation("test.library:b:4.0.0!!")
    }
}
v
Because if you use a strict version and depend or have a strict version constraint in Feature, then you also prevent Application to choose to use v5
⭐ 1
You can make it even uglier and exclude B from your A dependency and then add a dependency on B v4 but without strict version, so that Application could also choose to upgrade to v5
But really, as a library you should not care about version conflicts, that is the responsibility of the end-project
t
@Vampire Just tested that
constraint
block with
commonMainApi
and
api
didn't work
Thanks, those are some great points for me to consider
πŸ‘Œ 1
v
As I said, whatever you do with strict version or forcing is having the opposite effect of what you intend anyway πŸ™‚
🫠 1
t
it looks like defining a strict range of versions while deliberately excluding version 5 allows the most flexibility for my situation since version 5 is the only incompatible one in my case, so something like:
Copy code
implementation("test.library:b"){
    version{
        strictly("[2.0.0, 4.0.0]")
        prefer("4.0.0")
    }
}
But yes I see your point... this can only be done with the assumption that all consumers of
Feature
does not depend on v5 of
B
, and it removes their flexibility to do that in the future... So you're right, its the best to just make version conflict handling the responsibility of the end-project πŸ€”
v
As library you simply do not have the information you need to care about version conflicts, unless you know each and every consumer, existing and future ones. πŸ™‚
⭐ 1
So best you could do is either let the end-project care, or at most exclude the B from A and depend on B v4, but that's not the cleanest to do, but might be appropriate for your case.
t
Well I tried but the
exclude
block didn't work somehow, this didn't work:
Copy code
val androidMain by getting {
    dependencies {
        //***
        api("test.library:a:1.0.0"){
            exclude(group = "test.library", module = "b")
        }
    }
}