당근마켓 iOS 프로젝트에 XcodeGen 도입하기

Kanghoon
당근 테크 블로그
10 min readSep 28, 2020

--

XcodeGen Logo

최근 당근마켓에 합류하면서 프로젝트 개선을 위한 여러 작업을 진행했습니다. 이번 글에서는 XcodeGen 을 도입하게 된 배경과 그 과정에 대해 적어보려고 합니다.

XcodeGen 은 Project Spec 에 따라 Xcode 프로젝트를 생성해주는 툴입니다.

배경

iOS 개발을 하게 되면 필연적으로 만나게 되는 문제가 있습니다. 바로 프로젝트 파일 충돌 문제입니다. Xcode 는 프로젝트 정보를 xcodeproj 라는 파일을 통해 관리합니다. xcodeproj 에서는 파일을 프로젝트에 넣을 때마다 파일의 UUID 가 달라지고, 파일들이 정렬되지 않아 머지 시 불필요한 충돌이 자주 발생합니다.

충돌 문제를 해결하는 방법들은 여러 가지 있겠지만, 기존에 당근마켓 팀에서는 xUnique 라는 툴을 사용하고 있었습니다.

xUnique 는 동일한 파일에 같은 UUID 를 할당하고, 프로젝트를 이름 기준으로 정렬하여 충돌을 예방합니다.

하지만 xUnique 를 사용하더라도 충돌을 완전히 예방할 수는 없어, 팀에서 파일을 추가할 때는 develop 브랜치에 커밋 푸시한 뒤 슬랙을 통해 공유하며 충돌을 예방하고 있었습니다.

팀원이 늘어나면서 기존의 방식으로 작업하기에는 불편함이 컸고 XcodeGen 을 도입하기로 했습니다.

XcodeGen 은 git 에 프로젝트 관련 파일을 업로드하지 않고,각자 생성하여 사용하기 때문에 프로젝트 파일 충돌이 발생하지 않습니다.

도입 과정

1. Pods 폴더를 gitignore 에 추가하기 (번외)

여러 가지 이유로 팀에서 Pods 폴더를 git 에 포함해 업로드하고 있었습니다. git 에는 팀에서 작성한 최소한의 코드만 반영하기 위해 gitignore 에 Pods 를 추가하였습니다. (Swift 5 버전 업그레이드 같이한거 안비밀..)

팀에서 CI 는 Github Actions 를 CD 는 Bitrise 를 사용하고 있었는데, Pods 를 gitignore 에 추가하면서 CI/CD 환경에서 Pods 를 캐싱하여 속도에 영향이 없도록 하였습니다.

2. xcconfig 파일 추출하기

XcodeGen 은 프로젝트를 생성할 때 xcconfig 파일과, yaml 파일을 사용합니다. (configuration 에 따라 설정이 다른게 아니라면 yaml 만으로도 가능함)

먼저 xcconfig 에 대해서 알아보려고 합니다.

xcconfig 란?

프로젝트는 기본적으로 Debug, Release, Profile 세가지 configuration 을 사용합니다. Xcode 로 프로젝트를 생성하면 세가지 configuration 에 맞는 설정이 기본값으로 지정되고 팀 내에서 필요한 대로 수정해서 사용합니다. 이 값들은 Xcode - Build Settings 에서 볼 수 있습니다.

작업 당시 당근마켓 iOS 팀에서는 3개의 configuration 과 6개의 target 이 있었습니다. (현재는 더 많습니다)

# configuration (3개)

  • Development : 개발용 configuration
  • Alpha : 알파 버전용 configuration
  • Production: 실제 프로덕션용 configuration

# target (6개)

  • Karrot (앱 타겟) + Tests 타겟
  • KarrotNotificationServiceExtension (Notification 타겟) + Tests 타겟
  • KarrotChat (도메인 타겟) + Tests 타겟

BuildSettingExtractor 를 사용해 타겟별로 각 configuration 이 적용된 설정을 xcconfig 파일로 추출할 수 있습니다. 이를 이용해 당근마켓 프로젝트의 설정을 추출하여 정리한 결과는 아래와 같습니다.

추출 결과물을 정리한 파일들

생소할 수 있는 이 xcconfig 파일들에 대해 조금 더 자세히 알아봅시다.

// Project 공용 설정을 지정함
Project-Shared.xcconfig // 모든 Configuration 에서 기본값으로 사용
Project-Alpha.xcconfig
Project-Development.xcconfig
Project-Production.xcconfig
// 타겟 Karrot
Karrpt-Shared.xcconfig // 모든 Configuration 에서 기본값으로 사용
...
// 타겟 ...
...

Project 와 각 타겟별로 4개의 파일이 생성되었습니다.

-Shared.xcconfig 파일에는 configuration 에서 기본적으로 사용되는 세팅들을 정의합니다. Alpha, Devleopment, Production 의 xcconfig 파일에서는 각 configuration 에 맞는 세팅들을 기본 세팅에 override 하여 적용합니다.

실제로 각 configuration 별 xcconfig 파일을 보면 -Shared.xcconfig 를 include 하고 있는 것을 볼 수 있습니다.

// 예시 - Karrot-Development.xcconfig 파일 내#include "Karrot-Shared.xcconfig"

BuildSettingExtractor 의 결과물로 모든 타겟 별 xcconfig 가 추출됩니다. 프로젝트 설정을 따라도 되는 타겟의 xcconfig 파일들은 제거할 수 있습니다. 당근마켓의 경우 configuration 별로 설정이 필요한 Karrot, KarrotTests, KarrotNotificationServiceExtension 타겟의 xcconfig 파일들만 남기고 나머지 타겟들의 xcconfig 파일은 제거하였습니다.

⚠️ 유의 사항

추출된 결과물에 불필요한 내용이 포함되거나, 누락된 내용이 있을 수 있습니다. xcconfig 파일과 기존 프로젝트의 Build Settings 를 비교해보면서 내용을 정리하는 게 좋습니다.

3. project.yml 파일 작성

XcodeGen 에서 프로젝트의 정보를 yaml 혹은 json 으로 구성할 수 있습니다. 당근마켓은 yaml 파일을 사용해서 ProjectSpec 을 작성했습니다. 링크된 가이드를 읽어보시면 필요한 대부분의 속성들이 정의되어 있습니다.

먼저 ProjectSpec 은 메인 프로젝트와 타겟들을 정의해야 합니다. 한 파일에 작성하면 가독성이 떨어지기 때문에 크게 프로젝트와 타겟별로 파일을 구분하였습니다.

아래는 당시 당근마켓 프로젝트의 스펙 파일 목록입니다.

  • ./project.yml (프로젝트 정의 Spec)
  • ./projects/Karrot/project.yml (앱 타겟 정의 Spec)
  • ./projects/KarrotNotificationServiceExtension/project.yml (타겟 정의 Spec)
  • ./projects/KarrotChat/project.yml (타겟 정의 Spec)

모든 스펙에 대해 알아볼 수는 없어서, 프로젝트 스펙과 앱 타겟 스펙의 간단한 예시를 살펴보고 넘어가겠습니다. 실제로 프로젝트 스펙을 작성하실 때는 예시와 가이드 문서를 보면서 작성하시면 됩니다.

(임시로 작성한 파일이라 틀린 부분이 있을 수 있습니다.)

프로젝트 스펙 예시

타겟 스펙 예시

🔍 참고 사항

Target Template 이나 Scheme Template 를 사용하면 더 깔끔하게 작성할 수 있으니 참고해보셔도 좋을 것 같습니다.

4. CocoaPods 적용하기

CocoaPods 를 사용한다면 추가로 처리해야 하는 작업들이 있습니다.

  • 연관된 모든 xcconfig 에서 Pods 의 xcconfig 를 include 해줘야 합니다.
// 예시 - Karrot-Development.xcconfig 파일 내#include "Pods/Target Support Files/Pods-Karrot/Pods-Karrot.development.xcconfig"
  • 프로젝트 생성 이후에 무조건 pod install 을 추가로 실행해줘야 합니다. 위의 샘플 yaml 파일처럼 postGenCommand 를 활용한다면 자동으로 프로젝트 생성 이후에 pod install 이 실행되도록 할 수 있습니다.

5. 디렉토리 구조 변경

XcodeGen 을 사용할 때 디렉토리 구조를 그대로 그룹화하여 프로젝트 생성하도록 하였습니다. 이에 맞도록 디렉토리 구조를 변경하였습니다.

팀에서 사용한 디렉토리 구조는 다음과 같습니다.

- project.yml # 프로젝트 yml
- Projects
- Karrot
- project.yml # 타겟 yml
- Sources
- Resources
- Supporting Files
- Target A
- ...
- Target B
- ...

6. XcodeGen 명령어 사용하기

이제 구성은 끝났습니다. XcodeGen 명령어를 사용하여 프로젝트를 생성할 수 있습니다. 아래의 두 명령어를 사용해서 프로젝트를 생성할 수 있습니다.

# XcodeGen 캐시 옵션 사용$ xcodegen generate --spec project.yml --use-cache

🔍 참고 사항

Makefile 을 사용해 스크립트를 사용하기 편하게 만들 수도 있습니다.

7. xcodeproj 관련 파일을 .gitignore 에 추가하기

이제 마지막 단계입니다. gitignore 에 프로젝트 관련 파일들을 추가하시면 됩니다. iOS 개발자라면 모두 알고 계실 것 같아 별도로 나열하지는 않겠습니다.

.gitignore 변경 후에 아래 명령어를 통해 이미 git 에 추가된 파일들을 제거할 수 있습니다.

$ git rm -r --cached .

도입 후

사이드 프로젝트에서 반복적으로 XcodeGen 을 적용해보아서 실제 작업은 오래 걸리지 않았지만.. XcodeGen 작업 진행 중에 팀원들은 다른 작업을 하다보니 충돌과 리베이스 지옥에 빠졌었습니다.. XcodeGen 을 적용하시려는 분들은 다른 작업을 잠시 멈추고 진행한다면 조금 더 편하게 적용할 수 있을 것 같습니다.

누락된 설정이 있다면 생성된 프로젝트가 기존 프로젝트와 다른 부분이 있을 수 있습니다. 작업 이후에 생성한 프로젝트 파일과 기존 프로젝트 파일을 한 번쯤 비교해보시는 걸 권장합니다.

꼼꼼히 체크했다고 생각했지만 누락된 부분을 찾아주신 팀원 분들과 XcodeGen 학습에 도움을 주신 분들께 감사드립니다

마치며

여기까지 XcodeGen 을 도입한 배경과 과정에 대해서 알아봤습니다. XcodeGen 은 프로젝트 충돌을 방지한다는 장점 외에도 타겟을 쉽게 분리할 수 있고, Carthage, SPM 를 쉽게 사용할 수 있다는 장점도 있습니다.

실제로 당근마켓에서는 XcodeGen 을 도입한 이후 계층별로 모듈을 분리하고, 빌드 속도를 위해 Carthage 와 Rome 을 도입했습니다.

당근마켓에서는 이처럼 재밌는 일들을 많이 시도하고 있습니다. 글을 읽고 계시는 iOS 개발자분들 중 당근마켓에 합류하고 싶으신 분이 계신다면 이메일(ray@daangnpay.com)로 연락주세요.. 😆

추가로 궁금하신 점들은 댓글이나 이메일로 남겨주세요

--

--