Maven 부모 POM
Gradle 플러그인의 대응물은 부모 POM spider-silk-maven-parent로, 그 pluginManagement와 프로파일이 같은 패키징을 담고 있다.
Maven은 빌드 컨벤션을 플러그인이 아니라 상속으로 공유한다: Maven 플러그인은 골을 실행하며, Gradle 플러그인처럼 다른 플러그인의 설정을 선언할 수 없다.
<parent>
<groupId>net.benelog.spidersilk</groupId>
<artifactId>spider-silk-maven-parent</artifactId>
<version>1.1.0</version>
</parent>
부모 POM은 Maven이 기본적으로 검색하는 Maven Central에 발행(publication)되므로, <parent> 블록 곁에 저장소나 settings.xml 항목이 필요 없다.
선언으로 옵트인하기
모든 플러그인 설정이 부모의 pluginManagement에 들어 있으므로, 각 플러그인은 자식이 선언하기 전까지 비활성 상태로 머문다.
Gradle 쪽의 spiderSilk { jte() }가 그렇듯, 그 선언이 옵트인이다.
컨테이너화하고 jte를 미리 컴파일하는 자식은 둘 다 선언하고, 애플리케이션 자신의 것만 설정한다:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>flashcard.FlashcardApp</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<configuration>
<to>
<image>ghcr.io/benelog/spider-silk-flashcard:${spider-silk.image.tag}</image>
</to>
</configuration>
</plugin>
<plugin>
<groupId>gg.jte</groupId>
<artifactId>jte-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
메인 클래스는 jar 매니페스트에 한 번만 선언하는데, 실행 가능한 jar라면 어차피 그 자리에 필요하다.
Jib은 거기서 엔트리포인트를 추론하고, native-maven-plugin은 바이너리를 위해 같은 항목을 읽는다.
Jib과 native-maven-plugin에 따로 설정해도 동작은 하지만, 두 사본이 어긋나기 전까지만이다.
선언한 플러그인은 Gradle 플러그인이 설정하는 컨벤션을 상속한다:
-
Jib은
eclipse-temurin:25-jre위에 빌드하고 포트 8080을 노출한다.mvn package jib:buildTar는 Docker 데몬 없이target/jib-image.tar를 써내며, 배포가 설명하는 대로 레이어를 나눈다. -
maven.compiler.release는 25로 설정되며, 이는 Jib이 베이스 이미지와 대조할 Java 버전을 읽는 자리를 겸한다. 따라서 Gradle 플러그인이 우회하는targetCompatibility불일치는 Maven에서는 생기지 않는다. -
jte는
src/main/resources/jte아래의 모든 템플릿을 빌드 시점에 미리 컴파일하며, 네이티브 이미지 리소스도 포함해 템플릿이 설명하는 그대로다. -
네이티브 바이너리는
<fallback>false</fallback>으로, 대부분 정적으로(--static-nolibc, 그래서distroless/base컨테이너는 glibc 외에는 아무것도 빚지지 않는다), 그리고 도달 가능성 메타데이터 저장소를 활성화한 채로 빌드한다. 바이너리 이름은${spider-silk.native.image-name}이며, 기본값은 artifactId다.
생성되는 JSON 코덱
부모 POM은 모든 자식의 컴파일러 프로세서 경로에 silk-json-processor를 올리며, 이는 spiderSilk { json() }의 대응물이다.
컴파일러 플러그인은 모든 빌드에서 실행되므로 선언이 필요 없고, @JsonBound가 붙은 타입이 없는 자식에서는 아무 비용도 들지 않는다.
jakarta.json.bind 애너테이션을 쓰는 자식은 API를 provided 스코프로 직접 더하는데, 애너테이션은 컴파일 시점에 읽히고 다시는 필요 없기 때문이다:
<dependency>
<groupId>jakarta.json.bind</groupId>
<artifactId>jakarta.json.bind-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
프로세서가 무엇을 생성하는지는 생성되는 JSON 코덱이 설명한다.
native 프로파일
mvn -Pnative package jib:buildTar는 jar 대신 GraalVM 바이너리를 실어 보낸다.
이 프로파일은 native:compile-no-fork를 package에 바인딩하고, Jib의 베이스를 distroless/base로 바꾸며, 같은 바이너리 이름을 가리키는 Jib의 native-image 확장을 붙인다.
네이티브 이미지 장이 그 전환을 훑어본다.
위 자식의 이미지 이름에 있는 ${spider-silk.image.tag} 자리 표시자가 두 이미지를 구분한다: 부모는 기본값을 latest로 두고, 프로파일은 이를 native로 설정하며, 대신 태그를 하드코딩한 자식은 그 구분에서 빠진다.
Dockerfile 워밍업
플랫폼이 Dockerfile을 요구하는 곳에서는 mvn dependency:go-offline이 Gradle 플러그인의 resolveDependencies를 대신한다.
이 명령은 워밍업 레이어를 위해 의존성과 플러그인을 내려받으며, Gradle Dockerfile이 빌드 스크립트를 복사하는 것과 같은 방식으로 pom만 복사한 뒤에 실행된다.
부모 POM이 고정하는 것
부모 POM의 버전은 부모 POM이 관리하는 도구의 버전을 고정한다: jte 3.2.4, Jib 3.5.2(Maven 플러그인은 업스트림에서 Gradle 플러그인의 3.5.4보다 뒤처진다), GraalVM 빌드 도구 1.1.14, 그리고 부모 POM 자신의 버전인 silk-json-processor다.
이 도구들의 버전 올림은 부모 POM 릴리스로 이루어진다.
Gradle 쪽과 마찬가지로 여기서 순수한 Maven을 넘어서는 것은 없다: 모든 컨벤션은 부모 POM 자체에서 읽을 수 있으며, 블록을 직접 소유하려는 빌드는 그 블록을 복사해 가고 상속을 끊는다.