[webapp] 의존성을 관리하고 빌드 프로세스를 단순하게 묘사하는 groovy기반의 gradle
빌드 자동화 도구
빌드란 실행파일을 만드는 과정을 말한다.
작업한 파일들(소스코드, 라이브러리, 이미지)을 출시하기 적합한 형태로 포장하는 일을 말한다. 압축 변환
결국 빌드란 ✨ 소스코드 파일을 컴파일한 후 여러 개의 모듈을 묶어 실행파일로 만드는 과정이다.
빌드되어 나온 결과물을 🗿 artifact
라 한다.
자바 웹 애플리케이션의 경우,
.java
, .xml
, resource
, .jpg
.jpa
을 JVM (java virtual machine)이나 🐈 톰캣(tomcat) 같은 WAS가 인식할 수 있도록 패키징하는 과정 및 결과물을 말한다.
즉, .java
파일을 컴파일하여 .class
로 변환하고 resource
를 .class
가 참조할 수 있는 적절한 위치로 옮기고 META-INF
와 MANIFEST.MF
들을 하나로 압축하는 과정을 거친다.
그렇다면 빌드 자동화 도구란 빌드를 포함하여 테스트 및 배포를 자동화하는 도구를 말한다.
빌드 자동화 도구는 다음과 같은 작업을 순서대로 수행한다.
1
2
3
4
5
(1) 전처리(Preprocessing) - 종속성 다운로드
(2) 컴파일(Compile) - 소스코드를 바이너리코드로 변환
(3) 패키징(Packaging)
(4) 테스트(Testing)
(5) 배포 (Distribution)
🐘 gradle
은 groovy
라는 언어를 기반으로 한 오픈소스 형태의 빌드 자동화 도구이다.
특히 🪴 spring boot
프로젝트에서 많이 사용된다.
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
에 보면 아래와 같은데,
plugins
는 Project 오브젝트의 메서드이고,
{}
로 감싸진 부분은 메서드의 인자를 나타내는 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을 사용하는 이유
(1) Declarative Build Scripts
는 빌드 프로세스에서, 어떻게가 아닌 무엇을 해야하는지를 기술하는 방식에 초점을 맞춘다.
- 즉, 이러한 스크립트는 단순하고 직관적인 구문을 제공하며,
tasks
나dependencies
를 묘사하기 쉽다.
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 설치 없이 🦣 gradle wrapper
를 이용하여 빌드를 지원한다.
설정 주입 방식(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이 빌드를 수행할 때, 외부 라이브러리 혹은 모듈을 가져와 사용하도록 구성된다.
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 폴더를 지우고 다시 만든다
Apache 라이센스로 배포되는 오픈 소스 형태의 Java전용 빌드 자동화 도구이다.
필요한 라이브러리를 pom.xml(project object model)에 정의한다.
해당 파일에는 프로젝트 정보, 빌드 관련 설정, 빌드 환경, pom 연관정보를 담고 있다.
gradle보다 빌드 속도가 느리며, 멀티 프로젝트에서 특정 설정을 다른 모듈에서 사용하려면 상속을 받아야해서 번거롭다.
gradle build lifecycle
(1) Initialization
- 하나의 Project안에 여러 subproject가 있을 수 있는데
🐘 settings.gradle
을 토대로 각 subproject 안에 있는🐘 build.gradle
을 식별하는 역할을 한다.
(2) Configuration
🐘 build.gradle
에서 만들어진 tasks를 기반으로📉 tasks graph
를 만든다.
task dependency
와property
가 설정되며, 어떤 순서로 tasks가 실행되어야 할지 파악한다.
이때📉 tasks graph
가 순환되면 안된다.
(3) Execution
📉 task graph
를 토대로 의존성dependecies
에 기반해서 tasks의 action을 실행한다.
오래되거나 손실된 결과에 한해서 tasks를 실행한다.
최신의 캐시된 task는 skip한다.
Creation -> Configuration -> Execution
Creation : build lifecycle의 Configuration phase에 task가 만들어진다.
Configuration : build lifecycle의 Configuration phase에 task dependencies와 properties가 설정된다.
Execution : build lifecycle의 Execution phase에 task's action이 실행된다.
gradle init
하나의 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
파일이 있는 것을 확인할 수 있다.