Multi Module을 활용한 Mono Repo로 구성된 프로젝트를 수행하다가 문득
각 모듈들이 어떻게 빌드가 되고, 어떻게 하나의 어플리케이션으로 띄워지는지 궁금해졌다
project
ㄴ api
ㄴ...main..java...
ㄴApiApplication
ㄴbuild.gradle
ㄴ core
ㄴ...
ㄴbuild.gradle
ㄴbatch
ㄴ...
ㄴbuild.gradle
ㄴ gradle
ㄴwrapper
ㄴgradle-wrapper.jar
ㄴgradle-wrapper.properties
ㄴ gradlew
ㄴ gradlew.bat
ㄴ build.gradle
ㄴ settings.gradle
이렇게 Root Project를 기준으로 멀티 module로 구성된 프로젝트이고
여기서 각 build.gradle 과 settings.gradle 파일이
빌드과정에서 어떠한 역할을 담당하는지 궁금해졌다
🤔 Gradle 컨셉과 구성요소
Gradle Document에서 이야기 하는 Gradle의 주요 컨셉과 구성요소는 아래와 같다
👉 Project
- Gradle에서 Project는 빌드할 수 있는 응요프로그램 혹은 라이브러리의 조각
- Project가 한개일때는 Root Project, 2개 이상일 때는 한개의 Root Project와 N개의 Subproject들로 구성
👉 Build Script
- Gradle에세 프로젝트를 빌드하려면 어떠한 단계들을 거쳐야하는지 알려준다
- Build Flow를 정의한다
👉 Depenency Manager
- 프로젝트에 필요한 외부 리소스들을 선언하고, 자동적으로 관리
- 빌드 과정에서 Gradle이 외부 의존성들을 포함시킴
👉 Tasks
- 코드를 컴파일하거나 테스트를 실행하는 과정의 기본 일 단위
- Plugin과 Build Script에 정의된다
- Intellij 같은 IDE에서는 gradle 탭을 눌렀을때 보이는 것들이 task다 (./gradlew task -all 을 통해서도 볼 수 있음)
👉 Plugins
- Gradle 기능을 확장하고 task들을 지원
🤔 gradlew??
프로젝트 root에 gradlew가 있다면 gradle을 빌드 도구로 사용하는 프로젝트임을 나타냄
ProjectRoot
ㄴ...
ㄴgradle
ㄴwrapper
ㄴgradle-wrapper.jar // 버전에 맞는 gradle을 서버로부타 다운받는 코드가 있는 실행파일
ㄴgradle-wrapper.properties // 어디서 다운 받을 것인지, 파일에서 가져올 것인지
ㄴbuild.gradle
ㄴgradle.properties
ㄴgradlew // Unix용 실행 스크립트
ㄴgradlew.bat // Windows용 실행 스크립트
Gradle Wrapper를 실행하는 것이 gradlew 이다
gradlew를 통해서 buid를 실행하는 것을 권장한다
👉 Gradle Wrapper
로컬에서 선언된 버전의 Gradle을 호출하고, 없는 경우 Gradle 서버로부터 해당 버전에 맞는 환경을 다운로드하는 역할을 한다
- Gradle 버전에 맞게 빌드 환경을 표준화 가능
- 덕분에 환경에 종속되지 않고, 어디에서나 빌드할때 동일한 환경을 갖출 수 있다
- 개발자마다 다른 Gradle을 사용하는 경우 버전 차이로 빌드 에러가 생길수 있으므로, gradle 버전관리를 프로젝트에 종속시켜서 개발자별로 빌드 환경이 상이한 것을 방지 -> gradlew로 build 실행시, 개발자 로컬 환경의 gradle이 다른 버전이라도, gradle-wrapper에 설정된 gradle 버전으로 빌드
- Server로부터 설정한 버전에 따른 배포본을 다운 받고
- 로컬의 GRADLE_USER_HOME 환경변수에 설정된 경로에 저장하고 압축을 푼다
- Gradle이 Build를 시작할 때, 위의 경로에 있는 버전의 Gradle을 이용
gradle-wrapper를 공부하면서 왜 현재 프로젝트에서 세팅 초반에 gradle bin zip 파일을 다운받고, gradle-wrapper.properties 파일을 수정하라고 했는지 알게되었다
만약 회사의 보안 문제로 Gradle을 다운받아오는 Server에 연결할 수 없다면
- gradle 버전을 fix해놓고, 수정할 일이 없다면
- gradle 파일을 직접 다운받아서 properties 파일을 세팅해주면 된다
ProjectRoot
ㄴ...
ㄴgradle
ㄴwrapper
ㄴgradle-wrapper.jar // 버전에 맞는 gradle을 서버로부타 다운받는 코드가 있는 실행파일
ㄴgradle-wrapper.properties // 어디서 다운 받을 것인지, 파일에서 가져올 것인지
ㄴgradle-7.6.2-bin.zip
ㄴbuild.gradle
ㄴgradle.properties
ㄴgradlew // Unix용 실행 스크립트
ㄴgradlew.bat // Windows용 실행 스크립트
// gradle-wrapper.properties
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=../gradle-7.6.4-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributuinUrl이 Gradle 서버인, https://services.gradle.org가 아닌 프로젝트에 폴더에 다운로드해놓은 gradle로 path로 설정
🤔 settings.gradle???
모든 Gradle의 entry point다
프로젝트의 구조에 대해서 정의되는 파일이다 -> 그렇기 때문에 위치는 Project의 Root에 위치
- Project가 Root Project한개라면, 디렉토리를 따라가면 되어서 정의할 필요가 없으므로 해당 파일 없어도됨
- Project가 여러개라면, Root Project와 Subproject들을 정의해주어야
rootProject.name = 'Project Root Name'
include ':api, ':core', ':batch'
🤔 build.gradle??
모든 Gradle Build는 한개 이상의 build script를 가지고 있어야한다
build script를 나타내는 파일인 build.gradle 파일은 아래 두가지 타입의 dependency 들을 정의하고 있다
- Gradle과 Build Script가 빌드시에 의존하는 라이브러리 혹은 플러그인
- 프로젝트 소스코드가 의존하는 라이브러리
Plugin과 Dependency의 차이는??
👉 Plugin
특정 도메인에 포함된 task들의 집합 형태로 패키지화 되어있음
gradle에 포함되어있지 않은 task들을 plugin을 apply함으로써, task들을 따로 정의하지 않아도됨
task 뿐만 아니라, properties와 메서드들도 프로젝트에 추가
plugins {
id 'java'
}
예를 들어
java 플러그인은
- main과 test SourceSet을 세팅
- 프로젝트의 Main/Test 소스 코드들을 컴파일한 후에 Class 파일들과 결과물을 build 폴더를 생성하여 옮기는 Tasks들을 제공
다른 예로는 org.springframework.book 플러그인은
- 실행가능한 jar, war 파일을 패키징
- Sprint boot 어플리케이션 실행
- spring-booty-dependencies 플로그인에서 제공하는 종속성 관리 사용
- task와 기능들을 담고 있다
// api 모듈 build.gradle
plugins {
id 'org.springframework.boot' version '3.3.5'
}
tasks.named("bootJar") {
mainClass = 'com.example.ExampleApplication' // Main class가 무엇인지 커스텀할 수 있는 설정도 plugin에서 제공
}
프로젝트에서는 api 모듈을 중심으로 하나의 REST API 서버를 띄워야해서
- Domain과 Application 레이어에 해당하는 core 모듈의 bootJar task는 비활성화하고,
- Presentaion 레이어에 해당하는 api 모듈의 build.gradle에서 main 메서드가 있는 Application 파일을 바라보도록 설정했다
👉 Dependencies
프로젝트에서 사용할 소스코드 및 라이브러리를 정의하면 Gradle에서 빌드시점에 자동으로 필요한 파일들을 다운로드
dependencies {
implementation('org.springframework.boot:spring-boot-starter-web')
implementation('org.springframework.boot:spring-boot-starter-data-jpa')
}
- implementation - 컴파일과 런타임에 사용
- compileOnly - 컴파일 시에만 필요
- runtimeOnly - 런타임에만 필요
- testImplementation - implementation과 동일하나 test에만 사용
- testCompileOnly - compileOnly와 동일하나 test에만 사용
- testRuntimeOnly - testRuntimeOnly와 동일하나 test에만 사용
🤔 gradle.properties??
Build Script 내에서 사용할 변수들을 정의해놓는 데에 사용된다
- 프로젝트 속성 (JVM Options, 메모리 설정 등) 정의 -> 빌드 최적화
- secret들 관리
- gradle, plugin, dependency 버전 정의
# Plugin versions
springBootVersion=X.XX.XX
springDependencyManagementVersion=X.XX
// Build Script에서 아래와 같이 사용
plugins {
id 'java'
id 'org.springframework.boot' version "${springBootVersion}"
}
프로젝트에서는 gradle.properties를 사용하여 환경변수 / 버전 관리를 하였고,
gradle doc에 따르면 Gradle에서 제공하는 Version Catalog를 이용해서 버전 관리도 가능했다
https://docs.gradle.org/current/userguide/dependency_management_basics.html
Dependency Management Basics
To add a dependency to your project, specify a dependency in the dependencies block of your build.gradle(.kts) file. The following build.gradle.kts file adds a plugin and two dependencies to the project using the version catalog above: plugins { alias(libs
docs.gradle.org
다음 포스팅에서는
gradle build 시에 아래의 task들이 순차적으로 실행되는데
$ ./gradlew build
> Task :app:compileJava
> Task :app:processResources NO-SOURCE
> Task :app:classes
> Task :app:jar
> Task :app:startScripts
> Task :app:distTar
> Task :app:distZip
> Task :app:assemble
> Task :app:compileTestJava
> Task :app:processTestResources NO-SOURCE
> Task :app:testClasses
> Task :app:test
> Task :app:check
> Task :app:build
BUILD SUCCESSFUL in 764ms
7 actionable tasks: 7 executed
동작원리가 궁금해져서
뜯어보면서 Java Application이 빌드 -> run 되는 과정을 한번 살펴보고자한다