Gradle
출처 : https://jungseob86.tistory.com/21
Gradle이란 무엇인가?
Gradle은 Apache Ant와 Maven의 가장 좋은 기능을 결합하면서도, 유연성과 성능을 향상시킨 빌드 자동화 시스템입니다.
Ant와 Maven이 가진 장점을 모아 만들었다. 의존성 관리를 위한 다양한 방법을 제공하고 빌드 스크립트를 XML 언어가 아닌 JVM에서 동작하는 스크립트 언어 ‘그루비’ 기반의 DSL(Domain Specific Language)를 사용한다. 심지어 메이븐(Maven)의 pom.xml을 Gradle 용으로 변환할 수도 있으며 Maven의 중앙 저장소도 지원하기 때문에 라이브러리를 모두 그대로 가져다 사용할 수 있다.
- 그루비 : 자바에 파이썬, 루비, 등의 특징을 더한 동적 객체 지향 프로그래밍 언어
Ant
- XML 기반으로 빌드 스크립트를 작성한다.
- 자유롭게 빌드 단위를 지정할 수 있다.
- 간단하고 사용하기 쉽다.
- 유연하지만 프로젝트가 방대해지는 경우 스크립트 관리나 빌드 과정이 복잡해진다.
- 생명주기(Lifecycle)을 갖지 않아 각각의 결과물에 대한 의존관계 등을 정의해야 한다.
Maven
- XML 기반으로 작성한다.
- 생명주기(Lifecycle)와 프로젝트 객체 모델(POM, Project Object Model)이란 개념이 도입됐다.
- Ant의 장황한 빌드 스크립트를 개선했다.
- pom.xml에 필요한 라이브러리를 선언하면 자동으로 해당 프로젝트로 불러와 편리하다.
- 상대적으로 학습 장벽이 높다.
- 라이브러리가 서로 의존하는 경우 복잡해질 수 있다.
왜 Gradle을 사용해야 하는가?
Gradle Wrapper란?
Gradle을 각 개발자나 CI 서버에 깔지 않고, 프로젝트에 함께 포함시켜 배포할 수 있는 방법을 제공해준다.
Gradle 빌드에 권장되는 사용 방법은 Gradle Wrapper
를 사용하는 것이다. Wrapper
는 미리 선언된 버전의 Gradle을 호출하고, 필요한 경우 미리 다운로드한다. Java 프로젝트를 CI 환경에서 빌드할 때 CI 환경을 프로젝트 빌드 환경과 매번 맞춰줄 필요가 없다. 이미 존재하는 프로젝트를 새로운 환경에서 바로 빌드할 수 있다. java나 gardle도 설치할 필요가 없다. 또한 로컬에 설치된 Gradle 또는 Java의 버전도 신경쓸 필요가 없다. 즉, 환경에 종속되지 않고 프로젝트를 빌드할 수 있다.
gradle wrapper 명령어를 실행하면 아래처럼 파일들이 생성된다.
$ gradle wrapper
BUILD SUCCESSFUL in 545ms
1 actionable task: 1 executed
$ tree
.
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
└── gradlew.bat
2 directories, 4 files
Gradle Wrapper 구조
gradlew.bat
윈도우용 wrapper 실행 스크립트이다.
gradlew
유닉스용 wrapper 실행 스크립트이다. 컴파일, 빌드 등을 하는 경우 사용한다. ./gradlew {task}
형태로 실행한다.
> ./gradlew build
<참고> 다음 명령어는 local에 Gradle이 설치되어 있어야 하고 프로젝트의 Gradle버전과 local의 Gradle버전이 호환되지 않으면 문제가 발생할 수 있음.
> gradle build
gradle/wrapper/gradle-wrapper.jar
Wrapper 파일이다. 실행 스크립트(gradlew or gradlew.bat)가 동작하면 Wrapper에 맞는 환경을 로컬 캐시에 다운로드 받은 뒤에 실제 명령에 해당하는 task를 실행한다.
gradle/wrapper/gradle-wrapper.properties
Gradle Wrapper 설정파일이다.
이 파일의 wrapper버전을 변경하면 task 실행시, 자동으로 새로운 Wrapper파일을 로컬 캐시에 다운로드 받음.
build.gradle 파일
- 의존성이나 플러그인 설정 등을 위한 스크립트 파일.
settings.gradle 파일
- 프로젝트의 구성 정보를 기록하는 파일.
- 어떤 하위프로젝트들이 어떤 관계로 구성되어 있는지를 기술.
- Gradle은 이 파일에 기술된대로 프로젝트를 구성함.
의존성 관리
- repositories를 사용해서 의존성을 가져올 주소를 설정.
- dependencies를 사용해서 설정된 Repository에서 가져올 아티팩트를 설정.
allprojects {
repositories {
mavenCentral()
jcenter()
maven {
url "http://repo.mycompany.com/maven2"
}
ivy {
url "../local-repo"
}
}
dependencies {
// 로컬 jar 파일의 의존성 설정
compile fileTree(dir: 'libs', include: '*.jar')
// 로컬 프로젝트간 의존성 설정
compile project(':shared')
// 컴파일 타임에 의존성을 받아옴
compile 'com.google.guava', name: 'guava:23.0'
// 테스트시만 의존성을 받아옴
// 마이너 버전을 '+'로 설정해서 항상 4점대 최신 버전을 사용
testCompile group: 'junit', name: 'junit', version: '4.+'
// 컴파일할때는 사용하고, 아티팩트를 만들때는 포함하지 않음
compileOnly 'org.projectlombok:lombok:1.16.18'
// 실행할때 의존성을 받아옴(기본적으로 컴파일을 모두 포함)
runtime('org.hibernate:hibernate:3.0.5')
}
}
- uploadArchives를 사용해서 생성된 아티팩트를 배포하기 위한 주소를 설정.
uploadArchives {
repositories {
ivy {
credentials {
username "username"
password "pw"
}
url "http://repo.mycompany.com"
}
}
}
Gradle 스크립트의 이해
- Gradle 스크립트는 groovy를 사용해서 만든 DSL. (DSL이란 특정 도메인에 특화된 언어를 말함)
- 모든 Gradle 스크립트는 두 가지 개념(projects와 tasks)으로 구성.
- 모든 Gradle 빌드는 하나 이상의 projects로 구성.
- 각 project는 하나 이상의 task들로 구성.
- task는 어떤 클래스를 컴파일하거나 JAR를 생성하거나 javadoc을 만드는 작업들을 의미.
task squid {
doLast {
println 'Hello!! Codingsquid'
}
}
task octopus(dependsOn: squid) {
doLast {
println "I'm Octopus"
}
}
- 간단한 squid task를 위와 같이 직접 만들 수 있음. gradle -q squid로 해당 task만 실행해 볼 수 있음.
- Gradle 스크립트는 DSL이지만, 몇가지 약속을 제외하면 groovy라는 언어가 지닌 강점을 모두 이용할 수 있음.
- dependsOn을 사용해서 task간의 의존성을 만들 수 있음. 위 예제의 경우, octopus task를 실행하면 먼저 squid가 실행되고 octopus가 실행됨.
DSL
Domain-Specific Language(DSL)은 특정 도메인에 특화된 비교적 작고 간단한 프로그래밍 언어. 도메인 문제를 해결 할 때, 문제 해결 아이디어와 최대한 유사한 형태의 문법으로 프로그래밍 할 수 있도록 하는 것이 주요 컨셉임. DSL의 반대 개념은 General Purpose Language(GPL)이며 Java나 C++같은 보편적인 언어를 지칭.
sourceSet
- Java 플러그인에는 Source Set이라는 개념이 들어가 있으며, 이는 함께 컴파일과 실행되는 소스 파일들의 그룹을 뜻함.
- 소스 셋에는 자바 소스 파일과 리소스 파일들이 들어감.
- 다른 플러그인들이 그루비나 스칼라 소스를 추가할 수 있음.
- 소스 셋은 컴파일 클래스패스와 런타임 클래스패스와 관련됨.
- 소스 셋의 목적은 소스들를 논리적 그룹으로 묶고 그 목적을 설명하는 데 있음. 통합 테스트용 소스셋, API 인터페이스 클래스들, 구현체 클래스들 형태로 구분 가능.
- 기본 Java Source Set
- main : 실제 작동 소스코드. 컴파일해서 JAR 파일로 들어감.
- test : 단위 테스트 소스코드. 컴파일해서 JUnit이나 TestNG로 실행.
새로운 sourceSet 생성 후 jar에 묶는 법
sourceSets {
main {
java {
srcDir "src-gen/main/java"
}
}
}
jar {
baseName = "${artifactId}"
version = "${versionId}"
excludes = ['**/application.yml']
from sourceSets.main.allSource //sourceSets의 main의 모든 java파일과 resource를 포함
bootJar.enabled = false
jar.enabled = true
}
task에 타입 지정
def queryDslOutput = file("src-gen/main/java")
task generateQueryDSL(type: JavaCompile, group: "build") {
doFirst {
if (!queryDslOutput.exists()) {
logger.info("Creating `$queryDslOutput` directory")
if (!queryDslOutput.mkdirs()) {
throw new InvalidUserDataException("Unable to create `$queryDslOutput` directory")
}
}
}
source = sourceSets.main.java
classpath = configurations.compile
options.compilerArgs = [
"-proc:only",
"-processor",
"com.querydsl.apt.jpa.JPAAnnotationProcessor"
]
destinationDir = queryDslOutput
options.failOnError = false
}
compileJava {
dependsOn generateQueryDSL
source generateQueryDSL.destinationDir
}
- type을 javaCompile로 지정함으로써 javaCompile task를 수행할 때 같이 수행하게된다. (compileJava dependsOn을 확인)
dependency scope
- compileOnly
Spring Boot 의 AutoConfiguration 을 살펴보면, 모든 선택적 의존성에 Dependency Optional True 가 붙어 있는 것을 확인할 수 있음. Gradle 에서는 compileOnly 를 사용할 수 있음. 대부분의 선택적 의존성들은 Starter Pack 에 의하여 주입됨.
---------
출처 : https://hmdev.vercel.app/
Gradle에서 의존성 관리하기
repositories 메소드
- repositories 메소드는 저장소 설정을 담당한다. 클로저 내용은 RepositoryHandler를 통해 실행된다. RepositoryHandler는 메이븐 중앙 저장소 추가를 위한 mavenCentral 메소드와 Bintray의 jCenter 저장소 추가를 위한 jcenter 메소드를 제공한다.
- 예를 들어 사내 maven repository에서 라이브러리를 가져올 때 아래와 같이 명시한다.
repositories {
mavenCentral()
maven {
allowInsecureProtocol(true)
url 'https://repo.company.com/repository/maven-repository/'
}
}
ext 메소드
ext {
queryDslVersion = '4.4.0'
}
dependencies 메소드
- implementation
- api
- compileOnly
- testImplementation
- annotationProcessor
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
implementation 'com.github.javafaker:javafaker:1.0.2'
}
5. Gradle 태스크 이해하기
5.1. 태스크 정의하기
task 작성
task 태스크이름 {
... 작업들
}
task sayHello {
println 'Hello world'
}
5.2. 태스크 실행하기
task 실행
$ gradle sayHello
gradle sayHello
> Configure project :
Hello world
BUILD SUCCESSFUL in 512ms
# -q 옵션을 준 경우
$ gradle -q sayHello
Hello world
doFirst, doLast
task greeting {
doFirst {
println 'hello'
}
doLast {
println 'bye'
}
}
실행결과
$ gradle greeting
> Task :greeting
hello
bye
BUILD SUCCESSFUL in 598ms
1 actionable task: 1 executed
task 축약형
leftshift 연산자는 Gradle 5.0 에서 제거되었다.
task hello << {
println 'Hello world!'
}
task와 파라미터
task sayHi {
def loopCount = count.toInteger()
for(def i in 1..loopCount) {
println('LoopCount: ' + i)
}
}
실행결과
$ gradle -q sayHi -Pcount=3
LoopCount: 1
LoopCount: 2
LoopCount: 3
task간 의존성 설정
task AAA(dependsOn:['BBB', 'CCC']) {
doFirst {
println('doFirst: AAA')
}
doLast {
println('doLast: AAA')
}
}
task BBB {
doFirst {
println('doFirst: BBB')
}
doLast {
println('doLast: BBB')
}
}
task CCC {
doFirst {
println('doFirst: CCC')
}
doLast {
println('doLast: CCC')
}
}
실행 결과
$ gradle AAA
> Task :BBB
doFirst: BBB
doLast: BBB
> Task :CCC
doFirst: CCC
doLast: CCC
> Task :AAA
doFirst: AAA
doLast: AAA
BUILD SUCCESSFUL in 603ms
3 actionable tasks: 3 executed
다른 task 호출하기
execute는 Gradle 5.0 에서 제거되었다.
task sayHi {
doFirst {
println('say Hi')
tasks.sayBye.execute()
}
}
task sayBye {
doLast {
println('say Bye')
}
}
실행 결과
$ gradle sayHi -q
say Hi
say Bye
task methodTask {
printMessage('say Hi')
}
String printMessage(String msg) {
println msg
}
실행 결과
$ gradle methodTask
> Configure project :
say Hi
BUILD SUCCESSFUL in 593ms
task someTask {
ext.message = 'say Hi'
}
task sayHi {
println someTask.message
}
$ gradle -q sayHi
say Hi
6. Gradle 플러그인 사용하기
6.1. 플러그인이란 무엇인가?
6.2. 플러그인 설치 및 적용하기
plugins {
id 'org.springframework.boot' version '2.5.1'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
id "org.asciidoctor.convert" version "1.5.9.2"
id 'java'
}
7. Gradle이 빌드 속도가 빠른 이유
점진적 빌드(Incremental Build)
- 이미 빌드된 파일들을 모두 다시 빌드하는 것이 아닌 바뀐 파일들만 빌드
- gradle은 빌드 실행 중 마지막 빌드 호출 이후에 task의 입력, 출력 혹은 구현이 변경됬는지 확인한다.
- 최신 상태로 간주하지 않는다면 빌드는 실행되지 않는다.
- 예를 들어 Kotlin 파일이 20개 있는 상태에서 컴파일이 완료되었으면, 이후 파일들 중 5개만 바뀌었을 경우 20개 모두가 아니라 5개만 다시 컴파일 시킨다
Build Cache
- 두 개 이상의 빌드가 돌아가고, 하나의 빌드에서 사용되는 파일들이 다른 빌드들에 사용된다면 Gradle은 빌드 캐시를 이용햇 이전 빌드의 결과물을 다른 빌드에서 사용할 수 있다.
- 다시 빌드하지 않아도 되므로 빌드 시간이 줄어들게 된다.
Daemon Process
- Gradle은 데몬 프로세스를 지원, Gradle의 데몬 프로세스는 메모리 상에 빌드 결과물을 보관한다.
- 한 번 빌드된 프로젝트는 다음 빌드에서 매우 적은 시간만 소요된다. 실제로 안드로이드의 경우 프로젝트가 복잡해지면 처음 빌드하는데 긴 시간이 걸리는데, 둘 째 빌드부터는 매우 적은 시간이 소모된다.
- 다음 명령어로 데몬 프로세스를 수행하거나 수행하지 않도록 할 수 있다.
gradle build --daemon
gradle build --no-daemon