지난주 세 개의 레포가 동시에 커밋을 올렸고, CI에서는 동일한 사내 오디오 처리 프레임워크가 세 번이나 다시 컴파일되었습니다. 매번 거의 8분에 달하는 archive 과정을 거쳤죠. 세 번째 빌드가 서명 인증서 만료로 실패하자, 당직 동료가 채팅방에 "이거 어제 이미 빌드했던 거 아니었어? 왜 레포마다 또 다시 돌리는 거야?"라고 물었습니다. 이 한마디가 많은 팀의 아픈 곳을 찔렀습니다——공유 바이너리 프레임워크는 원래 한 번만 빌드하면 되는데, 캐시 메커니즘이 없다 보니 파이프라인마다 각자 같은 작업을 반복하게 된 겁니다.
HireVPS의 전용 클라우드 Mac에서 이 프로세스를 다시 구성해봤습니다. 멀티 슬라이스 XCFramework를 한 곳에서 빌드하고, 서명과 체크섬 계산까지 마친 뒤 가벼운 로컬 바이너리 캐시에 담아두면, 여러 레포가 직접 산출물을 가져다 쓸 수 있어 각자 다시 빌드할 필요가 없어집니다. 아래에 구체적인 방법을 정리했습니다.
XCFramework를 직접 캐시해야 하는 이유
Swift Package Manager는 binaryTarget으로 미리 컴파일된 XCFramework를 직접 참조할 수 있게 해줍니다. 이 자체는 새로운 지식이 아니지만, 이를 제대로 활용하려면 "산출물을 믿을 수 있고, 추적 가능하며, 여러 곳에서 공유할 수 있어야" 한다는 전제가 필요합니다. 레포마다 CI에서 각자 archive를 돌린다면, 실제로 얻는 건 "이론적으로는 같지만 실제 일치 여부는 검증되지 않은" 여러 개의 바이너리일 뿐입니다. 빌드 환경에 미세한 차이(예: Xcode 마이너 버전 업데이트)가 생기는 순간, 원인을 찾는 게 매우 골치 아파집니다. 중앙에서 빌드하고 일괄 배포해야만 모든 다운스트림이 완전히 동일한 산출물을 받는다는 게 보장됩니다.
경험담: 3개 이상의 레포에서 참조하는 사내 프레임워크라면 따로 바이너리 캐시로 빼놓을 가치가 있습니다. 참조하는 곳이 3개 미만이라면, 반복 빌드 비용이 캐시 유지 비용보다 오히려 낮을 수도 있습니다.
클라우드 Mac의 리소스 우위
이 워크플로우는 머신에 두 가지 필수 조건을 요구합니다. 첫째, 완전한 xcodebuild archive를 실행하려면 전용 macOS 그래픽/커맨드라인 환경이 필요합니다. 둘째, 여러 아키텍처 슬라이스를 빌드할 때 디스크 I/O 부담이 상당히 커서, 공유 리소스 머신에서는 서로 발목을 잡기 쉽습니다. HireVPS에서 하루 단위로 M4 또는 M4 Pro 모델을 대여하면, 노드를 선택하는 즉시 독립된 SSH 인증 정보를 받고 root 권한도 바로 사용할 수 있습니다. 다른 사용자와 CPU 타임슬라이스를 다툴 필요가 없어, 몇 시간씩 이어지는 멀티 아키텍처 빌드도 옆자리 작업에 방해받지 않고 계속 돌릴 수 있습니다. 어떤 모델과 어떤 노드가 현재 선택 가능한지는 콘솔에서 현재 구성을 확인하면 됩니다.
멀티 슬라이스 XCFramework 빌드하기
실기기와 시뮬레이터에서 모두 사용할 수 있는 XCFramework는 최소한 두 개의 슬라이스를 포함해야 합니다: ios-arm64(실기기용)와 ios-arm64-simulator(Apple Silicon 시뮬레이터용). 팀 내에 Intel Mac으로 시뮬레이터를 돌리는 사람이 있다면 ios-x86_64-simulator도 추가해야 합니다. 빠뜨리면 그 머신에서 테스트를 돌리는 순간 아키텍처 불일치 오류가 발생합니다.
두 슬라이스를 각각 아카이브하기
xcodebuild archive \
-scheme AudioCore \
-destination "generic/platform=iOS" \
-archivePath build/AudioCore-iOS.xcarchive \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
xcodebuild archive \
-scheme AudioCore \
-destination "generic/platform=iOS Simulator" \
-archivePath build/AudioCore-iOSSim.xcarchive \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
XCFramework로 병합하기
xcodebuild -create-xcframework \
-framework build/AudioCore-iOS.xcarchive/Products/Library/Frameworks/AudioCore.framework \
-framework build/AudioCore-iOSSim.xcarchive/Products/Library/Frameworks/AudioCore.framework \
-output build/AudioCore.xcframework
빌드가 끝나면 zip으로 압축합니다. 이 파일이 배포할 최종 산출물이며, 이후로는 수동으로 손대지 않도록 합니다.
서명과 체크섬
사내 프레임워크도 다운스트림에서 출처를 검증하기 쉽도록 서명하는 걸 권장합니다.
codesign --sign "Apple Distribution: Your Team" \
--timestamp \
build/AudioCore.xcframework
zip -r AudioCore-1.4.0.xcframework.zip build/AudioCore.xcframework
서명 후 패키징이 끝나면 SwiftPM에 내장된 명령으로 체크섬을 계산합니다. 이 값은 다운스트림 레포의 Package.swift에 기입해야 합니다.
swift package compute-checksum AudioCore-1.4.0.xcframework.zip
체크섬 검증 실패의 가장 흔한 원인은 패키징 후 zip 내용을 다시 수정했거나, 압축 도구를 바꿔서 파일 내부 순서가 달라진 경우입니다. 원칙은 간단합니다: 체크섬을 계산한 뒤에는 그 zip 파일을 동결하고, 더 이상 손대지 않습니다.
로컬 바이너리 캐시 구축하기
복잡한 서비스는 필요 없고, 정적 파일을 다운로드할 수 있는 디렉터리 구조만 있으면 충분합니다.
| 레이어 | 내용 | 경로 예시 |
|---|---|---|
| 프레임워크 이름 | 사내 프레임워크마다 하나의 디렉터리 | /cache/AudioCore/ |
| 버전 번호 | 버전마다 하나의 하위 디렉터리 | /cache/AudioCore/1.4.0/ |
| 산출물 | zip 패키지 + 체크섬 텍스트 | AudioCore-1.4.0.xcframework.zip, checksum.txt |
다운스트림 레포의 Package.swift에서는 다음과 같이 참조합니다.
.binaryTarget(
name: "AudioCore",
url: "https://cache.internal.example/AudioCore/1.4.0/AudioCore-1.4.0.xcframework.zip",
checksum: "이전 단계에서 계산한 체크섬"
)
저장 용량 계획은 중간 규모 팀이라면 핵심 프레임워크 3~4개의 최근 10개 버전을 각각 보관하는 정도로 충분합니다. 압축 파일 하나는 보통 20~80MB이며, 전체 용량은 몇 GB 이내로 관리하고, 90일 동안 가져가지 않은 오래된 버전은 정기적으로 정리하면 됩니다.
멀티 레포 CI 파이프라인에 연동하기
각 다운스트림 레포의 CI는 더 이상 xcodebuild archive를 실행할 필요가 없이, swift package resolve로 지정한 버전을 가져오기만 하면 됩니다. 몇 분 걸리던 빌드 시간이 몇 초짜리 다운로드로 줄어듭니다. 새 버전을 릴리스할 때는 캐시 디렉터리에 새 버전 번호 디렉터리를 만들고 해당 레포의 checksum 참조를 업데이트하기만 하면 되며, 캐시 서버 코드는 손댈 필요가 없습니다.
주의할 점: 여러 레포가 "latest" 같은 가변 태그를 동시에 참조하게 두면 절대 안 됩니다. 체크섬 메커니즘은 URL과 내용이 일대일로 대응한다는 것을 전제로 하는데, 같은 버전 번호 아래 파일을 덮어쓰는 걸 허용하면 SwiftPM의 무결성 검증이 무의미해집니다.
체크리스트
- 슬라이스가 실기기와 필요한 모든 시뮬레이터 아키텍처를 커버하는가
- 패키징이 끝난 즉시 체크섬을 계산했고, 이후 zip을 수정하지 않았는가
- 캐시 디렉터리의 버전 번호 디렉터리는 한 번만 쓰였고, 덮어쓰지 않았는가
- 다운스트림
Package.swift의 checksum이 실제 파일과 일치하는가 - 90일 동안 가져가지 않은 오래된 버전을 이미 정리했는가
자주 묻는 질문
실기기와 시뮬레이터를 동시에 지원하려면 어떤 슬라이스가 필요한가요?
최소한 실기기용 ios-arm64와 Apple Silicon 시뮬레이터용 ios-arm64-simulator 두 가지가 필요합니다. 팀에 Intel Mac으로 시뮬레이터를 돌리는 사람이 있다면 ios-x86_64-simulator도 추가해야 하며, 없으면 해당 머신에서 아키텍처 불일치로 즉시 실패합니다.
binaryTarget 체크섬 검증이 실패하는 주된 원인은 무엇인가요?
패키징 후 zip 내용을 수동으로 수정했거나, 다른 압축 도구를 써서 파일 순서가 바뀐 경우가 가장 흔합니다. 올바른 방법은 최종 배포할 zip 파일에 대해 swift package compute-checksum을 한 번만 실행하고, 이후 해당 파일을 절대 건드리지 않는 것입니다.
로컬 바이너리 캐시 서버는 저장 용량을 얼마나 계획해야 하나요?
중간 규모 팀 기준으로 핵심 프레임워크 3~4개의 최근 10개 버전을 보관한다면, 압축 후 하나당 보통 20~80MB이므로 총 몇 GB면 충분합니다. 클라우드 Mac의 SSD에 별도 파티션을 두고 90일 이상 요청되지 않은 버전은 정기적으로 정리하는 것을 권장합니다.
HireVPS Mac
지금 전용 클라우드 Mac mini를 이용해 보세요
일 단위 대여로 시작하고, 2분 안에 SSH/VNC 접속 정보를 받아 언제든지 사양을 업그레이드할 수 있습니다.