dropbox / dependency-guard

A Gradle plugin that guards against unintentional dependency changes.
Apache License 2.0
417 stars 15 forks source link

Can I easily protect all the dependencies of all the modules in a multi-module project? #37

Closed azabost closed 2 years ago

azabost commented 2 years ago

Hi.

First of all, thanks for creating this plugin. It's truly awesome 👍

However, I'm not sure how I could apply this plugin in a more generic way to cover all the possible dependencies in the whole multi-module project. Preferably, I would like to be able to guard literally all the dependencies, including the test dependencies (testImplementation / testAndroidImplementation etc.) of all the modules.

I figured out I can declare multiple configuration() { ... } blocks, for example in my app module I have:

dependencyGuard {
    listOf(
        // In most cases, "XRuntime" will contain "XCompile" dependencies but not always so we check all just in case
        "stageDebugCompileClasspath",
        "stageDebugRuntimeClasspath",
        "stageDebugUnitTestCompileClasspath",
        "stageDebugUnitTestRuntimeClasspath",
        "stageDebugAndroidTestCompileClasspath",
        "stageDebugAndroidTestRuntimeClasspath"
    ).forEach {
        configuration(it) {
            artifacts = true
            modules = false
            tree = false
        }
    }
}

However, I'm afraid that if app module has a dependency on featureX module, and featureX has testImplementation dependency on org.slf4j:slf4j-simple that is not used by app, then Dependency Guard can't protect against unwanted org.slf4j:slf4j-simple version changes, unless I apply the plugin to featureX module separately.

Therefore, I'm wondering what is the easiest way to cover that case.

  1. Is there any option to run the Dependency Guard against the whole project at once by configuring it once in a single place?
  2. Should I just apply this plugin to all my modules? If this is the suggested solution, then I have two more issues/questions:
    1. For m number of modules it's going to produce at least m text files (or m x n to cover n configurations in each module), where most of the dependencies are going to be the same (e.g. kotlin-stdlib). Therefore, it would be great to merge all these files into a single one for multi-module setup.
    2. When I tried using the plugin in allprojects in the root build.gradle.kts file, I applied the plugin and immediately stumbled upon a problem with the clean task. I'm not sure if it's a bug within this plugin or I simply did something wrong.
      org.gradle.internal.exceptions.LocationAwareException: Build file '/Users/azabost/projects/[redacted]/build.gradle.kts' line: 115
      Cannot add task 'clean' as a task with that name already exists.
      at org.gradle.kotlin.dsl.execution.InterpreterKt$locationAwareExceptionFor$2.invoke(Interpreter.kt:601)
      (...)
      Caused by: org.gradle.api.internal.tasks.DefaultTaskContainer$DuplicateTaskException: Cannot add task 'clean' as a task with that name already exists.
      at org.gradle.api.internal.tasks.DefaultTaskContainer.failOnDuplicateTask(DefaultTaskContainer.java:257)
      at org.gradle.api.internal.tasks.DefaultTaskContainer.registerTask(DefaultTaskContainer.java:398)
      at org.gradle.api.internal.tasks.DefaultTaskContainer.register(DefaultTaskContainer.java:375)
      at Build_gradle.<init>(build.gradle.kts:115)
      (...)

Can you advise something to cover my use case, please?

handstandsam commented 2 years ago

Thanks for trying out Dependency Guard!

You are correct that you can use dependencyGuard to monitor multiple configurations! The way you loop over an array is a nice way if you want to have the same configuration for multiple configurations.


I think you are looking for something that would do this: For all my projects, tell me what dependencies I am resolving so I can track it in a single place.

If that's your question, it's a hard answer. Each module + configuration will resolve dependencies in it's own way. If you are managing versions through a Version Catalog or similar, that's probably the easiest way to see your latest dependencies, but that can be out of date due to transitive updates.

For example: If you used library a for a module, then library b for another module, they could resolve to use different versions of Kotlin based on their transitive dependencies. That being said, you can only monitor a specific module + config and get this accurately.


I would suggest monitoring your "production apps". So if you have a :app then monitor it's releaseRuntimeClasspath (or whatever it is on yours).

You can delete the dependencyGuard config block (but leave the plugin applied), run it for a module and it will tell you possible configurations that can be monitored.


If you are looking to monitor test dependencies, then you would want to pick a specific test configuration.

I would not suggest monitoring EVERYTHING though. If you did want to monitor everything, you're probably looking for something like looks more like the .lockfile format, which is not the use case of this (at this time). If you can see value in that, then it could be considered.


There is a Gradle feature called "dependency locking" here: https://docs.gradle.org/current/userguide/dependency_locking.html#lock_state_location_and_format. It's very similar, but has everything in a single file (what you are looking for). However, only specified versions there can be used. It could be helpful on some projects, but I find it annoying when you do try to upgrade, it will not resolve the new versions unless you update the locks. Even if you update the version, it won't automatically update the transitive dependencies. It just is a weird interaction IMO and pretty annoying. I just want to know when committing the code what has changed, but I don't need to have it lock me to specific versions during gradle dependency resolution.

handstandsam commented 2 years ago

There is no way to specify dependency guard for all modules unless you did some sort of

rootProject.subprojects.forEach {
  // Apply Plugin
  // Configure Plugin
}

I think that would create more noise than signal though.

handstandsam commented 2 years ago

Since there was no reply in a month, I'm going to close this issue. Please feel free to comment and we can re-open.

azabost commented 2 years ago

Thanks a lot for all the detailed answers.

I suppose that expecting Dependency Guard to produce a single "merged" report for all the modules was unreasonable given what you said about transitive dependencies in libraries. I can clearly see the problem now.

Judging by the drawbacks of the other solutions such as the lockfiles etc., using Dependency Guard still seems to be the most reasonable approach.

Feel free to keep this issue closed as I've got the answers I needed.