진열사랑 2025. 5. 6. 13:52

출처 : https://jungseob86.tistory.com/21

Gradle이란 무엇인가?

Gradle은 Java, C/C++, Python 등과 같은 여러 언어를 지원하는 강력한 빌드 도구입니다. 자동화 빌드, 테스트, 배포, 출시 등의 프로세스를 관리하는 데 있어 도움을 줍니다.

Gradle은 Apache Ant와 Maven의 가장 좋은 기능을 결합하면서도, 유연성과 성능을 향상시킨 빌드 자동화 시스템입니다.

빌드 도구는 Ant -> Maven -> Gradle 순으로 발전되어 왔다.

 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은 성능적으로 뛰어납니다. 인크리멘탈 빌드를 지원하여 변경된 부분만 빌드할 수 있고, 병렬 처리를 통해 빌드 시간을 단축시킵니다. 또한, 코드 대신 선언적 언어를 사용한다.

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에서 의존성 관리하기

Gradle에서 의존성은 build.gradle 파일의 dependencies 블록 내에 선언합니다. Gradle은 선언된 의존성을 자동으로 다운로드 받고, 빌드와 테스트에 사용합니다.
파이썬에서 pip로 외부 라이브러리를 설치하는 것처럼, Gradle에서는 build.gradle의 repositories와 dependencies 설정을 통해 설치할 수 있다.

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 메소드는 그 인자를 buildScript 에서 전역변수로 사용하기 위해 사용된다.
 
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 태스크 이해하기

Gradle의 핵심 기능 중 하나는 태스크입니다. 태스크는 빌드, 테스트, 배포 등의 단계를 수행하는 작업 단위입니다.

5.1. 태스크 정의하기

Gradle 태스크는 build.gradle 파일의 task 블록 내에서 정의됩니다. 각 태스크는 이름, 설명, 그리고 실행할 동작을 포함합니다.

task 작성

그럼 지금부터 Gradle의 실행 작업 단위인 태스크(task)를 사용해보자. Gradle은 기본적으로 태스크를 구성하여 실행하며, 태스크를 구성하고 작성하는 것이 빌드 스크립트를 작성하는 과정이다.
task는 아래와 같은 구조를 작성하면 된다.
 
task 태스크이름 {
    ... 작업들
}
문자열을 출력하는 간단한 태스크를 작성해보자. build.gradle 파일에 아래와 같이 작성해주면 된다.
 
task sayHello {
    println 'Hello world'
}

5.2. 태스크 실행하기

Gradle 태스크는 명령 줄에서 gradle <taskName> 형식으로 실행할 수 있습니다. 여러 태스크를 동시에 실행하려면, 태스크 이름을 공백으로 구분하여 지정할 수 있습니다.

task 실행

실행은 터미널에서 gradle 태스크이름으로 형태로 입력하면 된다. gradle 명령어를 사용하면 현재 위치한 경로에서 build.gradle 파일을 찾는다. 실행할 때 -q(quiet) 옵션을 주면 오류에 대한 로그만 출력한다.
 
$ gradle sayHello

gradle sayHello

> Configure project :
Hello world

BUILD SUCCESSFUL in 512ms


# -q 옵션을 준 경우
$ gradle -q sayHello
Hello world

doFirst, doLast

태스크 내부에서 순서를 지정하고 싶을 때 사용한다. 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 에서 제거되었다.
태스크는 아래와 같이 축약한 형태로 작성할 수 있다. <<는 doLast와 동일하다.
 
task hello << {
    println 'Hello world!'
}

task와 파라미터

태스크를 실행할 때 -P파라미터이름=값을 이용하여 파라미터를 전달할 수 있다.
 
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간 의존성 설정

태스크가 실행될 때 의존성을 지정하여 태스크의 실행 순서를 지정할 수 있다. dependsOn를 사용하여 먼저 실행할 태스크를 지정할 수 있다.
 
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 플러그인 사용하기

Gradle 플러그인은 빌드와 배포 프로세스를 확장하는 데 사용됩니다. 예를 들어, Java 플러그인은 Java 컴파일, 테스트, JAR 패키징 등의 태스크를 제공합니다.

6.1. 플러그인이란 무엇인가?

플러그인은 특정 기능을 추가하거나 확장하는 소프트웨어 컴포넌트입니다. Gradle 플러그인은 빌드 스크립트에 새로운 태스크를 추가하거나, 기존 태스크의 동작을 변경합니다.
plugin을 등록하면 해당 플러그인에 포함된 수많은 Task들이 Gradle 파일로 들어온다.

6.2. 플러그인 설치 및 적용하기

Gradle 플러그인은 build.gradle 파일의 plugins 블록에서 선언하고 적용합니다. 선언된 플러그인은 Gradle이 자동으로 다운로드하고, 빌드 스크립트에 적용합니다.
 
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

8. 결론: Gradle을 효과적으로 사용하는 방법

Gradle을 효과적으로 사용하기 위해 몇 가지 팁과 추가 리소스를 제공합니다.

8.1. Gradle Best Practices

코드의 중복을피하고, 명확하고 가독성 있는 빌드 스크립트를 작성하는 것이 중요합니다. 또한, 항상 최신 버전의 Gradle을 사용하고, 필요한 의존성만 선언하는 것이 좋습니다.

8.2. 추가 리소스

Gradle에 대해 더 배우고 싶다면, 공식 Gradle 문서를 참조하거나, 각종 커뮤니티와 포럼을 활용해보세요. 또한, 실제 프로젝트의 빌드 스크립트를 살펴보는 것도 많은 도움이 될 것입니다.

이렇게 Gradle에 대한 기본적인 지식과 사용 방법을 알아보았습니다. Gradle은 강력하고 유연한 빌드 도구이므로, 이를 잘 활용하면 프로젝트의 빌드와 배포 과정을 훨씬 효율적으로 관리할 수 있습니다.