Swift Package Registry, Git URL 의존성 이후를 준비하는 방법
Swift Package Manager를 쓰다 보면 자연스럽게 Git URL 기반 의존성에 익숙해진다.
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.0")
지금까지는 이 방식이 Swift 패키지를 가져오는 가장 자연스러운 흐름이었다. 하지만 SwiftPM에는 이미 Git repository URL이 아니라 scope.package-name 형태의 패키지 식별자로 의존성을 해석하고 다운로드할 수 있는 Package Registry 기능이 들어와 있다.
.package(id: "apple.swift-log", from: "1.5.0")
이 변화는 단순히 URL을 문자열 하나 바꾸는 정도가 아니다. iOS 앱 개발자 입장에서는 CI 속도, 의존성 재현성, 사내 패키지 배포, 보안 정책, Xcode 프로젝트 설정까지 같이 봐야 하는 주제다.
Swift Package Registry가 해결하려는 문제
기존 SwiftPM은 의존성을 Git repository URL로 지정한다. SwiftPM은 처음 빌드할 때 repository를 clone하고, tag를 기준으로 버전을 해석한다. 이 방식은 이해하기 쉽고 GitHub 중심 생태계와 잘 맞는다. 하지만 팀 규모가 커지고 의존성이 많아지면 몇 가지 문제가 생긴다.
가장 먼저 체감되는 건 속도다. 필요한 건 특정 버전의 source archive인데, Git clone은 repository history까지 다루는 방식이다. repository가 크거나 dependency graph가 복잡하면 CI에서 패키지 resolve 시간이 무시하기 어려워진다.
재현성도 문제다. Git tag는 원칙적으로 다시 가리킬 수 있다. 물론 좋은 운영에서는 그러지 않지만, 기술적으로 불가능한 건 아니다. repository가 이동하거나 삭제되면 같은 Package.resolved를 가지고도 나중에 빌드가 실패할 수 있다.
엔터프라이즈 환경에서는 더 직접적인 문제가 생긴다. 회사 보안 정책상 github.com 접근이 차단되어 있으면 SwiftPM이 GitHub에서 dependency를 clone하지 못해 CI가 실패한다. 이 경우 보안팀은 보통 JFrog Artifactory, 내부 Git mirror, 내부 artifact 저장소 같은 승인된 경로를 요구한다. Swift Package Registry는 이런 환경에서 GitHub 직접 접근 없이 패키지를 공급하는 구조를 만들 수 있는 기반이 된다.
Git URL 대신 패키지 식별자로 의존성을 선언한다
Package Registry의 핵심은 package identity가 Git URL에서 scoped identifier로 바뀐다는 점이다.
.package(id: "mona.LinkedList", .upToNextMajor(from: "1.0.0"))
여기서 mona는 scope이고, LinkedList는 package name이다. 둘을 합친 mona.LinkedList가 registry dependency의 식별자가 된다.
SwiftPM은 registry가 설정되어 있으면 이 identifier를 기준으로 registry에 요청을 보낸다. 먼저 /{scope}/{name}으로 사용 가능한 release 목록을 가져오고, 필요한 버전의 Package.swift manifest를 가져온 뒤, 최종적으로 /{scope}/{name}/{version}.zip source archive를 다운로드한다.
이 구조가 중요한 이유는 Git clone이 아니라 HTTP 기반 artifact 다운로드로 의존성을 가져올 수 있기 때문이다. source archive는 immutable하게 관리할 수 있고, CDN이나 내부 artifact 저장소와도 잘 맞는다. 패키지 배포를 source control과 분리할 수 있다는 점도 크다.
다만 여기서 한 가지를 분명히 해야 한다. Registry가 Git을 없애는 것은 아니다. 코드는 여전히 GitHub, GitLab, GitHub Enterprise Server, 사내 Git 서버에서 개발할 수 있다. Registry는 개발 중인 source repository를 대체한다기보다, 특정 release를 소비자에게 배포하는 경로를 표준화하는 쪽에 가깝다.
설정은 간단하지만, Xcode 프로젝트는 별도로 봐야 한다
Swift Package 기준으로 registry 설정은 비교적 단순하다.
swift package-registry set https://packages.example.com
프로젝트 단위 설정은 다음 위치에 저장된다.
.swiftpm/configuration/registries.json
사용자 전역 설정은 다음 위치에 저장된다.
~/.swiftpm/configuration/registries.json
생성되는 설정은 대략 이런 형태다.
{
"registries": {
"[default]": {
"url": "https://packages.example.com"
}
},
"version": 1
}
Swift Package 라이브러리를 개발하는 경우에는 이 흐름이 자연스럽다. Package.swift가 있고, 그 안에서 .package(id:)를 선언하면 된다.
하지만 iOS 앱의 Xcode 프로젝트는 조금 다르다. 일반적인 앱 프로젝트에는 Package.swift가 없다. Xcode가 project.pbxproj, workspace, Package.resolved를 통해 SwiftPM dependency를 관리한다. 그래서 실제 앱 프로젝트에 registry를 도입하려면 “SwiftPM CLI에서 설정했으니 Xcode도 알아서 되겠지”라고 보면 안 된다.
실무적으로는 Xcode workspace나 project 내부의 SwiftPM configuration 위치를 확인해야 한다. 팀 단위로 적용할 경우에는 registry URL 설정 파일은 공유하되, access token 같은 인증 정보는 절대 repository에 커밋하지 않아야 한다.
이 지점 때문에 도구가 필요해진다. Xcode workspace나 .xcodeproj를 받아서 registries.json 위치를 찾아주고, 필요한 경우 mirrors.json까지 만들어주는 CLI나 GUI Setup Assistant가 있으면 도입 난이도가 크게 낮아진다.
Registry와 Git dependency를 섞을 때 생기는 충돌
Swift Package Registry를 도입할 때 가장 조심해야 하는 부분은 Git URL dependency와 registry dependency가 같은 graph 안에 섞이는 상황이다.
예를 들어 앱에서는 registry 방식으로 apple.swift-log를 직접 추가했다고 하자.
.package(id: "apple.swift-log", from: "1.5.0")
그런데 다른 dependency가 transitive dependency로 Git URL 방식의 swift-log를 끌고 올 수 있다.
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.0")
실제 코드는 같은 패키지지만, SwiftPM 입장에서는 origin이 다르면 서로 다른 package identity로 볼 수 있다. 이 경우 같은 module이나 product가 중복되어 들어오면서 product lookup 문제나 symbol 충돌이 생길 수 있다.
이를 완화하기 위해 registry specification에는 /identifiers?url= lookup endpoint가 있다. Git URL에 해당하는 registry package identifier를 찾아주는 역할이다. registry가 https://github.com/apple/swift-log.git를 apple.swift-log로 정확히 매핑할 수 있어야 SwiftPM이 URL 기반 dependency와 registry identifier를 더 안전하게 연결할 수 있다.
그래도 실무적으로는 원칙을 단순하게 가져가는 편이 좋다. 같은 dependency graph 안에서는 같은 패키지를 하나의 origin으로 통일해야 한다. registry로 전환할 패키지는 .package(id:)로 명시적으로 전환하고, 아직 전환하지 않은 패키지는 Git mirror나 기존 Git URL 방식으로 유지하는 식의 단계적 전략이 필요하다.
Mirror는 Registry가 아니지만 전환 과정에서 유용하다
SwiftPM에는 dependency mirroring 기능도 있다. Mirror는 기존 Git URL dependency를 다른 Git URL로 바꿔서 fetch하도록 만드는 기능이다.
예를 들어 원래 dependency가 GitHub를 가리키고 있어도:
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.0")
mirror 설정을 통해 실제 fetch는 사내 Git mirror에서 하게 만들 수 있다.
{
"object": [
{
"original": "https://github.com/apple/swift-log.git",
"mirror": "https://git.company.internal/mirrors/apple/swift-log.git"
}
],
"version": 1
}
여기서 중요한 건 mirror와 registry를 구분하는 것이다. Mirror는 .package(url:)를 유지한다. Git URL dependency를 다른 Git URL로 치환할 뿐이다. 반면 registry는 .package(id:)를 사용하고, source archive를 registry에서 다운로드한다.
보안망이나 내부망 환경에서는 mirror가 꽤 실용적이다. 기존 Xcode 프로젝트를 한 번에 registry identifier로 바꾸기 어렵다면, 먼저 GitHub 접근을 내부 mirror로 돌리고, 이후 안정적으로 registry 기반 배포로 전환할 수 있다.
인증과 패키지 서명은 별개의 문제다
Registry를 private하게 운영하려면 인증이 필요하다. SwiftPM은 swift package-registry login 명령으로 registry 인증 정보를 저장할 수 있다.
swift package-registry login https://packages.example.com --token "$TOKEN"
CI에서는 SWIFTPM_REGISTRY_TOKEN 같은 환경변수를 사용할 수 있다. 여기까지는 “registry에 접근할 권한”의 문제다.
패키지 서명은 다른 문제다. 서명은 “이 package artifact가 특정 private key를 가진 publisher 또는 조직에 의해 만들어졌고, 이후 변조되지 않았다”는 증거를 제공한다. SwiftPM은 서명된 archive를 받을 때 X-Swift-Package-Signature-Format, X-Swift-Package-Signature header를 확인하고, 서명과 인증서 체인을 검증한다.
서명하려면 code signing 용도의 X.509 인증서와 private key가 필요하다. macOS Keychain의 signing identity를 사용할 수도 있고, CI에서는 private key와 certificate chain 파일을 넘기는 방식도 가능하다.
swift package-registry publish mycompany.design-system 1.2.0 \
--url https://packages.example.com \
--private-key-path ./private-key.pkcs8.der \
--cert-chain-paths ./leaf-cert.der ./intermediate.der ./root.der
엔터프라이즈 환경에서는 이 부분이 특히 중요하다. 단순히 “서명되어 있다”가 아니라, 어떤 인증서가 어떤 scope나 package를 서명할 수 있는지 정책으로 관리해야 한다. 내부 CA를 쓰는 조직이라면 SwiftPM trust root 설정도 같이 배포해야 한다.
iOS 팀이 실제로 도입할 때 봐야 할 것
Swift Package Registry는 좋아 보이지만, 모든 팀이 바로 전환해야 하는 기능은 아니다. 작은 앱에서 의존성이 몇 개 없고 GitHub 접근에 문제가 없다면 체감 이득이 크지 않을 수 있다.
반대로 다음 조건에 해당하면 진지하게 볼 만하다.
CI에서 SwiftPM resolve 시간이 길다. 사내 Swift package가 많다. GitHub 접근이 제한된 네트워크에서 개발하거나 빌드한다. JFrog, Nexus, Artifactory 같은 내부 artifact 저장소를 이미 운영한다. SDK를 고객사에 private하게 배포해야 한다. 패키지 checksum, signing, audit log 같은 공급망 보안 요구사항이 있다.
특히 iOS 플랫폼 팀이 별도로 있는 조직이라면 Registry는 단순 개발 편의가 아니라 플랫폼 운영 문제에 가깝다. 어떤 패키지를 승인할지, 어떤 registry나 mirror를 사용할지, Xcode 프로젝트 설정을 어떻게 공유할지, CI에서 외부 host 접근을 어떻게 막을지까지 함께 설계해야 한다.
그래서 무엇부터 보면 좋을까
- 현재 프로젝트의 SwiftPM dependency 목록을 먼저 정리한다.
Package.resolved와 Xcode project 안에 어떤 Git URL이 들어있는지 확인한다.- CI에서 패키지 resolve 시간이 얼마나 걸리는지 측정한다.
- GitHub, GitHubusercontent, 외부 binary artifact URL 접근이 회사 네트워크에서 허용되는지 확인한다.
- registry로 전환할 package와 mirror로 유지할 package를 구분한다.
- 같은 package가 Git URL과 registry ID로 동시에 들어오는 mixed graph를 검사한다.
- Xcode workspace 또는
.xcodeproj기준으로registries.json을 어디에 둘지 정한다. - private registry를 쓴다면 token 저장 방식과 CI 환경변수 전략을 정한다.
- package signing을 요구할지, 요구한다면 어떤 CA와 인증서를 신뢰할지 정한다.
- 사내 JFrog나 Git mirror가 있다면 SwiftPM registry/mirror 전략과 어떻게 연결할지 검토한다.
마무리
Swift Package Registry는 SwiftPM의 의존성 배포 방식을 Git 중심에서 artifact 중심으로 확장하는 기능이다. 단순히 .package(url:)을 .package(id:)로 바꾸는 이야기가 아니다.
iOS 앱 개발자 입장에서는 CI 속도, 재현성, Xcode 설정, 내부망 빌드, package signing, 사내 패키지 운영까지 연결되는 주제다. 그래서 도입 여부는 기능 하나만 보고 판단하기보다 팀의 빌드 환경과 보안 요구사항을 같이 봐야 한다.
바로 할 수 있는 첫 번째 액션은 단순하다. 지금 프로젝트의 SwiftPM dependency graph를 열어보고, 어떤 패키지가 GitHub에 직접 의존하고 있는지 확인하는 것이다. 거기서부터 registry가 필요한 팀인지, mirror만으로 충분한 팀인지가 보이기 시작한다.
출처
-
Swift Evolution - SE-0292: Package Registry Service
- Swift Package Registry의 도입 배경, Git 기반 dependency resolution의 한계,
scope.package-name식별자, registry endpoint, checksum 동작을 참고했다.
- Swift Package Registry의 도입 배경, Git 기반 dependency resolution의 한계,
-
Swift Package Manager - Package Registry Usage
swift package-registry set,registries.json위치,.package(id:)사용법, registry authentication, CI 환경변수, signing validation, publish 명령 사용법을 참고했다.
-
Swift Package Manager - Swift Package Registry Service Specification
- registry service endpoint, release metadata, manifest fetch, source archive download,
/identifiers?url=, publish multipart request, problem response, checksum, signature header 요구사항을 참고했다.
- registry service endpoint, release metadata, manifest fetch, source archive download,
-
Swift Evolution - SE-0321: Package Registry Service - Publish Endpoint
- registry publish endpoint의 목적, source archive 업로드 흐름, 동기/비동기 publish 처리 개념을 참고했다.
-
Swift Evolution - SE-0378: Package Registry Authentication
swift package-registry login/logout, token authentication, credential 저장 방식의 방향을 참고했다.
-
Swift Evolution - SE-0391: Package Registry Publish
swift package-registry publish, package signing, metadata, signing certificate와 signature 처리 방향을 참고했다.
-
Swift Evolution - SE-0219: Package Manager Dependency Mirroring
- SwiftPM mirror 기능의 목적과 registry와의 차이를 설명하는 데 참고했다.
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.