Swift Package Manager는 소스 코드 배포를 관리하기 위한 도구다. 코드를 쉽게 공유하고 다른 사람의 코드를 재사용할 수 있도록 설계되었다. 이 도구는 Swift 패키지 컴파일 및 링크, 의존성 관리, 버전 관리, 유연한 배포 및 협업 모델 지원 등 다양한 문제를 직접적으로 해결한다.
GitHub 같은 서비스에서 패키지를 쉽게 공유할 수 있도록 시스템을 설계했다. 하지만 패키지는 개인 개발, 팀 내 코드 공유, 또는 기타 세분화된 단위에서도 유용하게 활용할 수 있다.
- 개요
- 패키지 사용하기
- 패키지 매니페스트 명세
- 플러그인 시작하기
- 패키지 컬렉션을 통한 패키지 탐색
- 패키지 레지스트리 서비스 명세
- 라이브러리로서 SwiftPM 사용하기
- 모듈 별칭 사용하기
패키지는 Swift 소스 파일들로 구성되며, 이 중 Package.swift 매니페스트 파일을 포함한다. 매니페스트 파일, 즉 패키지 매니페스트는 PackageDescription 모듈을 사용해 패키지의 이름과 내용을 정의한다. 패키지는 하나 이상의 타겟을 가진다. 각 타겟은 하나의 제품을 지정하고, 하나 이상의 의존성을 선언할 수 있다.
Swift는 코드를 모듈 단위로 구성한다. 각 모듈은 네임스페이스를 정의하고, 해당 코드의 어느 부분을 외부에서 사용할 수 있는지 접근 제어를 강제한다.
프로그램은 모든 코드를 하나의 모듈에 포함할 수도 있고, _의존성_으로 다른 모듈을 임포트할 수도 있다. OS X의 Darwin이나 Linux의 GLibc와 같은 시스템 제공 모듈을 제외하면, 대부분의 의존성은 코드를 다운로드하고 빌드해야 사용할 수 있다.
특정 문제를 해결하는 코드를 별도의 모듈로 분리하면 다른 상황에서도 해당 코드를 재사용할 수 있다. 예를 들어, 네트워크 요청 기능을 제공하는 모듈은 사진 공유 앱과 날씨 예보를 표시하는 프로그램에서 공유할 수 있다. 만약 더 나은 기능을 제공하는 새로운 모듈이 등장하면, 최소한의 변경만으로 쉽게 교체할 수 있다. 모듈화를 적극 활용하면 문제의 흥미로운 부분에 집중할 수 있고, 도중에 마주치는 문제를 해결하는 데 시간을 낭비하지 않을 수 있다.
일반적으로 더 많은 모듈을 사용하는 것이 더 적은 모듈을 사용하는 것보다 낫다. 패키지 매니저는 여러 모듈로 구성된 패키지와 앱을 최대한 쉽게 만들 수 있도록 설계되었다.
Swift 패키지 매니저와 빌드 시스템은 소스 코드를 어떻게 컴파일할지 이해해야 한다. 이를 위해 파일 시스템에서 소스 코드를 구성하는 방식에 기반한 관례적 접근 방식을 사용한다. 이 방식은 개발자가 세부 사항을 완전히 재정의하고 커스터마이징할 수 있도록 허용한다. 간단한 예를 들면 다음과 같다:
foo/Package.swift
foo/Sources/main.swift
Package.swift는 패키지에 대한 메타데이터를 포함하는 매니페스트 파일이다.Package.swift에 대한 자세한 내용은 후속 섹션에서 다룬다.
foo 디렉토리에서 다음 명령어를 실행하면:
swift buildSwift는 foo라는 이름의 단일 실행 파일을 빌드한다.
패키지 매니저에게 모든 것은 패키지이므로 Package.swift가 필요하다. 그러나 이는 소프트웨어를 반드시 외부에 공개해야 한다는 의미는 아니다. 다른 사람이 볼 수도 사용할 수도 없는 상태로 앱을 개발할 수 있다. 반면, 언젠가 프로젝트를 더 많은 사람들에게 공개하고 싶다면 소스 코드는 이미 공개할 준비가 된 형태로 되어 있다. 패키지 매니저는 특정 배포 방식에 종속되지 않으므로 개인 프로젝트, 작업 그룹, 팀 또는 회사 내에서 코드를 공유하거나 전 세계와 공유할 수 있다.
당연히, 패키지 매니저는 자체를 빌드하는 데에도 사용되므로, 그 자체의 소스 파일도 이러한 관례에 따라 구성되어 있다.
타겟은 라이브러리 또는 실행 가능한 바이너리를 제품으로 빌드할 수 있다. 라이브러리는 다른 Swift 코드에서 임포트할 수 있는 모듈을 포함한다. 실행 가능한 바이너리는 운영체제에서 실행할 수 있는 프로그램이다.
현대 개발 환경에서는 외부 의존성을 활용해 개발 속도를 높인다. 이는 시간을 절약하고 더 많은 작업을 수행할 수 있게 해주지만, 프로젝트에 의존성을 추가할 때는 조정 비용이 발생한다.
의존성의 소스 코드를 다운로드하고 빌드하는 것 외에도, 해당 의존성의 의존성도 함께 다운로드하고 빌드해야 한다. 이 과정은 전체 의존성 그래프가 완성될 때까지 반복된다. 더 복잡한 점은, 의존성이 특정 버전을 요구할 수 있고, 이는 동일한 의존성을 공유하는 다른 모듈의 버전 요구 사항과 조정해야 할 수도 있다는 것이다.
패키지 관리자의 역할은 프로젝트의 모든 의존성을 자동으로 다운로드하고 빌드하는 과정을 자동화하며, 코드 재사용과 관련된 조정 비용을 최소화하는 것이다.
의존성은 Package.swift 매니페스트 파일에 명시된다.
"의존성 지옥"은 프로젝트가 요구하는 의존성 그래프를 충족할 수 없는 상황을 비유적으로 표현한 말이다. 사용자는 이 문제를 해결해야 하는데, 보통 쉽지 않은 작업이다:
- 충돌은 사용자가 직접 요청하지 않은, 익숙하지 않은 의존성(또는 그 의존성의 의존성)에서 발생할 수 있다.
- 개발의 특성상 두 의존성 그래프가 동일할 가능성은 희박하다. 따라서 다른 사용자(심지어 패키지 작성자조차도)가 제공할 수 있는 도움은 제한적이다. 인터넷 검색도 큰 도움이 되지 않을 가능성이 높다.
좋은 패키지 관리자는 처음부터 의존성 지옥의 위험을 최소화하도록 설계되어야 한다. 그리고 이를 완전히 피할 수 없는 경우, 사용자가 최소한의 문제로 상황을 해결할 수 있도록 도구를 제공해야 한다. 패키지 관리자 커뮤니티 제안에서는 이러한 지옥을 염두에 두고 어떻게 개선할지에 대한 우리의 생각을 담고 있다.
다음은 가장 흔히 발생하는 "의존성 지옥" 시나리오들이다:
-
부적절한 버전 관리 - 패키지가 릴리스에 부적절한 버전을 지정할 수 있다. 예를 들어, 버전이
1.2.3으로 태그되었지만, 주요 버전 업그레이드인2.0.0으로 반영되어야 할 광범위한 API 변경이 포함된 경우다. -
호환되지 않는 주요 버전 요구사항 - 패키지가 동일한 패키지에 대해 호환되지 않는 버전 요구사항을 가진 의존성을 가질 수 있다. 예를 들어,
Foo가Baz버전~>1.0을 요구하고Bar가Baz버전~>2.0을 요구한다면, 두 요구사항을 모두 충족할 수 있는Baz버전은 없다. 이 상황은 많은 패키지가 공유하는 의존성이 새로운 주요 버전으로 업데이트되었을 때, 모든 패키지가 의존성을 업데이트하는 데 시간이 오래 걸리는 경우에 자주 발생한다. -
호환되지 않는 마이너 또는 업데이트 버전 요구사항 - 패키지가 너무 엄격하게 지정된 의존성을 가질 수 있다. 예를 들어,
Foo가Baz버전==2.0.1을 요구하고Bar가Baz버전==2.0.2를 요구한다면, 다시 한 번 두 요구사항을 모두 충족할 수 있는Baz버전은 없다. 이는 종종 패치 릴리스에서 발생한 회귀(regression)로 인해 패키지가 특정 버전에 의존성을 고정시키는 결과를 낳는다. -
네임스페이스 충돌 - 패키지가 동일한 이름을 가진 두 개 이상의 의존성을 가질 수 있다. 예를 들어,
Person패키지가 메일 주소를 할당하는 프로토콜을 정의하는Addressable패키지와, 다른 사람에게 공식적으로 말하는 프로토콜을 정의하는Addressable패키지를 동시에 의존할 수 있다. -
깨진 소프트웨어 - 패키지가 사용성, 보안, 성능에 영향을 미치는 버그가 있는 의존성을 가질 수 있다. 이는 패키지 관리자의 적시성 문제이거나, 패키지에 대한 기대치가 일치하지 않는 경우일 수 있다.
-
전역 상태 충돌 - 패키지가 동일한 전역 상태에 대해 독점적 접근을 가정하는 두 개 이상의 의존성을 가질 수 있다. 예를 들어, 한 패키지가 특정 파일 경로에서 읽는 동안 다른 패키지가 동일한 파일 경로에 쓰는 것을 허용하지 못할 수 있다.
-
패키지 사용 불가 - 패키지가 사용할 수 없게 된 의존성을 가질 수 있다. 이는 소스 URL에 접근할 수 없게 되거나, 관리자가 게시된 버전을 삭제한 경우에 발생할 수 있다.