spring-gradle-plugins / dependency-management-plugin

A Gradle plugin that provides Maven-like dependency management functionality
691 stars 87 forks source link

Document how to declare a dependency using a provider to avoid problems with importedProperties and managedVersions causing premature bom resolution that interferes with subsequent dependency management and dependency configuration #193

Open andersonkyle opened 6 years ago

andersonkyle commented 6 years ago

Here is the relevant snippet from my build.gradle file that reproduces this issue:

dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')

    compile('io.dropwizard.metrics:metrics-core')

    //
    //Try switching switching between these two dependencies and checking 'gradlew dependencyManagement' for whether the Spring Cloud BOM was imported or not.
    //
    compile 'io.dropwizard.metrics:metrics-jvm:' + dependencyManagement.managedVersions['io.dropwizard.metrics:metrics-core']
    //compile 'io.dropwizard.metrics:metrics-jvm'

    testCompile('org.springframework.boot:spring-boot-starter-test')
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

The problem occurs when I call the dependencyManagement API from within a dependency {} declaration.

compile 'io.dropwizard.metrics:metrics-jvm:' + dependencyManagement.managedVersions['io.dropwizard.metrics:metrics-core']

If this API call is removed, the mavenBom is imported as expected.

Here is an example repo. To test whether the BOM is imported or not, use this command:

gradlew dependencyManagement

Spring Boot alone only contains about 5 Spring Cloud dependencies, so that's all you should see in the list. When you remove the API call mentioned above, you'll see dozens of Spring Cloud dependencies coming from the Spring Cloud BOM.

NOTE: Ordering seems to be a factor as if you move the dependencyManagement {} section above the dependencies {} section, it works as expected, even when calling the dependencyManagement API within a dependency declaration.

wilkinsona commented 6 years ago

The problem is that calling dependencyManagement.managedVersions causes the imported boms to be resolved. This marks the dependency management as resolved. The subsequent import of the Spring Cloud bom doesn't change this so it's effectively ignored. The fix is to reset the resolved flag each time a bom import is added.

There's a broader problem here, though. Using dependencyManagement.managedVersions has the unwanted side-effect of causing dependency resolution (for the imported boms) before task execution occurs. It would be good to be able to avoid that but I'm not sure how to do so. The best idea I can come up with is to use a placeholder for the version that's then spotted by a resolution strategy and replaced with the property version.

wilkinsona commented 6 years ago

The same broader problem also applies to dependencyManagement.importedProperties which is discussed in #56. Using a placeholder may also work in that case.

jjathman commented 6 years ago

To add another related use case, I would like to specify a version, but only if the version I am specifying is newer than the one that would otherwise be used. We would like to use the newer version because it includes a security fix, but whenever Spring Boot starts including a version that is newer than the one specified we would just want to use that. I attempted to use the dependencyManagement API like others here but ran in to similar problems.

wilkinsona commented 2 years ago

Gradle 6.5 has added support for declaring dependencies using a Provider. The provider's evaluated lazily which avoids the problem of prematurely resolving the imported boms. The problematic dependency in the original example can be fixed by writing it like this:

implementation provider { 'io.dropwizard.metrics:metrics-jvm:' + dependencyManagement.managedVersions['io.dropwizard.metrics:metrics-core'] }

I'll turn this into a documentation issue for 1.0.x.