[iOS-Swift] Tuist 시작하기 (ver. 4.25.0)
Tuist란?
Tuist는 Xcode 프로젝트의 생성과 유지 관리를 보다 효율적으로 도와주는 CLI(커맨드 라인 인터페이스) 도구입니다.
Tuist를 사용하면 복잡한 프로젝트를 진행할 때 구조와 의존성을 좀 더 정확하게 관리할 수 있고,
Xcode에서 프로젝트 설정을 할 때 관련된 내용들을 자동화 할 수 있게 됩니다.
- Git 충돌 방지
- Xcode의 프로젝트 파일은 텍스트 형식이 아니라서 conflict가 자주 발생하는데,
Tuist를 이용하면 이러한 문제를 줄일 수 있습니다.
- Xcode의 프로젝트 파일은 텍스트 형식이 아니라서 conflict가 자주 발생하는데,
- 모듈 간의 의존성 관리 용이
- 각 모듈 간의 의존성을 쉽게 파악할 수 있습니다.
- 빌드 속도 단축
- 모노리틱 앱 구조라면 코드 한 줄을 변경해도 전체 소스 코드를 다시 컴파일해야 하지만,
Tuist는 모듈별로 빌드가 가능합니다.
- 모노리틱 앱 구조라면 코드 한 줄을 변경해도 전체 소스 코드를 다시 컴파일해야 하지만,
모듈이란?
그렇다면 모듈은 무엇일까요?
모듈은 프로그램을 기능 단위로 나누어 독립적으로 관리할 수 있는 단위이다.
iOS 개발에서는 프레임워크, 라이브러리, 애플리케이션 등이 모듈에 해당됩니다.
import를 통해 외부 모듈을 사용할 수 있는데,
예를 들어, import Foundation과 같은 방식으로 외부의 유틸리티를 사용하는 방식입니다.
Tuist 시작하기 (ver 4.25.0 기준)
Tuist 설치
ver 4로 넘어오면서 상당 부분이 변경된 듯 합니다.
우선, 기존에는 curl을 통해 설치되었는데 ver 4부터는 런타임 관리 툴로 mise를 권장하고 있습니다.
(따라서, curl로 설치된 tuist는 삭제해주는 작업이 필요합니다.)
mise install tuist // 최신 버전의 Tuist 설치
mise use -g tuist // 전역으로 설정
프로젝트 생성
Tuist는 빈 폴더에서만 프로젝트 생성이 가능합니다.
mkdir TuistDemo
cd TuistDemo
tuist init --platform ios
명령어를 실행하면, 위처럼 iOS 플랫폼을 타겟으로 한 기본 프로젝트가 생성됩니다.
그 중 프로젝트 설정 파일 (Project.swift) 내부를 폴더링, 세부 사항 등을 수정하면, 이를 통해 모듈화 된 프로젝트가 만들어집니다.
Tuist 프로젝트 설정
앞서 설명한대로, Project.swift 파일을 수정하여 프로젝트의 구조를 정의하고, Tuist가 이를 기반으로 프로젝트를 생성합니다.
프로젝트 파일 편집
다음 명령어를 입력하면, Project 파일을 수정할 수 있는 프로젝트가 자동으로 열립니다.
tuist edit
Manifests 폴더 안의 파일들을 수정하는 것인데, 초기에 생성된 모습은 위와 같은 구조를 가지고 있습니다.
그 중에서 프로젝트 전역에 쓰이는 설정(Swift 버전, Xcode 버전)을 관장하는 Config.swift 파일입니다.
ver 4 이전에는 debug, release별로 분리해 각각 캐시를 적용하는 cache profile이 제공되었지만,
혼란을 야기한다는 문제점에 의해 deprecated 되었습니다.
폴더링하기
ver 3에서는 초기 생성 시 ProjectDescriptionHelpers가 기본으로 제공되었지만,
ver 4부터는 필요 시 직접 만들어서 사용해야 합니다.
그 외에도 다음과 같은 부분이 수정되었습니다 😄
- deploymentTarget deprecated
- destination, deploymentTargets로 분리
- destination: Destinations = [.iPhone, .iPad],
deploymentTargets: DeploymentTargets? = .iOS("15.0")
- destination: Destinations = [.iPhone, .iPad],
- 그 외의 deprecated API 확인은 https://github.com/tuist/tuist/pull/5560
- destination, deploymentTargets로 분리
- ProjectDescription 모델들의 init 생성자 deperecated
- e.g., Target(…) → Target.target(…)
import ProjectDescription
public extension Project {
static func makeModule(
name: String,
product: Product,
organizationName: String = "jaehyun",
packages: [Package] = [],
destinations: Destinations = [.iPhone, .iPad], // deploymentTarget deprecated
deploymentTargets: DeploymentTargets? = .iOS("15.0"), // deploymentTarget deprecated
dependencies: [TargetDependency] = [],
sources: SourceFilesList = ["Sources/**"],
resources: ResourceFileElements? = nil,
infoPlist: InfoPlist = .default
) -> Project {
let appTarget = Target.target(
name: name,
destinations: destinations,
product: product,
bundleId: "\(organizationName).\(name)",
deploymentTargets: deploymentTargets,
infoPlist: infoPlist,
sources: sources,
resources: resources,
dependencies: dependencies
)
let schemes: [Scheme] = [.makeScheme(target: .debug, name: name)]
let targets: [Target] = [appTarget]
return Project(
name: name,
organizationName: organizationName,
packages: packages,
targets: targets
)
}
}
extension Scheme {
static func makeScheme(target: ConfigurationName, name: String) -> Scheme {
return Scheme.scheme(
name: name,
shared: true,
buildAction: .buildAction(targets: ["\(name)"]),
testAction: .targets(
["\(name)Tests"],
configuration: target,
options: .options(coverage: true, codeCoverageTargets: ["\(name)"])
),
runAction: .runAction(configuration: target),
archiveAction: .archiveAction(configuration: target),
profileAction: .profileAction(configuration: target),
analyzeAction: .analyzeAction(configuration: target)
)
}
}
다음으로는, 각 모듈의 Project.swift 파일을 다음과 같이 생성합니다.
import ProjectDescription
import ProjectDescriptionHelpers
let project = Project.makeModule(
name: "Sample",
product: .app,
dependencies: [
.project(target: "Feature", path: .relativeToRoot("Projects/Feature"))
],
resources: ["Resources/**"],
infoPlist: .file(path: "Support/Info.plist")
)
전체적인 구조의 모습입니다.
외부 의존성 추가하기
Tuist 4에서는 외부 라이브러리 의존성 관리 방식이 이전 버전과 약간 다른데요,
예전에는 각 프로젝트에서 패키지를 직접 추가했지만, Tuist 4부터는 Package.swift 파일을 통해 의존성을 관리해야 합니다.
(진행 방법은 공식 문서를 참고했습니다!!)
Package.swift 파일에서 SnapKit을 예로 들어보겠습니다.
import ProjectDescription
import ProjectDescriptionHelpers
let project = Project.makeModule(
name: "ThirdPartyLibs",
product: .framework,
packages: [
.remote(url: "https://github.com/SnapKit/SnapKit.git", requirement: .upToNextMinor(from: "5.0.0"))
],
dependencies: [
.package(product: "SnapKit")
]
)
위는 프로젝트 내의 Packages에 직접 의존성을 추가하는 Ver 3. 이전 방식입니다.
// swift-tools-version: 5.9
import PackageDescription
#if TUIST
import ProjectDescription
let packageSettings = PackageSettings(
productTypes: [
"SnapKit": .framework
], baseSettings: .settings(
base: [:],
configurations: [
.debug(name: .debug),
.release(name: .release)
]
)
)
#endif
let package = Package(
name: "TuistDemo",
dependencies: [
.package(url: "https://github.com/SnapKit/SnapKit", .upToNextMajor(from: "5.0.0"))
]
)
Ver 4부터는 위처럼, Package.swift에 의존성을 추가하는 방식으로 변경되었습니다.
이후 tuist install 명령어를 사용해, 패키지를 설치하고 프로젝트에서 사용할 수 있습니다.