Carthage 의존성 이슈 관리하기

iOS 개발을 하면서 가장 많이 접하게 되는 CLI Tool은 Package Manager가 아닐까 싶다. 개인적으로 지금까지 주로 사용하던 패키지 매니저는 CocoaPods 이었는데 새로운 회사로 이직을 하면서 본격적으로 Carthage를 사용하게 되었다. Carthage를 사용하면서 겪었던 몇가지 이슈에 대하여 공유를 해보고자 한다.

Carthage 의 특성

카르타고의 Github 페이지에 나온 첫 설명문은 아래와 같다.

Carthage is intended to be the simplest way to add frameworks to your Cocoa application. Carthage builds your dependencies and provides you with binary frameworks, but you retain full control over your project structure and setup. Carthage does not automatically modify your project files or your build settings.

여기서 중요한 부분은 프로젝트 구조를 바꾸지 않으면서 Framework를 사용할 수 있다는 부분인데, 이 말인 즉슨 CocoaPods는 빌드 과정까지 편하게 만들어 주지만 Carthage의 경우엔 그렇지 않다는 것이다. Carthage 를 사용할 때 해야 할 일들은 Carthage Github 페이지에 상세히 나와 있으니 참고하면 좋을 것 같다.

물론 Framework를 다루는 방법을 알고 메뉴얼을 꼼꼼히 읽어 본다면 '어렵지 않게' 사용할 수 있을 거라고 생각하지만, 숨어 있는 복병이 너무 많은 것이 문제다.

문제 1. 몇 시간째 업데이트가 안되는데요

Package Manager 의 가장 중요한 덕목 중 하나는 패키지 간의 의존성 해결이다. 많은 패키지들이 다른 패키지에 대해 의존성을 가지고 있는데, Carthage는 의존성 해결에 문제가 있는지 걸핏하면 Github Repository 들을 불러 오면서 CLI가 멈춰 버리는 현상이 발생한다. 이 이슈는 보고되고 무려 1년이 지났지만 수정이 되지 않고 있는데, 이 문제에 대해서 커뮤니티 내에서 크게 두가지 해결 방법이 제시되고 있다.

첫번째 방법은 --new-resolver 옵션을 사용해서 시도해 보는 것이다.

$ carthage update --platform iOS --new-resolver

이 옵션은 의존성 해결을 진행할 때 새로 개선된 Resolver 를 사용하겠다고 명시적으로 선언하는 옵션이다. 하지만 의존성이 많이 꼬인 몇몇 Repository 들을 사용 하는 경우 (ex. RxSwift vs. Moya vs. etc..) 이 옵션을 사용해도 해결하지 못하는 경우가 있다.

여기서 두번째 방법이 등장한다. 바로 해당 문제가 해결된 Fork 버전을 사용하는 것이다.

얼마나 답답했으면..

기존에 Carthage 가 설치 되어 있는 환경이라고 가정한 상태로 진행 하자면 아래와 같이 진행하면 된다.

$ brew uninstall --force carthage
$ brew tap nsoperations/formulas
$ brew install -s nsoperations/formulas/carthage
$ carthage update --platform iOS

이 Fork 버전의 차이는 Fetching 단계에서 먹통이 되는 일이 현저하게 적고, 문제가 있을 경우 어느 부분에 문제가 있다고 보여주기 때문에 사용자가 문제를 해결하기 위해 삽질을 하는 일이 적어진다.

다만, Carthage 팀에서 정식으로 Support 하는 버전이 아니기 때문에 보안이나 유지보수 작업에 대한 이슈가 생길 수 있는 점은 감안하고 사용해야 할 것이다.

문제 2. 원하는 패키지 버전이 설치 되지 않습니다.

Carthage 의 의존성 문제를 해결할 때 버전 값 제한을 지우다 보면 Fetching 단계는 진행이 되다가 빌드 단계에서 문제가 생길 경우가 많다. 여러가지 이유가 있을 수 있겠지만, 내가 겪은 케이스는 버전 값 제한을 안 둔 패키지가 매우 오래된 버전으로 Fetch 되어 빌드를 시도하면서 Swift 버전 오류나 문법 오류를 일으키는 경우이다. 이럴 때는 Cartfile.resolved 파일을 직접 수정해 주는 방법이 도움이 될 수 있다.

우선 기존 방법대로 carthage update 를 해주고 나면 다음과 같은 Resolved 파일이 생성 될 것이다.

$ carthage update --platform iOS
# Cartfile.resolved
github "Alamofire/Alamofire" "4.9.0"
github "Alamofire/AlamofireImage" "3.5.2"
github "Moya/Moya" "13.0.1"
github "Quick/Nimble" "v8.0.2"
github "Quick/Quick" "v2.1.0"
github "ReactiveCocoa/ReactiveSwift" "5.0.1"
github "ReactiveX/RxSwift" "4.5.0"
github "SwiftyJSON/SwiftyJSON" "5.0.0"
github "Swinject/Swinject" "2.7.0"

여기서 몇가지 패키지들이 구 버전으로 Resolved 된 것을 볼 수 있을 것이다. 여기서는 ReactiveSwift 와 RxSwift 패키지가 구 버전으로 설치 되어서 Swift 5 컴파일이 실패했다.

Cartfile.resolved 파일의 버전을 직접 수정해 주면 원하는 버전을 선택해 줄 수 있다. 단, Framework 간 버전 호환성은 개발자가 직접 체크해야 한다. 여기서는 Swift 5 지원이 가능한 버전으로 설치하기 위해 ReactiveSwift 를 6.1.0으로, RxSwift 를 5.0.1로 설정하고 설치를 하도록 한다.

# Optional: 설정된 버전이 제대로 들어갔는지 확인합니다
$ carthage checkout 
*** Checking out Moya at "13.0.1"
*** Checking out Result at "4.1.0"
*** Checking out RxSwift at "5.0.1"
*** Checking out ReactiveSwift at "6.1.0"
...

# update 가 아니고 build!
$ carthage build --platform iOS 

기존 방식인 carthage update 명령어를 사용하게 되면 resolved 파일에서 수정한 버전이 원래대로 돌아가기 때문에 그 대신 carthage build 를 사용해야 한다. 호환되는 버전들을 제대로 입력 했다면 정상적으로 빌드가 완료되어 Framework 생성이 완료되는 것을 볼 수 있을 것이다.

결론: 굳이 이렇게까지 써야 하나? 🤔

물론 Carthage 가 좋은 취지에서 나온 Tool인건 분명하다고 생각한다. CocoaPods에도 초기 설치 속도 이슈나 프로젝트 구조 종속, Centralized 와 같은 고질적인 이슈들이 존재하기 때문이다.

하지만 이유를 알기 힘든 먹통 현상이나 손이 많이 가는 의존성 관리 이슈들 때문에 Carthage 의 매력을 상당부분 잃어버리게 했다. 추후에 개인적으로 새로운 프로젝트를 시작하게 된다면 당분간은 Carthage 를 선택하진 않을 것이라고 생각한다.