A Gradle plugin that guards against unintentional dependency changes.

Overview

🛡️ Dependency Guard

A Gradle plugin that helps you guard against unintentional dependency changes.

Surface Transitive Dependency Changes

Comparison to a baseline occurs whenever you run the dependencyGuard Gradle task.

A small single version bump of androidx.activity from 1.3.1 -> 1.4.0 causes many libraries to transitively update.

Provide Custom Rules for Allowed Dependencies

For a given configuration, you may never want junit to be shipped. You can prevent this by modifying the allowRule rule to return !it.contains("junit") for your situation.

Why Was Dependency Guard Built?

As platform engineers, we do a lot of library upgrades, and needed insight into how dependencies were changing over time. Any small change can have a large impact. On large teams, it's not possible to track all dependency changes in a practical way, so we needed tooling to help. This is a tool that surfaces these changes to any engineer making dependency changes, and allows them to re-baseline if it's intentional. This provides us with historical reference on when dependencies (including transitive) are changed for a given configuration.

Goals:

  • Surface transitive and non-transitive dependencies changes for our production build configurations. This helps during version bumps and when new libraries are added.
  • Provide the developer with the ability to easily accept changes as needed.
  • Add deny-listing for production build configurations to explicitly block some dependencies from production.
  • Include a record of the change in Git when their code is merged to easily diagnose/debug crashes in the future.
  • Be deterministic. De-duplicate entries, and order alphabetically.

Real World Issues Which Dependency Guard Addresses

  • Accidentally shipping a testing dependency to production because implementation was used instead of testImplementation - @handstandsam
    • Dependency Guard has List and Tree baseline formats that can compared against to identify changes. If changes occur, and they are expected, you can re-baseline.
  • Dependency versions were transitively upgraded which worked fine typically, but caused a runtime crash later that was hard to figure out why it happened.
    • You would see the diff off dependencies in Git since the last working version and be able to have quickly track down the issue.
  • After adding Ktor 2.0.0, it transitively upgraded to Kotlin 1.6.20 which caused incompatibilities with Jetpack Compose 1.1.1 which requires Kotlin 1.6.10 - @joreilly
    • During large upgrades it is important to see how a single version bump will impact the rest of your application.
  • Upgrading to the Android Gradle Plugin transitively downgraded protobuf plugin which caused issues - @AutonomousApps

Setup and Configuration

Step 1: Adding The Dependency Guard Gradle Plugin and Baselining

// sample/app/build.gradle.kts
plugins {
  id("com.dropbox.dependency-guard")
}

Step 2: Run ./gradle dependencyGuard and Configure

This will show a list of available configuration(s) for this Gradle module.

You can choose the configurations you want to monitor.

We suggest monitoring your release configuration to know what is included in production builds. You can choose to monitor any classpath configuration with Dependency Guard.

// sample/app/build.gradle.kts
dependencyGuard {
    // All dependencies included in Production Release APK
    configuration("releaseRuntimeClasspath") 
}

NOTE: Checkout the "Adding to the Buildscript Classpath" section below if the plugin can't be resolved.

Step 3: Run ./gradlew dependencyGuard to Detect Changes

It's suggested to run in your CI or pre-commit hooks to detect these changes. If this task is not run, then you aren't getting the full benefit of this plugin.

Bump Version and Detect Change

Step 4: Rebaselining to Accept Changes

If any dependencies have changed, you will be provided with the ./gradlew dependencyGuardBaseline task to update the baseline and intentionally accept these new changes.

Rebaseline to Accept Change

Additional Configuration Options

Allow Rules for Dependencies (Optional)

If you have explicit test or debugging dependencies you never want to ship, you can create rules for them here.

dependencyGuard {
    configuration("releaseRuntimeClasspath") {
        allowRule = {
            // Disallow dependencies with a name containing "junit"
            !it.contains("junit")
        }
    }
}

Configuring Your Dependency Baseline (Optional)

By default, Dependency Guard tracks modules and artifacts in a list format is generated at dependencies/${configurationName}.txt.

:sample:module1
:sample:module2
org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10
org.jetbrains.kotlin:kotlin-stdlib:1.6.10
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2
org.jetbrains:annotations:13.0

You can customize choose to not track modules or artifacts:

dependencyGuard {
  configuration("releaseRuntimeClasspath") {
    // What is included in the list report
    modules = true // Defaults to true
    artifacts = true // Defaults to true
  }
}

Tree Format (Optional)

The builtin Dependencies task from Gradle is :dependencies. The tree format support for Dependency Guard leverages this under the hood and targets it for a specific configuration.

The dependency-tree-diff library in extremely helpful tool, but is impractical for many projects in Continuous Integration since it has a lot of custom setup to enable it. (related discussion). dependency-tree-diff's diffing logic is actually used by Dependency Guard to highlight changes in trees.

The tree format proved to be very helpful, but as we looked at it from a daily usage standpoint, we found that the tree format created more noise than signal. It's super helpful to use for development, but we wouldn't recommend storing the tree format in Git, especially in large projects as it gets noisy, and it becomes ignored. In brainstorming with @joshfein, it seemed like a simple orded, de-duplicated list of dependencies were better for storing in CI.

dependencies/releaseRuntimeClasspath.tree.txt


------------------------------------------------------------
Project ':sample:app'
------------------------------------------------------------

releaseRuntimeClasspath - Resolved configuration for runtime for variant: release
\--- project :sample:module1
     +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10
     |    +--- org.jetbrains.kotlin:kotlin-stdlib:1.6.10
     |    |    +--- org.jetbrains:annotations:13.0
     |    |    \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10
     |    \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10
     |         \--- org.jetbrains.kotlin:kotlin-stdlib:1.6.10 (*)
     \--- project :sample:module2
          +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10 (*)
          \--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2
               \--- org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2
                    +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30 -> 1.6.10 (*)
                    \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.5.30 -> 1.6.10

(*) - dependencies omitted (listed previously)

A web-based, searchable dependency report is available by adding the --scan option.

Enabling Tree Format

Enable the tree format for a configuration with the following option:

dependencyGuard {
  configuration("releaseRuntimeClasspath") {
    tree = true // Enable Tree Format
  }
}

Creating a Tree Format Baseline

Detecting Changes in Tree Format

Rebaselining Tree Format

Additional Information

Which Configurations Should be Guarded?

  • Production App Configurations (what you ship)
  • Production Build Configurations (what you use to build your app)

Changes to either one of these can change your resulting application.

How Does Dependency Guard Work?

Dependency Guard writes a baseline file containing all your transtitive dependencies for a given configuration that should be checked into git.

Under the hood, we're leveraging the same logic that the Gradle dependencies task uses and displays in build scans, but creating a process which can guard against changes.

A baseline file is created for each build configuration you want to guard.

If the dependencies do change, you'll get easy to read warnings to help you visualize the differences, and accept them by re-baselining. This new baseline will be visible in your Git history to help understand when dependencies changed (including transitive).

Full Configuration Options

return true // Defaults to true } } }">
dependencyGuard {
  configuration("releaseRuntimeClasspath") {
    // What is included in the list report
    modules = true // Defaults to true
    artifacts = true // Defaults to true

    // Tree Report
    tree = false // Defaults to false

    // Filter through dependencies
    allowRule = {dependencyName: String ->
        return true // Defaults to true
    }
  }
}

Suggested Workflows

The Dependency Guard plugin adds a few tasks for you to use in your Gradle Builds. Your continuous integration environment would run the dependencyGuard task to ensure things did not change, and require developers to re-baseline using the dependencyGuardBaseline tasks when changes were intentional.

Gradle Tasks added by Dependency Guard

dependencyGuard

Compare against the configured transitive dependency report baselines, or generate them if they don't exist.

This task is added to any project that you apply the plugin to. The plugin should only be applied to project you are interested in tracking transtive dependencies for.

dependencyGuardBaseline

This task overwrites the dependencies in the dependencies directory in your module.

Baseline Files

Baseline files are created in the "dependency-guard" folder in your module. The following reports are created for the :sample:app module by running ./gradlew :sample:app:dependencyGuardBaseline

Adding to the Buildscript Classpath

Snapshot versions are under development and change, but can be used by adding in the snapshot repository

// Root build.gradle
buildscript {
    repositories {
        mavenCentral()
        google()
        gradlePluginPortal()
        // SNAPSHOT Versions of Dependency Guard
        maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots" }
    }
    dependencies {
        classpath("com.dropbox.dependency-guard:dependency-guard:0.1.0")
    }
}

License

Copyright (c) 2022 Dropbox, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Comments
  • Feature request: for multiplatform deps, only include target platform

    Feature request: for multiplatform deps, only include target platform

    If a kotlin multiplatform lib is depended on and has, say, org.jetbrains.compose:runtime as a common dependency, it gets included in an android dependency guard classpath even though there is no android platform exposed for that dep.

    opened by ZacSweers 6
  • Fix

    Fix "baseline created" message in DependencyGuardTreeDiffer

    Hello!

    When DependencyGuardTreeDiffer writes a tree baseline file, it prints the following message (sources):

    "Dependency Guard Tree baseline created for : for configuration classpath."
    

    But DependencyGuardTreeDiffer writes a tree baseline file for the given project.path and configurationName, not only for : and classpath. So it would be useful to print the correct project.path and configurationName in the output message.

    Expected example of the output message:

    "Dependency Guard Tree baseline created for :lib for configuration testCompileClasspath."
    
    opened by qwert2603 6
  • Update dependency-tree-diff

    Update dependency-tree-diff

    Hello!

    dependencyTreeDiff sources are copied to this project from dependency-tree-diff from commit 2548d3e95aa709c2c2b95ed61241a49bfadddd72 (file's history).

    And now there is a newer version in the original repo (commit 4e86e45bbb032535d8106aec2811399f4bce7b49). It would be great to update sources in this repo to receive bug fixes from the original repo.

    opened by qwert2603 4
  • Can I easily protect all the dependencies of all the modules in a multi-module project?

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

    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?

    opened by azabost 4
  • Don't apply the LifecycleBasePlugin if already applied

    Don't apply the LifecycleBasePlugin if already applied

    Ran into this crash while testing this out in our root project

    Caused by: org.gradle.api.internal.plugins.PluginApplicationException: Failed to apply plugin class 'org.gradle.language.base.plugins.LifecycleBasePlugin'.
            at org.gradle.api.internal.plugins.DefaultPluginManager.doApply(DefaultPluginManager.java:173)
            at org.gradle.api.internal.plugins.DefaultPluginManager.addImperativePlugin(DefaultPluginManager.java:89)
            at org.gradle.api.internal.plugins.DefaultPluginManager.addImperativePlugin(DefaultPluginManager.java:96)
            at org.gradle.api.internal.plugins.DefaultPluginContainer.apply(DefaultPluginContainer.java:77)
            at com.dropbox.gradle.plugins.dependencyguard.DependencyGuardPlugin.attachToCheckTask(DependencyGuardPlugin.kt:50)
            at com.dropbox.gradle.plugins.dependencyguard.DependencyGuardPlugin.apply(DependencyGuardPlugin.kt:42)
            at com.dropbox.gradle.plugins.dependencyguard.DependencyGuardPlugin.apply(DependencyGuardPlugin.kt:16)
            at org.gradle.api.internal.plugins.ImperativeOnlyPluginTarget.applyImperative(ImperativeOnlyPluginTarget.java:43)
            at org.gradle.api.internal.plugins.RuleBasedPluginTarget.applyImperative(RuleBasedPluginTarget.java:51)
            at org.gradle.api.internal.plugins.DefaultPluginManager.addPlugin(DefaultPluginManager.java:187)
            at org.gradle.api.internal.plugins.DefaultPluginManager.access$100(DefaultPluginManager.java:52)
            at org.gradle.api.internal.plugins.DefaultPluginManager$AddPluginBuildOperation.run(DefaultPluginManager.java:282)
            at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
            at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
            at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
            at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
            at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
            at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
            at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)
            at org.gradle.api.internal.plugins.DefaultPluginManager.lambda$doApply$0(DefaultPluginManager.java:167)
            at org.gradle.configuration.internal.DefaultUserCodeApplicationContext.apply(DefaultUserCodeApplicationContext.java:44)
            at org.gradle.api.internal.plugins.DefaultPluginManager.doApply(DefaultPluginManager.java:166)
            at org.gradle.api.internal.plugins.DefaultPluginManager.apply(DefaultPluginManager.java:146)
            at org.gradle.plugin.use.internal.DefaultPluginRequestApplicator.lambda$applyLegacyPlugin$2(DefaultPluginRequestApplicator.java:160)
            at org.gradle.plugin.use.internal.DefaultPluginRequestApplicator.applyPlugin(DefaultPluginRequestApplicator.java:201)
            ... 179 more
    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 org.gradle.language.base.plugins.LifecycleBasePlugin.addClean(LifecycleBasePlugin.java:58)
            at org.gradle.language.base.plugins.LifecycleBasePlugin.apply(LifecycleBasePlugin.java:44)
            at org.gradle.language.base.plugins.LifecycleBasePlugin.apply(LifecycleBasePlugin.java:33)
            at org.gradle.api.internal.plugins.ImperativeOnlyPluginTarget.applyImperative(ImperativeOnlyPluginTarget.java:43)
            at org.gradle.api.internal.plugins.RuleBasedPluginTarget.applyImperative(RuleBasedPluginTarget.java:51)
            at org.gradle.api.internal.plugins.DefaultPluginManager.addPlugin(DefaultPluginManager.java:187)
            at org.gradle.api.internal.plugins.DefaultPluginManager.access$100(DefaultPluginManager.java:52)
            at org.gradle.api.internal.plugins.DefaultPluginManager$AddPluginBuildOperation.run(DefaultPluginManager.java:282)
            at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
            at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
            at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
            at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
            at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
            at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
            at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)
            at org.gradle.api.internal.plugins.DefaultPluginManager.lambda$doApply$0(DefaultPluginManager.java:167)
            at org.gradle.configuration.internal.DefaultUserCodeApplicationContext.apply(DefaultUserCodeApplicationContext.java:44)
            at org.gradle.api.internal.plugins.DefaultPluginManager.doApply(DefaultPluginManager.java:166)
            ... 204 more
    
    
    
    opened by ZacSweers 4
  • Dependency change only on CI though

    Dependency change only on CI though

    This is a bit of a weird one. The dependencyTreeDiffReleaseRuntimeClasspath tasks fails for me. However it's only on CI. This issue started happening with these changes (adding the plugin to more modules): https://github.com/vanniktech/Emoji/commit/3fe08a3f16a7c1adaacdfb5107a7e4feba91cef7

    This is the CLI output:

    > Task :emoji-facebook:dependencyTreeDiffReleaseRuntimeClasspath FAILED
    See the report at: file:///home/runner/work/Emoji/Emoji/emoji-facebook/build/tmp/dependency-guard/releaseRuntimeClasspath.tree.txt
    ***** DEPENDENCY CHANGE DETECTED *****
    -+--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.0
    -|    +--- org.jetbrains.kotlin:kotlin-stdlib:1.7.0
    -|    |    \--- org.jetbrains:annotations:13.0
    -|    \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.0
    -|         \--- org.jetbrains.kotlin:kotlin-stdlib:1.7.0 (*)
     +--- project :emoji
    -|    \--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.0 (*)
    +|    \--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.0
    +|         +--- org.jetbrains.kotlin:kotlin-stdlib:1.7.0
    +|         |    \--- org.jetbrains:annotations:13.0
    +|         \--- org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.0
    +|              \--- org.jetbrains.kotlin:kotlin-stdlib:1.7.0 (*)
    +\--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.0 (*)
    

    Running the task locally just works for me: Screen Shot 2022-07-01 at 00 33 47

    Could it be that dependencies are differently sorted on that particular machine and the tree diffing gets confused?

    opened by vanniktech 3
  • Tree format requires Gradle 7.4 or higher

    Tree format requires Gradle 7.4 or higher

    Hi team!

    I wanted to play around with the tree format described in the README and came across the following error:

    java.lang.NoClassDefFoundError: org/gradle/configurationcache/extensions/CharSequenceExtensionsKt
    	at com.dropbox.gradle.plugins.dependencyguard.internal.DependencyTreeDiffTaskNames.createDependencyTreeTaskNameForConfiguration(DependencyTreeDiffTaskNames.kt:8)
    	at com.dropbox.gradle.plugins.dependencyguard.DependencyGuardPlugin$registerTreeDiffTasks$1.execute(DependencyGuardPlugin.kt:108)
    	at com.dropbox.gradle.plugins.dependencyguard.DependencyGuardPlugin$registerTreeDiffTasks$1.execute(DependencyGuardPlugin.kt:16)
    

    I'm using version 0.3.0 of the plugin with Gradle 7.2 in our project.

    I think this is because it relies on the CharSequence.capitalized() extension defined in /src/main/kotlin/org/gradle/configurationcache/extensions/CharSequenceExtensions.kt which only landed in Gradle 7.4.

    image

    Perhaps a small update to the README to point this out might be useful? Alternatively there is probably a Kotlin extension that does something similar?

    Cheers.

    opened by mezpahlan 3
  • Crash while using in root project

    Crash while using in root project

    @ZacSweers reported: Ran into this crash while testing this out in our root project

    Caused by: org.gradle.api.internal.plugins.PluginApplicationException: Failed to apply plugin class 'org.gradle.language.base.plugins.LifecycleBasePlugin'.
            at org.gradle.api.internal.plugins.DefaultPluginManager.doApply(DefaultPluginManager.java:173)
            at org.gradle.api.internal.plugins.DefaultPluginManager.addImperativePlugin(DefaultPluginManager.java:89)
            at org.gradle.api.internal.plugins.DefaultPluginManager.addImperativePlugin(DefaultPluginManager.java:96)
            at org.gradle.api.internal.plugins.DefaultPluginContainer.apply(DefaultPluginContainer.java:77)
            at com.dropbox.gradle.plugins.dependencyguard.DependencyGuardPlugin.attachToCheckTask(DependencyGuardPlugin.kt:50)
            at com.dropbox.gradle.plugins.dependencyguard.DependencyGuardPlugin.apply(DependencyGuardPlugin.kt:42)
            at com.dropbox.gradle.plugins.dependencyguard.DependencyGuardPlugin.apply(DependencyGuardPlugin.kt:16)
            at org.gradle.api.internal.plugins.ImperativeOnlyPluginTarget.applyImperative(ImperativeOnlyPluginTarget.java:43)
            at org.gradle.api.internal.plugins.RuleBasedPluginTarget.applyImperative(RuleBasedPluginTarget.java:51)
            at org.gradle.api.internal.plugins.DefaultPluginManager.addPlugin(DefaultPluginManager.java:187)
            at org.gradle.api.internal.plugins.DefaultPluginManager.access$100(DefaultPluginManager.java:52)
            at org.gradle.api.internal.plugins.DefaultPluginManager$AddPluginBuildOperation.run(DefaultPluginManager.java:282)
            at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
            at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
            at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
            at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
            at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
            at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
            at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)
            at org.gradle.api.internal.plugins.DefaultPluginManager.lambda$doApply$0(DefaultPluginManager.java:167)
            at org.gradle.configuration.internal.DefaultUserCodeApplicationContext.apply(DefaultUserCodeApplicationContext.java:44)
            at org.gradle.api.internal.plugins.DefaultPluginManager.doApply(DefaultPluginManager.java:166)
            at org.gradle.api.internal.plugins.DefaultPluginManager.apply(DefaultPluginManager.java:146)
            at org.gradle.plugin.use.internal.DefaultPluginRequestApplicator.lambda$applyLegacyPlugin$2(DefaultPluginRequestApplicator.java:160)
            at org.gradle.plugin.use.internal.DefaultPluginRequestApplicator.applyPlugin(DefaultPluginRequestApplicator.java:201)
            ... 179 more
    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 org.gradle.language.base.plugins.LifecycleBasePlugin.addClean(LifecycleBasePlugin.java:58)
            at org.gradle.language.base.plugins.LifecycleBasePlugin.apply(LifecycleBasePlugin.java:44)
            at org.gradle.language.base.plugins.LifecycleBasePlugin.apply(LifecycleBasePlugin.java:33)
            at org.gradle.api.internal.plugins.ImperativeOnlyPluginTarget.applyImperative(ImperativeOnlyPluginTarget.java:43)
            at org.gradle.api.internal.plugins.RuleBasedPluginTarget.applyImperative(RuleBasedPluginTarget.java:51)
            at org.gradle.api.internal.plugins.DefaultPluginManager.addPlugin(DefaultPluginManager.java:187)
            at org.gradle.api.internal.plugins.DefaultPluginManager.access$100(DefaultPluginManager.java:52)
            at org.gradle.api.internal.plugins.DefaultPluginManager$AddPluginBuildOperation.run(DefaultPluginManager.java:282)
            at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
            at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
            at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
            at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
            at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
            at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
            at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
            at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)
            at org.gradle.api.internal.plugins.DefaultPluginManager.lambda$doApply$0(DefaultPluginManager.java:167)
            at org.gradle.configuration.internal.DefaultUserCodeApplicationContext.apply(DefaultUserCodeApplicationContext.java:44)
            at org.gradle.api.internal.plugins.DefaultPluginManager.doApply(DefaultPluginManager.java:166)
            ... 204 more
    

    See: https://github.com/dropbox/dependency-guard/pull/36

    opened by handstandsam 3
  • Why the dependency on kotlin-gradle-plugin?

    Why the dependency on kotlin-gradle-plugin?

    When I applied this plugin to one of my projects, I ran into unrelated issues in my build, which I traced to the unintentional upgrade of the kotlin-gradle-plugin. Are the implementation dependencies on kotlin-gradle-plugin and the kotlin BoM necessary? I tried removing them - the tests still pass and the tasks seem to function appropriately in the sample app as well.

    opened by jbluntz 3
  • Configuration cache pt1

    Configuration cache pt1

    This PR is the part 1 (first 5 commits) of #51 to simplify reviewing and merging.

    1. more test cases added to be more confident when making large changes and refactoring afterwards.
    2. usage of project property during task's execution (@TaskAction) breaks configuration cache. That's why project.path is now saved while configuring the task in DependencyGuardTreeDiffer.
    3. checks from ConfigurationValidators are now called in tasks's configurationAction instead of @TaskAction to avoid breaking configuration cache (these validations use project property).
    4. gradle version is bumped to 7.5.1.

    Like in original PR, changes are split to commits to simplify review.

    opened by qwert2603 2
  • Tasks not compatible with the configuration cache

    Tasks not compatible with the configuration cache

    First of all, this is not surprising, as dependency analysis in general is not well-supported by the configuration cache (CC). However, since I'm adding this to my project, I want an issue to refer to explain why CC has been disabled for these tasks.

    I'm using the runtime API for this, available since Gradle 7.4. This looks like:

    tasks.named('dependencyGuard') {
      notCompatibleWithConfigurationCache('https://github.com/dropbox/dependency-guard/issues/4')
    }
    

    I'm not sure which versions of Gradle you want to support, but you might bake this in with something like

    tasks.register(...) {
      if (GradleVersion.current() > GradleVersion.version("7.4")) {
        notCompatibleWithConfigurationCache('https://github.com/dropbox/dependency-guard/issues/4')
      }
    }
    

    And of course, ideally, one day, this could be compatible out of the box.

    opened by autonomousapps 2
  • Add tests for various Gradle versions

    Add tests for various Gradle versions

    Now tests in dependency-guard/gradleTest run only against GradleVersion.current(). It would be great to add parameterized tests to test against various Gradle versions. It will ensure support for older versions as well as newer.

    As have been mentioned in #51.

    Also check GradleVersion. isAtLeast73 can be removed if we decide to remove Gradle 7.3 support.

    opened by qwert2603 0
  • Better integration with renovate

    Better integration with renovate

    So I use renovate to keep my dependencies up to date. Like here: https://github.com/vanniktech/TextBuilder/pull/77 material version was updated, but then the task from dependency guard files since obviously that version hasn't been updated properly.

    Slightly off-topic but: Do you happen to know if there is way in which we can feed the text files into renovate so that it'll just do a search + replace?

    That would solve the problem of regenerating the file manually and pushing, while still catching regressions in case a new version has been introduced.

    opened by vanniktech 2
  • Feature request: filter out BOM artifacts

    Feature request: filter out BOM artifacts

    These are not actually a real dependency, rather they just dictate the version of other dependencies owned by it. This leaks across buildscript classpaths too. For example - buildscript { dependencies { classpath(platform(libs.coroutines.bom)) } } will make this show up as a configuration dependency in all subprojects using dependencyGuard as well even if they don't use coroutines. My sense here is that it should filter these.

    opened by ZacSweers 3
Releases(0.3.2)
  • 0.3.2(Sep 14, 2022)

    What's Changed

    • 0.3.2-SNAPSHOT Version Bump for Development by @handstandsam in https://github.com/dropbox/dependency-guard/pull/42
    • update JWDependencyTreeDiff by @qwert2603 in https://github.com/dropbox/dependency-guard/pull/47
    • Dependency Tree Diff Output - Dynamic messaging for the correct project. by @handstandsam in https://github.com/dropbox/dependency-guard/pull/49
    • Version 0.3.2 by @handstandsam in https://github.com/dropbox/dependency-guard/pull/50

    New Contributors

    • @qwert2603 made their first contribution in https://github.com/dropbox/dependency-guard/pull/47

    Full Changelog: https://github.com/dropbox/dependency-guard/compare/0.3.1...0.3.2

    Source code(tar.gz)
    Source code(zip)
  • 0.3.1(Sep 14, 2022)

    What's Changed

    • Don't apply the LifecycleBasePlugin if already applied by @ZacSweers in https://github.com/dropbox/dependency-guard/pull/36
    • Backport of the String.capitalized() extension for Pre Gradle 7.4. Fixes #40 by @handstandsam in https://github.com/dropbox/dependency-guard/pull/41

    New Contributors

    • @ZacSweers made their first contribution in https://github.com/dropbox/dependency-guard/pull/36

    Full Changelog: https://github.com/dropbox/dependency-guard/compare/0.3.0...0.3.1

    Source code(tar.gz)
    Source code(zip)
  • 0.3.0(Sep 14, 2022)

    What's Changed

    • Fixes #32 - Remove unused Kotlin dependencies. by @handstandsam in https://github.com/dropbox/dependency-guard/pull/33
    • Attached "dependencyGuard" to the "check" task. Fixes #30 by @handstandsam in https://github.com/dropbox/dependency-guard/pull/34

    Full Changelog: https://github.com/dropbox/dependency-guard/compare/0.2.0...0.3.0

    Source code(tar.gz)
    Source code(zip)
  • 0.2.0(Sep 14, 2022)

    What's Changed

    • Fixed error messaging and added tests. by @handstandsam in https://github.com/dropbox/dependency-guard/pull/11
    • Set artifacts=true and modules=false as the default config. by @handstandsam in https://github.com/dropbox/dependency-guard/pull/17
    • Opt all tasks out of tracking and configuration caching. by @autonomousapps in https://github.com/dropbox/dependency-guard/pull/20
    • baselineMap - Modify a dependency name or remove it (by returning null) from the baseline file by @handstandsam in https://github.com/dropbox/dependency-guard/pull/18
    • Renamed allowRule -> allowedFilter by @handstandsam in https://github.com/dropbox/dependency-guard/pull/21
    • Uses file:///something.txt syntax now! by @handstandsam in https://github.com/dropbox/dependency-guard/pull/24
    • Cleanup of Diffing Code by @handstandsam in https://github.com/dropbox/dependency-guard/pull/22

    Full Changelog: https://github.com/dropbox/dependency-guard/compare/0.1.0...0.2.0

    Source code(tar.gz)
    Source code(zip)
  • 0.1.0(Sep 14, 2022)

    What's Changed

    • Use Action instead of Kotlin lambda. by @autonomousapps in https://github.com/dropbox/dependency-guard/pull/5
    • Removing Extra Gradle Wrapper by @handstandsam in https://github.com/dropbox/dependency-guard/pull/6
    • Moving dependency-guard-gradle-plugin -> dependency-guard to save keystrokes. by @handstandsam in https://github.com/dropbox/dependency-guard/pull/7
    • Improve test coverage. by @autonomousapps in https://github.com/dropbox/dependency-guard/pull/8
    • Use domain object container to avoid afterEvaluate. by @autonomousapps in https://github.com/dropbox/dependency-guard/pull/9
    • Configuration: isAllowed -> allowRule by @handstandsam in https://github.com/dropbox/dependency-guard/pull/10

    Full Changelog: https://github.com/dropbox/dependency-guard/commits/0.1.0

    Source code(tar.gz)
    Source code(zip)
Owner
Dropbox
Dropbox
Gradle Plugin to automatically upgrade your gradle project dependencies and send a GitHub pull request with the changes

Gradle Plugin to automatically upgrade your gradle project dependencies and send a GitHub pull request with the changes

Dipien 142 Dec 29, 2022
Gradle plugin to ease Kotlin IR plugin development and usage in multimodule gradle projects

Gradle plugin to ease Kotlin IR plugin development and usage in multimodule gradle projects. Former: kotlin-ir-plugin-adapter-gradle

null 2 Mar 8, 2022
A Gradle plugin that generates plugin.yml for Bukkit/BungeeCord/Nukkit plugins based on the Gradle project

plugin-yml is a simple Gradle plugin that generates the plugin.yml plugin description file for Bukkit plugins, bungee.yml for Bungee plugins or nukkit.yml for Nukkit plugins based on the Gradle project. Various properties are set automatically (e.g. project name, version or description) and additional properties can be added using a simple DSL.

Plexus 0 Apr 10, 2022
Gradle plugin which validates the licenses of your dependency graph match what you expect

Gradle plugin which validates the licenses of your dependency graph match what you expect

Cash App 502 Dec 31, 2022
Graphfity is a Gradle Plugin which creates a dependency node diagram graph about your internal modules dependencies, specially useful if you are developing a multi-module application

Graphfity creates a dependency nodes diagram graph about your internal modules dependencies, specially useful if you are developing a multi-module app

Iván Carrasco 27 Dec 20, 2022
Gradle Plugin that allows you to decompile bytecode compiled with Jetpack Compose Compiler Plugin into Java and check it

decomposer Gradle Plugin that allows you to decompile bytecode compiled with Jetpack Compose Compiler Plugin into Java and check it How to use Run bui

Takahiro Menju 56 Nov 18, 2022
Gradle Plugin to enable auto-completion and symbol resolution for all Kotlin/Native platforms.

CompleteKotlin Gradle Plugin to enable auto-completion and symbol resolution for all Kotlin/Native platforms. What this plugin provides This zero-conf

Louis CAD 235 Jan 3, 2023
A Gradle plugin that improves the experience when developing Android apps, especially system tools, that use hidden APIs.

A Gradle plugin that improves the experience when developing Android apps, especially system tools, that use hidden APIs.

Rikka apps 124 Dec 31, 2022
Grazel is a Gradle plugin to automate generation of valid Bazel files for a given Android/Kotlin/Java project.

Grazel Grazel stands for Gradle to Bazel. It is a Gradle plugin that enables you to migrate Android projects to Bazel build system in an incremental a

Grab 228 Jan 2, 2023
Gradle Plugin for Continuous Integration of AppSweep App Testing.

This Gradle plugin can be used to continuously integrate app scanning using AppSweep into your Android app build process

Guardsquare 28 Nov 13, 2022
Artifactory is a gradle plugin to assist in developing Minecraft mods that can target different modloaders.

Artifactory Artifactory is a gradle plugin to assist in developing Minecraft mods that can target different modloaders. Currently, Fabric and Forge ar

Zach Kozar 3 Dec 29, 2021
A Gradle Plugin to determine which modules were affected by a set of files in a commit.

A Gradle Plugin to determine which modules were affected by a set of files in a commit. One use case for this plugin is for developers who would like to only run tests in modules which have changed in a given commit.

Dropbox 491 Dec 23, 2022
EasyVersion is a Gradle plugin that manage your app or library version.

EasyVersion EasyVersion is a Gradle plugin that manage your app or library version. Before Downloading Create easy_version.json in your root project d

Kosh Sergani 8 Nov 26, 2022
Android Gradle Plugin -- Auto Check big image and compress image in building.

McImage I will continue to update, please rest assured to use 中文文档 Android优雅的打包时自动化获取全部res资源 McImage is a Non-invasive plugin for compress all res in

smallSohoSolo 1.1k Dec 28, 2022
Gradle plugin that generates a Swift Package Manager manifest and an XCFramework to distribute a Kotlin Multiplatform library for Apple platforms.

Multiplatform Swift Package This is a Gradle plugin for Kotlin Multiplatform projects that generates an XCFramework for your native Apple targets and

Georg Dresler 262 Jan 5, 2023
A Gradle plugin helps to proxy all the BuildConfig fields in the Android project.

Readme A Gradle plugin helps to proxy all the BuildConfig fields in the Android project. Background In android BuildConfig, We might have different co

Jack Chen 4 Oct 11, 2021
Gradle Plugin for publishing artifacts to Sonatype and Nexus

Introduction Due to Sonatype's strict validation rules, the publishing requirement must be satisfied by every artifact which wants to be published to

Johnson Lee 21 Oct 14, 2022
Gradle-i18n-plugin is meant for generating a JSON file with internationalized texts, based on a Google Sheet.

Gradle-i18n-plugin Gradle-i18n-plugin is meant for generating a JSON file with internationalized texts, based on a Google Sheet. The plugin assumes th

Acto ApS 2 Oct 11, 2021
Gradle plugin that parses version updates and assigns them to groups of people.

Notifier Gradle Plugin This gradle plugin serves the need of automating how dependencies are handles in a project. More specifically, it functions usi

Plum 4 Oct 27, 2022