5 minute read

해당 포스트는 이전 포스트와 연결되는 내용이 존재하므로 이전 포스트를 먼저 보고 오시기를 추천드립니다.

Android에서 프로젝트를 만들어 나갈 때 프로젝트의 규모가 커질수록 단독으로 구성되기보다는 점점 더 많은 여러 의존성, 플러그인 그리고 모듈들이 결합되어 형성되기 시작합니다. 하나의 프로젝트에서는 모든 기능을 담당하여 개발하기는 어려우며 여러 모듈을 분리하여 개발하거나 재사용할 경우 더욱 효율적인 아키텍처 구성이 만들어질 수 있기 때문입니다.

이러한 모듈을 연동하기 위해서는 AAR 배포를 통해 생성한 라이브러리 모듈을 프로젝트 환경에 주입하거나 Github PackagesMaven Central 같이 원격 저장소를 통해 라이브러리를 배포할 수 있습니다.

하지만 이러한 방식들을 제외하고도 GradleGit을 통해서 저장소를 독립적으로 관리하면서 모듈을 결합할 수 있는 방법이 존재합니다. 해당 방식은 원격저장소에 배포되거나 컴파일된 AAR 라이브러리에 반해 프로젝트에서 라이브러리 코드로 자유롭게 이동하여 코드를 수정하고 디버깅할 수 있으며, 별도 라이브러리의 배포 설정 및 프로세스 없이 즉각적으로 반영하여 피드백을 받을 수 있다는 장점이 존재합니다.

이 방식에 접근하기 위해서는 Gradle의 Composite Build와 Git의 Git Submodule의 배경지식을 알아야 하며, 이 두 가지를 결합하여 유연한 멀티 모듈 프로젝트를 구성할 수 있게 될 수 있습니다.

이번 포스트에서는 지난 포스트에서 다룬 GradlePlugin 모듈을 바탕으로 Composite BuildGit Submodule을 결합하여 멀티 모듈 프로젝트를 빌딩하는 과정에 대해 다루어보겠습니다.

배경지식

Gradle Composite Build

Composite Build는 서로 다른 독립적인 Gradle 프로젝트를 하나의 프로젝트로 묶어서 빌드하는 기능입니다. 기존 Android 개발자들이 사용하는 멀티 모듈 프로젝트 방식은 일반적으로 settings.gradle.kts 하나에 여러 가지 하위 모듈을 종속하는 구조였습니다. 하지만 Composite Build는 이와 다르게 자신만의 settings.gradle.kts를 보유하고 있으며 각자의 독립적인 프로젝트를 서로 연결을 수행합니다.

Composite Build는 다른 빌드 프로세스를 포함하는 빌드이며, 독립적으로 개발되는 빌드 프로세스를 결합하는 방식을 의미합니다.

IncludeBuild

includeBuild는 Composite Build를 구성할 때 사용되는 구문입니다. 해당 구문은 settings.gradle.kts 내에서 사용되며 특정 프로젝트를 해당 빌드에 포함시켜줍니다. 이를 통해 라이브러리 의존성 등을 연동할 수 있습니다. 예를 들어 루트 프로젝트에 settings.gradle.kts를 두고 my-app 과 my-utils 라는 프로젝트를 대상으로 Composite Build를 구성하고 싶을 경우 아래와 같이 구성하면 됩니다.

//settings.gradle.kts
rootProject.name = "my-composite"

includeBuild("my-app")
includeBuild("my-utils")

이때 my-utils 프로젝트에는 아래와 같은 모듈이 있다고 가정합니다.

//my-utils.settings.gradle.kts
rootProject.name = "my-utils"

include("number-utils", "string-utils")

이러한 환경에서 Composite Build가 이루어질 경우 my-app 모듈에서는 my-utils 프로젝트 내에 위치하는 number-utils 모듈과 string-utils를 사용할 수 있게 됩니다. Composite Build를 통해 독립적인 프로젝트들을 서로 연결하였기 때문입니다.

//my-app:app.build.gradle.kts
...

dependencies {
    implementation("org.sample:number-utils:1.0")
    implementation("org.sample:string-utils:1.0")
}

...

해당 샘플은 gradle에서 제공하는 소스로 자세한 내용은 [해당 주소]를 참고하시길 바랍니다.

Plugin IncludeBuild

Gradle은 빌드 구조를 만들 때 가장 먼저 어떤 Plugin을 적용할지를 찾습니다. 하지만 일반적인 includeBuild를 사용할 경우 해당 작업이 플러그인을 찾는 것 보다 후속적으로 실행되어서 CompositeBuild 대상으로 정의한 Plugin이 적용되지 않습니다.

이러한 문제를 해결하기 위해 Gradle은 Plugin을 찾는 단계에서 다른 프로젝트의 플러그인을 찾을 수 있도록 하는 방법을 지원하고 있습니다. 이를 통해 외부 프로젝트의 Plugin을 정의하는 빌드를 만들어낼 수 있게 됩니다.

방식은 settings.gradle.ktspluginManagement 블럭에서 includeBuild 구문을 사용하면 됩니다. 방식은 동일할지여도 실제로 작동되는 코드가 다르다는 것을 아래와 같이 확인하실 수 있습니다.

CompositeBuild는 이 외에도 모듈을 구성하기 위해 사용되는 여러 가지 보조 기능들을 제공하니, 자세한 정보는 [해당 문서]를 참고하시길 바랍니다.

Git Submodule

Git Submodule은 하나의 Git 저장소 안에 다른 Git 저장소를 포함하는 기능입니다. Git Submodule을 사용할 경우 독립적인 프로젝트들에 대한 히스토리를 유지할 수 있게 되며 특정 Branch나 Tag를 지정하여 특정 타겟에 대한 소스를 불러올 수 있게 됩니다. 무엇보다 하나의 프로젝트에서 여러 Git 프로젝트를 불러오고 관리할 수 있게 됩니다.

AndroidStudio와 같은 Jetbrain IDE에서는 아래와 같이 Git Submodule 을 포함하여 해당 프로젝트에 연동된 모든 Git 히스토리들을 열람할 수 있도록 지원해 주고 있습니다.

앞서 Gradle의 Composite Build에 대한 내용을 소개할 때 여러 가지 특징과 장점들을 열거하였지만, 무엇보다 중요한 사실을 언급하지 않았습니다. Composite Build를 통해 여러 프로젝트를 하나의 빌드에 포함할 수는 있지만, 정작 하나의 빌드에 묶기 위해서 여러 프로젝트를 로드해야하는 방식은 굉장히 많은 비용과 불편함을 초래할 수 있습니다.

Git Submodule은 이러한 불편함을 해소하는데 중요한 역할을 합니다. 프로젝트 별로 독립적인 Git 저장소를 만들어 저장해두어도 Git Submodule을 구성하는 파일만 있다면 Git 저장소에 저장된 독립적인 한 번에 불러올 수 있게 됩니다. 이렇게 불러와진 프로젝트들에 대해서 includeBuild를 적용할 경우 손쉽게 버전 관리와 더불어 멀티 모듈 프로젝트를 구성할 수 있게 됩니다.

구현

지난 포스트에서는 생산성 향상을 위해 Gradle Plugin을 직접 구성하는 내용을 다루었습니다. 하지만 Plugin을 개발하는 과정에서 최종적으로 한가지의 한계점을 마주하며 마무리하게 되었습니다. 바로 어떻게 Plugin을 실제로 사용할 프로젝트에 적용하고 테스트할지에 대한 방법입니다.

이러한 문제점을 해결하기 위해서는 원격 저장소 배포와 같은 여러 가지 방법 등이 있겠지만, 빠른 구성과 관리를 위해 Git Submodule과 Gradle의 CompositeBuild를 사용하겠습니다.

가장 먼저 새로운 Android 프로젝트와 Git 저장소를 만드는 절차가 필요합니다. 이 방식은 AndroidStudio IDE와 Github에서 지원하고 있으며, 관련된 자료도 많다 보니 별도로 만드는 방법에 대해서 다루지 않을 것입니다.

실제 구현된 프로젝트를 확인하고 싶으실 경우 [해당 소스]를 참고하시길 바랍니다.

프로젝트를 생성하였을 경우 Git Submodule를 통해 이전 포스트에서 생성하였던 Plugin 프로젝트를 불러옵니다. Plugin 프로젝트는 사전에 [해당 소스] 처럼 Git 저장소에 저장 되어있는 상태입니다. Git Submodule를 구성하기 위해선 메인 Git 프로젝트 폴더로 이동해 아래 명령어를 수행하면 됩니다.

git submodule add [저장소_URL] [경로] // 브랜치를 지정하지 않고 불러오기 (default branch)
git submodule add -b [브랜치명] [저장소_URL] [경로] // 브랜치를 지정하여 불러오기

해당 명령어를 수행할 경우 아래와 같이 .gitmodules 파일과 build-system 디렉터리가 추가되게 됩니다. build-system 디렉터리는 앞서 개발된 Plugin 프로젝트의 저장소입니다.

이제 Git Submodule을 통해 build-system 프로젝트를 불러오게 되었고 Git Submodule에서 제공하는 기능들을 통해 서브 Git 저장소를 유연하게 관리할 수 있게 되었습니다.

이어서 GradleIncludeBuild 기능을 사용하여 Composite Build를 구성하겠습니다. 이번 포스트에서 구성할 프로젝트는 플러그인을 대상으로 하므로 pluginManagement 블럭에 빌드 종속성을 선언해야합니다. 선언 방법은 아래와 같이 includeBuild 메서드를 추가하고 파라미터로 include 할 프로젝트의 경로를 작성하면 됩니다.

CompositeBuild 구성 후 Sync를 하게 되면 AndroidStudio IDE에서는 디렉터리 아이콘이 모듈 아이콘으로 변환되어 보입니다. 이를 통해 정상적으로 Include Build가 되었는지 판단할 수 있습니다.

이제 요구되었던 멀티 모듈 구성은 모두 완료되었습니다. 정상적으로 되었는지 테스트하기 위해 아래와 같이 app 모듈 내에 이전 포스트에서 제작한 플러그인을 등록하고 ./gradlew app:tasks 명령어를 사용하여 Task가 등록되었는지 확인할 수 있습니다.


이번 포스트에서는 Git Submodule을 통해 독립적인 플러그인 프로젝트를 불러오기 Gradle의 Composite Build를 통해 별도의 배포 과정 없이 메인 프로젝트에 통합하는 과정을 다루어보았습니다.

Git Submodule은 각 프로젝트의 독립적인 버전 관리를 도와주며 Composite Build는 이들을 하나의 프로젝트로묶어 빠른 멀티 모듈 구성에 기여합니다. 궁극적으로 모듈 간의 독립성을 지키면서 원격 저장소 배포같이 별도의 모듈 배포 과정을 생략할 수 있어 개발 속도를 향상할 수 있습니다.

포스트에서 소개된 멀티 모듈 환경은 단순한 구조일 뿐이며, 아래와 같은 전략들을 활용한다면 더 체계적이고 안전한 구조를 바탕으로 확장할 수 있을 것입니다.

  • Git의 Tag를 통한 정교한 버전 관리
  • 프로젝트 저장소별 권한 부여를 통한 보안 관리
  • 모듈별 Git 저장소를 생성하여 공통 라이브러리 생성 및 확장

Gradle 시리즈에서 사용되었던 소스들은 해당 프로젝트에 적용 중이니, 포스트의 내용을 더 이해하는데 도움이 되길 바라며 글을 마칩니다 (프로젝트 기여는 환영입니다. 🙂)