Coder Social home page Coder Social logo

affectedmoduledetector's Introduction

Affected Module Detector

Maven Central

Build Status

codecov

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.

Overview

The AffectedModuleDetector will look at the last commit and determine which files have changed, it will then build a dependency graph of all the modules in the project. The detector exposes a set of APIs which can be used to determine whether a module was considered affected.

Git

The module detector assumes that it is being applied to a project stored in git and a git client is present on the system. It will query the last commit on the current branch to determine the list of files changed.

Dependency Tracker

The tracker will evaluate the project and find all modules and their dependencies for all configurations.

Affected Module Detector

The detector allows for three options for affected modules:

  • Changed Projects: These are projects which had files changed within them โ€“ enabled with -Paffected_module_detector.changedProjects)
  • Dependent Projects: These are projects which are dependent on projects which had changes within them โ€“ enabled with -Paffected_module_detector.dependentProjects)
  • All Affected Projects: This is the union of Changed Projects and Dependent Projects (this is the default configuration)

These options can be useful depending on how many tests your project has and where in the integration cycle you would like to run them. For example, Changed Projects may be a good options when initially sending a Pull Requests, and All Affected Projects may be useful to use when a developer merges their pull request.

The detector exposes APIs which will be helpful for your plugin to use. In particular, it exposes:

  • AffectedModuleDetector.configureTaskGuard - This will apply an onlyIf guard on your task and can be called either during configuration or execution
  • AffectedModuleDetector.isProjectAffected - This will return a boolean if the project has been affected. It can only be called after the project has been configured.

In the example below, we're showing a hypothetical project graph and what projects would be considered affected if the All Affected Projects option was used and a change was made in the :networking module.

Installation

// settings.gradle(.kts)
pluginManagement {
  repositories {
    mavenCentral()
    gradlePluginPortal()
  }
}

// root build.gradle(.kts)
plugins {
  id("com.dropbox.affectedmoduledetector") version "<latest-version>"
}

Note that the plugin is currently published to Maven Central, so you need to add it to the repositories list in the pluginsManagement block.

Alternatively, it can be consumed via manual buildscript dependency + plugin application.

Apply the project to the root build.gradle:

buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath "com.dropbox.affectedmoduledetector:affectedmoduledetector:<LATEST_VERSION>"
  }
}
//rootproject
apply plugin: "com.dropbox.affectedmoduledetector"

If you want to develop a plugin using the APIs, add this to your buildSrc's dependencies list:

implementation("com.dropbox.affectedmoduledetector:affectedmoduledetector:<LATEST_VERSION>")

Configuration

You can specify the configuration block for the detector in the root project:

affectedModuleDetector {
    baseDir = "${project.rootDir}"
    pathsAffectingAllModules = [
            "buildSrc/"
    ]
    logFilename = "output.log"
    logFolder = "${project.rootDir}/output"
    compareFrom = "PreviousCommit" //default is PreviousCommit
    excludedModules = [
        "sample-util", ":(app|library):.+"
    ]
    ignoredFiles = [
        ".*\\.md", ".*\\.txt", ".*README"
    ]
    includeUncommitted = true
    top = "HEAD"
    customTasks = [
        new AffectedModuleConfiguration.CustomTask(
            "runDetektByImpact",
            "detekt",
            "Run static analysis tool without auto-correction by Impact analysis"
        )
    ]
}
  • baseDir: The root directory for all of the pathsAffectingAllModules. Used to validate the paths exist.
  • pathsAffectingAllModules: Paths to files or folders which if changed will trigger all modules to be considered affected
  • logFilename: A filename for the output detector to use
  • logFolder: A folder to output the log file in
  • specifiedBranch: A branch to specify changes against. Must be used in combination with configuration compareFrom = "SpecifiedBranchCommit"
  • ignoredFiles: A set of files that will be filtered out of the list of changed files retrieved by git.
  • compareFrom: A commit to compare the branch changes against. Can be either:
    • PreviousCommit: compare against the previous commit
    • ForkCommit: compare against the commit the branch was forked from
    • SpecifiedBranchCommit: compare against the last commit of $specifiedBranch using git rev-parse approach.
    • SpecifiedBranchCommitMergeBase: compare against the nearest ancestors with $specifiedBranch using git merge base approach.

Note: specify the branch to compare changes against using the specifiedBranch configuration before the compareFrom configuration

  • excludedModules: A list of modules that will be excluded from the build process, can be the name of a module, or a regex against the module gradle path
  • includeUncommitted: If uncommitted files should be considered affected
  • top: The top of the git log to use. Must be used in combination with configuration includeUncommitted = false
  • customTasks: set of CustomTask

By default, the Detector will look for assembleAndroidDebugTest, connectedAndroidDebugTest, and testDebug. Modules can specify a configuration block to specify which variant tests to run:

affectedTestConfiguration {
     assembleAndroidTestTask = "assembleAndroidReleaseTest"
     runAndroidTestTask = "connectedAndroidReleaseTest"
     jvmTestTask = "testRelease"
}

The plugin will create a few top level tasks that will assemble or run tests for only affected modules:

  • ./gradlew runAffectedUnitTests - runs jvm tests
  • ./gradlew runAffectedAndroidTests - runs connected tests
  • ./gradlew assembleAffectedAndroidTests - assembles but does not run on device tests, useful when working with device labs

SpecifiedBranchCommit vs SpecifiedBranchCommitMergeBase

  • SpecifiedBranchCommit using git rev-parse command for getting sha.
  • SpecifiedBranchCommitMergeBase using git merge base command for getting sha.

What does it mean? When we run any AMD command we compare the current branch with the specified parent branch. Consider an example when, during the development of our feature, another developer merged his changes (9 files) into our common remote parent branch - "origin/dev".

Please, look at picture: specified_branch_difference.png

Suppose we have changed 6 files in our "feature" branch.

  1. Behaviour of SpecifiedBranchCommit: AMD will show the result that 15 files were affected. Because our branch is not updated (pull) and AMD will see our 6 files and 9 files that were merged by another developer.
  2. Behaviour of SpecifiedBranchCommitMergeBase: AMD will show the result that 6 files were affected. And this is the correct behavior.

Hence, depending on your CI settings you have to configure AMD appropriately.

Sample Usage

Running the plugin generated tasks is quite simple. By default, if affected_module_detector.enable is not set, the generated tasks will run on all the modules. However, the plugin offers three different modes of operation so that it only executes the given task on a subset of projects.

Running All Affected Projects (Changed Projects + Dependent Projects)

To run all the projects affected by a change, run one of the tasks while enabling the module detector.

./gradlew runAffectedUnitTests -Paffected_module_detector.enable

Running All Changed Projects

To run all the projects that changed, run one of the tasks (while enabling the module detector) and with -Paffected_module_detector.changedProjects

./gradlew runAffectedUnitTests -Paffected_module_detector.enable -Paffected_module_detector.changedProjects

Running All Dependent Projects

To run all the dependent projects of projects that changed, run one of the tasks (while enabling the module detector) and with -Paffected_module_detector.dependentProjects

./gradlew runAffectedUnitTests -Paffected_module_detector.enable -Paffected_module_detector.dependentProjects

Using the Sample project

To run this on the sample app:

  1. Publish the plugin to local maven:
./gradlew :affectedmoduledetector:publishToMavenLocal
  1. Try running the following commands:
cd sample
 ./gradlew runAffectedUnitTests -Paffected_module_detector.enable

You should see zero tests run. Make a change within one of the modules and commit it. Rerunning the command should execute tests in that module and its dependent modules.

Custom tasks

If you want to add a custom gradle command to execute with impact analysis you must declare AffectedModuleConfiguration.CustomTask which is implementing the AffectedModuleTaskType interface in the build.gradle configuration of your project:

// ... 

affectedModuleDetector {
     // ...
     customTasks = [
           new AffectedModuleConfiguration.CustomTask(
                   "runDetektByImpact", 
                   "detekt",
                   "Run static analysis tool without auto-correction by Impact analysis"
           )
     ]
     // ...
}

NOTE: Please, test all your custom commands. If your custom task doesn't work correctly after testing, it might be that your task is quite complex and to work correctly it must use more gradle api's. Hence, you must create buildSrc module and write a custom plugin manually like AffectedModuleDetectorPlugin

Notes

Special thanks to the AndroidX team for originally developing this project at https://android.googlesource.com/platform/frameworks/support/+/androidx-main/buildSrc/src/main/kotlin/androidx/build/dependencyTracker

License

Copyright (c) 2021 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.

affectedmoduledetector's People

Contributors

alekkras avatar blundell avatar changusmc avatar chris-mitchell avatar dependabot[bot] avatar digitalbuddha avatar edenman avatar evleaps avatar fmrsabino avatar jonnycaley avatar joshafeinberg avatar julioz avatar kozaxinan avatar kpagratis avatar mezpahlan avatar rallat avatar rharter avatar rougsig avatar victorireri avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

affectedmoduledetector's Issues

Missing excludedModules

According to the docs, we can specify excludedModules while in the sample code its using version 0.1.2-SNAPSHOT and the version available on maven central is 0.1.1, any plans to release this new version ?? This is kinda blocking us from adopting this plugin, thx in advance.

Run arbitrary tasks

Hi there!

If I understand correctly this plugin figures out where a change came from and then run all the unit tests, all the instrumentation tests, or simply build everything that falls under the scope depending on the configuration and where the change was made.

However, what if I want to run additional tasks? Say, for example, Sonarqube or other static analysis? Does this plugin support that? Or could it in the future?

Many thanks.

runAffectedUnitTests -Paffected_module_detector.enable fails on CI only

Hey,
this was mentioned before but in my case it's only failing on CI
When I run ./gradlew runAffectedUnitTests -Paffected_module_detector.enable on Bitrise it fails in my project with error:

* What went wrong:
Could not evaluate onlyIf predicate for task ':module:testDebugUnitTest'.
> Nonzero exit value running git command.

the task is working fine locally
My git project is setup on CI and i do git clone before invoking this command

Not checking modules correctly

Apologies in advance if i'm using the plugin wrong.

When running the plugin on my experimentation project it doesn't seem to be working correctly. If i make a change in a module that is an immediate dependency of the app module (either core or core-navigation) then the command ./gradlew runAffectedUnitTests -Paffected_module_detector.enable works correctly.

However, if I make a change to a module that is a transitive dependency of core (e.g. feature-pokemondetail or feature-pokemonlist) then the output of the logs does not work correctly.

Feel free to clone my repo and try for yourself. Any help appreciated ๐Ÿ˜„

Screenshot 2020-12-10 at 09 36 01

Screenshot 2020-12-10 at 09 33 59

Unable to find method affectedTestConfiguration()

I am running the sample app and changed the version to 0.1.0 since I do not able to access the snapshot version form all mavens. When trying to build the sample, I get the following build error:

Desktop-screenshot (9)

I get the same error when I try to integrate it to another project, as shown below:

Desktop-screenshot (8)

Question about AMD use

Hello ๐Ÿ‘‹

I have one question : what happens when a Pull Request has more than one commits ? For example a Pull Request might have 3 commits: Commit A changes Module_A, Commit B changes Module_B and Commit C changes Module_C. Assuming the last one was Commit C then trying to run AMD would only run tests for Module_C. From the tests I made all three options: ForkCommit, PreviousCommit and specifiedBranch will only run Module_C's tests. But ideally what we want here is to run tests for all affected modules, cause that Pull Request would affect all 3 modules I mentioned.

Is that the case or maybe I don't understand something ? If yes then how do you deal with it ? What's the "proper" way to utilise this cool library to save me some time?

Thank you!!

Exclude defined modules from being considered

Proposal: Parse a list of module names into a configuration which wont be considered by the dependency decision making process, as well as the test apk building and running.

Please let me know of any objections, otherwise I will start the work for this in the upcoming days.

excludedModules is not working with custom task.

Environment

  • macOS 12.5
  • Gradle 7.5
  • AMD 0.1.6
  • AGP 7.2.1
  • Detekt 1.21.0
  • Java 17
  • Kotlin 1.6.21

Description

I'm integrating AMD into my project and I created two custom tasks. When I run `./gradlew runAffectedDetekt', I'm getting this error even though linter is specified in the excludedModules.

* What went wrong:
Could not determine the dependencies of task ':runAffectedDetekt'.
> Task with path ':linter:detekt' not found in root project 'Rappi-Android-User'.

This error makes sense since Detekt is not applied in the linter module; however, it should be ignored by AMD if it's specified in the excluded modules.

Configuration

    import com.dropbox.affectedmoduledetector.AffectedModuleConfiguration

    apply plugin: "com.dropbox.affectedmoduledetector"

    def branch = System.getenv('DESTINATION_BRANCH')
    if (!branch) {
        branch = "origin/develop"
    }

    logger.lifecycle("Destination branch: $branch")

    affectedModuleDetector {
        baseDir = "${project.rootDir}"
        pathsAffectingAllModules = [
                "tools/global-dependencies.gradle"
        ]
        logFilename = "output.log"
        logFolder = "${project.rootDir}"
        specifiedBranch = "$branch"
        compareFrom = "SpecifiedBranchCommit"
        excludedModules = ["linter", ":linter"]
        customTasks = [
                new AffectedModuleConfiguration.CustomTask(
                        "runAffectedDetekt,
                        "detekt",
                        "Run detekt"
                ),
                new AffectedModuleConfiguration.CustomTask(
                        "runAffectedAndroidLint",
                        "lintDebug",
                        "Run lint"
                ),
        ]

Task 'runAffectedUnitTests' not found in root project

Whenever I apply the plugin to a project and run the ./gradlew runAffectedUnitTests -Paffected_module_detector.enable command, I get the following error:

FAILURE: Build failed with an exception.

* What went wrong:
Task 'runAffectedUnitTests' not found in root project 'my-project'.

Running ./gradle tasks or ./gradlew tasks -Paffected_module_detector.enable results in no tasks added by this plugin either.

I have tried with multiple Android/Java only projects which all have different configurations, but all come up with the same results. I even copy pasted this entire block into the root build.gradle (thus having two buildscript blocks) to no success.

buildscript {
  repositories {
    maven {
      url "https://plugins.gradle.org/m2/"
    }
  }
  dependencies {
    classpath "com.dropbox.affectedmoduledetector:affectedmoduledetector:0.1.0"
  }
}
apply plugin: "com.dropbox.affectedmoduledetector"

set affectedTestConfiguration globally

I cannot set like this in project root's build.gradle.kts file.

subprojects {
   affectedTestConfiguration { jvmTestTask = "test" }
}

all my subprojects are java, it would be nice if we can set this at one place instead of forced to add affectedTestConfiguration { jvmTestTask = "test" } to every subprojects

my multi-module project : https://github.com/xmlking/micro-apps

Different behaviour between CI and local machine

Hi! Please, help me with setup AMD on the CI.

According to a AMD log file a lot of modules on the CI marked as true, but on the local machine the modules marked as false.
command: ./gradlew runAffectedUnitTests -Paffected_module_detector.enable -Pci=true --scan

Environment:
Doker + GitHub actions

Changed files:
Only 1 changed file for test with minor syntax change in feature module.

Expected result: Only few modules was affected.
Actual result: 89 modules was affected on the CI.

Example:
CI machine - 89 modules affected (true) 23 modules is not affected (false)
Local machine - only 3 modules affected (true) 109 modules is not affected (false)

Hence, build time on the CI is not efficent.

build.gradle

affectedModuleDetector {
    baseDir = "${project.rootDir}"
    pathsAffectingAllModules = []
    logFolder = "${project.rootDir}/tools/impact-analysis/output"
    excludedModules = [
        "appLint"
    ]
    specifiedBranch = "origin/dev"
    compareFrom = "SpecifiedBranchCommit"
    includeUncommitted = false
}

CI: github workflow

  unit-test_by_impact:
    runs-on: [ some-build ]
    if: ${{ !startsWith(github.head_ref, 'release') }}
    steps:
      - uses: actions/checkout@v2
        with:
          ref: ${{ github.event.pull_request.head.sha }}
      - name: 'Fetch dev'
        run: git fetch origin dev:dev
      - name: 'Run unit tests by impact'
        run: |
          source ./ci/_builder_docker.sh
          runInContainer "./ci/unit_tests_by_impact.sh"
      - name: 'Upload test_by_impact log file'
        uses: actions/upload-artifact@v2
        with:
          name: test_by_impact-log-${{ github.run_number }}
          path: tools/impact-analysis/output
          retention-days: 1

Add ability to compare between two branches

Currently, the git client will only query for HEAD~1 and determine the files in the last commit.

We should add the ability to let a developer specify a branch to compare against and determine all the changed files between HEAD and that branch.

We should configure this in the extension and let the user decide which option to use and the branch to compare against.

`assembleAffectedAndroidTests` assembles APKs for all modules

Hello!

I am running into an issue where calling

gradle assembleAffectedAndroidTests -Paffected_module_detector.enabled

assembles the APKs for all modules.

Based on the documentation, I am under the assumption that this task should generate test apks for only the affected module(s).

Here are the configurations I have set up:

affectedModuleDetector {
    baseDir = "${project.rootDir}"
    logFolder = "${project.rootDir}/artifacts"
    specifiedBranch = "${base_branch}"
    compareFrom = 'SpecifiedBranchCommit'
}

affectedTestConfiguration {
    assembleAndroidTestTask = 'assembleStandardDebugAndroidTest'
    runAndroidTestTask = 'assembleStandardDebugAndroidTest'
    jvmTestTask = 'testStandardDebugUnitTest'
}

Cross-referencing the logs and the apk output, I see that

[INFO] [amd] checking whether I should include :lib:featuretoggle and my answer is false

but

./lib/featuretoggle/build/outputs/apk/androidTest/standard/debug/featuretoggle-standard-debug-androidTest.apk

is still being generated.

Please let me know if this is the expected behavior or if I am doing something wrong. Thank you!

Tests being run for all projects

I set up the plugin by adding this to my top-level build.gradle:

apply plugin: "com.dropbox.affectedmoduledetector"

affectedModuleDetector {
    baseDir = "${project.rootDir}"
    specifiedBranch = "tmp2"
    compareFrom = "SpecifiedBranchCommit"
    includeUncommitted = false
    logFilename = "output.log"
    logFolder = "${project.rootDir}/output"
}
subprojects {
    affectedTestConfiguration {
        jvmTestTask = "foo"
    }
    task foo() {
        println "${project.name} running tests"
    }
}

Then made a code change to the gradle module called :monographs, so that HEAD diffs with tmp2 to show only this code change.

Then I run: ./gradlew runAffectedUnitTests -Paffected_module_detector.enable -Paffected_module_detector.changedProjects

which gives an output of:

> Configure project :
app running tests
articles running tests
authlib running tests
monographs running tests
pharma running tests
platform running tests
rteditor running tests
search running tests
shared-api running tests
shared-contract running tests
shared-test running tests
shared-ui running tests
snippets running tests
SwipeLayout running tests

> Configure project :app
WARNING:DSL element 'dexOptions' is obsolete and should be removed.
It will be removed in version 8.0 of the Android Gradle plugin.
Using it has no effect, and the AndroidGradle plugin optimizes dexing automatically.
The Hilt configuration option 'enableTransformForLocalTests' is no longer necessary when com.android.tools.build:gradle:4.2.0+ is used.
...

So, tests are being run for every module in my project! I would expect to see only monographs running tests, and no others in the output.

Here's my log file from AMD:

Details
[INFO] [amd] Modules provided: null
[INFO] [amd] Using real detector with CHANGED_PROJECTS
[INFO] [amd] running command git rev-parse tmp2 in /home/hin/code/android-avocado
[INFO] [amd] Response: 47a71a0bfe4891534a49826a9f25726dddb7de47

[INFO] [amd] running command git --no-pager diff --name-only -M95 HEAD..47a71a0bfe4891534a49826a9f25726dddb7de47 in /home/hin/code/android-avocado
[INFO] [amd] Response: monographs/src/main/java/de/miamed/amboss/monograph/feedback/MonographFeedbackConfig.kt

[INFO] [amd] Paths affecting all modules: []
[INFO] [amd] initializing ProjectGraph
[INFO] [amd] creating node for :app
[INFO] [amd] relative path: app , sections: [app]
[INFO] [amd] creating node for :articles
[INFO] [amd] relative path: articles , sections: [articles]
[INFO] [amd] creating node for :authlib
[INFO] [amd] relative path: authlib/lib , sections: [authlib, lib]
[INFO] [amd] creating node for :monographs
[INFO] [amd] relative path: monographs , sections: [monographs]
[INFO] [amd] creating node for :pharma
[INFO] [amd] relative path: pharma , sections: [pharma]
[INFO] [amd] creating node for :platform
[INFO] [amd] relative path: platform , sections: [platform]
[INFO] [amd] creating node for :rteditor
[INFO] [amd] relative path: rteditor/RTEditor , sections: [rteditor, RTEditor]
[INFO] [amd] creating node for :search
[INFO] [amd] relative path: search , sections: [search]
[INFO] [amd] creating node for :shared-api
[INFO] [amd] relative path: shared-api , sections: [shared-api]
[INFO] [amd] creating node for :shared-contract
[INFO] [amd] relative path: shared-contract , sections: [shared-contract]
[INFO] [amd] creating node for :shared-test
[INFO] [amd] relative path: shared-test , sections: [shared-test]
[INFO] [amd] creating node for :shared-ui
[INFO] [amd] relative path: shared-ui , sections: [shared-ui]
[INFO] [amd] creating node for :snippets
[INFO] [amd] relative path: snippets , sections: [snippets]
[INFO] [amd] creating node for :SwipeLayout
[INFO] [amd] relative path: AndroidSwipeLayout/library , sections: [AndroidSwipeLayout, library]
[INFO] [amd] finished creating ProjectGraph com.dropbox.affectedmoduledetector.ProjectGraph$Node@106a7c49
[INFO] [amd] finding containing project for monographs/src/main/java/de/miamed/amboss/monograph/feedback/MonographFeedbackConfig.kt , sections: [monographs, src, main, java, de, miamed, amboss, monograph, feedback, MonographFeedbackConfig.kt]
[INFO] [amd] finding [monographs, src, main, java, de, miamed, amboss, monograph, feedback, MonographFeedbackConfig.kt] with index 0 in root
[INFO] [amd] finding [monographs, src, main, java, de, miamed, amboss, monograph, feedback, MonographFeedbackConfig.kt] with index 1 in :monographs
[INFO] [amd] no child found, returning :monographs
[INFO] [amd] search result for monographs/src/main/java/de/miamed/amboss/monograph/feedback/MonographFeedbackConfig.kt resulted in :monographs
[INFO] [amd] For file monographs/src/main/java/de/miamed/amboss/monograph/feedback/MonographFeedbackConfig.kt containing project is project ':monographs'. Adding to changedProjects.
[INFO] [amd] checking whether I should include :app and my answer is false
[INFO] [amd] checking whether I should include :authlib and my answer is false
[INFO] [amd] checking whether I should include :articles and my answer is false
[INFO] [amd] checking whether I should include :monographs and my answer is true
[INFO] [amd] checking whether I should include :search and my answer is falsealse
[INFO] [amd] checking whether I should include :rteditor and my answer is false
[INFO] [amd] checking whether I should include :pharma and my answer is false
[INFO] [amd] checking whether I should include :shared-contract and my answer is false
[INFO] [amd] checking whether I should include :shared-test and my answer is false
[INFO] [amd] checking whether I should include :shared-ui and my answer is false
[INFO] [amd] checking whether I should include :snippets and my answer is false
[INFO] [amd] checking whether I should include :SwipeLayout and my answer is false

Is there something I am missing here?

Task '.enable' not found in root project 'my-multi-module-project'.

I'm not able to use this plugin like described as I'm running into the issue from the title as soon as the parameters are attached to my gradle command.

this is my root build.gradle

plugins {
    id "com.dropbox.affectedmoduledetector" version "0.1.5"
}

apply plugin: 'java'
apply plugin: 'idea'

affectedModuleDetector {
    baseDir = "${project.rootDir}"
    pathsAffectingAllModules = []
    logFolder = "${project.rootDir}/tools/impact-analysis/output"
    specifiedBranch = "reactive"
    compareFrom = "SpecifiedBranchCommit"
    includeUncommitted = false
}

settings.gradle

pluginManagement {
    repositories {
        mavenLocal()
        mavenCentral()
        gradlePluginPortal()
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }
    plugins {
        id "${quarkusPluginId}" version "${quarkusPluginVersion}"
        id "com.nike.pdm.localstack" version "1.0.0"
    }
}

The logs

PS C:\Users\My-PC\Workspace\my-multi-module-project> ./gradlew build -Paffected_module_detector.enable                 

Welcome to Gradle 7.4.2!

Here are the highlights of this release:
 - Aggregated test and JaCoCo reports
 - Marking additional test source directories as tests in IntelliJ
 - Support for Adoptium JDKs in Java toolchains

For more details see https://docs.gradle.org/7.4.2/release-notes.html


FAILURE: Build failed with an exception.

* What went wrong:
Task '.enable' not found in root project 'my-multi-module-project'.

* Try:
> Run gradlew tasks to get a list of available tasks.
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 2s
PS C:\Users\My-PC\Workspace\my-multi-module-project> 

What am I missing?

runAffectedUnitTests -Paffected_module_detector.enable failed for onlyIf

When run ./gradlew runAffectedUnitTests -Paffected_module_detector.enable it fails in my project with error:

* What went wrong:
Could not evaluate onlyIf predicate for task ':somemodule:testDebugUnitTest'.
> Nonzero exit value running git command.

Stacktrace

* Exception is:
org.gradle.api.GradleException: Could not evaluate onlyIf predicate for task ':api:somemodule:testDebugUnitTest'.
        at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:46)
        at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:79)
        at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:79)
        at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
        at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:74)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:408)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:395)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:388)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:374)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:127)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:191)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:182)
        at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:124)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
        at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
        at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
        at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.lang.IllegalStateException: Nonzero exit value running git command.
        at com.dropbox.affectedmoduledetector.GitClientImpl$RealCommandRunner.execute(GitClient.kt:162)
        at com.dropbox.affectedmoduledetector.GitClientImpl$RealCommandRunner.executeAndParse(GitClient.kt:166)
        at com.dropbox.affectedmoduledetector.GitClientImpl$RealCommandRunner.executeAndParseFirst(GitClient.kt:176)
        at com.dropbox.affectedmoduledetector.commitshaproviders.ForkCommit.get(ForkCommit.kt:8)
        at com.dropbox.affectedmoduledetector.GitClientImpl.findChangedFiles(GitClient.kt:80)
        at com.dropbox.affectedmoduledetector.GitClient$DefaultImpls.findChangedFiles$default(GitClient.kt:31)
        at com.dropbox.affectedmoduledetector.AffectedModuleDetectorImpl.findChangedProjects(AffectedModuleDetector.kt:403)
        at com.dropbox.affectedmoduledetector.AffectedModuleDetectorImpl.access$findChangedProjects(AffectedModuleDetector.kt:314)
        at com.dropbox.affectedmoduledetector.AffectedModuleDetectorImpl$changedProjects$2.invoke(AffectedModuleDetector.kt:354)
        at com.dropbox.affectedmoduledetector.AffectedModuleDetectorImpl$changedProjects$2.invoke(AffectedModuleDetector.kt:314)
        at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
        at com.dropbox.affectedmoduledetector.AffectedModuleDetectorImpl.getChangedProjects(AffectedModuleDetector.kt)
        at com.dropbox.affectedmoduledetector.AffectedModuleDetectorImpl.findAffectedProjects(AffectedModuleDetector.kt:462)
        at com.dropbox.affectedmoduledetector.AffectedModuleDetectorImpl.access$findAffectedProjects(AffectedModuleDetector.kt:314)
        at com.dropbox.affectedmoduledetector.AffectedModuleDetectorImpl$affectedProjects$2.invoke(AffectedModuleDetector.kt:350)
        at com.dropbox.affectedmoduledetector.AffectedModuleDetectorImpl$affectedProjects$2.invoke(AffectedModuleDetector.kt:314)
        at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
        at com.dropbox.affectedmoduledetector.AffectedModuleDetectorImpl.getAffectedProjects(AffectedModuleDetector.kt)
        at com.dropbox.affectedmoduledetector.AffectedModuleDetectorImpl.shouldInclude(AffectedModuleDetector.kt:366)
        at com.dropbox.affectedmoduledetector.AffectedModuleDetector$Companion.isProjectAffected(AffectedModuleDetector.kt:254)
        at com.dropbox.affectedmoduledetector.AffectedModuleDetectorPlugin$withPlugin$1$$special$$inlined$let$lambda$1$1.isSatisfiedBy(AffectedModuleDetectorPlugin.kt:98)
        at com.dropbox.affectedmoduledetector.AffectedModuleDetectorPlugin$withPlugin$1$$special$$inlined$let$lambda$1$1.isSatisfiedBy(AffectedModuleDetectorPlugin.kt:36)
        at org.gradle.api.specs.AndSpec.isSatisfiedBy(AndSpec.java:50)
        at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:44)
        ... 30 more

Add an option to exclude jvm test paths

Hey,

Could it be possible to add support to exclude certain path(s) from jvm test execution?

There is a exclude(Iterable<String> excludes) function in org.gradle.api.tasks.testing.Test class for it.

Use case:
We need it to avoid running e.g. paparazzi screenshot tests as part of main unit test task launched by runAffectedUnitTests task.

Up until now we use command line parameter:

-PexcludeTests="**/screenshots/**, **/another_excluded_path/**" That is being read and applied in testOptions.unitTest block

Fix link in README to AndroidX project

Issue

The link to the original AndroidX project in the README leads to a 404 Not Found error.
https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/buildSrc/src/main/kotlin/androidx/build/dependencyTracker

Reason

This is because the androidx-master-dev branch was replaced by androidx-main:
https://android.googlesource.com/platform/frameworks/support/+/c19a44147515bcc4404409e27db5de4e6e9aa73c

Solution

The link should be updated to:
https://android.googlesource.com/platform/frameworks/support/+/androidx-main/buildSrc/src/main/kotlin/androidx/build/dependencyTracker


Side note: I found this repo via this Dropbox blog which also has the old link. ๐Ÿ˜‰

runAffectedUnitTests still runs all tests!

git status     
On branch develop
Your branch is up to date with 'origin/develop'.

nothing to commit, working tree clean

my configuration

affectedModuleDetector {
    baseDir = "${project.rootDir}"
    pathsAffectingAllModules = setOf("gradle/libs.versions.toml")
    logFilename = "output.log"
    logFolder = "${rootProject.buildDir}/affectedModuleDetector"
    specifiedBranch = "develop"
    compareFrom = "SpecifiedBranchCommit" // default is PreviousCommit
}

./gradlew runAffectedUnitTests -Paffected_module_detector.enable

runs all tests
I am using org.gradle.parallel=true. is this a problem?

How to reproduce: please clone repo: https://github.com/xmlking/micro-apps and run above command.

Feature request: Need support for includeBuild

More details about includeBuild here

Include build can be used as dependency, but for gradle that kind of dependency is external dependency.
ProjectGraph can't work correct with includeBuild dependencies. For ProjectGraph includeBuild dependencies is external dependencies, not project dependencies. In that case not all modules can be found, and marked as unknownFiles.

Consider renaming project

AffectedModuleDetector is a lot of syllables and could be something more concise while still descriptive.

DropTask has been a suggestion.

Open to other suggestions as well.

AffectedModuleDetectorPlugin is eagerly creating tasks at configuration time

This plugin still needs to be updated to support the Gradle task configuration avoidance APIs documented here:

https://docs.gradle.org/current/userguide/task_configuration_avoidance.html

Some examples of the use of the old/wrong APIs can be found in filterAndroidTests:

    private fun filterAndroidTests(project: Project) {
        val tracker = DependencyTracker(project, null)
        project.tasks.all { task ->
        ...

...and filterJvmTests:

        project.tasks.withType(Test::class.java) { task ->
            AffectedModuleDetector.configureTaskGuard(task)
        }

As a result of the above, our large project is eagerly creating thousands of testing tasks at configuration time.

The migration guide may be useful as a resource.

Plugin doesn't work for java services.

Hey Team,

I am trying to apply the plugin for one of our repos which mainly consists of java libraries and applications. The libraries have java-library plugin applied. However, the applications have only java plugin applied.

So the affecttedModule detector won't run the tests of applications as part of runAffectedUnitTests .

I propose to change the implementation to support java applications as well.

This can be achieved by changing this piece of code from :

`                val pluginIds = setOf("com.android.application", "com.android.library", "java-library", "kotlin")
                
                pluginIds.forEach { pluginId ->

                    if (pluginId == "java-library" || pluginId == "kotlin")`
                    .
                    .
                    .
                    }

to

                val pluginIds = setOf("com.android.application", "com.android.library", "java", "kotlin")

                pluginIds.forEach { pluginId ->

                    if (pluginId == "java" || pluginId == "kotlin")
                    .
                    .
                    .
                    
                    }

in AffectedModuleDetectorPlugin class.

Also, what is the process for raising a PR? Thank you so much for this lovely library/plugin.

Support ForkCommit with rebasing

"ForkCommit" as I understand it currently requires a merge commit in order to determine the parent branch. However if a project is practicing rebasing as a way of working and merging work, then there will be no merge commits. Not sure what the solution to this would be off the top of my head, just pointing out this is an issue.

variantToTest is not implemented

I am playing around with this plugin and it seems like the variantToTest that is mentioned in the README isn't actually implemented.

affectedTestConfiguration{
   variantToTest = "debug" //default is debug
}

You can see variantToTest isn't defined in AffectedTestConfiguration. However, you can see an example of this in the sample app AffectedTasksPlugin

To reproduce, you can try adding variantToTest in one of the sample/build.gradle files such as https://github.com/dropbox/AffectedModuleDetector/blob/main/sample/sample-app/build.gradle#L8-L10. I actually run into a compile error when I try to build after adding variantToTest.

I am working on making changes so this works but so far, still only seeing the tests for debug variant running.

I changed the AffectedTestConfiguration to:

open class AffectedTestConfiguration {

    var variantToTest : String = "debug"
    var assembleAndroidTestTask : String? = "assemble${variantToTest.capitalize()}AndroidTest"
    var runAndroidTestTask : String?  = "connected${variantToTest.capitalize()}AndroidTest"
    var jvmTestTask : String? = "test${variantToTest.capitalize()}UnitTest"


    companion object {
        const val name = "affectedTestConfiguration"
    }
}

Any pointers? Would love to make a PR with the fix.

Error when adding to plugins {} block.

Hi,

I am looking to set this up on our project and also leverage it for what modules to run lint for via custom plugin.

Ideally, I would include this as part of our existing plugins block in root/build.gradle:

plugins {
    ...
    ...
    id "com.dropbox.affectedmoduledetector" version "0.1.0.2"
}

The above results in an error (see below) but if I define the version as 0.1.0 it works. However, that's an older version.

Build file '/Users/michael.carrano/Development/android-wwmobile/build.gradle' line: 45

Plugin [id: 'com.dropbox.affectedmoduledetector', version: '0.1.0.2'] was not found in any of the following sources:

* Try:
Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Exception is:
org.gradle.api.plugins.UnknownPluginException: Plugin [id: 'com.dropbox.affectedmoduledetector', version: '0.1.0.2'] was not found in any of the following sources:

- Gradle Core Plugins (plugin is not in 'org.gradle' namespace)
- Plugin Repositories (could not resolve plugin artifact 'com.dropbox.affectedmoduledetector:com.dropbox.affectedmoduledetector.gradle.plugin:0.1.0.2')
  Searched in the following repositories:
    Gradle Central Plugin Repository
	...

It looks like there are a few versions missing from https://plugins.gradle.org/plugin/com.dropbox.affectedmoduledetector and I'm not familiar with how to publish them there.

Compare that to the versions listed at maven central: https://search.maven.org/artifact/com.dropbox.affectedmoduledetector/affectedmoduledetector

For the time being, using the apply plugin method defined in the README works for me.

See if we can make tasks cacheable

This will be a tracking ticket. In 0.1.6 we will be marking those tasks as not cacheable but would like to investigate if it's possible to somehow do this. Our current problem is this stack trace

Caused by: org.gradle.api.UnknownDomainObjectException: Extension with name 'AffectedModuleDetectorPlugin' does not exist. Currently registered extension names: [ext]
        at org.gradle.internal.extensibility.ExtensionsStorage.unknownExtensionException(ExtensionsStorage.java:144)
        at org.gradle.internal.extensibility.ExtensionsStorage.getByName(ExtensionsStorage.java:123)
        at org.gradle.internal.extensibility.DefaultConvention.getByName(DefaultConvention.java:174)
        at com.dropbox.affectedmoduledetector.AffectedModuleDetector$Companion.getInstance(AffectedModuleDetector.kt:195)
        at com.dropbox.affectedmoduledetector.AffectedModuleDetector$Companion.getOrThrow(AffectedModuleDetector.kt:200)
        at com.dropbox.affectedmoduledetector.AffectedModuleDetector$Companion.configureTaskGuard$lambda-3(AffectedModuleDetector.kt:233)
        at org.gradle.api.specs.AndSpec.isSatisfiedBy(AndSpec.java:50)
        at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:44)
        ... 24 more

As noted in this slack message on the gradle slack, the apply method is not called on plugins which cached so the call in our onlyIf block to check if a project is enabled and affected no longer works. If we could move that check into the task possibly rather then the plugin maybe this is doable

Allow modules to specify which tasks to run

Using an extension, allow each module to specify which task it would like to run when executing tests.

At the moment, the plugin may not find tests associated with java only modules, and subsequently, not run any tests in those modules.

Build failure for task `createDebugAndroidTestApkListingFileRedirect` upon upgrading to `gradle plugin 7.1.2`

Hello!

I am noticing failures when running ./gradlew assembleAffectedAndroidTests ... when updating gradle plugin to version 7.1.2. It looks like the task createDebugAndroidTestApkListingFileRedirect fails for modules that are marked to skip apk generation.

Here's the failure output:

A problem was found with the configuration of task ':lib:core:createStandardDebugAndroidTestApkListingFileRedirect' (type 'ListingFileRedirectTask').
  - In plugin 'com.android.internal.version-check' type 'com.android.build.gradle.internal.tasks.ListingFileRedirectTask' property 'listingFile' specifies file '/Users/runner05/actions-runner/_work/android/android/lib/core/build/outputs/apk/androidTest/standard/debug/output-metadata.json' which doesn't exist.

Include uncommitted code

Currently the command used when including uncommitted code to be compared to a commit is (seen here):

git --no-pager diff --name-only HEAD..$sha

this is incorrect as HEAD points to the last commit on the branch and therefore does not include uncommitted changes.

The command you are looking for is:

git --no-pager diff --name-only $sha

I can put a fix in after my PR is merged

Support exotic non standard Gradle project structures

Carrying on the discussion from here. I plan to raise a PR to support exotic / non standard Gradle projects. These are defined by Gradle projects whose sub projects are not descendants of the root project (in the File system sense). This is a non standard, but valid, layout in the Gradle sense.

Yes it is an edge case but there are no special instructions in AMD to state compatibility of Gradle project layouts so I feel we should support these, still valid, set ups.

A "traditional" Gradle project will look like this:

my-app <-- projectRootDir & gitRootDir
|
-- .git
|
-- module-1
|
-- module-2
|
-- module-3
|
-- settings.gradle.kts

The .git folder is a child of the my-app folder on the file system and thus the my-app folder is the gitRootDir. There are no customisations in settings.gradle.kts other than to include the modules module-1, module-2, and module-3. Gradle will also see my-app as the projectRootDir. This is what most Android projects look like and is a fine and sensible layout to assume.

However I came across this setup at work:

android-project <-- gitRootDir
|
-- .git
|
-- diagrams
|
-- ci
|
-- proposals
|
-- my-app <-- projectRootDir
    |
    -- settings.gradle.kts
|
-- common
    |
    -- module-1
    |
    -- module-2
    |
    -- module-3

By instructing settings.gradle.kts where to look on the File system for the sub projects this type of layout is valid. It is certainly non standard, I will admit ๐Ÿ˜† .

I think I have tracked the issue down to the init block in ProjectGraph where we build up a relationship between the Gradle project graph and the File system graph (later to be used as the Git graph). If we are in a non standard Gradle project we will start to see ".." entries in the sections that we need to remove otherwise we start seeing false negatives in the detection. An example of the changes look like this.

image

Obviously I will add tests in a proper PR but this highlights the issue and opens it up for discussion.

DependencyTracker written in Groovy

I had to re-write the DependencyTracker in Groovy, seems a waste to just delete it now I've finished.
Here is it for posterity.

class DependencyTracker {
    private final Project rootProject
    private final Logger logger

    DependencyTracker(Project rootProject, Logger logger) {
        this.rootProject = rootProject
        this.logger = logger
    }

    @Lazy Map<Project, Set<Project>> dependentList = {
        Map<Project, Set<Project>> result = new HashMap<>()
        rootProject.subprojects.forEach { project ->
            logger?.debug("checking ${project.path} for dependencies")
            project.configurations.forEach { config ->
                logger?.debug("checking config ${project.path}/$config for dependencies")
                config
                        .dependencies
                        .findAll { it instanceof ProjectDependency }
                        .forEach {
                            logger?.debug("there is a dependency from ${project.path} to " + it.dependencyProject.path)
                            def value = result.get(it.dependencyProject)
                            if (value == null) {
                                value = new HashSet<Project>()
                            }
                            value.add(project)
                            result.put(it.dependencyProject, value)
                        }
            }
        }
        result
    }()

    private def addAllDependents(Set<Project> result, Project project) {
        if (result.add(project)) {
            dependentList[project]?.forEach {
                addAllDependents(result, it)
            }
        }
    }

    Set<Project> findAllDependents(Project project) {
        println("finding dependents of ${project.path}")
        Set<Project> result = new HashSet<>()
        addAllDependents(result, project)
        logger?.debug("dependents of ${project.path} is ${result.collect { it.path }}")
        // the project isn't a dependent of itself
        return result - project
    }
}

`ProjectSubset.CHANGED_PROJECTS` should respect `pathsAffectingAllModules`

Currently, if a commit changes only a path listed in pathsAffectingAllModules and you specify Paffected_module_detector.changedProjects when running AMD, no modules will be found as affected.

The relevant code is in the findAffectedProjects method of AffectedModuleDetector.kt, where we return early if projectSubset == ProjectSubset.CHANGED_PROJECTS. At that point, if the only file change is a path that affects all modules, changedProjects will be an empty set.

This goes against our documentation where ProjectSubset.ALL_AFFECTED_PROJECTS is the union of CHANGED_PROJECTS and DEPENDENT_PROJECTS. In the example, ALL_AFFECTED_PROJECTS would be every project whereas CHANGED_PROJECTS and DEPENDENT_PROJECTS would be empty sets.

We should update findChangedProjects to include the check for whether any of the changed files affect all modules. In the case that it does, every module should be added to the set of changed projects. The logic in findAffectedProjects around detecting if a path changed that affects all projects should be removed and we should define the affectedProjects field to be the union of changedProjects and dependentProjects

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.