Cocoapods Binary Cache 와 함께 버저닝된 Prebuilt Pod 캐시 운영하기

Wooseong Kim
29CM TEAM
Published in
12 min readOct 8, 2021

안녕하세요, 29CM iOS 개발자 김우성입니다.

iOS 프로젝트에서 사용하는 오픈소스 라이브러리들을 주로 CocoaPods, Carthage, SPM 세 가지 도구로 관리하는데요, 이 중 가장 범용적으로 많이 사용되는 CocoaPods 를 Prebuilt framework 로 만들고 캐싱을 할 때 각 개발자 머신/빌드 머신 환경에 맞춰 잘 분리된 캐시를 효율적으로 관리하는 방법을 소개하려고 합니다.

Cocoapods-Binary-Cache 에 대해선 레포 README 도 잘 되어 있고 이미 여러 글이 있어서 이번 글에서는 캐시 운영 부분에 조금 더 집중하려고 합니다.

1. 왜 도입하게 되었는지?

프로젝트 규모가 커져갈수록 빌드 속도는 느려져만 갑니다. 일정 규모를 넘어서면 팀의 생산성에 영향을 미칠 정도가 되는데요, 물론 좋은 성능의 맥을(M1이라던가..) 사용하는 방법이 가장 좋지만 분명 한계가 있습니다 😅

프로젝트 빌드 속도를 향상시키기 위해 주로 많이 택하는 두 가지 방식이

  1. Carthage 를 도입해 오픈소스 라이브러리를 Prebuilt framework 로 관리하기
  2. 프로젝트를 여러 개의 모듈로 분리하기

인데요, 2번은 1번에 비해 효과를 보려면 기반 작업도 많이 필요하고 (모든 프로젝트는 레거시가 있기 마련이니까요..) 단기간에 유의미한 속도 개선을 이루기 어렵습니다. iOS 팀의 규모가 크다면 동시에 진행하는게 더 좋지만 작은 규모에서는 1번을 많이 택하곤 합니다.

그런데 Carthage 의 경우 관리를 안하기로 워낙 유명해서 Xcode 버전이 올라갈 때마다 불안함이 있고 가끔은 곤혹을 치루기도 해서(M1 초기에 쉽지 않았습니다..) CocoaPods 기반으로 Prebuilt framework 를 사용할 수 없나 고민하던 차 Grab 에서 만든 Cocoapods-Binary-Cache 를 찾게 되었습니다.

처음엔 이 라이브러리의 원조인 Cocoapods-Binary 를 사용했었는데 Prebuilt framework 를 순차적으로 만드는 점, 캐시 관리 등 아쉬운 점이 많아 Grab 의 라이브러리로 최종 정착하게 되었어요. 다만 이 라이브러리도 기능이 충분하지 않아 현재는 해당 레포를 fork 해서 개선 뒤 사용하고 있습니다.

최초 도입 시를 기준으로 클린 빌드가 약 40% 정도 빨라지는 효과를 보았으며, 증분 빌드 속도도 마찬가지로 유의미하게 향상되었습니다.

2. 도입과 Remote Cache 활용

레포의 README 에 설명이 잘 되어 있고 도입 자체는 크게 어렵지 않습니다. 저희는 Bundler 를 쓰고 있어 Gemfile 에 `cocoapods-binary-cache` 의존성을 추가해 설치하고 Podfile 에서 적절한 설정을 해주었어요.

config_cocoapods_binary_cache(
excluded_pods: [
... # 혹시 모를 이슈를 대비해 메이저 SDK 라이브러리들을 주로 추가했습니다
],
cache_repo: {
"default" => {
"remote" => "https://#{ENV['IOS_BOT_USERNAME']}:#{ENV['COCOAPODS_BINARY_CACHE_TOKEN']}@github.com/#{ENV['IOS_POD_CACHE_REPO']}",
"local" => "./.cocoapods-binary-cache/prebuilt-frameworks"
}
},
prebuild_config: "Debug",
xcframework: true,
device_build_enabled: true
)

README 와 다른 부분만 조금 더 보강하자면 `remote` 부분인데요, 저희 팀은 GitHub Actions 러너를 돌리는 빌드 머신이 있어 위와 같은 방식을 택했고 빌드 머신과 개발자 머신에서 각각 사용할 수 있도록 민감한 정보들을 환경 변수로 분리하고 git 으로 트래킹하지 않도록 했어요. (빌드 머신은 GitHub Secrets 에서 주입, 개발자 머신은 환경 변수 파일)

`prebuild_config` 는 현재 버저닝된 캐시를 사용하고 있고 사내 빌드를 아카이브할 땐 릴리즈용 캐시를 사용하도록 해 두어서 개발자 머신에선 별도 지정없이 사용하도록 `Debug` 를 기본값으로 지정해 두었습니다. (릴리즈 Prebuilt framework 로 아카이브하지 않으면 Pod 의 dSYM 파일이 아카이브에 포함되지 않습니다)

How it works | 음.. 그렇다고 하네요..

3. 운영상의 한계와 버저닝 캐시를 위한 개선

위처럼 세팅하면 쉽고 빠르게 효과를 볼 수 있는데요, 제공되는 기능만 사용할 때 운영상의 한계가 존재합니다.

  • Podfile 에 라이브러리 변화가 있을 때 개발자가 직접 prebuild + push 를 해 주어야 한다는 점 (휴먼 에러가 발생할 가능성이 항상 존재합니다)
  • Xcode 업데이트로 인해 Swift 버전이 일시적으로 여러 개 사용될 때 개발자 머신과 빌드 머신에서의 캐시 운영이 어려워지는 점
  • Debug/Release 캐시 운영에 추가 공수가 듦 (마찬가지로 휴먼 에러 가능성 존재)

위와 같은 문제들을 겪으면서 운영에 지속적인 리소스가 들다 보니 자연스럽게 다음과 같은 생각을 하게 되었습니다.

혹시 이걸 자동화할 순 없을까? 아니면 차라리 ‘pod install’ 처럼 활용할 수는 없을까?

개선 작업의 초기에는 위와 같은 추상적인 생각만 가지고 접근을 했었는데 그러다보니 이번 글에서 소개드리는 내용을 구축하기 전까지 이런저런 시행착오가 많았습니다 ㅎㅎ;

시행착오의 흔적들.. 많이 갈아엎었네요 😅

개발자가 직접 개입하면 어떤 방식으로든 실수가 나올 수 있어서 결과적으로는 최대한 자동화 된 버저닝 캐시를 만드는 방향을 시도하게 되었습니다. 이를 이룰 수 있는 스펙을 다음과 같이 세워 두고 개선 작업을 시작했습니다.

  • 개발자가 Podfile 에 의존성을 추가하거나 삭제하고 스크립트를 실행하면 pod install 과 최대한 유사한 방식으로 Prebuild 한다. 이후 Podfile.lock 이 변하니 이 체크섬을 분기로 새로운 캐시를 Remote 에 바로 푸시해 준다.
  • 개발자 머신/빌드 머신 등 환경이 달라지면 캐시도 분리해주고 이 경우에도 각 머신 환경에 맞는 캐시가 없다면 Prebuild 후 분기된 캐시를 새로 생성해서 Remote 에 푸시해 준다.
  • 환경에 맞는 캐시가 있다면 프로젝트 구성 명령어에서 먼저 캐시를 fetch 해 Prebuild 없이 바로 프로젝트를 구성할 수 있어야 한다.

이런 이슈를 개선할 PR 을 레포에 작성하기도 했었는데요, 아쉽게도 최근엔 운영이 활발하지 않기도 하고 오랫동안 답이 없으셔서 결국 fork 를 뜬 저희 레포에서 개선해 사용하기로 했습니다. 아래 레포의 `support-diversified-cache` 브랜치에 위 스펙을 지원할 수 있는 변경사항들을 적용했어요.

https://github.com/29CM-Developers/ios-cocoapods-binary-cache

그래서 Gemfile 에선 다음과 같이 변경하게 되었습니다.

# Gemfile
...
gem "cocoapods-binary-cache",
git: 'https://github.com/29CM-Developers/ios-cocoapods-binary-cache.git',
branch: 'support-diversified-cache'
...

4. 버저닝 캐시 활용

개선 이후엔 다음과 같이 사용하고 있습니다. 위에서 언급한대로 `pod install` 처럼 사용하기 위해 Makefile 에 `pod_binary_fetch` 과 `pod_install` 이라는 명령어를 만들었어요. 저희는 Xcodegen 을 쓰고 있어 결과적으로 Makefile 내 프로젝트 구성 명령어에서

  • xcodegen generate
  • Pod 캐시 fetch (Swift Ver.+Podfile.lock 체크섬+Debug/Release 조합 키)
  • Pod prebuild (+ 캐시가 없었다면 Prebuild 후 캐시 푸시)

순서로 수행하면서 프로젝트를 구성하게 됩니다. 혹시 Carthage + Rome 을 같이 사용 중이시라면 xcodegen generate 다음에 먼저 수행하도록 하시면 되구요.

그리고 평소에는 Pod 변경이 없으니 일상적인 개발에서 사용할 목적으로 `xcodegen + pod install` 만 하는 명령어도 만들어 두었습니다.

# Makefile...project:
make xcodegen_generate
make pod_binary_fetch
make pod_install
project_release:
make xcodegen_generate
make pod_binary_fetch_release
make pod_install_release
# 일상적인 개발에 project 대신 사용하는 명령어입니다.
project_fast:
make xcodegen_generate
bundle exec pod install
pod_install:
bundle exec pod binary prebuild --no-fetch || bundle exec pod binary prebuild --repo-update --no-fetch
bundle exec pod binary push "swift$$(make swift_version)-xcframework-$$(make podfile_lock_checksum)"
# 앱을 배포할 때 아카이브에 Pod 의 dSYM 들을 포함하기 위해 사용합니다.
pod_install_release:
bundle exec pod binary prebuild --config=Release --no-fetch || bundle exec pod binary prebuild --repo-update --config=Release --no-fetch
bundle exec pod binary push "swift$$(make swift_version)-xcframework-release-$$(make podfile_lock_checksum)"
# Pod 캐시를 fetch 만 하고 싶을 경우 사용합니다.
pod_binary_fetch:
bundle exec pod binary fetch "swift$$(make swift_version)-xcframework-$$(make podfile_lock_checksum)"
pod_binary_fetch_release:
bundle exec pod binary fetch "swift$$(make swift_version)-xcframework-release-$$(make podfile_lock_checksum)"
...

이번 글에는 내용이 없지만 릴리즈용으로 만들어 둔 명령어들은 GitHub Action workflow 에서 앱 아카이브 시 사용됩니다.

그 결과 팀에서 Swift 버전이 다른 N개의 Xcode 가 사용되고 있거나 빌드 머신에서 배포를 위해 Release 캐시가 필요하더라도 각각의 환경에 맞는 캐시가 전부 만들어지며 캐시 생성 또한 개발자나 빌드 머신이 특별하게 신경쓸 필요가 없게 되었습니다.

여러 개의 빌드 머신이 있다면 보통은 한 번에 macOS 나 Xcode 업데이트를 진행하기에 버전이 통일되어 있을 가능성이 높아 한 곳에서만 새로 캐시가 만들어진다면 다른 빌드 머신들은 이를 사용할 수 있게 됩니다. 또한 별도의 글로 소개드리겠지만 빌드 머신에서 Xcode 업데이트를 진행할 때에도 캐시가 분리되어 있기 때문에 다른 빌드 머신에는 영향을 끼치지 않도록 하는 효과도 생기게 됩니다.

5. 마치며

Cocoapods-Binary-Cache 를 활용하면 빌드 속도를 많이 향상시킬 수 있으나 운영상 불편한 점이 여럿 있었는데요, 버저닝 캐시를 도입한 뒤부터는

  • Debug/Release
  • Swift Ver.
  • Podfile.lock 체크섬
  • Etc. (like macOS Ver, architecture)

등 여러 환경마다 각각 캐시를 만들고 캐시가 없다면 만들어서 푸시까지 해 주어 편하게 운영이 가능해졌습니다. 그리고 추후 필요하면 캐시 버저닝을 더 디테일하게 확장할 수도 있구요. (갑자기 이슈가 생겼을 때 캐시를 분리하기 위해 v1, v2 와 같은 postfix 를 사용할 수 있겠죠)

이번 글을 통해 Cocoapods-Binary-Cache 를 도입하시는데 도움이 되셨으면 좋겠네요!

읽어주셔서 감사합니다 🙏

PS)앞으로도 글을 몇개 더 쓰려고 하는데요, 다음과 같은 주제들이 예정되어 있습니다. 혹시라도 먼저 보고 싶으신 내용이 있으시다면 순서를 조정해 보겠습니다 :)

  • Microsoft App Center 와 함께 쉽고 빠르게 사내 빌드 전달하기
  • Feature Flag 기반으로 개발 중인 피쳐를 Dev / Canary / RC / Stable 빌드 중 원하는 곳에만 노출하기
  • Feature Flag + GitHub Actions 로 매주 안정적으로 배포하기
  • GitHub Actions Runner label 을 활용해 빌드 머신에서 Xcode 업데이트하기

함께 성장할 동료를 찾습니다

29CM (에이플러스비) 는 3년 연속 거래액 2배의 성장을 이루었습니다.

함께 성장하고 유저 가치를 만들어낼 동료 개발자분들을 모십니다.
많이 지원해 주시면 감사하겠습니다 :)

🚀 29CM 채용 페이지 : https://www.29cmcareers.co.kr/

--

--

Wooseong Kim
29CM TEAM

Software Craftman @ 29CM. Love books, coffee, simplicity. An enthusiastic follower of Steve Jobs.