Gradle
Gradle이란
Gradle은 빌드를 자동화해주는 도구(Build Tool)이다.
단순하게 컴파일만 하는게 아니라 아래 작업들을 자동으로 처리해준다.
- 코드 컴파일
- 애플리케이션 실행
- 테스트 자동 수행
- .jar 또는 .war 패키징
- 외부 라이브러리 다운로드
- 빌드 결과 캐싱 및 병렬 처리
쉽게 말하면 빌드 + 실행 + 테스트 + 배포까지 한번에 도와주는 자동화 도구이다.
Gradle이 필요한 이유
자바를 개발할때는 여러 복잡한 작업들을 사용자가 직접 실행시켜줘야만 한다.
위에 작성된 모든 일들을 사용자가 직접 입력 해서 처리를 해주는데 이런 과정을 Gradle은 자동으로 수행해주면서 생산성과 일관성을 보장한다.
단적으로 컴파일과 실행에 대해서 예시를 들어보자.
Gradle이 없으면 자바로 직접 파일을 지정하여 컴파일을 해줘야한다.
예를 들어 MainTest.java라는 파일이 존재한다고 해보자.
이 java파일은 .class 파일로 컴파일이 진행되어야 실행이 가능한 형태가 된다.
그렇기 위해서 자바에선 javac.exe라는 응용 프로그램을 통해서 .java파일을 .class파일로 컴파일을 진행한다.
컴파일을 진행하려면 해당 프로그램으로 하여금 .java파일을 알려 해당 파일을 컴파일 해주세요 라는 요청을 보내줘야한다.
{Path}/javac.exe MainTest.java
이렇게 명령어로 파라미터를 전달하면서 javac를 실행하면 아래와 같이 컴파일된 class 파일이 생성된다.
그리고 나서 해당 프로그램을 실행시키기 위해서는 java.exe를 사용해서 class파일을 실행시켜줘야한다.
{Path}/java.exe MainTest
java.exe를 실행할때 주의할 점은

이렇게 패키지 구조가 없이 생성된 자바 파일의 경우는

이렇게 단순하게 class 파일 이름만으로 실행이 가능한 반면, 코드 내부에 아래와 같이 패키지 구조가 작성되어 있는 경우는


이렇게 바로 실행이 불가능하게 된다.
그 이유는 Java는 .class 파일의 패키지 구조를 기준으로 실행을 찾기 때문이다.
MainTest는 com.example.demo 패키지로 선언되어 있기에 Java는 이걸 기준으로 실행할 MainTest라는 파일을 찾는데 java MainTest는 디폴트 패키지로 간주되어 충돌이 발생하는 것이다.
쉽게 말하면 패키지 구조가 소스내부에 작성되어 있다면

해당 경로를 기준으로 패키지 구조가 생성되어 있고 이를 실행할때에는

이렇게 해당 class파일의 패키기 구조를 작성해서 전달해줘야만 한다는 것이다.
java.exe [소스내부에 작성된 패키기 구조].MainTest[.class 생략해야함]
그래야지만

이렇게 실행이 된다라는 점을 알아두자.
그리고 이렇게 실행하기 위해서는 패키지 구조의 상위 단계인 자바의 root에서 실행해줘야만 실행이 가능하다는 점까지 알아두자.
Gradle을 사용하면 아래 명령어로 빌드 뿐만 아니라 실행까지 가능하게 된다
./gradlew run
이걸 IDE라고 불리우는 개발 도구에서는 GUI로 만들어
이렇게 단순한 버튼 하나로 Build 부터 실행까지 가능하도록 만들어준다.
간단하게 정리하면 해당 언어의 컴파일, 실행 등등의 작업을 단순한 명령어로 변경시켜서 실행하고 관리 할 수 있도록 해주는것이 Gradle같은 프로그램이고 이를 GUI로 쉽게 작업할 수 있도록 해주는 것이 IDE(IntelliJ, Eclipse 등..)인 것이다.
그럼 Gradle은 우리가 어떤 언어를 사용할지 어떤 라이브러리를 사용할지를 어떻게 알게 되는 것일까?
build.gradle 분석
이는 Gradle은 특정 파일을 기준으로 해당 프로젝트에 대한 정보를 인식한다
build.gradle이라는 파일에는 Gradle이 읽고 어떻게 해당 프로젝트를 바라볼지에 대한 정보가 적혀 있다.
단순하게 해당 프로그램을 자바라는 언어로 만들었고, 스프링 부트라는 프레임워크를 사용하며 라이브러리를 관리하는 매니져를 어떤걸 사용할지에 대해서 설정해두면 Gradle은 해당 프로젝트를 자바의 구조로 만들어
이런 구조로 해당 프로젝트를 바라보고 스프링의 기능과 라이브러리를 해당 라이브러리(의존성)를 어떤 방식으로 관리할지를 결정할 수 있도록 만들어준다는 것이다.
해당 부분은 plugins라는 항목으로 작성해주고
이런식으로 작성되어 있다.
사용하는 언어(이걸 기준으로 어떻게 컴파일할지, 어떻게 실행할지를 Gradle이 바라봄, 단순하게 javac를 사용해서 컴파일 할지, 다른 언어의 경우는 다른 프로그램을 사용해서 컴파일 할지에 대해서 결정함)를 사용할것이고 Spring Boot 프레임워크를 사용할것이고 의존성 관리는 dependency-management 플러그인을 통해 처리하겠다는 의미가 된다.
다음
이 부분은 프로젝트의 메타정보(metadata)를 설정하는 부분이다.
group = 'com.example'
이건 패키지 네임스페이스(도메인)같은 개념으로 주로 Java에서는 패키지 이름과 관련되며 Gradle에서는 라이브러리의 소속을 나타낸다.
쉽게 말하면 이건 이 프로젝트는 누구 것인지를 알려주는 이름표(이름공간, 네임스페이스) 같은 것이라고 보면 된다.
예를 들어보면 프로젝트 그룹은 보통 해당 프로젝트를 만드는 회사의 이메일을 역순으로 작성하게 되는데 프로젝트의 경로가
com.naver.demo
이라면 naver.com을 가진 회사가 만든 demo라는 프로젝트라는 것이다.
이에 대한 정보를 작성해두는 것이 group이라는 값이다.
여기서 version의 경우는 우리가 만든 프로젝트의 현재 버전을 의미하는 것으로
version = '0.0.1-SNAPSHOT'
현재 만든 프로젝트는 0.0.1버전이고 SNAPSHOT이라는 것은 아직 개발중이라는 의미로 배포용 정식 버전은 아님을 나타낸다.
결론적으로 두 부분을 합쳐서 naver.com에서 만든 demo라는 프로젝트는 현재 0.0.1-SNAPSHOT 버전으로 개발을 시작하고 진행중인 단계이다 라는 정보를 담는다.
그리고 나중에 해당 프로젝트를 .jar파일로 만다면
demo-0.0.1-SNAPSHOT.jar
이와 같이 파일이 생성된다.
인텔리제이의 터미널에서
// 자바를 인텔리제이가 설치한대로 사용하고 있어 환경변수에 임시로 넣어주자
$env:JAVA_HOME="C:\Users\Administrator\.jdks\azul-17.0.16"
$env:PATH="$env:JAVA_HOME\bin;$env:PATH"
./gradlew build // .jar로 마는 명령어
와 같이 실행해주면
이렇게 생성되는 것을 볼 수 있다.
이 부분은 Gradle이 자바 몆버전을 사용해서 프로젝트를 컴파일 할지를 결정한다.
만약 현재 시스템에 위에 작성된 버전이 설치되어 있지 않더라도 Gradle이 자동으로 설치해서 사용한다.
이는 여러 버전의 Java가 설치되어 있거나 CI/CD 환경에서 자바 버전 차이로 인한 오류 방지에 유용하다.
만약 이 설정이 없는 경우엔 현재 시스템의 기본 Java를 사용하고 해당 버전으로 컴파일한다.
시스템 기본 자바라는 것은 환경변수에 설정되어 있는 기본 자바를 의미하고 cmd나 powershell에서 아래와 같이 입력했을때
나오는 이 버전을 의미한다
이 경우에는 프로젝트가 버전의 불일치로 에러가 발생할 경우가 있다는 점을 알아둬야 한다.
이 부분은 라이브러리(디펜던시)를 받기 위해서 어떤 저장소를 사용할지에 대해서 작성해둔 것으로 Maven Central은 자바 개발자들이 공용으로 사용하는 라이브러리 저장소이다.
대부분의 자바 오픈소스 라이브러리들은 여기에 등록되어 있고 Gradle은 이 저장소에 접근해서 dependencies {}에 선언된 라이브러리를 다운로드한다.
이게 함수처럼 생긴건 실제 함수기 때문인데, mavenCentral()라는 함수를 호출하면 Maven Central 저장소를 추가해주는 것이다.
이는 내부적으로
maven {
url = uri("https://repo.maven.apache.org/maven2")
}
이렇게 되어 있는걸을 함수로 단축시킨 버전이라고 생각하면 되고 추가로 우리가 메이븐 저장소를 원하는 곳을 추가하겠다고 한다면
maven {
url = uri("저장소URL")
}
과 같이 넣어주면 된다.
단순하게 우리가 갖고 있는 로컬 폴더에 라이브러리를 둘 수 도 있는데
repositories {
mavenLocal() // ~/.m2/repository
flatDir {
dirs 'libs' // 프로젝트 내 libs 폴더에 있는 jar 파일들을 라이브러리로 인식
}
}
와 같이사용도 가능하다.
그 아래에 있는 dependencies는 위에서 지정한 저장소에서 해당 라이브러리들을 프로젝트로 가져오겠다는 의미로
mavenCentral()
maven {
url = uri("저장소URL")
}
위와 같이 외부 저장소에서 해당 라이브러리들을 가져와서 다운받고 이를 .jar이나 .war로 빌드할 시점에 classpath에 포함시키기 위해서 작성된다.
여기서 추가로 우리가 위에서 프로젝트 내부의 디렉터리를 저장소로 설정한 경우는 라이브러리 파일은 존재하나 이를 빌드할때 포함시키지 않는 경우가 있기에 얘도 빌드할때 무조건 포함시켜서 빌드해라 라고 설정하는 것이라고 보면 된다.
여기서 앞에 작성되어 있는 implementation, testImplementation, testRuntimeOnly의 경우는 해당 라이브러리들이 어떤 시점에 어떻게 사용될지, 어떤 범위(scope)로 사용되는지를 지정하는 키워드로 아래와 같은 기능을 한다.
implementation | 컴파일 시 + 런타임 시 | 가장 일반적인 의존성으로 내가 만든 코드에서 사용할거고 실행할 때도 필요하다는 의미로 외부 모듈에서 이 라이브러리를 직접 참조할 수는 없다 |
compileOnly | 컴파일 시만 | 컴파일 할때만 필요하고 실행할때는 필요 없다 ( Lombok 같은 라이브러리들.. ) |
runtimeOnly | 런타임 시만 | 컴파일 시에는 사용되지 않고 실행할 때에만 필요한 라이브러리(JDBC 드라이버 같은 라이브러리..) |
testImplementation | 테스트 컴파일 + 테스트 실행 시 | 테스트 코드에서만 사용하는 라이브러리로 본 코드에는 영향을 주지 않는다 |
testRuntimeOnly | 테스트 실행 시만 | 테스트 실행 시에만 필요한 라이브러리(JUnit런처 같은 라이브러리) |
그래서 해당 리스트는 어떤걸 설치할지에 대한 정보 + 이걸 어떤 시점에 어디서 사용할지에 대한 정보를 둘다 포함하는 리스트라고 봐야한다.
그리들에는 빌드를 단계별 테스크(tasks)라는 것으로 나눠서 처리를 한다.
compileJava | 자바 소스 코드를 컴파일 |
processResources | 리소스 복사 (예: application.properties) |
classes | 클래스 빌드 완료 |
test | 테스트 실행 |
bootJar | 스프링 부트 .jar 파일 생성 |
여기서 test라는 이름의 task는 JUnit Platform이라는 기술을 사용해서 진행하겠다는 의미이다.
해당 테스크에 대한 설정을 추가하는 방식이 두가지가 존재하는데 하나는 즉시 설정(eager config)라는 것으로
tasks.getByName("test") {
...
}
이런 코드를 사용하는데 이는 build.gradle이 처음 로딜될 때 바로 실행되는 것으로 해당 task가 존재하지 않는다면 에러를 발생시킨다.
이런 것을 사용할 수 있는 경우는
task myCustomTask {
doLast {
println "내 작업"
}
}
tasks.getByName("myCustomTask") {
println "커스텀 작업 설정 추가"
}
이렇게 직접 그 상단에 테스크를 커스텀으로 생성해서 사용하는 경우에 사용된다
대부분의 테스크에 대한 작업은 지연설정 방식을 사용하는 것을 추천하며 방식은 아래와 같다
tasks.named("test") {
...
}
이는 Gradle이 task를 구성 한 후 해당 이름의 task가 필요할 때 설정이 적용되기 때문에 좀 더 안전한 방식이다.