Cordova 프로젝트에서 어제까지 되던 Android 빌드가 오늘 갑자기 Gradle 단계에서 깨지는 경우가 있습니다. 대부분은 플러그인 간 dependency 충돌이거나, 권한/Manifest 병합 과정에서 서로 다른 설정이 부딪히는 패턴입니다. 이 글은 “로그를 어떻게 읽고, 무엇부터 제거/고정해야 하는지”를 체크리스트 형태로 정리했습니다.

플러그인 충돌과 권한 이슈를 상징하는 케이블 매듭

아래 순서대로만 확인해도 원인을 꽤 빠르게 좁힐 수 있어요.

1) 먼저 실패 지점을 분류하기: Gradle 로그에서 키워드만 뽑기

같은 “BUILD FAILED”라도 원인은 완전히 다릅니다. 우선 로그에서 아래 키워드가 있는지 찾아 분류부터 하세요. (Android Studio가 아니라 CLI라도 동일합니다)

  • Manifest merger failed: 권한/컴포넌트/속성 충돌(주로 AndroidManifest.xml 병합)
  • Duplicate class: dependency가 중복 포함됨(플러그인 2개가 같은 라이브러리를 다른 버전으로 포함)
  • AndroidX, jetifier, android.support: AndroidX 전환/호환 문제(구 support 라이브러리 혼재)
  • requires compileSdkVersion / minSdkVersion / targetSdkVersion: 특정 플러그인이 요구하는 SDK 레벨이 현재 설정과 안 맞음
  • Could not resolve / Could not find: Maven repository/네트워크/버전 지정 문제

분류가 되면, “플러그인 충돌”인지 “권한 이슈”인지 초점이 바로 잡힙니다. 둘이 섞여 보일 때도 있지만, 시작점은 대개 하나입니다.

2) 플러그인 충돌 체크리스트: “어느 플러그인 때문에”를 빠르게 찾는 순서

Gradle 문제는 결국 어떤 플러그인이 어떤 Gradle 설정/라이브러리를 추가했는지로 귀결됩니다. 아래 순서가 가장 안전합니다.

플러그인 의존성 충돌을 보여주는 노드와 화살표 다이어그램

  • 최근 변경부터 의심: 최근 추가/업데이트한 플러그인, 최근 수정한 config.xml, 최근 변경한 Gradle 관련 설정을 먼저 되돌려 봅니다.
  • cordova plugin list로 목록을 고정(캡처/저장): 문제 재현 중에 플러그인 버전이 흔들리면 원인 추적이 어렵습니다.
  • 중복 기능 플러그인 확인: 같은 영역(예: camera, file, push, analytics)을 여러 플러그인이 동시에 건드리면 dependency/manifest 충돌이 잦습니다.
  • Duplicate class가 보이면: 거의 항상 “두 플러그인이 같은 라이브러리를 포함”한 겁니다. 한쪽 플러그인을 제거하거나, 버전을 맞추거나, 한쪽에서 포함을 끄는 설정이 있는지 확인합니다.
  • AndroidX 관련 문구가 보이면: support 라이브러리 기반 플러그인이 섞였을 가능성이 큽니다. 플러그인의 최신 버전/AndroidX 대응 여부부터 확인합니다.
  • 특정 artifact 버전 충돌: 같은 라이브러리를 플러그인 A는 1.x, 플러그인 B는 2.x로 요구하는 상황이 자주 나옵니다. 이 경우 “둘 다 만족”이 안 되면 결국 플러그인 조합을 조정해야 합니다.

팁 하나: “한 번에 다 바꾸기”가 아니라, 플러그인을 하나씩 빼면서 빌드가 되는 지점을 찾는 게 가장 빠릅니다. (시간은 들지만 결론이 명확합니다)

3) 권한(Manifest) 충돌 체크리스트: Manifest merger failed를 다루는 법

Manifest merger failed는 Cordova에서 매우 흔합니다. 플러그인들이 각자 권한/컴포넌트/속성을 추가하는데, 같은 항목을 서로 다른 값으로 선언하면 병합이 실패합니다.

Manifest 병합 과정과 충돌 지점을 나타낸 흐름도

  • 권한 중복은 대부분 무해하지만, 같은 permission/feature를 서로 다른 조건으로 선언하면 충돌할 수 있습니다.
  • application 태그 속성 충돌: allowBackup, usesCleartextTraffic, networkSecurityConfig 같은 값이 플러그인마다 달라지면 병합 실패가 납니다.
  • provider/receiver/service 중복: 특히 provider의 authorities가 충돌하면 자주 막힙니다(서로 같은 authorities를 쓰거나, placeholder가 제대로 치환되지 않는 경우).
  • minSdk/targetSdk 요구: 어떤 플러그인이 특정 SDK 레벨 이상을 요구하면서 Manifest/Gradle 설정과 충돌하는 케이스가 있습니다.

현장에서 가장 흔한 해결 흐름은 이렇습니다.

  • 로그에서 “Attribute ... is also present” 또는 “uses-sdk:minSdkVersion” 같이 충돌 항목을 먼저 찾는다
  • 해당 항목을 추가한 플러그인을 추적한다(플러그인 문서/이슈, 또는 plugin.xml의 config-file/manifest 변경 부분 확인)
  • 가능하면 플러그인 버전 업데이트로 해결(Manifest/AndroidX 대응이 반영된 경우가 많음)
  • 정말 필요할 때만 config.xml의 platform-android 설정으로 보정(프로젝트 정책상 허용되는 범위에서만)

주의: 권한을 “무조건 추가/무조건 삭제”로 접근하면, 빌드는 통과해도 런타임에서 기능이 깨지거나 심사/정책에 걸릴 수 있습니다. 충돌의 원인을 제거하는 쪽(플러그인 정리/업데이트)이 우선입니다.

4) 자주 나오는 케이스별 빠른 조치(플러그인/권한 관점)

  • Duplicate class: 같은 라이브러리를 두 플러그인이 끌고 온다 → 둘 중 하나를 대체 플러그인으로 변경하거나, 한쪽을 제거/다운그레이드/업그레이드로 버전 정렬
  • AndroidX / support 혼재: AndroidX 미지원 플러그인이 섞였다 → 플러그인을 AndroidX 대응 버전으로 교체(가능하면 공식/활성 프로젝트 우선)
  • Manifest merger failed (provider): authorities 충돌 → 동일 계열 플러그인 동시 사용 여부 확인, placeholder 치환 누락 여부 확인
  • usesCleartextTraffic 충돌: 어떤 플러그인이 보안 설정을 강제 → 앱 정책에 맞게 하나로 통일(필요 시 서버를 HTTPS로 정리하는 게 장기적으로 안전)
  • minSdkVersion 충돌: 특정 플러그인이 최소 SDK를 올려야 함 → 프로젝트 요구사항과 충돌하면 해당 플러그인 교체가 현실적인 선택

5) 재발 방지용 운영 체크리스트(플러그인/권한 관리 습관)

  • 플러그인 버전 고정: 팀 작업에서 “자동 최신” 상태를 피하고, 버전을 명시해 재현성을 확보
  • 기능 중복 플러그인 금지: 같은 기능 영역은 1개 플러그인 원칙(특히 camera/file/push/auth)
  • 권한 추가는 최소화: “필요할 때만” 추가하고, 사용하지 않는 권한은 정리(정책/보안/사용자 신뢰 측면에서도 유리)
  • 업데이트 전 빌드 로그 보관: 업데이트 직전/직후의 빌드 로그를 남기면 원인 추적이 매우 쉬워짐
  • AndroidX 정책 통일: 프로젝트는 AndroidX 기준으로 정리하고, 미대응 플러그인은 도입 전에 대안을 검토

마무리

Cordova의 Gradle 빌드 실패는 복잡해 보이지만, 실제로는 “어떤 플러그인이 무엇을 추가했는지”와 “Manifest 병합에서 무엇이 충돌했는지”로 정리됩니다. 위 체크리스트대로 분류→추적→정리 순서로 가면, 감으로 만지다가 더 꼬이는 일을 줄일 수 있어요.

그래도 원인이 안 보이면, 실패 로그에서 키워드(Manifest merger failed / Duplicate class / AndroidX)와 함께 플러그인 목록을 기준으로 ‘최근 변경’부터 하나씩 되돌리는 방식이 결국 가장 빠른 해법입니다.