본문 바로가기

Android

모듈 디펜던시 관계와 그래프를 얻는 플러그인

https://github.com/vanniktech/gradle-dependency-graph-generator-plugin

 

GitHub - vanniktech/gradle-dependency-graph-generator-plugin: Gradle plugin that generates dependency graphs from your project.

Gradle plugin that generates dependency graphs from your project. - GitHub - vanniktech/gradle-dependency-graph-generator-plugin: Gradle plugin that generates dependency graphs from your project.

github.com

 

가끔 실수로 모듈 관계를 이상하게 해놓고 발견 못할 때가 있습니다. 이 플러그인은 Android module 간의 관계를 그래프와 텍스트로 출력해 주는 gradle 플러그인 입니다.

 

설정

현재 최신버전인 0.8.0 버전에는 문제가 있어, 0.7.0을 사용해야 합니다.

 

Gradle 3.3 기준

buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath "com.vanniktech:gradle-dependency-graph-generator-plugin:0.7.0"
  }
}
apply plugin: "com.vanniktech.dependency.graph.generator"

 

 

Gradle 7 이후 버전 기준

plugins {
    id 'com.android.application'

    // 아래에 추가
    id "com.vanniktech.dependency.graph.generator" version "0.7.0"
}

 

 

실행

모든 모듈간 의존관계를 보기 위해서는 아래 커맨드를 실행합니다.

./gradlew generateDependencyGraph

 

현재 프로젝트내의 모듈간 의존관계를 보기 위해서는 아래 커맨드를 실행합니다.

./gradlew generateProjectDependencyGraph

 

결과

 

app/build/reports/dependency-graph : 모든 모듈간의 의존관계
app/build/reports/project-dependency-graph: 해당 프로젝트 내 모듈간의 의존관계

위 폴더에 결과 값이 생성되는데, png 와 svg 파일은 위와같이 이미지로 표시되며, dot 파일은 아래와 같이 텍스트 형태로 의존관계를 표현합니다.

 

"화살을 맞는 녀석은 누가 쐈는지 모른다" 는 표현 방식에 따라, 화살표가 시작되는 쪽이 화살표를 맞는 쪽에 의존한다는 의미입니다.

":app" -> ":base" ["style"="dotted"]    
":app" -> ":moduleA" ["style"="dotted"]
":moduleA" -> ":base" ["style"="dotted"]

 

응용: 만약 이번 커밋에 영향을 받는 모든 모듈의 이름을 얻고 싶다면
(이전 브랜치는 항상 master에 머지되며, master가 직전 릴리즈라고 가정)

더보기

App Gradle, 빌드 타입 부분에 추가:

tasks.whenTaskAdded { task ->
    if (task.name.contains("assemble")) {
        task.dependsOn affectedAreaTask
    }
}

 

 App gradle, 아래쪽 아무곳에나, 다른 영역에 속하지 않는 최상위 부분에 추가

task affectedAreaTask {
    doLast {
        affectedArea()
    }
}

 

 App gradle, 아래쪽 아무곳에나, 다른 영역에 속하지 않는 최상위 부분에 추가

def affectedArea() {
    println "Affected area"

    // Get dependencies
    def commandGenerateDependencyGraph = "./gradlew generateProjectDependencyGraph"
    def processGenerateDependencyGraph = commandGenerateDependencyGraph.execute()
    processGenerateDependencyGraph.waitFor()
    if (processGenerateDependencyGraph.exitValue() != 0) {
        println "Error, ${processGenerateDependencyGraph.err.text}"
        System.exit(-1)
    }
    String fileContents = new File("$projectDir/build/reports/project-dependency-graph/project-dependency-graph.dot").getText('UTF-8')
    List dependency = fileContents
            .split('\n')
            .findAll { it.contains('->') }
            .collect { it.split('\\[').first().replaceAll("\"", "").replaceAll(":", "") }

    def keyDependsOnValueMap = [:]
    dependency.forEach {
        String[] twoWords = it.split("\\->")
        if (twoWords[1].trim() != twoWords[0].trim()) {
            keyDependsOnValueMap.multiPut(twoWords[1].trim(), twoWords[0].trim())
        }
    }
    println "keyDependsOnValueMap: $keyDependsOnValueMap"

    // Get changed modules
    def commandGetDiff = "git diff origin/master --name-only" // replace master to develop, if you want to compare it with develop branch.
    def processGetDiff = commandGetDiff.execute()
    processGetDiff.waitFor()

    if (processGetDiff.exitValue() != 0) {
        println "Error, ${processGetDiff.err.text}"
        System.exit(-1)
    }
    def changedModuleSet = [] as Set<String>
    def affectedAreas = processGetDiff.in.text.readLines().collect {
        def found = it.find("(.*?)[/]") // find first item of split by "/"
        if (found != null) {
            found = found.replaceAll("/", "")
        }
        found
    }
    affectedAreas.collect {
        if (it != null) {
            changedModuleSet.add(it)
        }
    }
    println "changedModuleSet: $changedModuleSet"

    // Calculate affected area
    def affectedModules = [] as Set
    addFound(keyDependsOnValueMap, affectedModules, changedModuleSet)
    println "affectedModules: $affectedModules"
}

private void addFound(Map keyDependsOnValueMap, Set affectedModules, Set found) {
    for (module in found) {
        affectedModules.add(module)
        def nestedFound = keyDependsOnValueMap[module] as Set
        addFound(keyDependsOnValueMap, affectedModules, nestedFound)
    }
}