앱 타깃에서 쓰는 경우
// Project.swift
let project = Project(
name: "ABC",
packages: abcAppPackages
)
// dependency definition
.external(name: "GoogleSignIn")이런 경우 앱 target은 `GoogleSignIn`을 dependency로 붙여 사용할 수 있다. 즉 프로젝트/앱 레벨에서는 외부 패키지를 알고 있다.
이 문서는 특정 iOS 앱 프로젝트를 기준으로, `packages`, `dependencies`, `target`, `scheme`, `build configuration`이 서로 어떻게 연결되는지 질문 중심으로 정리한 문서다.
2026-04-10
이 프로젝트를 레고 조립이라고 생각하면 쉽다. `package`는 부품 상자 목록이고, `dependency`는 어떤 부품을 실제로 쓰겠다는 선언이고, `target`은 실제 완성품 하나고, `scheme`은 그 완성품을 어떤 모드로 실행할지 정하는 버튼이다.
flowchart TD
A["Package.swift (외부 SPM과 product 타입 정책을 모아 두는 파일)"]
B["ABCAppPackages.swift (프로젝트가 인식할 로컬 패키지 목록 파일)"]
C["ABCAppDependencies.swift (앱 타깃 의존성을 적는 파일)"]
D["ABCAppProject.swift (앱 타깃 생성 규칙을 모아 둔 helper 파일)"]
E["Project.swift (packages, targets, schemes를 최종 연결하는 파일)"]
F["생성된 .xcodeproj (Tuist가 만들어 낸 Xcode 프로젝트 결과물)"]
G["Target (앱, 테스트, 위젯, 확장 기능처럼 실제로 빌드되는 단위)"]
H["Scheme (어떤 타깃을 어떤 설정으로 실행할지 정하는 실행 묶음)"]
I["Build Configuration (Debug_Qa, Release_QA 같은 빌드 환경 이름)"]
A --> C
B --> E
C --> D
D --> E
E --> F
F --> G
F --> H
H --> I
G --> I
| 박스 | 이게 뭔지 | 이게 무엇인가 |
|---|---|---|
| 외부 SPM을 중앙에서 등록하고 product 타입 정책을 정하는 파일 | 중앙 설정 파일 | 프로젝트 전체에서 공통으로 쓰는 외부 라이브러리 목록과 static/dynamic 같은 링크 정책을 모아 두는 곳이다. |
| 프로젝트가 알고 있어야 할 로컬 패키지 목록을 모아 둔 파일 | 패키지 등록 파일 | `App`, `Core`, `Feature` 아래 있는 로컬 SPM 중에서 이 프로젝트가 조립 대상으로 인식해야 하는 패키지들을 등록하는 곳이다. |
| 앱 타깃이 실제로 링크할 로컬 패키지, 외부 패키지, 다른 타깃을 적는 파일 | 의존성 선언 파일 | 앱이 실제 빌드될 때 무엇을 붙일지 적는 목록이다. 여기에는 로컬 SPM product, 외부 SPM product, 위젯/확장 타깃 같은 항목이 함께 들어갈 수 있다. |
| 앱 타깃과 설정값을 어떤 방식으로 만들지 helper 함수로 정의한 파일 | 타깃 생성 규칙 파일 | 타깃을 하나하나 직접 쓰기보다, 공통 규칙으로 앱 타깃을 만들 수 있게 도와주는 helper 정의다. 소스, 리소스, 스크립트, configuration 연결 규칙이 여기 들어간다. |
| 실제 프로젝트를 조립하면서 packages, targets, schemes를 연결하는 메인 프로젝트 파일 | 최종 조립 파일 | 위에서 준비한 패키지 목록, 의존성 목록, 타깃 생성 helper, 스킴 정의를 한곳에서 묶어 실제 프로젝트 구조를 결정하는 최종 조립 지점이다. |
| Tuist가 위 설정들을 읽고 실제로 만들어 낸 Xcode 프로젝트 | 생성 결과물 | 개발자가 직접 여는 Xcode 생성물이다. 결국 Xcode는 이 결과물을 보고 타깃, 스킴, 빌드 설정을 보여 준다. |
| 앱, 테스트, 위젯, 확장 기능처럼 실제로 빌드되는 단위 | 빌드 단위 | 실제로 컴파일되어 결과물이 만들어지는 단위다. 앱 하나, 테스트 번들 하나, 위젯 하나가 각각 타깃이 된다. |
| 어떤 타깃을 어떤 설정으로 빌드, 실행, 테스트, 아카이브할지 정하는 실행 묶음 | 실행 시나리오 | Xcode 상단에서 선택하는 실행 세트다. 같은 타깃이라도 QA용, Dev용처럼 여러 스킴으로 다르게 돌릴 수 있다. |
| Debug_Qa, Release_QA처럼 실행 환경과 빌드 방식을 나누는 설정 이름 | 빌드 환경 이름 | 디버그/릴리즈 구분뿐 아니라 서버 환경, 번들 ID, plist, 서명 설정 같은 차이를 나누기 위한 빌드 설정 이름이다. |
let project = Project(
name: "ABC",
packages: abcAppPackages,
targets: abcAppTargets(variant: .global)
)여기서 `packages: abcAppPackages`는 프로젝트가 알고 있는 로컬 패키지 목록이다. 반면 각 `target` 안의 `dependencies`는 그 타깃이 실제로 링크할 대상이다.
| dependency 종류 | 무엇에 포함되어 있어야 하나 |
|---|---|
| `.App.*`, `.Feature.*`, `.Core.*` | 원본 패키지가 `packages: abcAppPackages`에 등록되어 있어야 한다. |
| `.external(name: "...")` | 해당 외부 패키지가 central package registry file 에 선언되어 있어야 한다. |
| `.target(name: "...")` | 그 이름의 target이 같은 프로젝트 안 `targets:` 배열에 실제로 있어야 한다. |
dependency definition 을 보면
앱 타깃이 붙이는 의존성이 한곳에 모여 있다.
public let abcAppDependencies: [ProjectDescription.TargetDependency] = abcSharedAppDependencies + [
.target(name: "ABCWidgetExtension"),
.target(name: "ABCShareExtension"),
.target(name: "ABCNotificationServiceExtension")
]| 파라미터 | 의미 |
|---|---|
| `name` | Xcode 상단에서 보이는 스킴 이름이다. |
| `buildAction` | 이 스킴에서 빌드할 타깃 목록이다. |
| `testAction` | 어떤 테스트 플랜을 어떤 configuration으로 실행할지 정한다. |
| `runAction` | 앱 실행 시 어떤 configuration으로 돌릴지 정한다. |
| `archiveAction` | Archive 시 어떤 configuration을 사용할지 정한다. |
Scheme.scheme(
name: "StreamingPlayQAApp",
buildAction: .buildAction(targets: ["StreamingPlayQAApp"]),
testAction: TestAction.testPlans(["ABCTests.xctestplan"], configuration: "Debug_Qa"),
runAction: .runAction(configuration: "Debug_Qa"),
archiveAction: .archiveAction(configuration: "Release_QA")
)위 예시는 “`StreamingPlayQAApp` 타깃을 QA 디버그 설정으로 실행하고, 아카이브는 QA 릴리즈 설정으로 하라”는 뜻이다.
선언이 안 된 것은 아니다. 실제 생성된 스킴 파일이 이미 존재한다.
QRLoginTestApp.xcschemesample app scheme fileV3LoginApp.xcschemeLocalNotificationTestApp.xcscheme보통 안 보이는 원인은 아래 쪽이다.
이건 단순 문자열이 아니라 커스텀 build configuration 이름이다. 기본 Xcode는 보통 `Debug`, `Release`만 두지만, 이 프로젝트는 환경이 많아서 이를 더 세분화했다.
| configuration | 의미 |
|---|---|
| `Debug` | 기본 디버그 빌드 |
| `Debug_Dev` | 개발 환경용 디버그 빌드 |
| `Debug_Qa` | QA 환경용 디버그 빌드 |
| `Debug_Qa2` | 두 번째 QA 환경용 디버그 빌드 |
| `Release_Qa`, `Release_Qa2` | QA/QA2 환경용 릴리즈 빌드 |
| 질문 | 답 |
|---|---|
| 한 타깃에 여러 스킴이 가능하나 | 가능하다. 같은 타깃을 `Debug`, `QA`, `Sandbox`처럼 여러 실행 방식으로 돌릴 수 있다. |
| 한 스킴에 여러 타깃이 가능하나 | 가능하다. `buildAction`에 여러 타깃을 넣어 함께 빌드할 수 있다. |
다만 역할은 다르다. `target`은 결과물 단위이고, `scheme`은 그 결과물을 빌드/실행/테스트하는 시나리오 단위다.
스킴의 `buildAction` 안에 적힌 타깃은 새로 만드는 게 아니라, 프로젝트 안에 이미 정의돼 있는 target 이름을 참조하는 것이다.
// Project.swift
targets: abcAppTargets(variant: .global) + [
qrLoginTestApp,
secondOTPTestApp,
streamingPlayTestApp,
v3LoginApp,
noticeTestApp,
localNotificationTestApp
]
// same file
Scheme.scheme(
name: "QRLoginTestApp",
buildAction: .buildAction(targets: ["QRLoginTestApp"])
)위 예시에서는 `qrLoginTestApp` helper가 실제 target `QRLoginTestApp`을 만들고, 스킴은 그 이름을 build 대상으로 다시 참조한다.
| 프로젝트에 정의된 helper | 실제 target 이름 | 대응 스킴 buildAction |
|---|---|---|
| `qrLoginTestApp` | `QRLoginTestApp` | `QRLoginTestApp.xcscheme`에서 확인됨 |
| `secondOTPTestApp` | `SecondOTPTestQAApp` | `SecondOTPTestQAApp.xcscheme`에서 확인됨 |
| `streamingPlayTestApp` | `StreamingPlayQAApp` | `sample app scheme file`에서 확인됨 |
| `v3LoginApp` | `V3LoginApp` | `V3LoginApp.xcscheme`에서 확인됨 |
| `noticeTestApp` | `NoticeTestQAApp` | `NoticeTestQAApp.xcscheme`에서 확인됨 |
| `localNotificationTestApp` | `LocalNotificationTestApp` | `LocalNotificationTestApp.xcscheme`에서 확인됨 |
| `abcTest` | `ABCTests` | `ABCTests.xcscheme`에서 확인됨 |
확인 결과, 대부분은 포함된다. 실제로 아래 buildAction 타깃은 프로젝트 target과 대응하고 생성된 `.xcscheme` 파일도 존재한다.
하지만 `HomeTestQAApp`은 예외 가능성이 있다. 현재 확인한 범위에서는:
결론부터 말하면 앱 타깃 수준에서 아는 것과 로컬 Swift Package 내부 코드가 직접 import 가능한 것은 다르다.
| 어디에 선언했는가 | 무엇이 가능해지나 |
|---|---|
| `Project(... packages: ...)` | 그 Xcode 프로젝트와 앱 target이 그 패키지를 dependency로 붙여 링크할 수 있다. |
| 각 로컬 패키지의 package manifest | 그 패키지 내부 소스가 직접 `import` 해서 사용할 수 있다. |
// Project.swift
let project = Project(
name: "ABC",
packages: abcAppPackages
)
// dependency definition
.external(name: "GoogleSignIn")이런 경우 앱 target은 `GoogleSignIn`을 dependency로 붙여 사용할 수 있다. 즉 프로젝트/앱 레벨에서는 외부 패키지를 알고 있다.
// local package manifest
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.7.1"))`Feature/ABCV3Home` 소스 안에서 `import Alamofire`를 하려면 이 패키지 자신의 package manifest에 `Alamofire`가 있어야 한다. `Project.swift` 선언만으로는 부족하다.
`dependency definition`에서
`.external(name: "GoogleSignIn")`, `.external(name: "LineSDK")`, `.external(name: "AmplitudeSwift")`
같은 선언을 할 수 있는 이유는,
central package registry file 에서 외부 package를 먼저 등록해 두었기 때문이다.
let package = Package(
name: "PackageName",
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire", from: "5.0.0"),
.package(url: "https://example.com/company-sdk", exact: "5.14.0"),
.package(url: "https://github.com/amplitude/Amplitude-Swift", from: "1.17.0")
]
)여기서 중요한 건 `.external(name: ...)`는 URL이 아니라 그 package가 노출하는 product 이름을 참조한다는 점이다. 그래서 Facebook SDK처럼 package 하나에서 `FacebookCore`, `FacebookLogin`, `FacebookBasics` 여러 product가 나올 수도 있다.
질문의 핵심은 이것이다. `central package registry file`에서 `Alamofire`를 `.staticFramework`로 강제했으면, 로컬 패키지에서 `Alamofire`를 쓸 때도 static으로 들어오나?
이 프로젝트 기준으로는 그렇게 보는 게 맞다. 즉 Tuist가 생성하는 프로젝트 안에서는 `Alamofire` product를 어떤 타깃이 참조하든, 그 product type 정책은 중앙에서 통일된다고 보는 편이 정확하다.
let packageSettings = PackageSettings(
productTypes: [
"Alamofire": .staticFramework,
"PanModal": .staticFramework,
"RxSwift": .staticFramework,
"LineSDK": .framework
]
)| 경우 | 해석 |
|---|---|
| 앱 target이 `Alamofire`를 직접 dependency로 붙일 때 | `Alamofire`는 static framework 정책으로 들어온다. |
| 로컬 패키지(`Feature/ABCV3Home` 등)가 `Alamofire`를 dependency로 쓸 때 | 그 로컬 패키지 안에서 참조하는 `Alamofire` product도 같은 static 정책으로 해석된다. |
다만 로컬 패키지 소스가 `Alamofire`를 직접 쓰려면, 여전히 그 패키지 자신의 package manifest에 `Alamofire` dependency 선언은 있어야 한다. 즉 “쓸 수 있느냐”와 “어떤 타입으로 링크되느냐”는 다른 문제다.
앞으로 `App`, `Core`, `Feature` 아래 로컬 패키지 논의는 이 문서에 계속 이어 붙인다. 이유는 `Package`, `Dependency`, `Target`, `Scheme`, `Configuration`, 로컬 패키지 구조가 모두 서로 연결돼 있기 때문이다.
| 그룹 | 개수 | 의미 |
|---|---|---|
| `App` | 25 | 앱 공통 서비스, 리소스, 인터페이스 |
| `Core` | 8 | 기반 유틸, 네트워크, 정책, SDK 래퍼 |
| `Feature` | 18 | 실제 화면과 기능 조합 |
패키지 파일들을 실제로 대조해 보면, 많은 로컬 패키지는 product 이름과 target 이름을 같게 만들어 둔다. 그래서 `"ABCV3LoginETCUseCases"` 같은 문자열을 보면 target 같아 보이지만, 실제로는 같은 이름의 product와 target이 동시에 존재하는 경우가 많다.
| 이름 | 실제 상태 |
|---|---|
| `ABCV3LoginETCUseCases` | local package manifest 기준으로 product도 있고 target도 있다. |
| `ABCV3LoginCommon` | product도 있고 target도 있다. |
| `ABCNetwork` | local package manifest 기준으로 product도 있고 target도 있다. |
| `STDS` | local package manifest 기준으로 product도 있고 target도 있다. |
반면 `.product(name: "RxCocoa", package: "RxSwift")` 같은 표기는 외부 package의 특정 product를 정확히 집어서 쓰겠다는 뜻이라 더 명시적이다.
실제 저장소에서 package manifest를 전부 확인해 보면
샘플용 Sample/SwiftConcurrencyPlayground와 Tuist 설정용 central package registry file를 제외한
로컬 Swift Package는 총 53개다.
| 구분 | 개수 | 설명 |
|---|---|---|
| 레포에 존재하는 로컬 패키지 전체 | 53 | `App`, `Core`, `Feature`, `stds-ios`, `abcfoundation-ios` 기준 전체 |
| `abcAppPackages`에 직접 등록된 패키지 | 48 | 루트 프로젝트가 알고 있는 로컬 package 목록 |
| 레포에는 있지만 루트 등록 목록에는 없는 패키지 | 5 | 다른 로컬 패키지가 자기 package manifest에서 직접 참조하는 성격이 강하다. |
| 영역 | 패키지 |
|---|---|
| `App` 25개 | `ABTest`, `Biometric`, `BlockUserInterface`, `CookieManager`, `Environment`, `GOTPLib`, `HomeTemplate`, `LocalNotificationManager`, `LoginState`, `ABCBaseUI`, `ABCChatManager`, `OTPServiceInterface`, `PerformanceTraceInterface`, `ProfileManager`, `RealtimeEventManagerInterface`, `RealtimeEventStream`, `RemoteConfig`, `RemoteNotification`, `Resource`, `SchemeInterface`, `SocialSchemeInterface`, `TinodeEventManager`, `TinodeServiceExtension`, `TokenManager`, `TokenManagerInterface` |
| `Core` 8개 | `ABCBaseUtil`, `ABCNetwork`, `ABCNetworkInterface`, `ABCPolicy`, `ABCSDKWrapper`, `ABCSDKWrapperInterface`, `NotificationUtils`, `ABCSocialData` |
| `Feature` 18개 | `CharacterShop`, `Gamification`, `GamificationList`, `LanguageSetup`, `LoginHistory`, `ABCV3EditProfile`, `ABCV3Home`, `ABCV3Login`, `Notice`, `OneTimeLogin`, `PlatformPopup`, `QRLogin`, `RemoteLogout`, `ABCSocialUI`, `SecondAuthenticationOTP`, `SocialScene`, `ABCGameString`, `StreamingPlay` |
| `Root` 2개 | `stds-ios`, `abcfoundation-ios` |
| 패키지 | 왜 중요한가 |
|---|---|
| `App/SocialSchemeInterface` | `Feature/SocialScene` 등이 자기 package manifest에서 직접 가져다 쓴다. product 이름은 `ABCSocialSchemeInterface`다. |
| `Core/ABCBaseUtil` | 여러 Feature/App 패키지가 직접 의존한다. 앱 타깃 dependency에도 `.Core.ABCBaseUtil`로 들어간다. |
| `Core/ABCNetworkInterface` | `ABCV3Home`, `ABCSDKWrapper` 등에서 인터페이스 계층으로 사용된다. |
| `Core/ABCPolicy` | `ABCSocialData`, `ABCV3Login` 등에서 직접 참조한다. |
| `Core/ABCSDKWrapperInterface` | `ABCV3Home`, `ABCV3Login`, `Gamification` 등에서 SDK 추상화 인터페이스로 쓴다. |
flowchart TD
A["레포에 존재하는 로컬 패키지 53개"]
B["abcAppPackages에 직접 등록 48개"]
C["루트 등록 없음 5개"]
D["앱 타깃에서 직접 연결"]
E["다른 로컬 패키지 내부에서 재사용"]
A --> B
A --> C
B --> D
B --> E
C --> E
`ABCV3Home`는 이름만 보면 하나의 패키지처럼 보이지만, 실제 앱 연결에서는 하나의 폴더 안에 있는 여러 product 중 두 개가 앱 타깃에 직접 연결된다.
| 단계 | 실제 내용 |
|---|---|
| 로컬 패키지 등록 | package list definition 에 `Feature/ABCV3Home`가 들어간다. |
| Tuist dependency 별칭 | dependency alias file 에서 `.Feature.ABCV3Home = package(product: "ABCV3HomeFeature")`, `.Feature.ABCV3Data = package(product: "ABCV3HomeData")`로 매핑된다. |
| 앱 타깃 dependency | dependency definition 에 `.Feature.ABCV3Home`, `.Feature.ABCV3Data` 둘 다 들어간다. |
| 실제 product 정의 | local package manifest 에 `ABCV3HomeFeature`, `ABCV3HomeDataInterface`, `ABCV3HomeData` product가 정의되어 있다. |
| 패키지 내부 연결 | `ABCV3HomeFeature`는 `ABCV3HomeDataInterface`에 의존하고, `ABCV3HomeData`는 `ABCV3HomeDataInterface`를 구현하면서 `ABCNetworkInterface`, `ABCNetwork`, `ABTest`, `ABCSocialData` 등에 의존한다. |
flowchart LR
A["Feature/ABCV3Home package folder"]
B["product: ABCV3HomeFeature"]
C["product: ABCV3HomeData"]
D["product: ABCV3HomeDataInterface"]
E[".Feature.ABCV3Home"]
F[".Feature.ABCV3Data"]
G["abcAppDependencies"]
H["ABC / QA_ABC / DEV_ABC app target"]
A --> B
A --> C
A --> D
B --> E
C --> F
E --> G
F --> G
G --> H
B --> D
C --> D
이 다이어그램은 프로젝트 조립 관점의 핵심 의존성을 한 번에 보도록 만든 지도다. 기본 상태에서는 선을 거의 숨겨서 복잡해 보이지 않게 했고, 특정 타깃이나 패키지에 마우스를 올리면 관련된 패키지와 선만 강조되도록 만들었다.
이 대상이 의존하는 것
이 대상을 사용하는 것
flowchart LR
P1["로컬 패키지를 프로젝트에 등록하는 목록"]
P2["외부 SPM을 중앙에서 등록하고 타입 정책을 주는 목록"]
D1["앱 타깃이 실제로 붙이는 의존성 목록"]
T1["앱, 테스트, 확장 기능 등 실제 빌드되는 타깃 목록"]
S1["어떤 타깃을 어떤 설정으로 실행할지 정하는 스킴 목록"]
C1["Debug_Qa / Release_Qa2 같은 실행 환경 설정"]
X1["Tuist가 생성한 xcscheme 파일들"]
A1["Xcode 상단에서 고르는 스킴 선택 UI"]
P1 --> D1
P2 --> D1
D1 --> T1
T1 --> S1
C1 --> S1
S1 --> X1
X1 --> A1
flowchart LR
A["sample app helper가 테스트용 앱 타깃 정의를 만든다"]
B["실제로 생성되는 타깃 이름은 StreamingPlayQAApp 이다"]
C["이 타깃을 실행하기 위한 스킴 파일이 함께 생성된다"]
D["그 스킴의 buildAction은 StreamingPlayQAApp 을 빌드 대상으로 잡는다"]
E["그 스킴의 runAction은 Debug_Qa 설정으로 실행한다"]
F["그 스킴의 archiveAction은 Release_QA 설정으로 아카이브한다"]
A --> B
B --> C
C --> D
C --> E
C --> F
이 예시는 테스트용 샘플 앱 helper 하나가 먼저 `StreamingPlayQAApp`이라는 실제 타깃을 만들고, 그 다음 스킴이 그 타깃을 빌드 대상으로 선택한 뒤, 실행할 때는 `Debug_Qa`, 아카이브할 때는 `Release_QA`를 사용하도록 묶는 흐름이다.