Post

[webapp] 의존성을 관리하고 빌드 프로세스를 단순하게 묘사하는 groovy기반의 gradle

빌드 자동화 도구

🐀 빌드 자동화 도구, Gradle

빌드란 실행파일을 만드는 과정을 말한다.
작업한 파일들(소스코드, 라이브러리, 이미지)출시하기 적합한 형태로 포장하는 일을 말한다. 압축 변환
결국 빌드란 ✨ 소스코드 파일을 컴파일한 후 여러 개의 모듈을 묶어 실행파일로 만드는 과정이다.
빌드되어 나온 결과물을 🗿 artifact라 한다.

자바 웹 애플리케이션의 경우,
.java, .xml, resource, .jpg .jpaJVM (java virtual machine)이나 🐈 톰캣(tomcat) 같은 WAS가 인식할 수 있도록 패키징하는 과정 및 결과물을 말한다.
즉, .java 파일을 컴파일하여 .class로 변환하고 resource.class참조할 수 있는 적절한 위치로 옮기고 META-INFMANIFEST.MF들을 하나로 압축하는 과정을 거친다.

그렇다면 빌드 자동화 도구빌드를 포함하여 테스트 및 배포를 자동화하는 도구를 말한다.
빌드 자동화 도구는 다음과 같은 작업을 순서대로 수행한다.

1
2
3
4
5
(1) 전처리(Preprocessing) - 종속성 다운로드
(2) 컴파일(Compile) - 소스코드를 바이너리코드로 변환
(3) 패키징(Packaging)
(4) 테스트(Testing)
(5) 배포 (Distribution)

🐘 gradlegroovy라는 언어를 기반으로 한 오픈소스 형태의 빌드 자동화 도구이다.
특히 🪴 spring boot 프로젝트에서 많이 사용된다.

gradle의 역할

🐘 gradle의 역할
  • 의존성 관리 (Dependency Management)
    우선 🐘 gradle은 필요한 라이브러리를 땡겨오거나 버전설정 및 빌드된 라이브러리의 라이프사이클 및 의존관계를 관리해주는 역할을 한다.

    🍪 의존관계 설정
    implementation : 컴파일 과정에서 필요한 라이브러리
    testImplementation : 테스트 시에 필요한 라이브러리
    api : Consumer에게 내가 사용하고 있는 라이브러리 dependency를 보여준다.

    ※ 의존관계는 group, name, version 순으로 기술한다.
    api보다 implementation의 성능이 더 좋다.

    1
    2
    3
    4
    
    dependecies {
      api project(':list')
      implementation project(":data")
    }
    

  • Build Process

    1
    2
    
    simplifies the process of building, testing and deploying software
    by automating tasks such as compiling code, packaging binaries (JAR files), running tests and publishing artifacts
    

    컴파일, 패키징, 테스팅, 배포 작업을 선언적 언어(groovy)를 이용하여 단순하게 묘사할 수 있다.
    gradle에서는 각각의 작업들을 Project 오브젝트 안의 task로 묘사한다.

    🥔 Project란
    빌드 시스템에서 하나의 프로젝트를 나타내는데, Project 오브젝트의 메서드를 통해 빌드 설정을 제어한다.
    gradle이 시작될 때, 인스턴스화되며 🐘 build.gradle 파일 자체가 Project 오브젝트이다.
    때문에 🐘 build.gradle에서 Project 객체를 위한 프로퍼티를 정의할 수 있다.
    🥔 task (class)란
    task 클래스는 빌드 시스템 안의 하나의 작업 단위이고, 이를 생성해서 Project 오브젝트에 붙이는 것이다.
    🐘 build.gradle에서 가장 먼저 plugins를 볼 수 있는데, plugins에 들어가는 각각의 plugin은 미리 구성해놓은 task들의 그룹이다.
     
    task는 여러개의 action으로 이루어져 있다. 각각의 action은 compiling code, packaging application.. 을 의미한다.

    ☄️ task dependencies

    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
    
    task publishDockerImage {
      ext.version = "1.0.0"
      def customVariable = "abc"
      println "customVariable ${customVariable}" // variance reference: $
    
      doFirst() { // action, doLast()도 있다.
          println "publishing DockerImage"
      }
    }
    
    // 해당 task를 실행하면 그 이전에 publishDockerImage가 실행된다.
    task deployingDockerImage(dependsOn: publishDockerImage) {
      onlyif { publishDockerImage.version == "1.0.0" }
    
      doFirst() {
          println "deploying DockerImage"
      }
    }
    
    task cleanUp {
      doFirst() {
          println "cleaning up..."
      }
    }
    
    deployingDockerImage.finalizedBy cleanUp
    
    // gradle만 쳤을 때도, 자동으로 실행하도록
    defaultTasks "deployingDockerImage"
    
    

🐘 build.gradle in springboot

⚔️ 예제: build.gradle in springboot

스프링부트 프로젝트에서 🐘 build.gradle에 보면 아래와 같은데,
pluginsProject 오브젝트의 메서드이고,
{}로 감싸진 부분은 메서드의 인자를 나타내는 Groovy의 클로저(Closure)이다.

🍍 클로저
나중에 실행할 코드 조각을 말한다, 즉 정의할 때가 아닌 호출할 때 실행된다.
즉, 클로저가 메서드 안에 파라미터로 들어간다.

sprint initializr로 프로젝트를 GENERATE하면 root디렉토리 아래에
다음과 같은 🐘 build.gradle 파일이 생성된다.

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
plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.17'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

group = 'com.test.test'
version  = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '11'
}

repositories { // 어디서 dependencies를 가져올 것인가
    mavenCentral() // 여기에 무수히 많은 기존의 java 라이브러리들이 있다.
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
    implementation 'org.springframework.boot:spring-boot-starter-web'
}

tasks.named('test') {
    useJUnitPlatform()
}

생략된 부분을 생략하지 않으면, 원래 아래와 같이 작성한 것이다.

1
2
3
4
5
6
7
project.plugins({
    id 'java'
    id 'org.springframework.boot' version '2.7.17'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
})

// ...

gradle을 사용하는 이유

🍝 gradle은 Declarative Build Scripts를 사용한다

(1) Declarative Build Scripts는 빌드 프로세스에서, 어떻게가 아닌 무엇을 해야하는지를 기술하는 방식에 초점을 맞춘다.

  • 즉, 이러한 스크립트는 단순하고 직관적인 구문을 제공하며, tasksdependencies를 묘사하기 쉽다.
📘 written by groovy
gradle은 groovy 라는 언어로 작성되었다. ※ kotlin으로 작성된 것도 있다.
groovy라는 언어의 유연한 구문이라는 특성은 선언적인 빌드 스크립트와 적합했고,
JVM 기반 언어이기 때문에, Java와도 호환되어 gradle의 DSL(Domain-Specific Language)를 구축하는데 적합했다.

Groovy DSL scripts🐘 build.gradle라는 이름의 파일에 스크립트 내용을 선언한다.

Groovy는 Java platform을 위한 Dynamic (typing) language 이다. ※ static typing도 가능

(2) gradle은 dependencies를 자동으로 로컬에 캐시한다.

  • 즉, 빌드할 때마다 repository에서 다운받지 않고, 캐시된 결과를 사용하므로 빌드 및 테스트 실행속도가 빠르다.

(3) 프로젝트에서 일부분만 컴파일할 수 있다.

  • 한번 컴파일된 부분은 checksums and timestamps를 확인해서, 변경된 부분만 컴파일할 수 있다.

(4) Multi 프로젝트(or 모듈)를 가진 복잡한 프로젝트의 빌드를 다루기 쉽다.

  • 각각의 subproject에 각각의 build.gradle을 넣어놓을 수 있다.

(5) Build Scans 기능을 제공한다.

(6) CI/CD (Continuous Integration and Deployment)

  • Jenkins에 쉽게 연결할 수 있다.

🦣 gradlew

🦣 gradle wrapper

✨ gradle 설치 없이 🦣 gradle wrapper를 이용하여 빌드를 지원한다.
설정 주입 방식(Configuration Injection) *을 사용하므로 재사용에 용이하다.

📘 설정 주입 방식(Configuration Injection)
플러그인(plugins), 의존성(dependencies), task 등의 설정을 빌드 스크립트에 외부로부터 주입하는 방식을 말한다.
빌드 로직을 재사용하거나 다른 프로젝트에서 쉽게 적용할 수 있도록 한다.
1
2
3
4
5
6
7
8
9
10
plugins {
    // 해당 플러그인이 제공하는 기본 빌드 설정과 태스크가 주입된다
    id 'java'
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web:2.5.4'
}
// 각 의존성은 gradle이 빌드를 수행할 때, 외부 라이브러리 혹은 모듈을 가져와 사용하도록 구성된다.

🦣 gradlew
gradle wrapper를 실행하는 스크립트다. 명령줄 도구 프로젝트에 gradle을 설치하지 않고도, gradle을 다운로드 하고
프로젝트에 지정된 버전의 gradle을 사용하여 빌드할 수 있다.
프로젝트를 공유하거나 다른 컴퓨터에서 빌드할 때 일관된 결과를 얻을 수 있다.
⚠️ 🐘 gradle은 로컬시스템에 gradle이 설치되어있지 않은 경우 프로젝트를 빌드할 수 없다.
1
2
3
4
5
6
7
# 빌드 및 실행
gradlew.bat build
cd build/libs
java -jar PROJECT_NAME-0.0.1-SNAPSHOT.jar

gradlew clean build
# build 폴더를 지우고 다시 만든다
🪶 Maven
Apache 라이센스로 배포되는 오픈 소스 형태의 Java전용 빌드 자동화 도구이다.
필요한 라이브러리를 pom.xml(project object model)에 정의한다.
해당 파일에는 프로젝트 정보, 빌드 관련 설정, 빌드 환경, pom 연관정보를 담고 있다.
gradle보다 빌드 속도가 느리며, 멀티 프로젝트에서 특정 설정을 다른 모듈에서 사용하려면 상속을 받아야해서 번거롭다.

gradle build lifecycle

🐘 Initialization -> Configuration -> Execution
  • (1) Initialization
    하나의 Project안에 여러 subproject가 있을 수 있는데
    🐘 settings.gradle을 토대로 각 subproject 안에 있는 🐘 build.gradle을 식별하는 역할을 한다.
  • (2) Configuration
    🐘 build.gradle에서 만들어진 tasks를 기반으로 📉 tasks graph를 만든다.
    task dependencyproperty가 설정되며, 어떤 순서로 tasks가 실행되어야 할지 파악한다.
    이때 📉 tasks graph가 순환되면 안된다.
  • (3) Execution
    📉 task graph를 토대로 의존성 dependecies에 기반해서 tasks의 action을 실행한다.
    오래되거나 손실된 결과에 한해서 tasks를 실행한다.
    최신의 캐시된 task는 skip한다.
🪶 task lifecycle
Creation -> Configuration -> Execution

Creation : build lifecycle의 Configuration phase에 task가 만들어진다.
Configuration : build lifecycle의 Configuration phase에 task dependenciesproperties가 설정된다.
Execution : build lifecycle의 Execution phase에 task's action이 실행된다.

gradle init

🌋 gradle init으로 Multi Module 새 프로젝트 생성

하나의 gradle build로 다수의 서로 연관된 subproject를 함께 조직하여 빌드할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
gradle init

---------------------------------
Select type of build to generate:
---------------------------------
    1: Application

-------------------------------
Select implementation language:
-------------------------------
    1: java

-----------------------------
Select application structure:
-----------------------------
    1: Single application project ㅇ // 일단은 간단한 형태에서 시작
    2: Application and library project // 이게 사실, Multi Module 프로젝트

------------------------
Select build script DSL:
------------------------
    2: Groovy
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
> 📂 app
    > src
        > main
            > java/org/example
                - ☕ App.java
            > resources
        > test
            > java/org/example
                - ☕ AppTest.java
            > resources
    - build.gradle
> 📂 build
    > reports/problems
        - problem-report.html
> 📂 gradle
    > wrapper
        - gradle-wrapper.jar
        - gradle-wrapper.properties
    - libs.versions.toml

> 📂 moduleA (임의생성)
> 📂 moduleB (임의생성)

🐘 gradle.properties
🦣 gradlew
🦣 gradlew.bat
🐘 settings.gradle
🐘 build.gradle (수동생성)

🐘 settings.gradle

1
2
3
4
5
6
7
8
9
10
plugins {
    // Apply the foojay-resolver plugin to allow automatic download of JDKs
    id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
}

rootProject.name = 'javaMultiSimpleProject'
include('app')
// 만약 Multi 모듈 프로젝트로 만들고 싶다면
// moduleA, moduleB 디렉토리를 만들고
// include('app', 'moduleA', 'moduleB')

subproject (ex. moduleA, moduleB)🐘 build.gradle에서 subproject(모듈)간의 의존관계도 설정할 수 있다.

🐘 moduleB.build.gradle

1
2
3
4
5
project(":moduleB") {
    dependencies {
        implementation project(":moduleA")
    }
}

이렇게 gradle :moduleB:build 했을 때 📂build > distributions > moduleB.zip을 열어보면 📂 lib > moduleA.jar, moduleB.jar 파일이 있는 것을 확인할 수 있다.

This post is licensed under CC BY 4.0 by the author.