Owl Life

Gradle의 root gradle file에서 멀티 프로젝트 관리. subprojects를 이용한 코드 공용화 작업. 본문

Maven & Gradle

Gradle의 root gradle file에서 멀티 프로젝트 관리. subprojects를 이용한 코드 공용화 작업.

Owl Life 2019. 10. 30. 23:32
반응형

안드로이드 앱 개발을 하다보면 모듈화를 위하여 "com.android.library" plugin이 적용된 여러 sub proejct들을 생성하게 됩니다. 그로 인해 build.gradle 파일이 계속해서 추가가 되고 조금만 신경쓰지 않으면 boilerplate code가 기하급수적으로 늘어나게 됩니다. 또한, 같은 외부 라이브러리를 버전만 다르게 각 모듈에서 적용하여 사용하는 경우도 발생하게 되는데 예기치 못한 컴파일 또는 런타임 에러가 종종 발생되기도 합니다.

 

관리 포인트가 늘어나게되면 유지보수 비용도 증가하게 되므로 최대한 중복 코드를 제거하여 중앙에서 컨트롤 할 수 있도록 빌드스크립트를 구성하여야 합니다. 본 포스팅에서는 프로젝트의 root에 위치한 build.gradle 파일을 수정하여 중복 코드를 삭제 하는 방법에 대하여 설명하고자 합니다.

 

예제에 사용된 샘플 프로젝트는 총 3개의 library module과 하나의 application module로 구성되어 있습니다.

common_module

view_module

usecase_module

app

 

module간의 의존 관계는 아래와 같습니다.

view_module과 usecase_module은 common_module에 의존하고 있습니다.

app은 view_module과 usecase_module을 의존하고 있습니다.

 

common_module - build.gradle

apply plugin: 'com.android.library'

println project.name + " sub project"

단 두줄로 구성되어 있습니다. plugin을 적용하고, task configuration 순서를 보기 위하여 콘솔에 프로젝트명을 보여주는 디버기용 코드입니다.

 

 

view_module과 usecase_module의 - build.gradle  (동일)

apply plugin: 'com.android.library'

println project.name + " sub project"

dependencies {
    implementation project(':common_module')
}

common_module과 큰 차이는 없습니다. dependencies {}에 common_module을 의존하고 있다는 것을 나타내는 코드 한줄이 추가되어 있습니다.

 

 

app - build.gradle

apply plugin: 'com.android.application'

android {
    defaultConfig {
        applicationId "com.owllife.jenkinstestproject"
        versionCode 1
        versionName "1.0"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation project(':usecase_module')
    implementation project(':view_module')

    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
}

 

application module로서 defaultConfig, buildTypes 등이 추가되어있고, 이 모듈에서만 사용하고 있는 constraintlayout lib 또한 의존성을 두고 있습니다.

 

각 sub module의 빌드 스크립트 코드가 매우 간결한 것을 볼 수 있습니다. 이제 root에 위치한 빌드 스크립트 파일은 어떻게 구성되어 있는지 한번 살펴보겠습니다.

buildscript {
    repositories {
        google()
        jcenter()
        maven { url "https://plugins.gradle.org/m2/" }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.1'
    }
}

subprojects {

    if (project.name == 'app') {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }

    repositories {
        google()
        jcenter()
    }

    apply plugin: 'checkstyle'
    task checkstyle(type: Checkstyle) {
        showViolations true
        ignoreFailures true
        configFile file(rootDir.path + "/checkstyle.xml")

        source 'src/main/java'
        include '**/*.java'
        exclude '**/gen/**'
        exclude '**/R.java'
        exclude '**/BuildConfig.java'

        classpath = files()
    }

    println project.name + " - root project"

    android {
        compileSdkVersion 29
        buildToolsVersion "29.0.2"
        defaultConfig {
            minSdkVersion 23
            targetSdkVersion 29
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        }

        lintOptions {
            abortOnError false
        }
    }

    dependencies {
        implementation 'androidx.appcompat:appcompat:1.1.0'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'androidx.test.ext:junit:1.1.1'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

 

subprojects scope를 보시면 일반적으로 많이 보던 코드들이 위치한 것을 확인 할 수 있습니다.

    if (project.name == 'app') {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }

이 코드는 android{}와 같은 closure를 읽기 위하여 필수적으로 추가 되어야 plugin입니다.

추가하지 않으면 이런 에러가 발생하게 됩니다.

> Could not find method android() for arguments [build_cl71o7g9m073cw0qlbvvkp09h$_run_closure1$_closure5@13e29c6c] on project ':app' of type org.gradle.api.Project.

 

plugin 적용 이후부터는 각 sub module에서 사용중인 boilerplate code 성격을 띄는것들을 추가하면 됩니다. 위 샘플에서는 defaultConfig나 lintOptions, dependencies 등을 추가하고 있습니다.

 

Task Configuration 순서는 어떻게 될까?

task를 configuration하는 순서를 보면 root gradle file에서 subproject 대상 갯수만큼 호출되고, 그 이후에 각 모듈별로 configuration을 진행하게 됩니다. module layer 관점에서 common_module이 가장 상단에 위치한다고 해서 root 와 sub project를 먼저 configuration 하지는 않습니다. 참고로 알아두시면 좋을것 같습니다. 무조건 root가 우선이고 그 다음에 차례대로 sub project의 configuration을 실행합니다.

> Configure project :
app - root project
common_module - root project
usecase_module - root project
view_module - root project

> Configure project :common_module
common_module sub project

> Configure project :usecase_module
usecase_module sub project

> Configure project :view_module
view_module sub project

allprojects와 subprojects 차이점?

일반적으로 대부분의 프로젝트는 subprojects closure를 사용 하면 됩니다.

allprojects closure는 root에 해당하는 프로젝트의 build.gradle 도 함께 실행 시킨다는 의미로 사용이 됩니다.

보통 같은 모듈의 성격을 가지는 여러 library module을 하나의 folder을 만들고 그 하단에 위치시키기도 합니다. 이 때 그 새로 생성한 folder에 build.gradle 파일을 추가하는 경우는 거의 없을 것입니다. (현업에서 한번도 보지 못했고, 필요성을 느끼지 못함)

만약, 이 폴더에 build.gradle 파일을 추가하여 별도의 동작을 수행해야 한다면 allprojects closure에 추가하면 됩니다.

자세한 차이점을 알고 싶다면 권남님의 위키 페이지를 참고 바랍니다.

 

권남님 위키 : https://kwonnam.pe.kr/wiki/gradle/multiproject

반응형
Comments