Study

[iOS-Swift] Tuist 시작하기 (ver. 4.25.0)

차코.. 2024. 9. 19. 14:52

Tuist란?

 

Tuist는 Xcode 프로젝트의 생성과 유지 관리를 보다 효율적으로 도와주는 CLI(커맨드 라인 인터페이스) 도구입니다.

 

Tuist를 사용하면 복잡한 프로젝트를 진행할 때 구조와 의존성을 좀 더 정확하게 관리할 수 있고,

Xcode에서 프로젝트 설정을 할 때 관련된 내용들을 자동화 할 수 있게 됩니다.

 

 

  • Git 충돌 방지
    • Xcode의 프로젝트 파일은 텍스트 형식이 아니라서 conflict가 자주 발생하는데,
      Tuist를 이용하면 이러한 문제를 줄일 수 있습니다.
  • 모듈 간의 의존성 관리 용이
    • 각 모듈 간의 의존성을 쉽게 파악할 수 있습니다.
  • 빌드 속도 단축
    • 모노리틱 앱 구조라면 코드 한 줄을 변경해도 전체 소스 코드를 다시 컴파일해야 하지만,
      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 초기 상태

 

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")
    • 그 외의 deprecated API 확인은 https://github.com/tuist/tuist/pull/5560
  • 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에 의존성을 추가하는 방식으로 변경되었습니다.

 

 

ThirdPartyLibs의 Project 파일

 

 

이후 tuist install 명령어를 사용해, 패키지를 설치하고 프로젝트에서 사용할 수 있습니다.

 

 


참고 자료