← 홈으로 돌아가기
Tuist · SPM · Build System

Tuist 의존성 관리 · Static/Dynamic 링킹 완벽 가이드

Tuist가 외부 패키지를 어떻게 관리하는지, Static/Dynamic Framework의 링크/임베드 차이, Xcode GUI에서의 의존성 추가 위치, pbxproj 충돌 문제까지 실무 중심 정리.

학습 날짜

기록 없음

1. Tuist 프로젝트 네비게이터 구조

Xcode 네비게이터에서 두 가지 다른 의존성 섹션이 보인다:

섹션 아이콘 관리 주체 내용
Dependencies 폴더 📊 xcodeproj Tuist xcframework 포함 패키지 (Firebase, Amplitude, Google 등)
Package Dependencies 📦 package Xcode SPM 소스 기반 SPM (Alamofire, RxSwift, 로컬 패키지 등)

왜 나뉘어 있나?

핵심: 패키지 배포 방식의 차이
패키지 배포 형태 Tuist 처리
Firebase, Google, Facebook, Amplitude 바이너리 (xcframework) xcodeproj로 변환 → Dependencies 폴더
Alamofire, RxSwift, Kingfisher 소스 코드 네이티브 SPM 유지 → Package Dependencies

productTypes 설정(staticFramework/framework)은 빌드 산출물 타입만 지정하는 것이고, xcodeproj 변환 여부와는 관계없다.

2. Tuist의 전체 역할

xcframework → xcodeproj 변환은 Tuist가 하는 여러 일 중 하나일 뿐. 프로젝트 생성 자동화가 본질이다.

역할 설명
프로젝트 생성 (핵심) xcodeproj/xcworkspace를 코드로 생성
빌드 설정 관리 20개+ Configuration을 코드로 선언 (Debug, Release, Qa, vn 등)
의존성 선언 통합 로컬 + 외부 패키지를 한 곳에서 관리
로컬 모듈 구조화 50개+ 로컬 패키지 관계를 manifest로 관리
xcframework 처리 바이너리 패키지를 xcodeproj로 변환

3. Static vs Dynamic Framework

productTypes 설정의 의미

// Tuist/Package.swift
let packageSettings = PackageSettings(
    productTypes: [
        "Alamofire": .staticFramework,   // Static으로 빌드
        "LineSDK": .framework,           // Dynamic으로 빌드
    ]
)
Static Framework Dynamic Framework
링크 타임 코드가 앱 바이너리에 복사 참조만 기록 (코드 복사 X)
런타임 별도 로드 없음 (이미 앱 안에 있음) dyld가 별도 로드
앱 번들 위치 실행파일 안에 포함 Frameworks/ 폴더에 별도 존재
앱 바이너리 크기 커짐 (코드 포함) 작음
앱 시작 속도 빠름 약간 느림 (로드 필요)

"링커가 참조를 기록"의 의미

Static (코드 복사)

링크 타임:
Alamofire ──복사──→ MyApp 실행파일
                   (코드가 실행파일 안에)

런타임:
MyApp 실행파일 하나만 있으면 됨

Dynamic (참조 기록)

링크 타임:
LineSDK ──참조──→ MyApp 실행파일 헤더:
                 "LineSDK.framework 필요"

런타임:
dyld가 Frameworks/LineSDK.framework 로드

실행파일 헤더에 기록된 정보 확인:

$ otool -L MyApp

MyApp:
    @rpath/LineSDK.framework/LineSDK    ← "이 프레임워크 필요해"
    /usr/lib/libSystem.B.dylib          ← "시스템 라이브러리 필요해"

4. Xcode GUI에서 의존성 추가 위치

General 탭

Frameworks, Libraries, and Embedded Content
├── + 버튼으로 추가
└── Embed 옵션 선택
    - Do Not Embed
    - Embed & Sign
    - Embed Without Signing

Build Phases 탭

Link Binary With Libraries    ← 링크 (컴파일 타임)
Embed Frameworks             ← 복사 (Dynamic만 해당)

관계 정리

위치 역할 언제
Frameworks, Libraries... 통합 UI (추가하면 아래 두 곳에 자동 반영) -
Link Binary With Libraries 링커에게 "이 라이브러리 링크해" 빌드 타임
Embed Frameworks 앱 번들에 .framework 복사 빌드 후
개발자는 General 탭에서만 작업하면 됨
Embed 옵션 Link Binary With Libraries Embed Frameworks
Do Not Embed ✅ 자동 추가
Embed & Sign ✅ 자동 추가 ✅ 자동 추가
Embed Without Signing ✅ 자동 추가 ✅ 자동 추가

Link Binary With Libraries에 뭐가 들어가나?

Static과 Dynamic 둘 다 들어간다.

Link Binary With Libraries
├── Alamofire.framework      ← Static
├── RxSwift.framework        ← Static
├── LineSDK.framework        ← Dynamic
└── libz.tbd                 ← System library
Link Binary With Libraries Embed Frameworks
Static Framework
Dynamic Framework

Link = "이 라이브러리 사용한다" 선언 (둘 다 필요)
Embed = 앱 번들에 파일 복사 (Dynamic만 필요)

5. pbxproj 충돌 문제

.xcodeproj 구조

MStove.xcodeproj/
├── project.pbxproj       ← 이 파일이 문제
├── xcshareddata/
└── xcuserdata/

pbxproj 내용 (일부)

/* Begin PBXFrameworksBuildPhase section */
    3E9F8A2B1C8B4D5E00123456 /* Frameworks */ = {
        isa = PBXFrameworksBuildPhase;
        files = (
            3E9F8A2C1C8B4D5E00789ABC /* Alamofire.framework */,
            3E9F8A2D1C8B4D5E00DEF012 /* RxSwift.framework */,
        );
    };
/* End PBXFrameworksBuildPhase section */

충돌 시나리오

팀원 A: Kingfisher 추가 → pbxproj에 UUID 추가
팀원 B: Lottie 추가    → pbxproj에 다른 UUID 추가
        ↓
    동시에 PR
        ↓
    pbxproj CONFLICT 💥
왜 pbxproj 충돌이 어려운가?
  • UUID 기반: 3E9F8A2B1C8B4D5E00123456 이런 식
  • 순서 변경: Xcode가 임의로 순서 바꿈
  • 한 줄 변경 → 수십 줄 diff
  • 수동 머지 거의 불가능

Tuist의 해결책

팀원 A: MStoveAppDependencies.swift에 Kingfisher 추가
팀원 B: MStoveAppDependencies.swift에 Lottie 추가
        ↓
    일반 Swift 코드라 머지 쉬움
        ↓
    tuist generate → pbxproj 재생성

pbxproj를 커밋 안 하거나, 생성된 걸로 덮어씀 → 충돌 없음

6. 의존성 선언 통합의 가치

Tuist 없이 (순수 Xcode)

타겟이 10개라면:

MStove          → Xcode GUI에서 Alamofire 추가
MStoveVN        → Xcode GUI에서 Alamofire 추가
QA_MStove       → Xcode GUI에서 Alamofire 추가
QA_MStove_VN    → Xcode GUI에서 Alamofire 추가
DEV_MStove      → Xcode GUI에서 Alamofire 추가
... (10번 반복)
문제점:
  • 새 의존성 추가 시 10번 반복 작업
  • 하나 빠뜨리면 빌드 에러
  • 버전 불일치 위험
  • .xcodeproj 파일 conflict (팀 작업 시)

Tuist 사용

// 한 번만 선언
private let mstoveSharedAppDependencies: [TargetDependency] = [
    .external(name: "Alamofire"),
    .external(name: "RxSwift"),
    // ...
]

// 모든 타겟이 공유
public let mstoveAppDependencies = mstoveSharedAppDependencies + [...]
public let mstoveVNAppDependencies = mstoveSharedAppDependencies + [...]

tuist generate 하면 모든 타겟에 자동 적용

7. Configuration 중복 문제와 해결

현재 프로젝트에서 같은 Configuration 목록이 여러 파일에 중복되어 있다:

MStoveAppProject.swift

public let mstoveProjectSettings = Settings.settings(configurations: [
    .debug(name: "Debug"),
    .debug(name: "Debug_Dev"),
    .debug(name: "Debug_Qa"),
    // ... 20개
])

Package.swift

baseSettings: Settings.settings(configurations: [
    .debug(name: "Debug"),
    .debug(name: "Debug_Dev"),
    .debug(name: "Debug_Qa"),
    // ... 동일한 20개
])
문제점: 새 Configuration 추가 시 여러 파일 동시 수정 필요

개선 가능한 구조

// Tuist/ProjectDescriptionHelpers/Configurations.swift
public let allConfigurations: [Configuration] = [
    .debug(name: "Debug"),
    .debug(name: "Debug_Dev"),
    // ... 한 곳에서 정의
]

// Package.swift (#if TUIST 블록 안에서)
import ProjectDescriptionHelpers
let packageSettings = PackageSettings(
    baseSettings: Settings.settings(configurations: allConfigurations)
)

// MStoveAppProject.swift
public let mstoveProjectSettings = Settings.settings(
    configurations: allConfigurations
)
#if TUIST 블록 안에서는 ProjectDescriptionHelpers import 가능하므로, 한 곳 정의 → 모든 곳 참조 가능하다.

Q&A

Q: Tuist는 xcframework로 배포된 외부 패키지만 xcodeproj로 변환해?

아니다. xcframework → xcodeproj 변환은 Tuist가 하는 여러 일 중 하나일 뿐.

Tuist의 핵심 역할은 프로젝트 생성 자동화:

  • xcodeproj/xcworkspace 코드로 생성
  • 빌드 설정 (Configuration) 통합 관리
  • 의존성 선언 통합
  • 로컬 모듈 구조화
Q: productTypes에 staticFramework라고 되어 있으면 xcodeproj로 변환되나?

아니다. productTypes빌드 산출물 타입만 지정하는 것.

xcodeproj 변환 여부는 패키지 배포 방식에 따라 결정:

  • 바이너리(xcframework) 포함 패키지 → xcodeproj로 변환
  • 소스 코드만 있는 패키지 → 네이티브 SPM 유지
Q: Package Dependencies에 있는 것들은 Xcode SPM이 관리해?

맞다.

  • Dependencies 폴더 (xcodeproj 아이콘) → Tuist가 SPM을 xcodeproj로 변환
  • Package Dependencies (package 아이콘) → Xcode 네이티브 SPM 영역

Tuist는 Package Dependencies의 패키지들을 선언만 하고, 실제 fetch/resolve/build는 Xcode SPM이 담당.

Q: 개발자가 Frameworks, Libraries...에만 추가하면 Link/Embed는 자동?

맞다. General 탭의 "Frameworks, Libraries, and Embedded Content"에서 추가하면:

  • Link Binary With Libraries → 자동 추가
  • Embed Frameworks → Embed 옵션 선택에 따라 자동 추가

Build Phases는 직접 건드릴 필요 없다.

Q: Link Binary With Libraries에 Static만 들어가?

아니다. Static과 Dynamic 둘 다 들어간다.

Link는 "이 라이브러리 사용한다" 선언 — 둘 다 필요.

  • Static: 링커가 코드를 실행파일에 복사
  • Dynamic: 링커가 참조를 기록 (런타임에 로드할 위치)

Embed Frameworks는 Dynamic만 필요 (앱 번들에 파일 복사).

관련 문서