Tuist 로 가는 여정 Part 1 — XcodeGen 에서 Tuist 로 전환하기

Hyeongseok Park
29CM TEAM
Published in
15 min readDec 5, 2022

안녕하세요? 29CM iOS 엔지니어 박형석입니다.

서비스가 성장하고 팀원이 늘어갈 때 프로젝트 관리 도구는 선택이 아닌 필수입니다. 그래서 어떤 도구를 선택할지, 어떤 장단점이 있는지, 어떻게 적용할지, 기존 도구를 사용하고 있다면 어떻게 전환 할지를 고민하게 됩니다.

29CM iOS 팀은 최근 프로젝트 관리 도구를 XcodeGen 에서 Tuist 로 전환하는 작업을 진행했었는데요. 피쳐를 계속 개발하면서 전환 작업을 동시에 진행하는 것을 목표로 했기에 작업 과정에서 여러 고민과 이슈가 있었습니다.

이번 글에서는 이 과정에서 생긴 고민과 이슈들을 어떻게 해소했는지 소개드리려 합니다.

그리고 Tuist 전환과 관련해서는 아래와 같이 2부작으로 풀어내려고 합니다.

  • Tuist 로 가는 여정 Part 1 — XcodeGen 에서 Tuist 로 전환하기
  • Tuist 로 가는 여정 Part 2 — Tuist 에서 오픈소스 라이브러리 관리하기

Why XcodeGen to Tuist?

Xcode 로 iOS 팀이 함께 개발을 하다보면 빈번하게 .pbxproj 의 reference 가 변경되고 UUID 가 바뀌어 Git Conflict 이 자주 발생합니다. 특히 새로운 파일 생성 및 제거, 정렬, 폴더링에서 자주 발생하는데 아주 골치거리죠.

XcodeGen 은 이런 문제를 해결하는 대중적이면서 훌륭한 도구입니다. 위와 같은 문제를 해결할 뿐만 아니라 타겟을 쉽게 분리할 수 있고, 오픈소스 라이브리러도 쉽게 관리할 수 있습니다.

실제로 저희 29CM 앱도 XcodeGen 을 사용해 Git Conflict 을 해결했고 Workspace 를 멀티 타겟으로 손쉽게 관리할 수 있어 효율적으로 일할 수 있었습니다. 하지만 프로젝트 설정파일을 yml 스크립트로 작성하는 일, 멀티 프로젝트 관리의 어려움, 의존성 캐싱을 따로 관리해야 하는 아쉬운 점도 있었죠.

저희 팀은 하나의 Workspace 를 여러 개의 모듈로 구성한 Modular Architecture 를 만들어 가고 있는데요. 서비스가 계속 성장하면서 팀이 대응해야 하는 도메인이 더 넓어지고 복잡해짐에 따라 Workspace 를 더 효율적으로 만들고 빠르게 협업할 수 있는 구조를 피쳐 개발과 동시에 구축할 필요가 있었습니다.

이 상황을 개선할 방법을 고민하던 중 Tuist 의 아래 장점들이 도움이 되리라 생각했습니다.

1. 프로젝트 설정 파일을 Xcode 내에서 Swift 로 작성하고 관리할 수 있습니다.

tuist edit 을 사용하면 별도의 프로젝트(Manifests)로 Tuist 설정과 관련된 파일을 한번에 관리할 수 있는데요.

함수, 변수, enum, extension 등 Swift 가 제공하는 기능과 타이포 감지, 자동 완성, 빌드 등 Xcode 에서 제공하는 기능도 사용할 수 있어 yml 로 스크립트를 작성하는 것보다 훨씬 효율적으로 작업할 수 있습니다.

2. Tuist 3.x 부터는 Tuist Dependencies 를 지원해 외부 라이브러리 의존성 캐싱을 쉽게 관리할 수 있습니다.

저희는 Cocoapods-binary-cache 를 기반으로 외부 라이브러리 의존성을 관리해 왔습니다. Tuist 역시 3.x 부터 의존성 캐싱 기능을 지원합니다. XcodeGen 과 달리 Tuist 는 외부 라이브러리 의존성과 29CM의 모듈 의존성을 함께 관리할 수 있습니다. 간단한 예시를 들어보겠습니다.

3. Workspace 를 멀티 프로젝트로 관리하기 쉬워집니다.

관련한 설명은 아래에서 저희 프로젝트를 소개하며 더 자세히 다루겠습니다.

4. Workspace 내 타겟 의존성 그래프를 손쉽게 그릴 수 있고 이를 통한 모듈간 의존성 관리가 편리해 집니다.

tuist graph 를 사용하면, 아래와 같은 그래프를 그릴 수 있고 각 모듈의 Mach-O 타입, 모듈간의 의존성 등을 확인할 수 있어 Modular Architecture 를 설계하거나 모듈 사이에서 일어날 수 있는
Circular Dependency, Static Library 중복 적재와 같은 문제를 발견하는데 유용하게 사용할 수 있습니다.

Tuist 공식 문서에서 가져온 이미지입니다.

점진적인 Tuist 전환 과정

Tuist 전환을 결정하고 처음 맥에 앉았을 때 막막했던 기억이 떠오르네요.. 프로젝트 관리 도구인 만큼 하나의 PR 로, 완벽하게 전환해야 한다는 부담이 있었습니다. 그리고 동시에 아래와 같은 제약사항도 고려해야 했습니다.

  1. 각 스쿼드에서 진행되는 피쳐 개발을 멈출 수 없었기에 전환 작업을 동시에 진행할 수 있어야 했습니다.
  2. 프로젝트 도구의 급격한 전환은 프로젝트에나 팀에게나 안전하지 않기에 리스크를 줄일 수 있는 방법이 필요했습니다.
  3. Tuist 3.x 로 업데이트되면서 CocoaPods 을 사용하는 인터페이스가 내부에 사라지기도 했고 권장하지 않는 방식이 되었습니다. 저희는 CocoaPods 만사용하고 있어서 모든 외부 의존성을 SPM 과 Carthage 로 전환해야 했습니다.

위와 같은 이유로 점진적으로 전환하는 방법을 고민했고 다음 과정을 단계별로 밟아가기로 논의했습니다.

  1. XcodeGen 과 Tuist 를 별도의 Workspace 로 관리하자.
  2. Next-Gen 모듈 구조를 적용하며 모듈 단위로 하나하나 전환하자.
  3. Xcode 의 설정을 Tuist 에 적용하자.
  4. 오픈소스 라이브리러리를 SPM 과 Carthage 로 전환하자.
  5. CI / CD 환경을 Tuist 맞춤으로 수정하자.

각 단계들을 조금 더 상세하게 소개드리겠습니다:)

(아래 내용들은 지면 관계상 XcodeGen 과 Tuist 에 대한 기본 이해를 가지고 계시다는 것을 전제하고 있습니다)

Tuist 로 별도의 Workspace 생성하기

점진적 전환의 가장 중요한 포인트는 ‘Tuist 전환이 끝나기 전까지는 XcodeGen 으로 생성한 Workspace 를 사용해 개발하고, 전환이 완료된 후에는 Tuist 로 생성한 Workspace 를 사용해 개발한다’ 입니다.

팀원들은 XcodeGen 으로 Workspace 를 생성해 사용하다 어느 날 “이제 Tuist 사용하셔도 됩니다!” 라는 말을 듣고 바로 넘어갈 수 있도록 하는 것이죠.

이를 위해 XcodeGen 과 독립적인 작업 공간, 즉 Tuist 만을 위한 Workspace 를 생성할 필요가 있습니다. 팀원들은 현재 안정적인 환경인 App29CM.workspace 에서 작업하고, 저는 위험한(?) 작업을 App29CM_Tuist.workspace 에서 하도록 별도의 작업 공간을 만드는 것이죠.

Workspace.swift 라는 파일을 생성하고 아래 코드를 작성해 별도의 Workspace 가 생성되도록 했습니다.

*참고로 기존 프로젝트 루트 디렉토리에서 tuist init 을 실행하면 작동하지 않는데요. 별도의 디렉토리를 생성해 init 을 하신 뒤 프로젝트를 옮겨 작업하시는 것을 추천드립니다.

아래 명령어를 사용해서 비어있는 App29CM_Tuist.workspace 가 만들어지는지 확인하면 기반 작업은 끝입니다.

$ tuist generate

XcodeGen 으로 생성한 모듈을 Tuist 모듈로 하나씩 전환하기

이제 별도의 Tuist 전용 Workspace 가 만들어졌으니, 여기에 기존 모듈을 하나하나 옮겨가면 됩니다! 그전에 XcodeGen 으로 구성된 저희 프로젝트를 간략하게 소개드릴게요.

XcodeGen

XcodeGen 으로 구성한 저희 Workspace 는 1 Project-Multi Target 으로 되어 있었고, 모듈화가 되어 있는 레포였기에 많은 project.yml 파일이 서로 의존하며 Workspace 를 구성하고 있습니다.

Workspace 를 위한 project.yml
Projects 디렉토리에 있는 각 모듈의 project.yml 을 연결시켜주는 project.yml
각 모듈(타겟)에 있는 project.yml
간편하게 모듈을 구성하기 위한 Template project.yml
위 Template 을 Modular Architecture 의 Layer 에 따라 다르게 구성되도록 설정하는 project.yml

위의 project.yml 들은 각 타겟의 구체적인 정보를 담고 있습니다. path, type, dependencies 등 하나의 모듈로서 역할을 하기 위한 상세한 정보들이 기록되어 있는데 저희 Workspace 에서는 이런 타겟들이 조합되어 하나의 앱을 구성하고 있었습니다.

project.yml 파일을 Project.swfit 파일로 전환

다시 본론으로 돌아와서, 이 하나의 Workspace 를 단계적으로 전환해 보겠습니다. 전략은 아주 간단합니다. 언급한 project.yml 파일을 하나하나 Project.swift 로 번역하는 것입니다. 저는 각 모듈 단위로 전환을 진행했기 때문에 아래와 같이 1:1 치환이 가능했습니다.

project.yml 파일에는 이미 각 모듈이 어떤 의존성을 가지고 있고 어떤 설정을 해야 하는지에 대한 정보를 가지고 있습니다. 우리는 단순히 그 내용을 Tuist 식으로 번역해주면 됩니다. 아래에 간단한 예시를 들어보겠습니다.

  • Entity 모듈의 project.yml 일부
  • Entity 모듈의 Project.swift 일부

위 두 코드를 자세히 살펴보면 사실상 큰 차이가 없습니다. XcodeGen 의 Template 역할은 Tuist 의 makeFrameworkTargets 이라는 함수가 대신하고 있습니다. 의존성, Test, identifier 등 다 드러나지 않지만 모듈이 가져야 하는 대부분의 정보를 그대로 옮겼습니다.

차이점 중 하나는 Tuist 에서는 Project 를 생성하고 있다는 점입니다. 앞서 Tuist 의 장점 중 하나로 Multi-project Workspace 관리가 쉽다고 소개드렸던 것처럼 위 코드를 적용하면 Multi-project Workspace 를 손쉽게 구성할 수 있습니다.

이로써 각 프로젝트는 독립적이고 고립된 환경을 가지게 되고, 피쳐 앱을 위한 App Target 도 간편하게 제작할 수 있게 됩니다.

아래처럼 각 모듈의 디렉토리에 Project.Swift 를 넣고 tuist generate 를 실행하면 아래와 같이 하나의 프로젝트를 가진 Workspace 가 생성됩니다. 물론 이 단계에서는 앱을 실행하지 못하지만, 하나하나 모듈을 옮기고 설정까지 완료하면 앱을 실행할 수 있게 됩니다.

*제대로 전환이 되었는지는 어떻게 확인하면 될까요? 간단합니다! 옮긴 모듈의 타겟을 빌드해 보시면 의존성이 제대로 연결되어 있는지, 불필요한 코드나 빠진 로직이 없는지 확인이 가능합니다.

팁을 드리자면, 위에서 장점으로 소개드린 tuist graph 를 활용하시면 더욱 좋습니다. 또 Modular Architecture 에서 가장 독립적이고 하위인 모듈부터 우선적으로 옮겨 나가시면 더욱 편리하게 전환하실 수 있습니다.

Xcode 설정을 Tuist 로 번역하기

마지막으로 Xcode 설정을 Tuist 에서 사용할 수 있도록 세팅해 보겠습니다. 저희 팀은 Xcode 의 설정을 .xcconfig 를 사용해서 세팅하고 있습니다.

아래를 보시면 아주 많은 .xcconfig 파일이 있는데요! 이중 하나를 어떻게 번역했는지 예시로 소개하겠습니다.

아래는 저희 29CM 앱의 Debug 설정이 담긴 .xcconfig 파일입니다. #include 로 공용으로 사용할 설정, Pods 의 Debug 설정을 임포트하고 있습니다. // ... 주석 아래에는 App29CM 에만 필요한 Debug 설정을 추가하고 있습니다.

이 설정을 Tuist 에서는 어떻게 사용했을까요? 저희는 Tuist 로만 설정을 관리하고 불필요한 설정을 삭제하며 진행하기로 했기 때문에 테스트하며 Setting 을 하나하나 옮겨주었습니다.

*물론 tuist migration 을 통해 더 간단하게 하는 방법도 있습니다!

먼저, XcodeGen 의 App29CM-Shared.xcconfig 을 대체할 공용 Setting 를 작성했습니다. 그리고 Shared 에서 필요한 설정들을 옮겨주었습니다.

다음으로 해당 Setting 을 간편하게 사용하기 위해 Settings Extension 으로 직접 default 설정을 만들어 주었습니다.

그리고 실제로 사용할 때는 다음과 같이 사용하도록 했습니다.

만약 추가 설정이 필요한 경우에는 merging 메서드를 사용해 추가 설정을 구현했습니다. 그리고 Project.swift 최상단에 해당 세팅을 두어 어떤 설정값을 가지고 있는지 확인할 수 있는 컨벤션도 구축했죠!

저희처럼 필요한 설정을 직접 관리하는 경우 최종적으로는 Project Setting 의 defaultSetting 을 none 으로 하시는게 좋습니다. 하지만 최초 전환시에는 recommended 로 진행하셔서 속도를 내시고 어느 정도 진행된 다음, 마무리 과정에서 다시 none 으로 바꾼 뒤 발생하는 문제들을 해결하는 식으로 처리해 나가시면 유려하게 작업해 가실 수 있습니다.

전환과정에서의 트러블 슈팅

Workspace 의 Default Settings 을 .none 으로 했을 때

위에서 말씀드린 것처럼, Workspace 설정을 .recommended 가 아니라 .none 으로 했을 때는 옵션을 하나하나 설정해줘야 합니다. 이 과정에서 옵션이 누락되어서 문제가 생기는 경우가 많습니다.

저희도 @rpath 관련 Library Not Loaded 에러를 발생시킨 옵션이 있었는데요. 범인은LD_RUNPATH_SEARCH_PATHS 이었습니다. 해당 옵션을 릴리즈 세팅에만 넣고, 디버그 세팅에는 넣지 않았기 때문입니다.

이렇게 누락된 옵션으로 인해 빌드가 되지 않거나 릴리즈 세팅에만 넣어서 디버그 모드에서 작동하지 않거나 하는 문제가 생길 수 있으니 세팅 값을 넣으실 때는 휴먼 에러에 주의하세요.

disableBundleAccessors, disableSynthesizedResourceAccessors 옵션

자동으로 리소스를 코드화시켜주는 옵션입니다. Tuist 전환 후 빌드할 때 image 네이밍 중복 에러가 떠서 찾아본 옵션입니다. 이전에는 Config 에 있었는데 3.x 부터 Project 의 옵션으로 바뀌었습니다.

저희는 따로 리소스를 관리하는 모듈을 사용하고 있기 때문에 true 로 설정해서 사용하고 있습니다. 아래는 Resource 모듈에서 해당 설정을 false 로 했기 때문에 자동으로 생긴 파일들입니다.

Xib 파일을 Copy Bundle Resources 에서 인식하지 못하는 문제

저희 프로젝트에는 여전히 레거시 .xib 파일이 남아있습니다. 앱 타겟에 아직도 무수히 많은데요. 이 파일들을 Tuist 에서 인식하기 위해서는 path 를 꼭 잡아줘야 합니다.

아래와 같이 해당 타겟 Resource 경로를 설정하는 곳에 .xib 파일의 경로를 설정해 주세요. 다른 Resource 들도 마찬가지로 모두 제대로 된 경로 설정이 필요합니다.

앞으로의 방향성

이번 글에서 소개드린 것 처럼 저희 iOS Workspace 는 현재 Tuist 로 전환이 완료된 상태인데요. 하나의 모듈로 오랫동안 개발되어 왔던 프로젝트이기에 저희 팀이 지향하는 만큼 충분히 모듈 분리가 되진 않았습니다.

그렇기에 이번에 소개드린 Tuist 환경 위에서, 도메인 로직들을 피쳐 모듈로 쉽게 분리해 개발해 나갈 수 있도록 기반을 더 만들어나갈 예정입니다.

저희가 지향하는 수준까지 모듈화 완료되면 각 도메인의 피쳐 앱에서 개발을 해 나가거나, 디자인 시스템 역시 별도의 앱에서 개발하는 등 다양한 가능성을 가질 수 있게 되리라 예상합니다.

마치며

이번 글에서는 저희 팀의 Tuist 로 전환기를 소개드렸는데요. 저희 팀은 Tuist 를 사용하면서 모듈 관리의 효율성과 편의성, 생산성 향상을 경험하고 있습니다.

Tuist 전환을 단계적으로 진행하시면 시행착오를 줄이고 피쳐 개발에도 영향이 없이 진행하실 수 있기 때문에, 새로운 브랜치에서 시도해 보시길 추천드립니다 😊

이번 글이 Tuist 전환에 관심이 있으신 iOS 팀에 도움이 되셨으면 좋겠습니다.

읽어주셔서 감사합니다.

함께 성장할 동료를 찾습니다

29CM (무신사) 는 3년 연속 거래액 2배의 성장을 이루었습니다.

빠르게 성장을 해나가면서도 더 큰 성장에 대비하기 위해 Modular Architecture 를 꾸준히 확장해 나가며 Tuist 를 도입하는 등 팀에 필요한 여러 기술적인 시도를 진행하고 있습니다.

함께 성장하고 유저 가치를 만들어낼 동료 개발자분들을 찾습니다.
많은 지원 부탁드립니다!

🚀 29CM 채용 페이지 : https://www.29cmcareers.co.kr/

--

--