Post

Gradle

Gradle 개념 정리

Gradle

Gradle은 빠르고 신뢰할 수 있으며, 적응력이 뛰어난 오픈 소스 빌드 자동화 도구로, 우아하고 확장 가능한 선언적 빌드 언어를 갖추고 있습니다.

왜 Gradle인가?

Gradle는 널리 사용되는 성숙한 도구로, 활발한 커뮤니티와 강력한 개발자 생태계를 가지고 있습니다.

주요 장점:

  • JVM을 위한 최고의 빌드 시스템
    • Gradle은 JVM을 위한 가장 인기 있는 빌드 시스템이며, Android와 Kotlin 멀티플랫폼 프로젝트의 기본 시스템입니다. 풍부한 커뮤니티 플러그인 생태계를 자랑합니다.
  • 다양한 빌드 시나리오 자동화
    • Gradle은 내장된 기능, 서드파티 플러그인, 또는 사용자 정의 빌드 로직을 사용하여 광범위한 소프트웨어 빌드 시나리오를 자동화할 수 있습니다.
  • 고수준의 선언적이고 표현력 있는 빌드 언어
    • Gradle은 읽고 쓰기 쉬운 고수준의 선언적이고 표현력 있는 빌드 언어를 제공합니다.
  • 빠르고 확장 가능
    • Gradle은 빠르고 확장 가능하며, 모든 크기와 복잡성의 프로젝트를 빌드할 수 있습니다.
  • 신뢰할 수 있는 결과
    • Gradle은 증분 빌드, 빌드 캐싱, 병렬 실행 등의 최적화를 통해 신뢰할 수 있는 결과를 제공합니다.
  • Build Scan® 서비스
    • Gradle, Inc.는 Build Scan®이라는 무료 서비스를 제공하여 빌드에 대한 광범위한 정보와 인사이트를 제공합니다. 문제를 식별하거나 디버깅 도움을 위해 스캔을 공유할 수 있습니다.

Core Concept

https://docs.gradle.org/current/userguide/img/gradle-basic-1.png

  • 프로젝트
    • Gradle 프로젝트는 애플리케이션이나 라이브러리와 같이 빌드할 수 있는 소프트웨어의 한 부분을 의미합니다.
      • 단일 프로젝트 빌드: 루트 프로젝트라 불리는 단일 프로젝트를 포함합니다.
      • 다중 프로젝트 빌드: 하나의 루트 프로젝트와 여러 개의 하위 프로젝트를 포함합니다.
  • 빌드 스크립트
    • 빌드 스크립트는 Gradle에게 프로젝트를 빌드하기 위한 단계들을 상세히 기술합니다.
      • 각 프로젝트는 하나 이상의 빌드 스크립트를 포함할 수 있습니다.
  • 의존성 관리
    • 의존성 관리는 프로젝트에서 필요한 외부 리소스를 선언하고 해결하는 자동화된 기법입니다.
      • 각 프로젝트는 Gradle이 빌드 중에 해결할 여러 외부 의존성을 포함합니다.
  • 태스크
    • 태스크는 코드 컴파일이나 테스트 실행과 같은 기본 작업 단위입니다.
      • 각 프로젝트는 빌드 스크립트나 플러그인 내에서 정의된 하나 이상의 태스크를 포함합니다.
  • 플러그인
    • 플러그인은 Gradle의 기능을 확장하고 선택적으로 프로젝트에 태스크를 추가하는 데 사용됩니다.

Gradle 프로젝트 구조

많은 개발자들은 기존 프로젝트를 통해 처음으로 Gradle과 상호작용하게 됩니다.

프로젝트의 루트 디렉토리에 gradlew 및 gradlew.bat 파일이 존재하는 것은 Gradle이 사용된다는 명확한 지표입니다.

Gradle 프로젝트는 다음과 비슷한 구조를 가질 것입니다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
project
├── gradle                              
│   ├── libs.versions.toml              
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew                             
├── gradlew.bat                         
├── settings.gradle(.kts)               
├── subproject-a
│   ├── build.gradle(.kts)              
│   └── src                             
└── subproject-b
    ├── build.gradle(.kts)              
    └── src                             
  • gradle : wrapper 파일들과 다른 파일들을 저장하는 디렉토리 입니다.
    • libs.versions.toml : 의존성 관리를 위한 Gradle 버전 카탈로그입니다
  • gradle.w : gradle wrapper script
  • setting.gradle : 루트 프로젝트 명과 서브 프로젝트들을 정의하는 세팅 파일
  • build.gradle : 두 서브 프로젝트의 빌드 스크립트
  • src : 프로젝트 소스 코드

Gradle Wrapper

Gradle build를 실행할 때 권장되는 방법은 Gradle wrapper를 사용하는 것입니다.

https://docs.gradle.org/current/userguide/img/gradle-basic-2.png

Wrapper script는 선언된 버전의 Gradle을 호출하며, 필요할 경우 사전에 다운로드합니다.

https://docs.gradle.org/current/userguide/img/wrapper-workflow.png

Gradle wrapper 이해하기

1
2
3
4
5
6
7
.
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar  
│       └── gradle-wrapper.properties   
├── gradlew 
└── gradlew.bat 
  • gradle-wrapper.jar : 이 작은 JAR 파일은 Gradle 래퍼 코드를 포함하고 있습니다. 이 파일은 프로젝트에 맞는 Gradle의 올바른 버전을 다운로드하고 설치하는 역할을 합니다.
  • gradle-wrapper.properties : 이 파일은 Gradle 래퍼의 구성 속성을 포함하고 있습니다. 예를 들어, Gradle을 다운로드할 배포 URL과 배포 유형(ZIP 또는 TARBALL)을 지정합니다.
  • gradlew : 이 쉘 스크립트는 (유닉스 기반 시스템에서) gradle-wrapper.jar을 감싸는 래퍼 역할을 합니다. 유닉스 기반 시스템에서 Gradle을 수동으로 설치하지 않고도 Gradle 작업을 실행하는 데 사용됩니다.

settings.gradle

세팅 파일은 gradle 프로젝트의 엔트리 포인트입니다.

https://docs.gradle.org/current/userguide/img/gradle-basic-3.png

세팅 파일의 주목적은 서브 프로젝트들을 추가하는 것입니다.

settings script

세팅 파일은 다음과 같이 작성할 수 있습니다.

1
2
3
4
5
rootProject.name = 'root-project'   

include('sub-project-a')            
include('sub-project-b')
include('sub-project-c')
  • rootProject.name : 프로젝트 이름
  • include : 서브 프로젝트 추가

build.gradle

보통 빌드 스크립트는 빌드 설정, 태스크, 플러그임들을 설정합니다.

https://docs.gradle.org/current/userguide/img/gradle-basic-4.png

빌드 파일에서 두 종류의 의존성을 추가할 수 있습니다.

  1. Gradle과 빌드 스크립트가 의존하는 라이브러리 및/또는 플러그인.
  2. 프로젝트 소스(즉, 소스 코드)가 의존하는 라이브러리.

dependency management basics

https://docs.gradle.org/current/userguide/img/gradle-basic-7.png

Gradle 빌드 스크립트는 외부 의존성이 필요할 수 있는 프로젝트를 빌드하는 과정을 정의합니다. 의존성은 JAR 파일, 플러그인, 라이브러리 또는 프로젝트 빌드를 지원하는 소스 코드를 의미합니다.

version catalog

버전 카탈로그는 libs.versions.toml 파일에 의존성 선언을 중앙 집중화하는 방법을 제공합니다.

이 카탈로그는 하위 프로젝트 간에 의존성과 버전 구성을 공유하는 작업을 단순화합니다. 또한, 대규모 프로젝트에서 팀이 라이브러리와 플러그인의 버전을 강제할 수 있게 합니다.

버전 카탈로그는 일반적으로 네 개의 섹션으로 구성됩니다:

  1. [versions]: 플러그인과 라이브러리가 참조할 버전 번호를 선언합니다.
  2. [libraries]: 빌드 파일에서 사용할 라이브러리를 정의합니다.
  3. [bundles]: 의존성 세트를 정의합니다.
  4. [plugins]: 플러그인을 정의합니다.
1
2
3
4
5
6
7
8
9
10
[versions]
androidGradlePlugin = "7.4.1"
mockito = "2.16.0"

[libraries]
googleMaterial = { group = "com.google.android.material", name = "material", version = "1.1.0-alpha05" }
mockitoCore = { module = "org.mockito:mockito-core", version.ref = "mockito" }

[plugins]
androidApplication = { id = "com.android.application", version.ref = "androidGradlePlugin" }

dependency 선언하기

버전 카탈로그에 명시한 의존성은 다음과 같이 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
plugins {
   alias(libs.plugins.androidApplication)  
}

dependencies {
    // Dependency on a remote binary to compile and run the code
    implementation(libs.googleMaterial)    

    // Dependency on a remote binary to compile and run the test code
    testImplementation(libs.mockitoCore)   
}

Task

task는 컴파일, jar 파일 생성, javadoc 생성 혹은 레포지토리 배포와 같은 특정 작업 단위를 지정할 수 있습니다. https://docs.gradle.org/current/userguide/img/gradle-basic-5.png

./gradlew tasks 명령어로 실행 가능한 task 들을 확인할 수 있고 ./gradlew run 명령어로 실행 가능합니다. task 별로 의존성이 있어서 build 같은 작업을 수행하면 compileJava 태스크가 먼저 실행되게 됩니다.

Plugin

Gradle은 플러그인 시스템을 기반으로 구축되어 있습니다. Gradle 자체는 주로 정교한 의존성 해결 엔진과 같은 인프라로 구성되어 있으며, 나머지 기능은 플러그인에서 제공합니다.

플러그인은 Gradle 빌드 시스템에 추가적인 기능을 제공하는 소프트웨어입니다.

https://docs.gradle.org/current/userguide/img/gradle-basic-6.png

플러그인들은 빌드 스크립트에 추가되어 새로운 태스크, 설정 혹은 빌드 스크립트를 작성할 수 있습니다.

플러그인 적용하기

플러그인은 아래와 같이 적용할 수 있습니다.

1
2
3
plugins {
    id «plugin id» version «plugin version»
}

core plugin

gradle에 기본적으로 포함된 플러그인들입니다. core plugin에는 다음과 같은 플러그인들이 있습니다.

  • java : 자바 프로젝트를 빌드하는데 도움을 줍니다.
  • groovy : groovy 소프 파일을 컴파일하고 테스트하는 용도
  • ear : 엔터프라이즈 애플리케이션을 위한 ear 파일 빌드

core plugin은 다음과 같이 버전 정보가 필요하지 않습니다.

1
2
3
plugins {
    id("java")
}

코어 플러그인 더보기

community plugin

gradle 커뮤니티에서 개발된 플러그인들이고 스프링 부트 플러그인도 커뮤니티 플러그인입니다.

1
2
3
plugins {
    id("org.springframework.boot") version "3.1.5"
}

커뮤니티 플러그인들 더보기

local plugins

특정 프로젝트나 조직에서 공개적으로 공유하지 않고 사용하는 플러그인입니다. 로컬 플러그인 만드는 법

multi project

gradle은 멀티 프로젝트 빌드를 지원합니다. https://docs.gradle.org/current/userguide/img/gradle-basic-9.png

소규모 프로젝트 혹은 모놀리식 애플리케이션들은 하나의 빌드 파일 혹은 소스 트리를 사용할 수 있지만, 프로젝트가 작은 모듈들로 나눠지는 경우가 흔하다.

multi-project structure

https://docs.gradle.org/current/userguide/img/multi-project-structure.png

settings.gradle 파일은 모든 서브 프로젝트들을 명시하고, 서브 프로젝트들은 build.gradle 파일이 있어야 한다.

multi-project standards

gradle 커뮤니티는 멀티 프로젝트를 위한 두가지 프로젝트 구조를 제안합니다. https://docs.gradle.org/current/userguide/img/multi-project-standards.png

  1. buildSrc : 서브 프로젝트와 같은 디렉터리로 모든 빌드 로직을 포함합니다.
    • buildSrc 디렉토리에 커스텀 task, plugin 들을 작성해서 사용할 수 있습니다.
  2. build-logic : 재사용 가능한 빌드 로직을 포함하는 Gradle 프로젝트 루트의 빌드 디렉토리입니다.

identifying project structure

./gradlew -q projects 명령어로 프로젝트 구조를 확인할 수 있습니다. 서브 프로젝트에 gradle 명령어를 수행하기 위해서는 ./gradlew :app:tasks 같은 명령어를 이용해서 사용할 수 있습니다.

build lifecycle

gradle는 어떤 task를 실행하기 전에 task graph를 빌드합니다.

https://docs.gradle.org/current/userguide/img/task-dag-examples.png

build phases

https://docs.gradle.org/current/userguide/img/author-gradle-1.png

gradle build는 3가지 단계를 통해 진행됩니다.

  1. Initialization phase
    • settings.gradle 파일을 찾습니다.
    • Settings 인스턴스를 생성합니다.
    • settings 파일로부터 빌드할 프로젝트들을 찾고 각 프로젝트 인스턴스를 생성합니다.
  2. Configuration
    • build 파일로부터 요구되는 task에 task graph를 생성합니다.
  3. Execution
    • 선택된 task들을 스케줄하고 실행합니다.
    • task는 병렬적으로 실행될 수 있습니다. https://docs.gradle.org/current/userguide/img/build-lifecycle-example.png

writing settings files

settings file은 모든 gradle 빌드에 엔트리 포인트입니다. https://docs.gradle.org/current/userguide/img/author-gradle-7.png

세팅 파일의 목적 중 하나는 빌드에 필요한 모든 프로젝트들을 선언할 수 있습니다.

settings object

settings object는 gradle api의 일부입니다.
https://docs.gradle.org/current/javadoc/org/gradle/api/initialization/Settings.html
setting object groovy 문서

settings properties는 다음과 같습니다.

NameDescription
buildCachebuild cache 설정
plugins세팅에 적용할 플러그인들
rootDir빌드할 루트 디렉터리, 루트 프로젝트의 프로젝트 디렉터리
rootProject빌드의 루트 프로젝트
settingssettings 오브젝트

settings의 메소드는 다음과 같습니다.

NameDescription
include()프로젝트를 빌드에 추가
includeBuild()특정 경로에 빌드 추가

setting script structure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pluginManagement { // 프로젝트에 적용할 플러그인 레포지토리 설정                                 
    repositories {
        gradlePluginPortal()
        google()
    }
}

plugins { // 세팅에 적용할 플러그인 설정
    id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
}

rootProject.name = 'root-project'                           

dependencyResolutionManagement { // 플러그인 의존성 관련 설정                   
    repositories {
        mavenCentral()
    }
}

include('sub-project-a')                                    
include('sub-project-b')
include('sub-project-c')

build script

gradle build life cycle에서 프로젝트 루트 디렉터리에 있는 settings 파일을 이용해서 루트 프로젝트와 서브 프로젝트를 찾습니다. 찾은 프로젝트마다 gradle은 Project instance를 생성합니다. gradle은 Project마다 상응하는 build script 파일을 찾아 configuration phase에서 사용합니다.

project object

project 오브젝트는 gradle api의 일부입니다.
https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html
groovy project 오브젝트 문서

project properties는 다음과 같습니다.

NameTypeDescription
namestring프로젝트 디렉터리 이름
pathstring유효한 프로젝트 명
descriptionstring프로젝트 설명
dependenciesDependencyHandler프로젝트 의존성 핸들러
repositoriesRepositoryHandler프로젝트 repository 핸들러
layoutProjectLayout프로젝트 주요 location에 엑세스 제공
groupObject프로젝트 그룹
versionObject프로젝트 버전

project의 메소드는 다음과 같습니다.

NameDescription
uri()파일 경로
task()태스크 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
plugins {                                        // 빌드에 플러그인 적용                       
    id 'org.jetbrains.kotlin.jvm' version '1.9.0'
    id 'application'
}

repositories {                     // 의존성을 찾을 repository 명시                                     
    mavenCentral()
}

dependencies {                                           // 의존성 설정               
    testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5'
    testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.3'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
    implementation 'com.google.guava:guava:32.1.1-jre'
}

application {                                                           
    mainClass = 'com.example.Main'
}

tasks.named('test') {           // task 등록 & 설정                                        
    useJUnitPlatform()
}

Task

https://docs.gradle.org/current/userguide/img/author-gradle-5.png

task는 빌드가 수행하는 작업 단위입니다. 수행하는 작업으로는 클래스 컴파일, jar 파일 생성, 자바독 생성 혹은 레포지토리 배포 같은 작업이 될 수 있습니다.

./gradlew build 명령어를 실행하면 build와 함께 build가 의존하는 다른 task도 함께 실행됩니다.

task들은 빌드 스크립트 혹은 플러그인을 통해 추가됩니다.

task classification

실행 가능한 task에는 두 가지 종류가 있습니다.

  1. actionable tasks : 실행 가능한 액션과 연관된 task ex) compileJava
  2. lifecycle task : 액션이 연관되지 않은 task, 라이프사이클은 많은 actionable task를 실행하기 위해 사용합니다. ex) build, assemble

task registration and action

아래와 같이 task를 등록할 수 있다.

1
2
3
4
5
tasks.register('hello') {
    doLast {
        println 'Hello world!'
    }
}

빌드 스크립트에 hello 라는 task를 TaskContainer API를 이용해서 추가했습니다. 등록한 task는 다음과 같은 명령어를 통해 확인 가능합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
$ ./gradlew :app:tasks --all
Initialization phase

> Task :app:tasks

------------------------------------------------------------
Tasks runnable from project ':app'
------------------------------------------------------------

Application tasks
-----------------
run - Runs this project as a JVM application

Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the classes of the 'main' feature.
testClasses - Assembles test classes.

Distribution tasks
------------------
assembleDist - Assembles the main distributions
distTar - Bundles the project as a distribution.
distZip - Bundles the project as a distribution.
installDist - Installs the project as a distribution as-is.

Documentation tasks
-------------------
javadoc - Generates Javadoc API documentation for the 'main' feature.

Help tasks
----------
buildEnvironment - Displays all buildscript dependencies declared in project ':app'.
dependencies - Displays all dependencies declared in project ':app'.
dependencyInsight - Displays the insight into a specific dependency in project ':app'.
help - Displays a help message.
javaToolchains - Displays the detected java toolchains.
outgoingVariants - Displays the outgoing variants of project ':app'.
projects - Displays the sub-projects of project ':app'.
properties - Displays the properties of project ':app'.
resolvableConfigurations - Displays the configurations that can be resolved in project ':app'.
tasks - Displays the tasks runnable from project ':app'.

Verification tasks
------------------
check - Runs all checks.
test - Runs the test suite.

Other tasks
-----------
compileJava - Compiles main Java source.
compileTestJava - Compiles test Java source.
components - Displays the components produced by project ':app'. [deprecated]
dependentComponents - Displays the dependent components of components in project ':app'. [deprecated]
hello
model - Displays the configuration model of project ':app'. [deprecated]
processResources - Processes main resources.
processTestResources - Processes test resources.
startScripts - Creates OS specific scripts to run the project as a JVM application.

등록된 태스크는 다음과 같은 명령어로 사용 가능합니다.

1
2
3
4
5
$ ./gradlew hello
Initialization phase

> Task :app:hello
Hello world!

task group and description

위에서 선언한 task를 다음과 같이 변경하고 태스크를 확인해보면 다음과 같은 결과가 나옵니다.

1
2
3
4
5
6
7
tasks.register("hello") {
    group = "Custom"
    description = "A lovely greeting task."
    doLast {
        println("Hello world!")
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ ./gradlew :app:tasks --all
Initialization phase

> Task :app:tasks

------------------------------------------------------------
Tasks runnable from project ':app'
------------------------------------------------------------

Application tasks
-----------------
run - Runs this project as a JVM application

Build tasks
-----------
assemble - Assembles the outputs of this project.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
jar - Assembles a jar archive containing the classes of the 'main' feature.
testClasses - Assembles test classes.

Custom tasks
------------
hello - A lovely greeting task.

hello task에 그룹과 설명이 추가된 것을 확인할 수 있습니다.

task dependencies

1
2
3
4
5
6
7
8
9
10
11
tasks.register('hello') {
    doLast {
        println 'Hello world!'
    }
}
tasks.register('intro') {
    dependsOn tasks.hello
    doLast {
        println "I'm Gradle"
    }
}

intro 태스크를 실행해보면 hello 태스크가 선행되어 실행됩니다.

1
2
3
4
5
6
7
8
$ ./gradlew intro
Initialization phase

> Task :app:hello
Hello world!

> Task :app:intro
I'm Gradle

writing tasks

다음과 같이 커스텀 태스크를 작성할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
abstract class CreateFileTask : DefaultTask() {
    @TaskAction
    fun action() {
        val file = File("myfile.txt")
        file.createNewFile()
        file.writeText("HELLO FROM MY TASK")
    }
}

tasks.register<CreateFileTask>("createFileTask", ) {
    group = "custom"
    description = "Create myfile.txt in the current directory"
}

위 태스크를 실행하면 다음과 같이 새로운 파일이 생성되는 것을 확인할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ ./gradlew createFileTask

BUILD SUCCESSFUL in 1s
11 actionable tasks: 2 executed, 9 up-to-date
[16:08:24] [cost 2.077s] ./gradlew createFileTask


[16:08:26] dongjunkim ~/IdeaProjects/kotlin_project
$ ll
total 64
drwxr-xr-x  14 dongjunkim  staff   448 Aug  6 16:08 .
drwxr-xr-x@ 38 dongjunkim  staff  1216 Aug  6 15:56 ..
-rw-r--r--   1 dongjunkim  staff   214 Aug  6 15:56 .gitattributes
-rw-r--r--   1 dongjunkim  staff   103 Aug  6 15:56 .gitignore
drwxr-xr-x   8 dongjunkim  staff   256 Aug  6 16:08 .gradle
drwxr-xr-x   4 dongjunkim  staff   128 Aug  6 16:05 app
drwxr-xr-x   7 dongjunkim  staff   224 Aug  6 16:07 buildSrc
drwxr-xr-x   4 dongjunkim  staff   128 Aug  6 15:56 gradle
-rwxr-xr-x   1 dongjunkim  staff  8706 Aug  6 15:56 gradlew
-rw-r--r--   1 dongjunkim  staff  2918 Aug  6 15:56 flutergradlew.bat
drwxr-xr-x   4 dongjunkim  staff   128 Aug  6 15:56 list
-rw-r--r--   1 dongjunkim  staff    18 Aug  6 16:08 myfile.txt
-rw-r--r--   1 dongjunkim  staff   549 Aug  6 15:56 settings.gradle.kts
drwxr-xr-x   4 dongjunkim  staff   128 Aug  6 15:56 utilities
This post is licensed under CC BY 4.0 by the author.