버그 잡이

기존 레거시 프로젝트에 Tuist 적용해보기 본문

Swift

기존 레거시 프로젝트에 Tuist 적용해보기

버그잡이 2023. 6. 5. 21:24

이번에 현업 레거시 프로젝트에 Tuist를 적용해보았는데요. 

그 삽질기를 남겨보겠습니다.

(아직 Tuist에 대한 이해가 많이 부족하니 틀린 부분이 있다면 댓글로 남겨주시면 감사하겠습니다!)

 

Tuist 적용법

 

1. Cocoapod 제거

먼저 기존 프로젝트는 Cocoapod으로 라이브러리를 연동했었습니다.

Tuist를 쓰면서 SPM을 활용할 것이기 때문에 프로젝트 내에서 CocoaPod 제거하는 작업을 먼저 진행해줍니다.

pod deintegrate
pod cache clean --all

 + Podfile, .workspace 파일 지우기

 

2. Tuist 설치 및 Project.swift 파일 생성

아래 스크립트로 tuist를 설치해주고

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


프로젝트 루트 경로에서 Project.swift 파일을 만들어줍니다.

nano Project.swift

 

3. Project.swift 파일 내용 채우기

아래 코드가 제가 구성한 기본 Project.swift 코드 기본 구성입니다.

import ProjectDescription

// MARK: Constants
let projectName = "MyApp"
let organizationName = "ray"
let bundleID = "com.myApp.good"
let targetVersion = "13.0"

// MARK: Struct
let project = Project(
  name: projectName,
  organizationName: organizationName,
  settings: baseSettings(),
  targets: [
    Target(name: "MyApp-PROD",
           platform: .iOS,
           product: .app,
           bundleId: bundleID,
           deploymentTarget: .iOS(targetVersion: targetVersion, devices: [.iphone, .ipad]),
           infoPlist: "MyApp/PROD-info.plist",
           sources: ["\(projectName)/**"],
           resources: getResources(),
           entitlements: "MyApp-PROD.entitlements",
           scripts: [
            .FirebaseCrashlyticsString
           ],
           dependencies: defaultDependency(),
           settings: prodSettings()
          )
  ]
)

큰 구성은 아래와 같습니다.

1. Project의 이름, 셋팅, 번들아이디 등 기본 설정

2. infoPlist는 기존에 있던 plist 파일을 활용(경로 명시해주기)

3. sources는 소스코드의 위치인데요. 기존에 '프로젝트 루트 > MyApp > (소스코드)' 에 소스코드가 위치했기때문에 그 경로를 지정해줍니다. Tuist가 해당 경로에 있는 .swift 파일을 탐색해서 로드해줍니다.

나머지 함수로 처리된 부분들(resources, scripts, dependencies, settings)은 뒤에서 하나씩 알아보겠습니다.

결국 이 Project.swift 파일을 구성하는게 Tuist 도입의 핵심이기 때문에 그 과정에 대해서 하나씩 살펴보겠습니다.

 

 

Project 자세히 알아보기

 

dependencies

Tuist에서는 SPM 라이브러리를 연동하는 방법이 두 가지가 있습니다.

  • SPM을 직접 사용하는 방법
  • Tuist에서 package를 resolve한뒤 별도의 framework로 구성해주는 방식

기존 프로젝트는 연동하는 라이브러리가 많아 첫번째 방식으로 하면 resolve 하는데 시간이 많이 걸렸습니다.

두번째 방식은 최초 tuist fetch할때 시간이 다소 오래 걸리지만 이후에는 캐시된 것을 사용할 수 있기 때문에 더 빠르기 때문에 저는 두번째 방식을 선택하였습니다.

(첫번째 방식은 3.x 버전 기준으로 deprecated 워닝을 내고 있으니 두번째 방식이 권장되는 것 같습니다.)


사용법

1. 프로젝트 루트에 Tuist 폴더를 만들고 그 안에 Dependencies.swift 파일을 생성합니다.

(Dependencies 폴더는 tuist fetch 후 Tuist가 만들어주는 폴더로 따로 생성할 필요는 없습니다. 해당 폴더 안에 라이브러리들이 framework 형태로 로드됩니다.)

 

2. Dependencies.swift 파일 구성

import ProjectDescription

let dependencies = Dependencies(
    swiftPackageManager: SwiftPackageManagerDependencies(
        [
            .remote(
                url: "https://github.com/firebase/firebase-ios-sdk.git",
                requirement: .exact("9.6.0")
            ),
            .remote(
                url: "https://github.com/Alamofire/Alamofire.git",
                requirement: .exact("5.2.0")
            ),
            .remote(
                url: "https://github.com/onevcat/Kingfisher.git",
                requirement: .exact("5.14.0")
            ),
            .remote(
                url: "https://github.com/Moya/Moya",
                requirement: .exact("14.0.0")
            )
            
            ...
        ]
    ),
    platforms: [.iOS]
)

 

3. target에 dependencies 추가

private func defaultDependency() -> [TargetDependency] {
    return [
     .external(name: "Alamofire"),
     .external(name: "FirebaseCrashlytics"),
     .external(name: "FirebaseDynamicLinks"),
     .external(name: "FirebaseMessaging"),
     .external(name: "FirebasePerformance"),
     .external(name: "FirebaseRemoteConfig"),
     .external(name: "FirebaseAnalytics"),
     .external(name: "Kingfisher"),
     .external(name: "Moya"),
     
     ...
    ]
}

 

 

외부 라이브러리 말고도 프로젝트 경로 안에 있는 framework와 추후에 모듈화시 분리된 project도 여기서 추가가 가능합니다.

// 예제 코드로 경로는 알맞게 넣기
.xcframework(path: "../MyXCframework.xcframework")
.framework(path: "../MyFramework.framework")
.project(target: "CustomUI", path: "Projects/CustomUI")

 

이렇게 작업 후 tuist fetch -> tuist generate 하면 라이브러리들이 잘 import 되는 것을 볼 수 있습니다.

 

*추가적으로 "Tuist에서 package를 resolve한뒤 별도의 framework로 구성해주는 방식"이 안 되는 라이브러리들이 있는데요.

그런 라이브러리들은 SPM을 직접 사용하는 방식으로 로드하시면 됩니다. (또는 carthage를 사용)

let project = Project(
  name: projectName,
  organizationName: organizationName,
  packages: [
    .remote(
        url: "https://github.com/FLEXTool/FLEX.git",
        requirement: .exact("5.22.10")
    ),
  ]
  ...
 )

 

 

resources

.swift 파일을 제외한 파일들은 인식하지 못하기 때문에 Storyboard, xib, lottie, font 등의 파일은 그 경로를 지정해줘야합니다.

귀찮지만 하나씩 찾아줘야 합니다...
storyboard 뿐만 아니라 lottie, font, info.plist, xcassets 등 은근 리소스 파일들이 많더라구요.

private func getResources() -> ResourceFileElements {
    return [
        "MyApp/GoogleService-Info.plist",
        "MyApp/Settings/AlarmSetting/View/MarketingAgreement.storyboard",
        "MyApp/Fonts/Spoqa Han Sans Bold.ttf",
        "MyApp/Assets.xcassets",
        "MyApp/LottieAnim/onboarding_1.json",
        "MyApp/ko.lproj/**",
        "MyApp/en.lproj/**",
        "MyApp/Home/HomeViewController.storyboard",
        
        ...
        
       ]
}

 

저는 이중에서 로컬라이징 위치 찾기가 힘들었는데 "{프로젝트 경로}/ko.lproj/**" 경로로 하니까 로컬라이징 파일을 인식하더라구요

 

 

sripts

Script는 Build Phase에 있는 Run Script와 같은 역할을 해주는 옵션입니다.

저는 Firebase 라이브러리를 쓰기 때문에 Firebase 라이브러리를 run해주는 스크립트를 추가하겠습니다.

 

TargetScript를 extension하여 원하는 스크립트를 정의해둘 수 있습니다.

pre와 post가 있는데요. pre는 빌드 전, post는 빌드 후에 실행된다고 합니다.

주의사항으로는 "Crashlytics/run"이 있는 경로를 정확하게 찾아서 기입합니다.

public extension TargetScript {
    static let FirebaseCrashlyticsString = TargetScript.post(
        script: """
          "$SRCROOT/Tuist/Dependencies/SwiftPackageManager/.build/checkouts/firebase-ios-sdk/Crashlytics/run"
          """,
        name: "FirebaseCrashlyticsString",
        inputPaths: [
          "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}",
          "$(SRCROOT)/$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)"
        ],
        basedOnDependencyAnalysis: true
      )
}

 

 

settings

tuist 를 도입하면서 가장 걱정되었던게 info.plist와 build settings을 마이그레이션 하는 부분이었습니다.

다행히 info.plist는 기존의 plist를 그대로 사용할 수 있었고 

build setting은 tuist에서 migration을 도와주는 script가 있었습니다.

# 타겟의 settings 추출
tuist migration settings-to-xcconfig -p Project.xcodeproj -t MyApp -x MyApp.xcconfig

# 프로젝트의 settings 추출
tuist migration settings-to-xcconfig -p Project.xcodeproj -x MyAppProject.xcconfig

 

 

추출된 xcconfig는 아래와 같은 내용으로 구성됩니다.

CODE_SIGN_IDENTITY=iPhone Developer
ENABLE_PREVIEWS=YES
IPHONEOS_DEPLOYMENT_TARGET=13.0
SDKROOT=iphoneos
SUPPORTS_MACCATALYST=NO
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD=YES
SWIFT_VERSION=5.0
TARGETED_DEVICE_FAMILY=1,2
CODE_SIGN_IDENTITY = Apple Development

CODE_SIGN_STYLE=Automatic
ENABLE_BITCODE=NO
LIBRARY_SEARCH_PATHS=$(inherited)
LXSHIELD_ENABLED=YES
SWIFT_ENFORCE_EXCLUSIVE_ACCESS=off

...

 


이렇게 뽑은 xcconfig로 settings을 구성해보겠습니다.

프로젝트 루트 경로에 XCConfig 폴더를 만들고 안에 Project의 xcconfig파일과 App 폴더 안에 target의 xcconfig 파일을 두었습니다.

 

아래와 같이 setting을 설정해주면 xcconfig 파일을 바탕으로 build setting 구성이 가능합니다.

// Project 설정
private func baseSettings() -> Settings {
    var settings = SettingsDictionary()
    return Settings.settings(base: settings,
                             configurations: [
                                .debug(name:"Debug", xcconfig: .relativeToRoot("XCConfig/MyApp-debug.xcconfig")),
                                .release(name:"Release", xcconfig: .relativeToRoot("XCConfig/MyApp-release.xcconfig")),
                             ],
                             defaultSettings: .recommended(excluding: ["ASSETCATALOG_COMPILER_APPICON_NAME"]))
}

// Prod 타켓 설정
private func prodSettings() -> Settings {
    var settings = SettingsDictionary()
    return Settings.settings(base: settings,
                             configurations: [
                                .debug(name:"Debug", xcconfig: .relativeToRoot("XCConfig/App/PROD-debug.xcconfig")),
                                .release(name:"Release", xcconfig: .relativeToRoot("XCConfig/App/PROD-release.xcconfig")),
                             ],
                             defaultSettings: .recommended(excluding: ["ASSETCATALOG_COMPILER_APPICON_NAME"]))
}

 

"ASSETCATALOG_COMPILER_APPICON_NAME"를 해준건 App Icon을 타겟별로 다르게 해주고 싶어서 앱 아이콘이 default로 설정되지 않게 하는 옵션입니다. (같은 아이콘을 쓴다면 해당 옵션은 제거해되 됩니다.)

 

 

소감

Tuist를 도입하면서 기존에 무지성을 설정하던 프로젝트 설정과 프로젝트 구성에 대해서 다시 한번 생각해보게 되었습니다.

정말 다양한 build setting이 있고 아직 내가 모르는게 많다는 것을 느꼈습니다. 하나씩 채워나가야겠네요.

이제 Tuist를 도입했으니 최종 목표인 모듈화를 시도해봐야겠습니다. 

 

 

* 참고

https://docs.tuist.io/tutorial/get-started

https://ios-development.tistory.com/1210

https://okanghoon.medium.com/tuist-%EB%A1%9C-%EC%99%B8%EB%B6%80-%EC%9D%98%EC%A1%B4%EC%84%B1-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0-85609e70133c

반응형
Comments