Capacitor 8에서 SPM package traits를 사용하려면 다음을 설정하세요. experimental.ios.spm.swiftToolsVersion 대상 "6.1" 그리고 각 플러그인별 traits를 아래에 나열하세요 experimental.ios.spm.packageTraits Capacitor 설정에 추가한 후, 다음을 실행하세요. bunx cap sync ios. CLI가 해당 traits를 생성된 파일에 기록하고 CapApp-SPM/Package.swift, SwiftPM은 일치하는 선택적 의존성 및 code 경로를 사용하여 플러그인을 빌드합니다. 이 기능은 Capacitor CLI 8.3.0 이상이 필요하며, Swift Package Manager를 사용하는 iOS 프로젝트에만 적용됩니다.
Traits는 SPM과 CocoaPods 간 플러그인의 마지막 큰 기능 격차를 메웁니다: 기존 podspec subspec이던 선택적 네이티브 의존성입니다. 이 가이드에서는 앱 쪽, 플러그인 제작자 쪽, 그리고 현재 실험적 지원의 한계를 다룹니다.
Package traits란 무엇인가요?
Package traits는 Swift Evolution 제안인 SE-0450에서 비롯되었으며, Swift 6.1(Xcode 16.3 이상)에서 구현되었습니다. 패키지는 자신의 Package.swift에 이름이 지정된 traits를 선언합니다 Package.swift. 패키지를 사용하는 사용자는 의존성을 추가할 때 어떤 traits를 활성화할지 선택할 수 있습니다. trait는 다음을 할 수 있습니다:
- 의존성을 선택 사항으로 만듭니다 (
condition: .when(traits: [...])타겟 의존성에 대해), - code 경로를 활성화하면, 활성화된 모든 trait이 컴파일 조건으로 제공되므로 (
#if TraitName), - defines나 linker flags 같은 빌드 설정을 전환하거나,
- 다른 trait을 활성화할 수 있습니다.
패키지에서 trait을 기본값으로 지정할 수도 있습니다. 이 경우 사용자가 명시적인 목록을 전달하지 않는 한 기본으로 활성화됩니다.
CocoaPods의 subspecs나 Rust의 Cargo features를 안다면 같은 개념입니다.
Capacitor에서 이것이 중요한 이유
Capacitor가 앱의 Package.swift 를 대신 생성하므로, trait을 전달하려고 의존성 라인을 직접 수정할 수는 없습니다. CLI 8.3 이전에는 선택적 네이티브 SDK를 가진 플러그인 개발자가 SPM에서 두 가지 좋지 않은 선택을 해야 했습니다. SDK를 항상 링크해서 크기를 키우고 때로는 개인정보 보호나 수출 규정 준수 작업까지 추가하거나, 아니면 두 번째 플러그인을 배포하는 것이었습니다. 일반적인 사례는 다음과 같습니다:
- 암호화된 SQLite vs 일반 SQLite (SQLCipher),
- 광고 식별자 수집 유무에 따른 analytics SDK,
- 특정 결제 또는 로그인 제공업체를 위한 선택적 벤더 SDK
- 디버그 전용 툴링.
앱에서 traits 활성화하기
1. 버전 확인
bunx cap --version # 8.3.0 or later
xcodebuild -version # Xcode 26 (required by Capacitor 8 anyway)
프로젝트가 SPM을 사용해야 합니다: ios/App/CapApp-SPM 존재해야 합니다. CocoaPods를 사용 중이라면 Capacitor 앱을 SPM으로 마이그레이션하는 방법을.
2. 플러그인의 trait 이름 찾기
플러그인의 Package.swift 에서 node_modules 를 열고 traits: array를 확인하거나 README를 참조하세요. Trait 이름은 대소문자를 구분합니다.
3. Capacitor 설정하기
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.example.app',
appName: 'Example',
webDir: 'dist',
experimental: {
ios: {
spm: {
swiftToolsVersion: '6.1',
packageTraits: {
'@acme/capacitor-db': ['.defaults', 'SQLCipher'],
},
},
},
},
};
export default config;
Key는 플러그인의 npm 패키지 이름이며, Value는 활성화할 trait 목록입니다.
4. 동기화하고 출력 확인
bunx cap sync ios
생성된 ios/App/CapApp-SPM/Package.swift 이제 다음으로 시작합니다: // swift-tools-version: 6.1 플러그인의 의존성 라인에는 다음과 같은 traits가 포함됩니다:
.package(name: "AcmeCapacitorDb", path: "../../../node_modules/@acme/capacitor-db", traits: [.defaults, "SQLCipher"])
CLI는 다음과 같이 작성합니다 .defaults (또는 defaults, .default, default (어떤 경우든) SwiftPM .defaults 값으로, 그 외 모든 이름은 따옴표로 감싼 문자열로 처리합니다.
Xcode에서 빌드하세요. Xcode에 여전히 이전 그래프가 표시되면 다음을 사용하세요 File > Packages > Reset Package Caches.
CLI가 적용하는 규칙
Capacitor CLI는 sync 중에 설정을 검증합니다:
- 플러그인에 비어 있지 않은 trait 목록이 있는데
swiftToolsVersion누락된 경우, “Package traits require an explicit Swift tools version of 6.1 or higher.” 메시지와 함께 sync가 중단됩니다. - 만약
swiftToolsVersion버전이 6.1 미만이면, sync가 중단되며 버전이 너무 낮다는 안내가 표시됩니다. swiftToolsVersion다음과 같은 형태여야 합니다:6.1또는6.1.0.- 배열이 비어 있는 플러그인은 목록에 없는 것으로 간주됩니다.
기본 trait 유지 또는 제거
trait을 명시하면 기본값이 대체됩니다. 비교해 보세요:
| 설정 값 | 결과 |
|---|---|
| 목록에 없음 | 플러그인의 기본 trait |
['SQLCipher'] |
오직 SQLCipher, 기본값 비활성화 |
['.defaults', 'SQLCipher'] |
기본값 포함 SQLCipher |
기본 trait를 제거하기 전에 플러그인 문서를 먼저 읽어보세요. 플러그인이 표준 백엔드를 기본 trait 뒤에 두었을 수 있습니다.
자체 플러그인에 trait 추가
선택적 SDK가 있는 플러그인을 관리 중이라면, 다음은 전체 예시입니다 Package.swift. 패키지 이름은 Capacitor CLI가 npm 이름에서 파생하는 이름과 일치해야 합니다 ("@acme/capacitor-db 로 변경됩니다 AcmeCapacitorDb"). 규칙은 Capacitor 플러그인을 SPM으로 마이그레이션 에서 확인하세요.
// swift-tools-version: 6.1
import PackageDescription
let package = Package(
name: "AcmeCapacitorDb",
platforms: [.iOS(.v15)],
products: [
.library(name: "AcmeCapacitorDb", targets: ["DbPlugin"])
],
traits: [
.trait(name: "SQLCipher", description: "Link SQLCipher and enable encrypted databases."),
.default(enabledTraits: [])
],
dependencies: [
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0"),
.package(url: "https://github.com/sqlcipher/SQLCipher.swift.git", from: "4.10.0")
],
targets: [
.target(
name: "DbPlugin",
dependencies: [
.product(name: "Capacitor", package: "capacitor-swift-pm"),
.product(name: "Cordova", package: "capacitor-swift-pm"),
.product(
name: "SQLCipher",
package: "SQLCipher.swift",
condition: .when(traits: ["SQLCipher"])
)
],
path: "ios/Sources/DbPlugin"
)
],
swiftLanguageModes: [.v5]
)
복사하기 전에 SDK 자체 저장소에서 현재 패키지 URL과 제품명, 버전을 확인하세요.
놓치기 쉬운 포인트:
- Tools version 6.x에서는 기본 언어 모드가 Swift 6으로 변경됩니다. 타겟에 적용됩니다. Capacitor는 아직 Swift 6을 공식 지원하지 않으므로,
swiftLanguageModes: [.v5]사용 중인 code가 strict concurrency에 대응할 준비가 되어 있지 않다면. - 플러그인의 tools version을 6.1로 올리면 사용자는 Xcode 16.3 이상이 필요합니다. Capacitor 8 앱은 이미 Xcode 26을 요구하므로 새로운 제약은 아닙니다.
code에서 trait 사용
활성화된 traits는 패키지 타겟의 컴파일 조건입니다:
#if SQLCipher
import SQLCipher
#endif
@objc func open(_ call: CAPPluginCall) {
let key = call.getString("encryptionKey")
#if SQLCipher
// open with key
#else
if key != nil {
call.unavailable("Encryption requires the SQLCipher trait. Enable it in experimental.ios.spm.packageTraits.")
return
}
#endif
// open without encryption
}
기능이 꺼져 있을 때 해당 trait의 이름을 명시한 명확한 에러를 반환하세요. 그렇지 않으면 앱 개발자는 아무런 오류 없이 실패하는 것을 보고 플러그인 문제로 오해할 수 있습니다.
접두사가 붙은 이름을 선호한다면, swiftSettings: [.define("ACME_SQLCIPHER", .when(traits: ["SQLCipher"]))] 다음과 같이 직접 플래그를 정의할 수도 있습니다.
trait을 더하는 방식으로 설계
SwiftPM은 의존성 그래프 전체에서 traits를 통합합니다. 두 패키지가 서로 다른 traits로 플러그인에 의존하면 합집합이 활성화됩니다. 즉:
- trait 활성화는 기능을 추가해야지, API를 제거하거나 기본값을 바꿔서는 안 됩니다.
- 상호 배타적인 traits는 피하세요. 불가피하다면 빌드가 명확히 실패하도록 하세요:
#if TraitA && TraitB
#error("TraitA and TraitB cannot be enabled together")
#endif
모든 조합 테스트
swift build 자체적으로는 Mac용으로 빌드되지만, Capacitor에 의존하는 플러그인은 iOS용으로만 빌드됩니다. 따라서 iOS Simulator용으로 빌드한 Capacitor 테스트 앱으로 traits를 테스트하세요. 각 조합에 대해 (기본 traits는 항목 없음, ['SQLCipher'], ['.defaults', 'SQLCipher'] 등), 다음을 변경하세요: packageTraits 테스트 앱의 config에서 변경한 뒤 sync하고 build하세요:
bunx cap sync ios
xcodebuild build \
-project ios/App/App.xcodeproj \
-scheme App \
-destination 'generic/platform=iOS Simulator' \
CODE_SIGNING_ALLOWED=NO
각 조합을 CI에 별도 job으로 추가하세요.
CocoaPods 사용자도 지원하세요
traits는 SPM에만 존재합니다. 플러그인이 podspec도 함께 제공한다면, subspec으로 동일한 선택지를 제공하세요:
s.default_subspec = 'Core'
s.subspec 'Core' do |core|
core.source_files = 'ios/Sources/DbPlugin/**/*.swift'
core.dependency 'Capacitor'
end
s.subspec 'SQLCipher' do |sc|
sc.dependency 'AcmeCapacitorDb/Core'
sc.dependency 'SQLCipher', '~> 4.10'
sc.pod_target_xcconfig = { 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => '$(inherited) SQLCipher' }
end
subspec에 동일한 compilation condition 이름을 설정하면 하나를 공유할 수 있습니다 #if SQLCipher code 경로입니다. CocoaPods 앱은 Podfile에서 subspec을 선택합니다. Capacitor CLI는 플러그인당 한 pod 줄씩 내부에 작성하므로 capacitor_pods추가 Podfile 줄은 README에 기록해 두세요. Capacitor 8에서 CocoaPods를 사용하는 방법 커스텀 pod 라인이 어디에 들어가는지 보여줍니다.
제한 사항
- 실험적 기능입니다. 옵션은 다음 위치에 있으며
experimental향후 다음으로 이동할 수 있습니다.ios.spm.*향후 메이저 업데이트에서 변경될 예정입니다. 설정 이름이 변경될 수 있으니 주의하세요. - Swift 6은 아직 공식적으로 지원되지 않습니다. CLI 자체 문서에서도 다음과 같이 경고하고 있습니다.
swiftToolsVersion6.0 이상으로 설정할 경우 문제가 발생할 수 있습니다. 앱 전체, 특히 binary xcframework를 포함하는 플러그인을 반드시 테스트하세요. - iOS 전용. Capacitor에는 Android에 해당하는 기능이 없습니다. 플러그인은 Gradle 변수나
variables.gradle별도의 아티팩트를 통해 선택적 Android SDK를 처리합니다. - 플러그인이 직접 선택(opt in)해야 합니다. 대부분의 플러그인은 아직 traits를 정의하지 않았습니다. 사용 전 미리 확인하세요.
문제 해결
“Package traits require an explicit Swift tools version of 6.1 or higher” 오류와 함께 동기화에 실패합니다. 추가 swiftToolsVersion: '6.1' 옆에 packageTraits.
SwiftPM에서 알 수 없는 trait 오류가 발생합니다. Trait 이름은 대소문자를 구분하며, 반드시 플러그인의 Package.swift 설치된 버전에 존재해야 합니다.
Trait을 활성화한 후에도 기능이 여전히 작동하지 않습니다. Xcode에서 패키지 캐시를 초기화하고 빌드 폴더를 clean한 뒤, 생성된 CapApp-SPM/Package.swift 포함하고 있습니다 traits: CLI가 동기화할 때마다 파일을 다시 작성하므로, 해당 파일을 직접 수정해도 유지되지 않습니다.
도구 버전을 올린 후 Swift code에서 오류가 발생합니다. 제어 가능한 패키지에서 Swift 6 언어 모드를 사용하는 경우입니다. 다음을 추가하세요: swiftLanguageModes: [.v5].
플러그인 간의 이름 충돌이 발생합니다. CLI 8.4 버전부터, experimental.ios.spm.packageOptions 설정할 수 있습니다 symlink 또는 moduleAliases 플러그인별로 설정 가능합니다. 자세한 내용은 Capacitor iOS 트러블슈팅 가이드.
SPM 전반을 더 알아보려면, Capacitor를 위한 SPM vs CocoaPods 비교. SPM 프로젝트에 최신 Xcode가 적용된 클라우드 빌드를 원한다면, 다음을 참고하세요 Capgo 빌드.