Maven 프로젝트 구성하기

Maven 프로젝트 구성하기

기존 Java Maven 프로젝트에 Kotlin을 도입하거나 새 Kotlin Maven 프로젝트를 만들 때는, Kotlin 소스와 모듈을 컴파일하는 Kotlin Maven 플러그인을 추가해야 해요.

현재는 Maven v3만 지원돼요.

출처: Configure a Maven project

본문

자동 구성

Java-Kotlin 혼합 프로젝트와 순수 Kotlin 프로젝트 모두에서 <extensions> 옵션을 사용하면 Maven 구성을 간단히 할 수 있어요. 이렇게 하면 Maven 컴파일러 플러그인을 따로 구성할 필요가 없어서 시간을 아낄 수 있어요.

<extensions>로 Kotlin Maven 플러그인을 적용하려면 pom.xml 빌드 파일을 다음과 같이 업데이트해요:

  1. <properties> 섹션에서 Kotlin과 JVM의 대상 버전을 정의해요:
<properties>
    <maven.compiler.release>17</maven.compiler.release>
    <kotlin.version>2.4.20</kotlin.version>
</properties>
  1. <build><plugins> 섹션에서 <extensions> 옵션을 활성화한 Kotlin Maven 플러그인을 추가해요:
<build>
    <plugins>
        <!-- Kotlin compiler plugin configuration -->
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <version>${kotlin.version}</version>
            <extensions>true</extensions> <!-- Enable the extension -->
        </plugin>
        <!-- No need to configure Maven compiler plugin with extensions -->
    </plugins>
</build>

<extensions> 옵션은:

  • 이미 존재하지만 플러그인 구성에 명시되지 않은 src/main/kotlinsrc/test/kotlin 디렉터리를 소스 루트로 등록해요.
  • 프로젝트에 아직 정의되지 않았다면 kotlin-stdlib 의존성을 추가해요.
  • compile, test-compile, kapt, test-kapt 실행(execution)을 빌드에 추가하고, 적절한 라이프사이클 페이즈에 연결해요. 그래서 kapt, Kotlin의 compile, Java의 compile 실행이 올바른 순서로 실행되도록 <id><goals>를 넣은 <executions> 섹션을 직접 설정할 필요가 없어요.
  • JVM 대상 버전을 프로젝트에 구성된 Java 컴파일러 버전과 자동으로 일치시켜요.

Java와 Kotlin이 섞인 프로젝트라면, 이 구성은 다음을 보장해요:

  • Kotlin 코드가 먼저 컴파일돼요.
  • Java 코드는 Kotlin 다음에 컴파일되며 Kotlin 클래스를 참조할 수 있어요.
  • 기본 Maven 동작이 플러그인 순서를 덮어쓰지 않아요.

확장 구성은 <executions> 섹션 전체를 대체해요. 실행(execution)을 구성해야 한다면 Kotlin과 Java 소스 컴파일하기의 예시를 확인해 보세요.

여러 빌드 플러그인이 기본 라이프사이클을 덮어쓰고 거기에 <extensions> 옵션도 활성화했다면, <build> 섹션에서 마지막에 있는 플러그인이 라이프사이클 설정에 우선권을 가져요. 이전의 라이프사이클 설정 변경은 모두 무시돼요.

현재 <extensions>와 함께 사용되는 Maven 컴파일러 플러그인의 기본 버전은 3.10.1이에요. 다른 버전을 별도로 설정할 수도 있어요:

<build>
    <plugins>
        <!-- Kotlin compiler plugin configuration -->
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <version>${kotlin.version}</version>
            <extensions>true</extensions>
        </plugin>
        <!-- Maven compiler plugin configuration for Java classes -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.15.0</version>
        </plugin>
    </plugins>
</build>

JVM 대상 버전

<extensions> 옵션은 Kotlin과 Maven 컴파일러가 같은 바이트코드 버전을 대상으로 하도록 보장해요.

Kotlin Maven 플러그인은 다음 순서로 JVM 대상 버전을 자동으로 결정해요:

Kotlin 컴파일러 버전

프로젝트에 kotlin.compiler.jdkRelease 또는 kotlin.compiler.jvmTarget 프로퍼티 중 하나라도 정의되어 있으면, 그 값이 우선순위를 가져요.

이 Kotlin 컴파일러 옵션들은 동작 방식이 다르다는 점을 명심해요:

Kotlin 컴파일러 옵션 출력의 바이트코드 버전 제어 API를 지정된 JDK로 제한
kotlin.compiler.jvmTarget 코드의 JDK API에 제한 없음
kotlin.compiler.jdkRelease 예 − 특정 API 버전만 허용(Java의 --release 컴파일러 옵션과 동일)

kotlin.compiler.jdkReleasekotlin.compiler.jvmTarget에 서로 다른 JDK 옵션을 동시에 설정하지 마세요. 그렇게 하면 오류가 발생해요.

Maven 컴파일러 버전

  • kotlin.compiler.jdkReleasekotlin.compiler.jvmTarget도 설정되지 않았다면, 플러그인은 maven.compiler.release 버전을 사용해요. maven.compiler.release 버전은 프로젝트 프로퍼티로 정의하거나 maven-compiler-plugin 구성 안에 정의할 수 있어요.
  • Maven release 버전이 설정되지 않았다면, 플러그인은 maven.compiler.target 버전을 사용해요. 이것도 프로젝트 프로퍼티로 정의하거나 maven-compiler-plugin 구성 안에 정의할 수 있어요.

Maven 컴파일러의 targetrelease 옵션은 동작 방식이 다르다는 점을 명심해요:

Maven 컴파일러 옵션 Kotlin의 jvmTarget 설정 Kotlin의 jdkRelease 설정 API를 지정된 JDK로 제한
maven.compiler.target 아니요 아니요 − 빌드의 JDK 클래스패스가 그대로 보임
maven.compiler.release 예 − 특정 API 버전으로만

<extensions> 옵션은 프로젝트 레벨 프로퍼티와 전역 maven-compiler-plugin 구성만 확인해요. 플러그인의 <executions> 섹션에 정의된 구성은 확인하지 않아요.

수동 구성

Kotlin Maven 플러그인에서 <extensions>를 활성화하지 않으면, 소스 코드가 올바르게 컴파일되도록 프로젝트를 수동으로 구성해야 해요.

Java와 Kotlin 소스의 조합을 컴파일하도록 Maven 프로젝트를 설정하거나, Kotlin 전용 소스를 컴파일하도록 설정할 수 있어요.

Kotlin과 Java 소스 컴파일하기

Kotlin과 Java 소스 파일이 모두 있는 프로젝트를 컴파일하려면 Kotlin 컴파일러가 Java 컴파일러보다 먼저 실행되도록 해요.

Kotlin 선언은 .class 파일로 컴파일되기 전까지는 Java 컴파일러가 볼 수 없어요. Java 코드가 Kotlin 클래스를 사용한다면, cannot find symbol 오류를 피하려면 그 클래스들이 먼저 컴파일되어야 해요.

Maven은 두 가지 주요 요소에 따라 플러그인 실행 순서를 결정해요:

  • pom.xml 파일에서 플러그인 선언의 순서
  • default-compiledefault-testCompile 같은 내장 기본 실행들. 이들은 pom.xml 파일에서의 위치와 관계없이 항상 사용자 정의 실행보다 먼저 실행돼요.

실행 순서를 제어하려면:

  • kotlin-maven-pluginmaven-compiler-plugin보다 먼저 선언해요.
  • Java 컴파일러 플러그인의 기본 실행을 비활성화해요.
  • compile 페이즈를 명시적으로 제어하는 사용자 정의 실행을 추가해요.

Maven에서 none 페이즈를 사용하면 기본 실행을 비활성화할 수 있어요.

Kotlin Maven 플러그인을 적용하려면 pom.xml 빌드 파일을 다음과 같이 업데이트해요:

<build>
    <plugins>
        <!-- Kotlin compiler plugin configuration -->
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <version>${kotlin.version}</version>
            <executions>
                <execution>
                    <id>kotlin-compile</id>
                    <phase>compile</phase>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                    <configuration>
                        <sourceDirs>
                            <sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
                            <!-- Ensure Kotlin code can reference Java code -->
                            <sourceDir>${project.basedir}/src/main/java</sourceDir>
                        </sourceDirs>
                    </configuration>
                </execution>
                <execution>
                    <id>kotlin-test-compile</id>
                    <phase>test-compile</phase>
                    <goals>
                        <goal>test-compile</goal>
                    </goals>
                    <configuration>
                        <sourceDirs>
                            <sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
                            <sourceDir>${project.basedir}/src/test/java</sourceDir>
                        </sourceDirs>
                    </configuration>
                </execution>
            </executions>
        </plugin>

        <!-- Maven compiler plugin configuration -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.15.0</version>
            <executions>
                <!-- Disable default executions -->
                <execution>
                    <id>default-compile</id>
                    <phase>none</phase>
                </execution>
                <execution>
                    <id>default-testCompile</id>
                    <phase>none</phase>
                </execution>

                <!-- Define custom executions -->
                <execution>
                    <id>java-compile</id>
                    <phase>compile</phase>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                </execution>
                <execution>
                    <id>java-test-compile</id>
                    <phase>test-compile</phase>
                    <goals>
                        <goal>testCompile</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

이 구성은 다음을 보장해요:

  • Kotlin 코드가 먼저 컴파일돼요.
  • Java 코드는 Kotlin 다음에 컴파일되며 Kotlin 클래스를 참조할 수 있어요.
  • 기본 Maven 동작이 플러그인 순서를 덮어쓰지 않아요.

Maven이 플러그인 실행을 처리하는 방법에 대한 자세한 내용은 공식 Maven 문서의 기본 플러그인 실행 ID 가이드(Guide to default plugin execution IDs)를 확인해 보세요.

Kotlin 전용 소스 컴파일하기

Kotlin 소스 파일만 있는 프로젝트를 컴파일하려면 소스 루트를 선언하고 Kotlin Maven 플러그인을 구성해요:

  1. <build> 섹션에서 소스 디렉터리를 지정해요:
<build>
    <sourceDirectory>src/main/kotlin</sourceDirectory>
    <testSourceDirectory>src/test/kotlin</testSourceDirectory>
</build>
  1. Kotlin Maven 플러그인이 적용되도록 해요:
<build>
    <plugins>
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <version>${kotlin.version}</version>
            <executions>
                <execution>
                    <id>compile</id>
                    <goals>
                        <goal>compile</goal>
                    </goals>
                </execution>
                <execution>
                    <id>test-compile</id>
                    <goals>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

JDK 버전 설정하기

Kotlin은 빌드에서 JDK 버전을 관리하는 데 도움이 되는 Maven Toolchains을 지원해요.

빌드에 maven-toolchains-plugin을 구성하면, Maven을 실행하는 JVM 버전(JAVA_HOME 경로에 설정된 값)과 독립적으로 Kotlin 컴파일에 사용할 JDK 버전을 지정할 수 있어요. 그러면 Kotlin Maven 플러그인이 선택한 JDK 툴체인을 자동으로 사용해요.

이렇게 하면 Kotlin 컴파일을 포함해 빌드의 모든 플러그인에서 사용하는 JDK를 제어하는 단일 툴체인을 구성할 수 있어요. 예를 들어:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-toolchains-plugin</artifactId>
    <version>3.2.0</version>
    <executions>
        <execution>
            <goals>
                <goal>toolchain</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <toolchains>
            <jdk>
                <version>21</version>
            </jdk>
        </toolchains>
    </configuration>
</plugin>

JDK 버전을 설정하는 여러 방법의 우선순위를 명심해요:

  • kotlin-maven-plugin 구성의 jdkHome 옵션에 설정된 JDK 버전이 항상 툴체인 버전보다 우선해요.
  • maven-toolchains-plugin의 JDK 버전은 JAVA_HOME 경로에 설정된 JDK 버전을 덮어써요.

kotlin-maven-plugin의 툴체인에 JDK 버전을 직접 설정하는 플러그인 전용 <jdkToolchain> 옵션도 사용할 수 있어요. maven-toolchains-plugin을 사용하는 것과 비교해, 이 매개변수는 Kotlin 컴파일에만 영향을 주고 빌드의 다른 플러그인에는 영향을 주지 않아요.

현재 특정 JDK 버전을 사용하도록 maven-toolchains-plugin을 설정해도 kotlin-maven-pluginkapttest-kapt 목표에는 영향을 주지 않아요. 그 대신 JAVA_HOME 경로에 필요한 버전을 설정하세요.

Java 모듈 구성하기(JPMS)

Kotlin Maven 플러그인은 Java Platform Module System(JPMS)을 지원해서, module-info.java 디스크립터 옆에서 Kotlin 코드를 컴파일하고 결과 모듈을 다른 Java 모듈처럼 사용할 수 있어요.

빌드 파일에서 JPMS 전용 옵션은 따로 필요 없어요. Maven보다 먼저 Kotlin 컴파일러를 구성하기만 하면 돼요.

module-info.java 디스크립터가 있으면 Kotlin 컴파일러는 그것을 소스 파일로 사용하고, 클래스패스 대신 모듈 경로를 대상으로 컴파일해요. Kotlin 컴파일러는 디스크립터를 읽어 모듈 그래프를 해석하고, Maven 컴파일러는 그다음 그것을 module-info.class 파일로 컴파일해요.

Java 모듈을 구성하려면 module-info.java 파일을 ${project.basedir}/src/main/java 디렉터리에 만들어요. 모듈 디스크립터에서 모듈이 필요로 하는 모든 의존성과 내보내는 패키지를 선언해요. 예를 들어:

module org.example.myapp {
    requires transitive kotlin.stdlib;
    requires java.net.http;
    
    exports org.example.myapp;
}

다음을 명심해요:

  • Java 모듈은 선언한 것만 사용할 수 있어요. 컴파일이 클래스패스 대신 모듈 경로를 사용하므로, 디스크립터에는 Kotlin 코드가 사용하는 모든 의존성을 포함해야 해요: 표준 라이브러리, JDK 모듈(java.base 제외), 그 밖의 라이브러리들. 그렇지 않으면 Unresolved reference 오류가 발생할 수 있어요.
  • 모듈의 경우 Kotlin 파일의 패키지 이름이 module-info.java의 패키지 이름과 일치해야 Package is empty or does not exist 빌드 실패를 피할 수 있어요.
  • pom.xml 빌드 파일은 Kotlin이 Java보다 먼저 컴파일되도록 구성해야 해요. 자동 프로젝트 구성을 사용한다면 <extensions> 옵션이 이미 이를 보장해요.

더 알아보기

Kotlin Maven 프로젝트에서 의존성 설정도 살펴보세요. 상위 문서인 Maven도 참고하세요.