...
본문 바로가기

Xcode

Tuist 마이그레이션 후기

 

 

후..원래 9월부터 10월까지 Tuist공부하는게 목표였다.

Tuist만 공부하기로 했었는데, 하다보니 회사의 프로젝트도 Tuist로 바꿔볼수있겠다라는 자신감이 생겼다. (?)

 

누군가는 한달씩이나 공부할게있냐하지만.. 막상 퇴근하고 운동하고하면 시간이 2-3시간밖에 되지않았다. 주말에는 더 하고, 그렇게 했다. 

회사프로젝트도 마이그레이션해볼 생각을 하니, Build Settings들도 제대로 알아야겠다라는 생각이들었고, 그래서 코드사이닝도 다시보고, 프레임워크들도 다시보고 하다보니 오래걸렸던것 같다. 

(코드사이닝도 보니, Tuist뿐만 아니라 자동화배포도 다시 해볼수있겠다라는 생각이 들긴했다.)

 

하면서 느낀점은 이번기회덕분에 프로젝트 전반적 구성에 대해서 알게되었다는 점이 가장 큰것 같다. 

매번 일부분만 작업만 했지, 이렇게 큰 단위로 본적이 있었던가? 싶었다.  pkgproj도 알게됐고.. 

 

또 개인적으로도 의미가 굉장히 크다.

차근차근 하나씩 공부하고싶었던거를 이루고 있다는 점과,

예전에만 해도 회사프로젝트는 Tuist는 꿈이라고만 생각했었었다. 

하지만 시간을 들이고 하다보니 가능하게 됐다! 

( 막상 또 해보니, 별거아니다라는 공허함? 도 있기도하고...)

 

 

 

 

(원래는... 마이그레이션뿐만아니라 Tuist 정리글도 작성해보려했는데... 나중에.......ㅎ  워낙 Tuist가 유명하고, 잘 작성해둔곳이 많기도하고...ㅎㅎㅎ )

 

Tuist란 ? 

Tuist는 간단하다. 더이상 Xcode 인터페이스로 프로젝트를 관리하지않아도된다. 

Build Settings, Configuration, Run script, framework depenceny 등 뿐만아니라, 프로젝트 생성도 Tuist에서 제공하는 API 코드로 관리할 수 있다. 

Tuist 제공하는 API코드는 심지어 Swift로 작성되어있다.  API에서 내가 만든 Swift코드들도 사용하게 할수도있다. 

( API들도 기존처럼 Xcode로 열어서 작성도가능하여 매우 편했다. )

 

모듈화를 한다면 template를 만들어서 재사용가능하게 코드를 사용하여 손쉽게 만들어낼수있고, 손쉽게 메인타겟에 의존성도 추가할수있다. 

 

더이상 Xcode 인터페이스로 관리하지 않으니, pbxproj 의 merge conflict도 발생하지 않는다.  (소스파일 단위의 충돌은 있겠지만)

 

또, 아직 해보진않았지만, Tuist + SPM을 이용하면, SPM에서 받아올때, xcframework로 만들어서 빌드시간도 단축해준다거나, 하는 장점도 있다고 봤다. 

 

 

 

공식문서 

https://docs.tuist.io/en/

 

What is Tuist? | Tuist

 

docs.tuist.io

 

API 문서 

https://docs.tuist.io/en/references/project-description/structs/project

 

초반에는 문서도 잘안되어있었다고 했지만, 나는 뒤늦게 알게되어 생각보다 문서가 잘되어있었다. 마이그레이션하는 방법이나, 퀵하게 설치하는 방법등..

API도 자세한 설명은 없지만, 그래도 문서화는 되어있었다.

 

 

 

 

마이그레이션 과정

사실 Tuist 전환을 내부적으로 완전 확정은 아니다. 팀내에서 우려하는 부분도 있고 해서..

그래서 나는 언제든 기존프로젝트에 Tuist로 전환가능한 Makefile을 만들었다.

왜냐면 내가 Tuist전환한 프로젝트와 계속 코드가 늘어나는 기존프로젝트와 쌍으로 존재할수는 없을것 같았다. 다른 팀원분들이 추가하는 코드를 내가 계속 팔로우할수도 없고. 그래서 언제든 최신브렌치를 풀해오고, Makefile과 관련 template폴더만 있으면, make 명령어를 통해서 Tuist프로젝트로 전환가능하게끔 하고자 했다. 

 

아래처럼 make migrate하면 아래 명령어들이 진행된다.

migrate:
	make copy_source_from_project
	
	make xcconfigs_migration
	make make_dict_from_xcconfig
	
	make tuist_make_template_migration
	
	make move_copiedfile_to_template
	
	make tuist_generate_with_pod_install

 

1. 모든 소스코드, 파일들을 특정 폴더에 옮기도록 했고,

2. xcconfigs을 생성하는 마이그레이션코드, 이걸 어느정도 swift구조처럼 만드는 마이그레이션코드도 작성.. ( 결국엔 template에 넣어버림) 

3. Tuist를 이용하여 기존프로젝트처럼 구성하도록하는 template를 이용하여 프로젝트를 구성하고, 

4. 특정폴더에 옮겨놓았던 파일들을 모두 3번 프로젝트 디렉토리에 옮겨주고,

5. tuist generate 및 cocoapods install한다. 

 

 

하다보니.. swift로 변환하면서 남은 제대로 삭제되지않은 objective-c 파일들이 있어서 충돌도나고...  

 

 

 

마이그레이션 하면서 고민흔적들..

xccconfig  VS  코드 

코드로 하려다가, xcconfig로 하다가, 다시 코드로 바꿨다. 

xcconfig로 바꿨던 이유는, 나뿐만 아니라 팀원분들이 사용하게 될경우를 고려해서, 기존에 익숙한 방법이 좋을것 같아서였다.

또한 Tuist에서 제공하는 기존프로젝트에 Build Settings들을 마이그레이션하여 xcconfig로 만들어주는 api도 있어서, 안전해보였다. 

하지만, 하다보니, xcconfig가 tuist에 의해 오버라이딩 되는 키들이 종종있었다.

Target에 해당 키가 프로퍼티로 있다면, 환경변수를 사용하거나, settings에서 excluding에 추가하면 얼추 되긴했으나..

defaultSettings: .recommended(excluding:  ["ASSETCATALOG_COMPILER_APPICON_NAME",
                                                                    "PRODUCT_NAME",
                                                                    "PRODUCT_BUNDLE_IDENTIFIER",
                                                                    "CODE_SIGN_IDENTITY"]))

 

그럼에도 오버라이딩 되는 키를 발견해버렸다. UnitTest타겟에서 TEST_HOST 라는 키였다.  configuration에 따라 이름이 바껴야했는데, 계속 오버라이딩돼서...  불가피하게 코드를 해당 키에대해서 예외처리? 를 해줘야했다. 

 

나중가면 어떤키는 xcconfig에, 어떤키는 코드로 또 추가해줘야하는 상황이 또 발생할거고, 일관성이 없다고 판단하여, 

xcconfig가 안된다면, 코드로 다 짜야겠다해서 코드로 다 바꿨다.

덕분에...... 마이그레이션하여 나온 build settings들을 전부다 봤다. 

다행히 요즘은 gpt가 있기때문에 이해하기 한결 수월했다. 

정말 덕분에,  최소지원버전이 올라가서 더이상 포함시킬 필요없는 키도 발견했다. 

ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES  요건 10.15이상부터는 기본적으로 포함되어있다고 하니, NO로 해도되겠다. 

 

브렌치 변경할때마다 tuist generate 자동화하기 

또, 소스트리를 사용하고있는데, 팀원분의 의견으로 브랜치 전환마다 자동으로 tuist generate해주는 코드도 있으면 좋을것 같다하여, 요것도 글작성후에 알아봤다. 아래글에 따로 적어놨다. 

https://vapor3965.tistory.com/127

 

브렌치 변경시 tuist generate 자동화하기

아래글을 작성하고서 더 찾아본 내용이다. https://vapor3965.tistory.com/125 Tuist 마이그레이션 후기후..원래 9월부터 10월까지 Tuist공부하는게 목표였다.Tuist만 공부하기로 했었는데, 하다보니 회사의

vapor3965.tistory.com

 

 

 

절대경로, 상대경로 이슈..

비단 pkgproj뿐만아니라, 마이그레이션을 하게되면, 기존프로젝트에서 쉘스크립트에서나, 특정 파일에 대해 절대경로나, 상대경로로 지정해둔 케이스에 대해 발생할수 있는 문제다.

 

macOS앱이라, 패키징을 하는데, 이것도 쉘스크립트로 되어있다보니, pkgproj를 한번도 본적이 없었다. (큽..)

아카이브성공하고, 마지막 패키징에서 계속 앱아이콘이 없다고 실패하는데...  패키징스크립트의 폴더위치에서 상대경로로 앱아이콘을 가리키고있다보니, 마이그레이션하면서 폴더구조가 달라져서 찾을수없는 이슈였다.

pkgproj가 GUI도 지원하는데, 열어도 어디가 문제인지 못찾겠더라... 우클릭해서 패키지보기하니까 xcode로 열수있었고, xml언어로 볼수있었고, 덕분에 appIcon 경로를 지정하는곳을 찾을수 있었다. 휴.. 

 

Podfile도 당연히 프로젝트구조가 달라지다보니, 코드일부분을 수정해줘야했다.  

 

많았던것 같은데, 기억나는 부분은 요두개정도.. 

 

 

 

Compiler Flags 

회사 프로젝트는 아직 objective-c코드가 많은편이다. UI단들은 거의 swift로 옮겨가긴했으나, 아직 많다. 

그러다보니, 헤더파일도 잘 넣어줘야했다. 

이미 ARC를 이용하고있는데, 어떤 objecitve-c코드는 ARC기능을 off하고 사용하고 있던 코드가 있었다! 

target - build phases 에 Compile Sources 에 해당 파일에 대해 Compiler Flags를 지정할 수 있었고, 이미 해당 파일에 ARC기능을 끄고있었다. ( 처음알았다. ) 

다행히 tuist에서도 관련 옵션을 설정할 수 있는 파라미터가 있었다. 

SourceFileGlob에서 compilerFlags 파라미터를 제공하고있었다. 휴.. 

 

 

Derived 디렉토리 

처음에 옵션없이 프로젝트 생성하다보니, Derived에 Asset관련 코드들이 자동으로 생성되는데, 

컴파일에러가없었다면 그냥 냅뒀을것 같은데,  리소스 이름이 끝에 특수문자가 들어가면, tuist에서 생성된 asset코드들은 이를인식을 못하여 중복된 이름들이 발생한다는 컴파일에러가 났다 .- _ - ;  

일단은 필요하지않아보여 asset을 생성하지않도록 옵션을 추가했다. 

 

 

 

Tuist 래핑 

Tuist가 버전업이 굉장히 잦다고 들었고, 3에서 4로 올라갈때 큰변화가 있었다고 했고, 

이제 곧 5가 나오지않을까 싶기도하고...  

그래서 향후 업데이트에 대비하여 최대한 코드수정을 덜하기위해 사용하는 모든 코드들을 다 감쌌다. 

 

 

아래는 예시로, Target에서 sources, resources, headers, 에서 사용하는 코드들을 다 새로 만들었다. 

public enum VaporPath: ExpressibleByStringInterpolation {
    case relativeToRoot(String)
    case relativeToManifest(String)
    
    public init(stringLiteral value: StringLiteralType) {
        self = .relativeToManifest(value)
    }
    
}

public struct VaporSourceFileList {
    var path: VaporPath
    var excluding: [VaporPath]
    var compilerFlags: String?
    
    public static func path(_ path: VaporPath, compilerFlags: String? = nil) -> VaporSourceFileList {
        VaporSourceFileList(path: path, excluding: [], compilerFlags: compilerFlags)
    }
    
    public static func path(_ path: VaporPath, excluding: [VaporPath]) -> VaporSourceFileList {
        VaporSourceFileList(path: path, excluding: excluding, compilerFlags: nil)
    }
}


public struct VaporResourceFileList {
    var path: VaporPath
    var excluding: [VaporPath]
    
    public static func path(_ path: VaporPath) -> VaporResourceFileList {
        VaporResourceFileList(path: path, excluding: [])
    }
    
    public static func path(_ path: VaporPath, excluding: [VaporPath]) -> VaporResourceFileList {
        VaporResourceFileList(path: path, excluding: excluding)
    }
}


public struct VaporHeaders {
    var path: VaporPath
    
    public static func path(_ path: VaporPath) -> VaporHeaders {
        VaporHeaders(path: path)
    }
}

 

Path, Project, Scheme, Settings, Target, Dependencies, Script 일단 필요했던 코드들은 다 새로만들었다. 

 

그래서 Project에서 만들때만 Tuist API로 변환하는 코드만 한곳에 두었다. 

extension VaporSourceFileList {
    
    func tuist() -> SourceFileGlob {
        // SourceFileGlob
        
        if excluding.isEmpty {
            return .glob(path.tuist(), compilerFlags: compilerFlags)
        } else {
            return .glob(path.tuist(), excluding: excluding.map { $0.tuist()})
        }
    }
}
extension VaporResourceFileList {
    func tuist() -> ResourceFileElement {
        // ResourceFileElement
        if excluding.isEmpty {
            return .glob(pattern: path.tuist())
        } else {
            return .glob(pattern: path.tuist(), excluding: excluding.map { $0.tuist() })
        }
    }
}

....

 

 

기대하는 바는 향후.. 업데이트가있을때, 이곳에만 수정했으면 하는 바램이다. 

또 덕분에 Tuist제공하는 수많은 옵션을 다 사용하기보다는 우리 팀에서 필요한 옵션들만 남기도록 숨기는 장점도 있고..

 

하면서 느낀건데, 이거까지 다 래핑해야하나 ? 싶었지만, 이왕하는거 다 했다. 

사실 과연 향후 업데이트가 있을때 정말로 기존래핑한 코드들은 수정을 안할까?? 라는 의문이 있긴하다. ㅜ 

 

tuist 이전버전들을 보진않았지만, 지금구조가 정형화된것 같아보이고, 구조가 크게 바뀌지는 않을것 같은데.. 과연..... 

 

 

빌드시간감소 

글작성하고나서 다시보았다. 

 

기존프로젝트와 의존성구성방법을 그대로 가져가서 tuist로 만들었는데, 클린빌드시간이 40초나 차이가났다. 

( 왼쪽이 기존프로젝트, 오른쪽이 tuist ) 

언뜻 빌드세팅도 크게 바꾼게 없었다. 내가봤을때 ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES 이거빼고는 거의 동일했다고 생각했어서, 왜이렇게 차이나는지 슬래긍로 tuist 팀에게 물어봤다.  (회사동기가 슬랙으로도 물어볼수있다고해서! )

 

Marek Fort 느님이 답변주셨는데, Tuist와 같은 프로젝트를 새롭게 만들지않는다면, 기존프로젝트는 굉장히 엉망이될수있어서, 이런부분이 오래걸릴수 있다한다. 



그래서 혹시나, 프로젝트 구성하는데, 그래프같은거 시각적으로 볼수있는 툴이 있을까? 하고 잠깐 알아봤지만, 

의존성정도이지, 뭔가 더 딥한것들은 못얻을것 같아서, 빌드했을때, 로그를 보았다. 

 

빌드후, 특정소스코드를 약간만 수정하고 빌드했을때, 빌드시간이 10초차이가났다.

로그를 보니, swift컴파일이 두번이루어지는것 같았다. 

다시보니, swift말고도 다른게 두번이나 이루어지는거였고, 알고보니, 빌드세팅 문제였다.

ONLY_ACTIVE_ARCH 라는 세팅때문이였다. 

현재 디바이스의 아키텍쳐로 빌드할것인지인데, 개발용일때는 YES로, 릴리즈일때는 NO로하는게 맞아보인다. 

기존프로젝트는 xcconfig에는 디버깅용일때는 YES인데, xcode gui에서는 NO로 들어가있었다. ㅜ 

 

빨간색과 주황색이 두번나오고 있었다.  arm64와, x86_64 두가지용 로 빌드하고있었던것. 

Planning Swift module (arm64)

Planning Swift module (x86_64) 

 

 

역시.. 프로젝트 구성도 동일하고, 달라진게없는데 이렇게 빌드시간이 차이가 날리가없지...  

그래도 이번기회를 통해서 빌드세팅도 보고, 빌드시간도 줄어들었으니! 좋다. 

 

 

 

 

이외에도.. Sources와 Resources, AdditionalFiles, headers에 파일 추가하는것들.. 

최대한 팀원들이 기존프로젝트처럼 사용할수있게 최대한 예외처리도 해두었다. 

 

 

 

mission 


아직은 마이그레이션만 완료했고, 앞으로 Tuist이용해서 모듈분리도 해볼생각이고, Tuist캐시도 봐야한다.