sungyup's.

dev_toolkits / Tools / 1.2 Gradle

1.2Gradle

안드로이드 앱 개발의 빌드 자동화 도구, gradle

TL;DR

추억의 쪽지 시험

React Native 프로젝트는 React Native만 익혀선 제대로 완성할 수 없는데, 빌드나 배포 과정에서 안드로이드와 iOS 각각의 네이티브 모듈을 건드려야 할 때가 반드시 오기 때문이다.

안드로이드 빌드 과정에서 빼놓을 수 없는 도구가 Gradle이다.

gradle logo
안드로이드 앱 개발의 빌드 자동화 도구, gradle. 코끼리가 아주 귀엽다.
이 글은 React Native 개발 관점에서 필요한 최소한의 Gradle 지식을 공식 문서의 기초편을 읽고 정리한 것으로, 보다 상세한 내용은 공식 문서를 참고해주세요.

Gradle이란?

Gradle is the open source build system of choice for Java, Android, and Kotlin developers. -공식 문서-

Gradle은 Android 앱 개발의 빌드 자동화 도구이다.

이전 포스팅인 CocoaPods에서도 알아보았지만, 리액트 네이티브 앱은 자바스크립트 코드 외에 각 OS에 필요한 네이티브 코드를 포함하고 있다. Android 쪽에선 Java/Kotlin 코드가 포함되는데, Gradle은 안드로이드 프로젝트에서 아래의 작업들을 수행한다:

  • 각종 태스크(Tasks): Gradle의 기본 작업 단위를 Task라고 한다. 코드 컴파일, 테스트 시행 등이 해당된다.
  • 외부 의존성 추가(ex: Firebase, React Native 모듈)
  • 플러그인 추가(ex: Java 플러그인)
  • 빌드 환경 설정(Debug/Release 구분, SDK 버전 지정 등)
  • 빌드: 결과물 애플리케이션을 생성한다.

즉, CocoaPods가 iOS 네이티브 라이브러리의 의존성 관리(Pod 설치)를 담당한다면 Gradle은 Android의 빌드 전체를 담당하고 그 안에 의존성 관리(Maven 패키지의 Dependency)도 포함한다.

Gradle 프로젝트의 구성

Gradle 프로젝트는 보통 아래와 같은 폴더 구성으로 되어 있다.

bash
project ├── gradle ├── gradlew ├── gradlew.bat ├── settings.gradle ├── subproject-a │ ├── build.gradle │ └── src/ └── subproject-b ├── build.gradle └── src/

1. gradle 폴더

gradle 폴더 안에는 wrapper라는 폴더가 있고, 이 안에는 gradle-wrapper.jargradle-wrapper.properties라는 파일들이 있다.

Gradle WrapperGradle을 설치하지 않아도 프로젝트를 빌드할 수 있게 해주는 도구다. gradle-wrapper.jar는 Wrapper 실행에 필요한 자바 코드의 압축 파일(jar)이다. gradle-wrapper.properties는 사용할 Gradle 버전과 다운로드 경로를 정의한 파일이다.

이 파일들(Gradle Wrapper)을 통해 버전의 일관성이 보장되고, 새로 clone한 사람도 이후 소개할 Gradle Wrapper CLI인 ./gradlew build만으로 자동으로 필요한 Gradle 버전을 받아온다. 또, 빌드 서버에도 Gradle을 설치할 필요 없이 Wrapper만으로 빌드할 수 있다. 따라서 이들은 레포에 커밋해서 공유해야 할 파일들이다.

Gradle을 작동하는 방법이 Gradle을 시스템에 설치해서 gradle 명령어로 하는 방법과 Gradle Wrapper를 통해 gradlew 명령어를 쓰는 방법, 2가지가 있다보니 헷갈릴 수 있다. 하지만 공식문서에서도 말하듯이 Gradle Wrapper를 쓰는 방법이 앞서 말한 이점들 때문에 표준이다.

2. gradlew, gradlew.bat

gradlew는 Unix/Linux/Mac용 실행 스크립트, gradlew.bat은 Windows용 실행 스크립트다. 참고로 확장자 bat은 Windows의 Batch Script를 의미한다. 이 파일들은 조작하면 안된다.

React Native를 통해 개발하다보면 버그가 나서 ./gradlew clean이라는 명령어를 실행하래서 실행하게 될 경우가 아주 많다.

clean이전 빌드 산출물을 지우는 동작이다. 구체적으로는 build/ 폴더를 삭제해 안에 있는 컴파일된 클래스, 리소스, 캐시 등을 지운다. Android를 빌드할 때 의존성이 충돌하거나 Gradle 캐시가 꼬이는 경우(코드를 수정했는데 이전 산출물이 반영되는 경우)가 잦기 때문에 ./gradlew clean은 아주 많이 쓰는 명령어다. 물론 실행하려면 android 폴더에 있어야 한다.

clean 이외에도 많이 쓰는 것은 물론 빌드 명령어로, ./gradlew build, ./gradlew assembleDebug, ./gradlew bundleRelease 등을 상황에 따라 쓰게 된다. 이 명령어들이 어떤 태스크들을 어떻게 수행하는지는 글 후반에 다룬다.

3. settings.gradle

settings.gradle 파일은 모든 Gradle 프로젝트의 엔트리 포인트로, 다른 빌드 스크립트들(이후에 볼 build.gradle 파일들)보다 먼저 읽힌다. 이 파일은 프로젝트 전체의 빌드 구조를 정의하는데, 예를 들어 프로젝트 명이나 포함되는 서브 프로젝트들의 정보가 들어있다.

groovy
rootProject.name = 'root-project' include 'sub-project-a' include 'sub-project-b'

리액트 네이티브에선 include :app으로 app 폴더를 서브 프로젝트로 포함한다.

4. build.gradle

gradle build flow
이미지 출처 : #
Gradle은 build.gradle 파일의 정보를 토대로 빌드, 테스트, 배포를 수행한다.

리액트 네이티브 프로젝트 기준으로 android 폴더 아래엔 build.gradle이 있는데, android/app 폴더 아래에도 build.gradle이 있다. 이 파일들은 빌드 스크립트(Build Script)라고 부르며, 플러그인의존성 관련 정보들을 포함한다.

  • 플러그인: Gradle의 코드 컴파일, 테스트 시행 등의 태스크와 관련된 Gradle의 기능을 확장하는 툴들
  • 의존성: 외부 라이브러리들로, 크게 아래의 두 가지로 나뉜다.
    • Gradle and Build Script Dependencies: Gradle 자체나 빌드 스크립트 로직에 필요한 플러그인, 라이브러리 등
    • Project Dependencies: 프로젝트의 소스 코드를 컴파일하는데 필요한 라이브러리

글 작성 시점(25.9.20) Gradle 공식문서의 빌드 스크립트는 아래와 같이 작성하는게 표준이다:

groovy
plugins { id 'application' } dependencies { testImplementation libs.junit.jupiter testRuntimeOnly 'org.junit.platform:junit-platform-launcher' implementation libs.guava } application { mainClass = 'org.example.App' }

다만, React Native 프로젝트를 언제 만들었냐에 따라 코드가 위와는 다를 수도 있다. 예를 들어, 약간 예전 버전(7.6.2)은 아래와 같이 buildscript 안에 repositories, dependencies를 포함했다:

groovy
buildscript { repositories { mavenCentral() } dependencies { classpath group: 'commons-codec', name: 'commons-codec', version: '1.2' } }

React Native에선 android 폴더에 있는 build.gradle전체 프로젝트 공통 설정에 대한 부분으로, 루트 빌드 스크립트라고도 부른다. 예를 들어 전체 규칙/모든 모듈에서 쓸 저장소(repositories)/플러그인 등에 대한 설정이다.

android/app 폴더의 build.gradle앱 모듈에 대한 구체적 설정으로, 앱 모듈 빌드 스크립트로도 부른다. 앱 모듈에 대한 설정이라는 말은, 실제 앱의 빌드 결과(디버그/릴리즈, APK/AAB 등)에 직접 영향을 주는 요소들에 대한 정보가 포함된다는 의미다. 앱의 applicationId, versionCode, 빌드 타입(debug, release 등), productFlavors, sdk 버전, keystore(서명) 설정, 앱 전용 의존성 등이 포함된다.

과거에는 루트 build.gradle에서 ext { ... } 블록으로 프로젝트 전역 상수를 선언하곤 했다. 즉, 루트에서 ext { minSdkVersion = 28 ...}식으로 선언하면, app 모듈에선 android { minSdkVersion rootProject.ext.minSdkVersion}등으로 가져다 썼다. 이 경우도 "공통 값은 루트에서, 실제 적용은 모듈에서"라는 책임 분리 원칙을 지킨 것으로 볼 수 있다.

Gradle을 통한 빌드 과정

Task는 Gradle 빌드 과정에서 처리해야할 작업들의 단위다.

  • 코드 컴파일
  • 테스트 진행
  • 결과물 패키징(jar파일 또는 APK 파일 생성)

각 Task들은 독립적으로 실행되지만, 어떤 것들은 다른 Task가 먼저 수행되어야만 이어 실행될 수 있는것도 있다. Gradle은 가장 효율적으로 Task들을 수행할 수 있는 방법을 찾아 실행한다.

예를 들어, 빌드를 한다고 하자.

bash
./gradlew build

Gradle은 build 태스크를 수행하기 위해 이 태스크가 의존하는 모든 태스크들을 수행할 것이다.

bash
> Task :app:compileJava > Task :app:processResources NO-SOURCE > Task :app:classes > Task :app:run Hello World! BUILD SUCCESSFUL in 904ms 2 actionable tasks: 2 executed

위의 케이스(Task들을 보면 React Native는 아니고 일반 Java 애플리케이션으로 보인다)에선 우선 컴파일을 하는것부터 시작해 결국 run으로 이어져 "Hello World!"를 출력하였다. ./gradlew tasks 명령어를 통해서 프로젝트에서 어떤 태스크들이 수행가능한지 확인할 수 있다.

좀 더 실전적인 예시로, ./gradlew assembleDebug등의 명령어로 빌드를 지시하면 Gradle은 아래와 같이 동작한다.

  1. 초기화 단계: settings.gradle을 읽고 포함된 모듈(app, library등)을 인식한다.
  2. 설정 단계: 각 모듈의 build.gradle을 읽고 실행하며 태스크 그래프(Task Graph)를 구성한다. 즉, :app:assembleDebug라는 태스크를 실행하기 위해 어떤 태스크들이 선행되어야 하는지 계산한다. 이 시점에서 ext값(SDK 버전 등)이 평가되고, android {...}, dependencies {...} 같은 설정이 적용된다.
  3. 실행 단계: 요청된 태스크와 그에 필요한 의존 태스크만 실행한다. 빌드 명령어의 경우 소스 코드 컴파일, 리소스 처리, APK/AAB 생성 등이다.