In larger organizations, managing dependencies can be challenging. It’s easy to end up with multiple dependencies that serve the same purpose. In this blog post, I want to share an easy way to protect yourself against unwanted dependencies.
Detecting unwanted dependencies
Let’s say you want to use Moshi to work with JSON files. You don’t want two JSON libraries inside your app, so let’s block the usage of GSON. To do this, we can scan the Gradle dependency graph and fail the build if we encounter any dependency on GSON. Adding this snippet to your root-level build.gradle.kts file should do the trick:
This scans all configurations of every module for unwanted dependencies and throws an exception if any is encountered. You can see if it works by adding a dependency on GSON.
Distinguishing direct and transitive dependencies
Sometimes it’s necessary to handle direct and transitive dependencies differently. For example, you might want to prohibit developers from using kotlin-reflect directly, but you still want to allow third-party libraries to use it. To support this, we can check if a dependency comes from a Gradle project (direct), or from another library (transitive) and deal with it accordingly.
Some optimizations
Because we apply this logic to every Gradle project, we can make some optimizations. If we encounter a dependency on another project, we can skip verifying it, as its downstream dependencies will be validated separately:
We’re also verifying every configuration, which might not be what you want. For example, you could allow the usage of some dependencies for tests, but not for production code. In many cases it’s enough to check the final classpath of a project:
The final product
Putting all of this together, we have an easy way to guard our app against any unwanted dependencies, which will hopefully save us some headaches in the future.