Tuist설치 및 프로젝트 적용

Jayven·2024년 3월 14일
4

모듈화 With Tuist

목록 보기
2/4
post-thumbnail

안녕하세요 제이븐입니다 😁

이번에는 Tuist설치와 기본세팅에 대해서 알아보겠습니다!!

2024.03.09 기준입니다


Tuist 설치


여기서 잠깐 기존에 curl로 설치하는 방식이 Deprecated 되었습니다.

기존에 설치했다면 다음과 같은 경고창이 보입니다

기존 curl로 설치된 Tuist를 삭제하고 mise, Tuist를 재설치 해보도록 하겠습니다

👇👇👇



기존 curl로 설치된 Tuist 제거(optional)


만약 curl로 설치된 Tuist를 제거하시려면 터미널에서 아래 명령어를 입력하면 됩니다.

curl -Ls https://uninstall.tuist.io | bash



mise 설치


Tuist에서 mise로 설치하는 것을 권장하고 있습니다 homebrew와 마찬가지로 개발도구 설치를 도와주는 툴이라고 합니다.
그래서 어떻게 설치하느냐?? mise 설치 github 해당 깃허브에 설치 과정이 야무지게 나와있고 설치를 직접 해보도록 하겠습니다

1. mise 설치 및 버전확인

# mise 설치
curl https://mise.run | sh

# mise 버전확인
$ ~/.local/bin/mise --version

# 버전 확인 (2024.03.09 기준)
2024.3.1 macos-arm64 (0d95fde 2024-03-04)

2. Shell에 연결

자신의 쉘 환경에 맞게 아래 명령어로 연결하라고 나와있습니다

# note this assumes mise is located at ~/.local/bin/mise
# which is what https://mise.run does by default
echo 'eval "$(~/.local/bin/mise activate bash)"' >> ~/.bashrc
echo 'eval "$(~/.local/bin/mise activate zsh)"' >> ~/.zshrc
echo '~/.local/bin/mise activate fish | source' >> ~/.config/fish/config.fish

그래서 내 쉘 환경이 어떤지 어떻게 아냐???
echo $SHELL 을 입력해서 출력을 확인합니다. 이후 환경에 맞게 .bashrc, .zshrc, .fish 등으로 연결해주면 됩니다.

아마 대부분 .zshrc 환경일 것입니다.
+macOS Catalina(10.15) 이후부터 사용되는 기본 쉘


이제 해당 명령어로 연결시켜주겠습니다

eval "$(~/.local/bin/mise activate zsh)"

3. Node.js설치, Node.js 전역 설정

mise를 사용하여 전역 환경에 Node.js 20버전을 설치하고 설청해주겠습니다

# 설치 및 설정
mise use --global node@20

# 버전 확인
$ node -v

# 버전 확인 (2024.03.09 기준)
v20.11.1

4. mise로 Tuist 설치

mise install tuist            # 가장 최신 버전의 Tuist 설치
mise install tuist@x.y.z      # 특정 버전의 Tuist설치
mise install tuist@3          # 퍼지 버전 번호를 사용하여, 버전 3으로 시작하는 tuist의 최신 버전을 설치
mise use tuist@x.y.z          # 현재 프로젝트에서 tuist-x.y.z 버전을 사용하도록 설정
mise use -g tuist@x.y.z       # 버전을 전역 기본값으로 사용하도록 설정합니다. -g 옵션은 전역(global) 설정을 의미
mise use tuist@latest         # 현재 디렉토리에서 사용 가능한 tuist의 최신 버전을 사용하도록 설정합니다.
mise use -g tuist@system      # 시스템에 설치된 tuist를 전역 기본값으로 사용하도록 설정합니다. -g 옵션은 여기서도 전역 설정

위에 명령어로 상황에 따라서 설치 및 적용을 하시면 됩니다.


🥳 결과 🥳

mise plugin:tuist ✓ https://github.com/asdf-community/asdf-tuist.git#4f85be9
mise tuist@4.5.1 ✓ installed     

⛔️ 주의 ⛔️
만약 install로 Tuist를 설치하셨다면 프로젝트 폴더에서 Tuist 생성이 안될 수 있습니다
use -g 를 사용해서 전역설정을 해줘야됩니다

mise use -g tuist



Tuist 프로젝트 생성


자 이제 만들어 놓은 프로젝트에 Tuist를 생성해보겠습니다

Tuist 생성 명령어

tuist init --platform ios

바로 실패해 버리네요..☠️ 에러를 보시면 바로 아시다시피 폴더에 파일이 있어서 생성이 안되고 있습니다
✅ Tuist 생성은 빈 폴더에서만 가능합니다!!


다시 빈 폴더를 하나 만들어주고 다시 시도해봅시다

mkdir TuistStudy
cd TuistStudy
tuist init --platform ios

🎉성공🎉

Tuist 생성에 성공한 폴더를 보면 아래처럼 구성되어있습니다

이제 명령어를 사용해서 프로젝트의 구조를 변경하고 모듈을 생성하고 그래프를 찍어보도록 하겠습니다



프로젝트 관리, 수정

tuist generate

우선 프로젝트의 워크스페이스를 생성하고 실행하는 명령어 먼저 해봅시다

tuist generate


아까는 없었던 xcode프로젝트 파일과 workspace 파일이 생성되었습니다
tuist generate 는 위에 있는 Project.swift 를 기반으로 Xcode 프로젝트 및 워크스페이스 파일을 생성합니다.


tuist graph

모듈간의 의존성을 그래프로 표현한 이미지 파일을 제공하는 명령어입니다.

tuist graph


기본적으로 만든 앱에는 TestModule이 AppModule에 의존하는 그래프를 보여주네요

tuist edit

Project.swift 설정할때 사용하는 명령어 입니다.
그리고 모듈의 의존성 연결 및 설정등 generate하기 전에 프로젝트를 설정하기 위한 명령어입니다
이후 generate는 edit에서 수정된 파일에 따라서 프로젝트를 생성합니다.

tuist edit

Plugins

비대하지는 ProjectDescriptionHelper의 기능을 이전하여서 Plugin 모듈을 만들어서
Manifests에서 Plugin을 임포트해서 사용할 수 있습니다

프로젝트의 Root에서 Plugin 폴더를 생성합니다

mkdir Plugin

Plugin 생성

Plugin 폴더 안에 원하는 기능을 할 Plugin 폴더들을 만듭니다

Plugin 정의와 생성은 팀에 상황에 따라 논의 후 이름과 폴더를 나누면 됩니다 저는 기본적으로 3가지로 나눠보겠습니다

  • DependencyPlugin
    • 모듈의 의존성, Library의존성 등을 정의할 Plugin
  • ConfigPlugin
    • xcconfiguration을 정의할 Plugin
  • EnvPlugin
    • budleName, prefix, setting, infoplist등을 정의할 Plugin
  • UtilityPlugin
    • Util 관련 Plugin

Plugin 폴더안에 3가지 Plugin 폴더를 생성하겠습니다, 추가적으로 Plugin내에 ProjectDescriptionHelper 폴더를 동시에 만들겠습니다.
# 폴더 생성
mkdir -p Plugin/DependencyPlugin/ProjectDescriptionHelpers
mkdir -p Plugin/ConfigPlugin/ProjectDescriptionHelpers
mkdir -p Plugin/EnvPlugin/ProjectDescriptionHelpers
mkdir -p Plugin/UtilityPlugin/ProjectDescriptionHelpers

# tree 로 Plugin 구조 확인
tree Plugin

Plugin/
├── ConfigPlugin
│   ├── Plugin.swift
│   └── ProjectDescriptionHelpers
├── DependencyPlugin
│   ├── Plugin.swift
│   └── ProjectDescriptionHelpers
├── EnvPlugin
│   ├── Plugin.swift
│   └── ProjectDescriptionHelpers
└── UtilityPlugin
    ├── Plugin.swift
    └── ProjectDescriptionHelpers

🍯 tree 는 homebrew를 사용해서 설치해서 사용할 수 잇습니다 🍯


자 이제 각 플러그인에 Plugin.swift 파일을 만들고 이름을 지정해줘봅시다

# Plugin 파일 생성
touch Plugin/ConfigPlugin/Plugin.swift
touch Plugin/DependencyPlugin/Plugin.swift
touch Plugin/EnvPlugin/Plugin.swift
touch Plugin/UtilityPlugin/Plugin.swift

생성된 Plugin.swift 파일을 열어서 수정합니다

// ConfigPlugin
import ProjectDescription

let configPlugin = Plugin(name:"ConfigPlugin")

// DependencyPlugin
import ProjectDescription

let dependencyPlugin = Plugin(name:"DependencyPlugin")

// EnvPlugin
import ProjectDescription

let envPlugin = Plugin(name:"EnvPlugin")

// UtilityPlugin
import ProjectDescription

let utilityPlugin = Plugin(name:"UtilityPlugin")

그리고 Config.swift 파일을 만들어서 Local Plugin을 지정합니다


Tuist 폴더가 없다??! (Tuist 4.5.1)

원래 Tuist 생성시 Tuist 폴더가 자동으로 생성 되었던 것으로 기억하는데 이 폴더가 없습니다ㅜㅜ 버전이 올라가고 mise로 다시 만들면서 이전과 다른 점이 많은 것 같습니다
없다면 만들어주면 되죠 😢

mkdir Tuist
touch Tuist/Plugin.swift

Plugin.swift 파일에 각 Plugin의 경로를 설정해줍니다

import ProjectDescription

let config = Config(
    plugins: [
        .local(path: .relativeToRoot("Plugin/DependencyPlugin")),
        .local(path: .relativeToRoot("Plugin/ConfigPlugin")),
        .local(path: .relativeToRoot("Plugin/EnvPlugin")),
        .local(path: .relativeToRoot("Plugin/UtilityPlugin"))
    ]
)

이제 기본적인 설정은 끝났고 프로젝트 구조를 본격적으로 만들기 시작하위해 기존에 있는 Project.swift 파일과 TuistStudy 폴더를 삭제하고 처음부터 만들면서 모듈을 추가하고 의존성을 연결해보도록 하겠습니다


삭제 및 생성

자 이제 프로젝트 폴더에 필요없는 것들을 다 삭제해주면 Plugin, Tuist 두가지 폴더만 남습니다
이제 Projects폴더와 Workspace.swift를 생성해봅시다

  • Projects 폴더
    • 모듈들(App, Feature, Domain, Data)등이 들어가있는 폴더
  • workspace.swift
    • 워크스페이스에 포함시킬 프로젝트들을 지정하고 생성합니다

Projects 폴더, workspace파일 생성

프로젝트에 Root 경로에 해당 폴더와 파일 생성
기본으로 생성된 Project.swift, Sources, Resources 파일을 Projects 폴더로 임시로 옮겨 둡니다

# Project 모듈이 들어갈 폴더
mkdir Projects

# Projects 폴더안에 있는 모듈들로 workspace를 생성하기 위한 파일 
touch Workspace.swift

Workspace.swift 파일

import ProjectDescription

let workspace = Workspace(
    name: "TuistTest",
    projects: [
    	// Projects 폴더 내부의 모듈 전체
        "Projects/**"
    ]
)

이제 tuist edit 통해 프로젝트를 편집해봅시다!


FeatureTarget (모듈의 유형 정의)

Tuist폴더 안에 ProjectDescriptionHelpers을 생성해주고 FeatureTarget이라는 파일을 생성해줍니다.

앞으로 만들 모듈 (App, Interface, Demo, Testing, Tests) 등의 모듈을 구분하여 정의하기 위해서 enum으로 정의해줍니다

import ProjectDescription

public enum FeatureTarget: CaseIterable {
    case app                // App
    case interface          // Feature Interface
    case demo               // Demo App
    case testing            // Testing 모듈
    case tests              // Test 모듈
    case dynamicFramework   // DynamicFramework
    case staticFramework    // StaticFramework
    
    // 프레임워크인지 여부를 확인하기 위한 계산속성
    public var isFramework: Bool {
        switch self {
        case .dynamicFramework, .staticFramework: return true
        default: return false
        }
    }
}

Project+Templates

이 파일의 목적은 Project.swift에서 모듈을 생성하는 과정을 Templates로 넘겨서
간편하게 Project를 생성하고 Templates에서 Project 생성 관련된 코드를 관리하고 재사용할 수 있는 장점이 있습니다.
Tuist 폴더에 ProjectDescriptionHelpers 폴더를 만들고 Project+Templates를 만들어 줍니다

mkdir Tuist/ProjectDescriptionHelpers
touch Project+Templates.swift

Project를 확장해서 그 안에 Project를 리턴해주는 makeModule이라는 정적 함수를 만들어서 Project.swift 파일에서 함수를 사용하여 모듈을 쉽게 생성하고 모듈간의 의존성을 설정할 수 있습니다.

public extension Project {
    
    // Project에서 모듈을 생성하는 static 함수
    static func makeModule(
        name: String,
        target: Set<FeatureTarget>,
        packages: [Package] = [],
        internalDependencies: [TargetDependency] = [],  // 모듈간 의존성
        externalDependencies: [TargetDependency] = [],  // 외부 라이브러리 의존성
        interfaceDependencies: [TargetDependency] = [], // Feature Interface 의존성
        dependencies: [TargetDependency] = [],
        hasResourse: Bool = false
    ) -> Project {
        
        var projectTargets: [Target] = []
        var configurationName: ConfigurationName = "DEV"
        var configuration = XCConfig.project
        let baseSettings: SettingsDictionary = .baseSettings
        
        // MARK: - AppModule
        if target.contains(.app) {
            let setting = baseSettings.setAutomaticProvisioning()
            
            if name.contains("Prod") {
                configurationName = "PROD"
            } else if name.contains("Test") {
                configurationName = "TEST"
            }
            
            projectTargets.append(.makeTarget(name: name,
                                              product: .app,
                                              hasResource: hasResourse,
                                              infoPlist: .extendingDefault(with: Project.infoPlist),
                                              script: [],
                                              dependencies: [
                                                internalDependencies,
                                                externalDependencies
                                              ].flatMap { $0 },
                                              settings: .settings(base: setting,
                                                                  configurations: configuration)
                                             )
            )
            
            return Project(
            name: name,
            organizationName: AppEnvironment.workspaceName,
            packages: packages,
            settings: nil,
            targets: projectTargets,
            schemes: [Scheme.makeScheme(configs: configurationName, name: name)]
        )
        }

Project.swift

자 이제 모듈을 생성해봅시다

  1. 앱 모듈, Feature 모듈 하나를 생성한다
  2. 앱 모듈은 Feature 모듈을 의존한다
  3. tuist grapth 를 찍어서 모듈과 모듈간의 의존성을 확인한다

이렇게 순서대로 진행해보겠습니다

앱 모듈을 생성해줍니다 동시에 Feature에 RootFeature 모듈을 의존하게 해줍니다

let project = Project.makeModule(name: "TuistTemplate", // 모듈의 이름
                                 target: [.app], // 타겟의 타입 (.app, .staticFramework, .demo, interface ... )
                                 internalDependencies: [
                                    .Features.rootFeature // 생성하는 모듈이 의존할 모듈
                                 ], externalDependencies: [
                                    .SPM.Alamofire // 라이브러리
                                 ]
)

Feature모듈을 생성해줍니다

let project = Project.makeModule(name: "RootFeature",
                                 target: [.dynamicFramework]
)

tuist graph 로 그래프를 찍어 봅니다

짜잔 🎊

AppModule과 Feature 모듈이 만들어졌습니다
Tuist는 초기 설정이 복잡하고 시간이 많이 들어가지만 세팅 이후에는 모듈 생성과 모듈간의 의존성 정의가 매우 간편해집니다


Plugin

플러그인은 Tuist의 모듈 생성, 환경 변수, XCConfig, InfoPlist등의 파일을 생성을 돕기 위해서 만들어서 사용합니다
폴더의 구성은 UtilsPlugin 하나의 폴더에 할 수도 있고 Plugin들을 나눌 수도 있습니다 하지만 Plugin끼리의 참조는 불가능하니 주의해서 설계하는게 좋습니다.
그리고 Menifests에서는 모든 플러그인은 import해서 사용이 가능합니다.

그림으로 표현하면 이렇게 생겼다고 할 수 있을까요???

플러그인에 들어가야될 코드를 설명하고 싶지만 글이 너무 길어질 것 같아서 코드는 깃허브에 올리도록 하겠습니다!!


TuistTemplate 깃허브



마무리


오랜만에 Tuist를 처음부터 생성하고 만드는 과정을 진행해 봤습니다 Tuist 버전이 업데이트 되고나서 바뀐점이 있어서 찾아서 적용하느라 시간이 생각보다 많이 들어간 것 같습니다

Tuist가 초기 설정이 어렵고 러닝커브가 높아서 시간이 많이 들어가지만 그보다 이점이 더 많은 툴이라고 생각합니다

다음에는 CleanArchitecture를 기반으로한 모듈아키텍처 구성과 Scaffold를 사용한 모듈 생성을 다뤄보도록 하겠습니다

질문과 피드백은 언제나 감사합니다 🙇🙇🙇

profile
iOS 개발자

0개의 댓글