본문 바로가기
빌드노트 검색

빌드노트

Maven package는 성공했는데 다른 프로젝트가 JAR을 못 찾는다

target에 만든 JAR과 로컬 저장소의 의존성은 다르다. 별도 Maven 프로젝트가 요구하는 좌표와 저장소를 맞추고, package 뒤 실패하던 소비 빌드가 install 뒤 성공하는 차이를 확인한다.

Maven으로 라이브러리의 package를 성공시켰는데, 별도 프로젝트에서는 그 라이브러리를 찾지 못할 수 있다. target에 JAR이 있다는 것과 다른 빌드가 의존성으로 가져갈 수 있다는 것은 별개다. 일반적인 JAR 프로젝트에서 package는 산출물을 만들고, install은 그 산출물과 POM을 로컬 저장소에 넣는다. 두 프로젝트가 같은 좌표와 같은 저장소를 사용한다면, 라이브러리 쪽에서 install까지 실행했는지 확인한다. Maven 빌드 생명주기 (새 탭에서 열림)

JAR 파일명보다 실패한 의존성 좌표를 먼저 본다

소비 프로젝트의 오류가 lab.localinstall:message-lib:jar:1.0.0을 가리킨다고 하자. message-lib-1.0.0.jar라는 파일을 어디에 만들었는지만 찾아서는 부족하다. 라이브러리 POM의 최종 groupId, artifactId, version이 소비 프로젝트의 선언과 같은지 비교한다. 부모 POM이나 속성으로 정했다면 해석된 값을 기준으로 본다.

다음은 소비 프로젝트의 pom.xml에서 확인할 의존성 항목이다. 기존 dependencies 안의 해당 항목과 비교하며, 이름과 버전은 자기 라이브러리의 값으로 바꾼다.

<dependency>
  <groupId>lab.localinstall</groupId>
  <artifactId>message-lib</artifactId>
  <version>1.0.0</version>
</dependency>

이 선언은 별도 type이나 classifier가 없는 일반 JAR 의존성이다. 기본 저장소 배치 규칙에서 이 JAR의 상대 경로는 lab/localinstall/message-lib/1.0.0/message-lib-1.0.0.jar가 된다. producer/target/message-lib-1.0.0.jar는 생산 프로젝트의 빌드 출력 경로다. Maven은 일반 의존성을 해결하려고 옆 프로젝트의 target 폴더를 자동으로 뒤지지 않는다. Maven 저장소 경로 규칙 (새 탭에서 열림)

두 빌드가 사용하는 저장소를 맞추고 install한다

좌표가 맞으면 소비 프로젝트가 실제 사용하는 로컬 저장소를 확인한다. 기본 위치는 사용자 홈 아래 .m2/repository지만, settings의 localRepository나 명령행의 -Dmaven.repo.local로 달라질 수 있다. IDE와 터미널에서 Maven을 각각 실행했다면 저장소 설정도 비교한다. 같은 PC에서 실행했다는 사실만으로 같은 저장소를 썼다고 판단하지 않는다. 로컬 저장소 설정 (새 탭에서 열림)

다음은 Windows PowerShell에서 라이브러리를 설치한 뒤 소비 프로젝트를 다시 빌드하는 명령이다. JDK와 Maven을 사용할 수 있어야 한다. 두 프로젝트의 공통 상위 폴더에서 실행하고, producer와 consumer는 각각 자기 프로젝트 경로로 바꾼다. $repo에는 소비 프로젝트가 사용할 저장소의 절대 경로를 넣는다. 아래 경로는 예시다. 기존 빌드에 필요한 settings와 profile 옵션도 각 명령에 유지한다.

$repo = 'C:\maven-local-check'
mvn.cmd -B -ntp "-Dmaven.repo.local=$repo" -f .\producer\pom.xml install
if ($LASTEXITCODE -ne 0) { throw '라이브러리 install 실패: 소비 빌드 전에 원인을 확인하세요.' }
mvn.cmd -B -ntp "-Dmaven.repo.local=$repo" -f .\consumer\pom.xml package

mvn install은 기본 생명주기의 앞 단계인 컴파일과 패키징도 거친다. 성공 로그에서 라이브러리의 POM과 JAR이 $repo 아래에 설치됐는지 본다. 파일을 저장소 폴더에 수동 복사하기보다 Maven의 설치 단계를 사용한다. Install Plugin은 POM의 좌표로 설치 위치를 정하고 POM도 함께 설치한다. Install Plugin 설명 (새 탭에서 열림)

위 명령에는 -o를 붙이지 않았다. 새 저장소라면 빌드 플러그인과 외부 의존성을 먼저 받을 수 있어야 한다. 사내 저장소가 필요한 프로젝트는 해당 접속 설정도 필요하다. 설치 명령이 실패했으면 그 실패를 먼저 해결한다. 라이브러리가 설치되지 않은 채 소비 프로젝트만 반복 빌드해도 원인은 사라지지 않는다.

별도 프로젝트에서는 package 뒤 실패하고 install 뒤 성공했다

이 차이를 확인하려고 부모 POM이나 모듈 집계 관계가 없는 두 프로젝트를 새로 만들었다. 생산 프로젝트는 lab.localinstall:message-lib:1.0.0을 만들고, 소비 프로젝트는 위 의존성 선언으로 라이브러리의 메서드를 호출한다. 실제 서비스 코드와는 별개인 실험이다.

Windows 11, Maven 3.9.16, JDK 17.0.2에서 전용 settings와 전용 로컬 저장소를 사용했다. Resources 3.3.1, Compiler 3.13.0, Surefire 3.5.2, JAR 3.4.2, Install 3.1.3으로 플러그인 버전도 고정했다. 먼저 같은 플러그인을 쓰는 별도 준비 프로젝트를 온라인으로 빌드해 플러그인을 확보했다. 준비가 끝났을 때 실험 대상 라이브러리는 저장소에 없음을 확인했다. 이후 비교 명령은 모두 같은 저장소를 지정하고 -o로 실행했다.

  • 생산 프로젝트의 package는 종료 코드 0이었다. target에는 JAR이 생겼지만, 전용 저장소에는 해당 좌표의 JAR과 POM이 모두 없었다.
  • 소비 프로젝트의 package는 종료 코드 1이었다. 실패 대상은 빌드 플러그인이 아니라 message-lib 의존성이었다.

다음은 두 번째 명령의 실제 로그에서 실패 좌표를 보여 주는 두 줄이다.

[ERROR] Failed to execute goal on project consumer: Could not resolve dependencies for project lab.localinstall:consumer:jar:1.0.0
[ERROR] dependency: lab.localinstall:message-lib:jar:1.0.0 (compile)

이어서 생산 프로젝트에서 install을 실행하자 종료 코드 0으로 POM과 JAR이 같은 전용 저장소에 들어갔다. 설치된 JAR과 target의 JAR은 SHA-256도 같았다. 소비 프로젝트의 소스나 POM을 바꾸지 않고 다시 package를 실행하자 이번에는 종료 코드 0이었다. 저장소에 설치된 JAR을 클래스 경로로 지정해 소비 프로그램도 실행했고, 라이브러리가 반환하는 local-install-ok를 출력했다.

이 비교에서 확인한 것은 산출물 생성과 로컬 저장소 설치 사이의 경계다. 같은 reactor에 두 모듈을 참여시키는 -am의 효과를 측정한 실험은 아니다. 여기서는 생산 프로젝트와 소비 프로젝트를 각각 별도 Maven 실행으로 빌드했다.

offline 실패라면 무엇이 없다고 하는지 구별한다

플러그인을 준비하기 전, 완전히 빈 전용 저장소에서도 -o package를 실행해 봤다. 이때는 maven-resources-plugin:3.3.1을 해결하지 못해 실패했다. 라이브러리 설치 여부를 비교하기도 전에 빌드 도구가 부족했던 것이다. 그래서 플러그인 준비를 마친 뒤에만 위의 소비 실패를 라이브러리 누락 관찰로 사용했다. -o는 필요한 파일을 만들어 주는 옵션이 아니라 오프라인 실행 옵션이다. Maven 3.9.16 명령행 옵션 (새 탭에서 열림)

자기 결과가 다르면 오류에 나온 이름과 저장소부터 다시 본다.

  • install 뒤에도 같은 의존성이 없다고 하면, 설치 로그의 경로와 소비 빌드의 저장소를 대조한다. 요구 버전이나 classifier가 다르면 방금 설치한 일반 JAR로 충족되지 않을 수 있다.
  • package만 했는데 소비 빌드가 성공한다면, 이전에 설치했거나 원격에서 받은 같은 좌표의 아티팩트가 있을 수 있다. 성공만으로 방금 만든 target의 JAR을 사용했다고 결론 내리지 않는다.
  • 다른 개발자나 CI가 받아야 한다면 내 PC의 install만으로 해결되지 않는다. 로컬 설치와 원격 저장소에 공유하는 배포는 별도 단계다. Install Plugin 사용 안내 (새 탭에서 열림)

이번 확인은 일반 JAR의 고정 버전 1.0.0을 같은 전용 로컬 저장소에서 소비한 결과다. 서로 다른 저장소에서의 성공, SNAPSHOT 갱신, 별도 classifier, Maven 4나 사용자 정의 생명주기는 검증하지 않았다.

← 전체 글 보기