따라하며 살펴보는 gradle

비딴·2024년 1월 8일
0
post-thumbnail

글의 목적

RestDocs 설정을 하면서 Gradle 설정을 추가하거나 수정하는 일이 많이 생겼습니다.
Gradle 에 대한 이해가 부족하니, 어떤 동작을 하는 코드인지 알기가 어려웠습니다.
이 글을 통해 gradle 의 동작 방식과 구조에 대해 이해를 높이고자 합니다.

Gradle이란?

Gradle은 빌드 자동화 툴입니다. 프로젝트를 배포 가능한 형태로 만들어 줍니다.
GroovyKotlin 을 지원하여 Java 를 사용하는 개발자들이 빠르게 적응하고 사용할 수 있습니다.
Gradle 은 간편한 설정, GroovyKotlin 을 사용한 스크립트 작성, 멀티 프로젝트 기능을 지원하여 소규모 프로젝트부터 대규모 프로젝트까지 사용할 수 있습니다.

gradle의 간단한 구조

실습하며 이해하기 전에 gradle 의 간단한 구조에 대해 이해하고 넘어가겠습니다.
세부적인 동작 방식은 알기 어렵지만, 대략적인 구조를 알고 실습을 진행한다면 실습을 통한 이해가 잘 될 것입니다.

build.gradle

plugins { // 추가적인 빌드 기능을 가져옵니다.
	java
}

// build metadata
group = "org.example"
version = "1.0-SHANPSHOT"

repositories { // 의존성을 가지고 올 저장소
	mavenCentral()
}

dependencies { // 의존성, 코드를 빌드할 때 필요한 것들
	testImplementation("junit:junit:4.13.2")
}

build.gradleGradle 설정을 담당하고 있습니다.

gradle 프로젝트 구조

gradle 프로젝트 구조

4: 프로젝트에 특정 버전 Gradle 을 포함시켜, 개발자가 별도의 Gradle 설치 없이도 일관된 빌드 환경을 보장해줍니다. (Gradle Wrapper 라고 부릅니다)

5: build.gradle 에서 사용하는 속성들을 담고 있습니다.
6: Gradle Wrapper 를 사용하여 빌드 할 수 있는 스크립트가 작성되어 있습니다.(맥, 리눅스: gradlew, 윈도우: gradlew.bat)

실습

실습을 진행하기 위해 Java 17버전Gradle 이 설치되어 있어야 합니다. (Gradle은 최소 Java 8버전 이상에서 동작합니다)
실습 운영체제는 Mac 환경이고, gradle 언어는 Kotlin 을 사용합니다.

gradle 생성

mkdir get-going-with-gradle
cd get-going-with-gradle
gradle init

gradle init

Gradle 프로젝트를 생성합니다.

ls -al

ls -al

위와 같은 파일과 디렉토리들이 생성된 것을 볼 수 있습니다.

여기서 .gitignore 를 먼저 보게 되면

cat .gitignore

cat .gitignore

자동적으로 .gradlebuild 디렉토리를 git 에서 무시하도록 설정하고 있습니다.
.gradle: gradle 빌드할 때 생성되는 캐시 및 임시파일들을 저장하고 있습니다.
빌드 성능을 최적화 시키는 데에는 도움이 되지만, 프로젝트의 소스 코드나 설정과는 직접적인 관련이 없기 때문에 무시하는 것이 좋습니다.
build: 컴파일된 자바 클래스, jar 파일, 생성된 문서, 테스트 보고서 등 빌드 결과물을 저장하고 있습니다. 빌드 프로세스의 최종 산출물로 소스 코드나 설정과 직접적인 관련이 없기 때문에 무시하는 것이 좋습니다.

./gradlew tasks

./gradlew tasks

현재 gradle 이 사용할 수 있는 태스크들을 알 수 있습니다.
초기 상태에서는 사용할 수 있는 태스크들이 몇 개 없습니다.

gradle로 자바 실행

gradle 을 만들었으니, 이 위에 자바 프로젝트를 만들어 실행해보겠습니다.
자바 프로젝트를 실행하기 위해서는 일반적으로 다음과 같은 요구사항이 필요합니다.

  1. .java 파일을 .class 파일로 컴파일 해야 합니다.
  2. 텍스트 파일, 이미지 파일 등 코드에서 필요한 리소스 파일들이 존재한다면 있어야 합니다.
  3. .class 들과 리소스 파일들을 .jar 파일로 압축해야 합니다.
  4. 테스트를 실행할 수 있어야 합니다. 하지만 .jar 파일에 같이 패키징 되어선 안됩니다.
  5. 외부 라이브러리들을 가져올 수 있어야 합니다.(Apache Commons, Spring Boot 등)

자바 예제 코드 작성

리소스 파일과 소스 파일이 존재하는 프로젝트를 만들고 gradle 로 실행 과정을 확인해보겠습니다.
프로젝트는 "en"을 입력할 경우 "Hello!", "es"를 입력할 경우 "Holla!" 를 출력하는 간단한 프로젝트입니다.

// src/main/java/org/example/languageapp/SayHello.java

package org.example.languageapp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

public class SayHello {

    public static void main(String[] args) throws IOException {
        String language = args[0];

        InputStream resourceStream = SayHello.class.getClassLoader().getResourceAsStream(language + ".txt");
        assert resourceStream != null;

        BufferedReader bufferedInputStream = new BufferedReader(
                new InputStreamReader(resourceStream, StandardCharsets.UTF_8));

        System.out.println(bufferedInputStream.readLine());
    }
}
// src/main/resources/en.txt

Hello!
// src/main/resources/es.txt

Holla!

주석 경로에 .java, txt 파일을 만듭니다.

자바 플러그인 추가

// build.gradle.kts

plugins {
    java
}

Gradle 에 자바 플러그인을 추가하고 ./gradlew tasks 로 수행할 수 있는 태스크들을 봅니다.

./gradlew tasks

자바의 여러 태스크들이 추가된 것을 볼 수 있습니다.

자바 프로젝트 빌드

추가된 자바 플러그인의 기능을 이용해 자바 프로젝트를 빌드해야 합니다.
하지만 태스크에는 compileJavaprocessResources 가 존재하지 않습니다.
실제 태스크 명령어로 보여지는 것들은 여러 개가 뭉쳐있는 높은 레벨에 태스크들만 표시 되어 있습니다.

./gradlew tasks --all

./gradlew tasks --all

낮은 레벨까지의 모든 태스크들을 보고 싶다면 --all 옵션 붙이면 됩니다.
compileJavaprocessResources 를 순차적으로 명령하고 build 디렉토리에 어떤 변화가 생기는지 확인해보겠습니다.

./gradlew compileJava

./gradlew compileJava

./gradlew processResources

./gradlew processResources

compileJava.class 파일이 만들어지고,
processResources 시 리소스들이 빌드 디렉토리로 올라간 것을 볼 수 있습니다.
이제 jar 명령어로 패키징하여 실행해보겠습니다.

./gradlew jar

./gradlew jar

libs 디렉토리가 생기고 안에 .jar 이 생성되었습니다.

만들어진 .jar 파일을 실행해보겠습니다.

java -jar build/libs/get-going-with-gradle.jar en

java -jar build/libs/get-going-with-gradle.jar en

"기본 Manifest 속성이 없다" 라는 것은 jar 파일에 실행 가능한 메인 클래스에 대한 정보가 포함되지 않았다는 것입니다.

// build.gradle.kts
tasks.named<Jar>("jar") {
    manifest {
        attributes["Main-Class"] = "org.example.languageapp.SayHello"
    }
}

위와 같이 메인 클래스를 지정한 뒤

./gradlew jar
java -jar build/libs/get-going-with-gradle.jar en

./gradlew jar
java -jar build/libs/get-going-with-gradle.jar en

다시 명령을 수행하게 되면 애플리케이션이 정상 동작하는 것을 볼 수 있습니다.

이제 test 를 통해 제대로 된 값들이 넘어가는지 확인해보겠습니다.

gradle test

// src/test/java/org/example/languageapp/SayHelloTest.java

package com.org.example.languageapp;

import java.io.IOException;
import org.example.languageapp.SayHello;

public class SayHelloTest {

    @Test
    public void testSayHello() throws IOException {
        SayHello.main(new String[]{"en"});
    }
}

테스트 코드를 작성하고 테스트를 돌려보면 에러가 발생합니다.

./gradlew test

./gradlew test

테스트 라이브러리 의존성을 추가하지 않고 수행해서 발생한 문제입니다.
테스트 라이브러리를 추가한 뒤 다시 수행해보겠습니다.

// build.gradle.kts

repositories {
    mavenCentral() // 의존성을 가져올 저장소를 지정합니다.
}

dependencies {
	// junit5를 테스트 코드 컴파일과 실행에 필요한 의존성으로 지정합니다.
    testImplementation("org.junit.jupiter:junit-jupiter:5.8.1")
}



의존성을 추가하여 테스트 태스크도 정상 수행되었지만 정작 테스트 보고서에는 0개의 테스트가 수행되었습니다.
Gradle 에서 JUnit5 를 수행하기 위해서는 추가적인 설정이 필요합니다.(useJUnitPlatform() 설정을 사용하여 Junit5 테스트 엔진을 사용합니다)
다시 추가 설정을 하고 테스트를 진행하여 보고서를 확인해보겠습니다.

// build.gradle.kts

tasks.named<Test>("test") {
    useJUnitPlatform()
}

이제 정상적으로 테스트를 실행합니다.

실제 태스크를 사용할 때는 build를 수행하게 되면 지금까지 사용했던 모든 태스크들을 한번에 사용합니다.

참고

초보자를 위한 Gradle 코스 - Tom Gregory
Gradle 공식 사이트

profile
비 온 뒤 딴딴

0개의 댓글