Skip to content

번역

이 페이지를 번역하거나 기존 번역을 개선할 수 있습니다.

기여

The cost of convenience

작은 프로젝트에서 대규모 프로젝트까지 사용할 수 있는 코드 편집기를 설계하는 것은 어려운 작업입니다. 이 문제를 해결하기 위해 많은 툴은 솔루션을 계층화하고 확장성을 제공하는 방식으로 접근합니다. 최하위 계층은 매우 저수준이며 기본 빌드 시스템과 밀접하게 연결되어 있고, 최상위 계층은 사용하기 편리하지만 유연성이 떨어지는 고수준 추상화 입니다. 이렇게 함으로써, 간단한 것은 쉽게 만들고 그 외 모든 것을 가능하게 만듭니다.

그러나, Apple은 Xcode에 다른 접근 방식을 취하기로 결정했습니다. 그 이유는 명확하지 않지만, 대규모 프로젝트에 대해 최적화 하는 것이 목표가 아니었을 확률이 큽니다. 그들은 소규모 프로젝트에 대한 편리성에 과도하게 투자하고, 유연성은 거의 제공하지 않으며, 기본 빌드 시스템과 툴을 강하게 결합시켰습니다. 편리함을 제공하기 위해, Apple은 쉽게 대체할 수 있는 기본 설정을 제공하고, 대규모 프로젝트에서 문제를 일으키는 암시적인 빌드 타임 해석 동작을 추가했습니다.

명시성과 규모

대규모 작업을 할 때, 명시성은 핵심입니다. 명시성은 빌드 시스템이 사전에 프로젝트 구조와 의존성을 분석하고 이해하도록 하며, 그렇지 않으면 불가능한 최적화 작업을 수행합니다. 동일한 명시성은 SwiftUI 프리뷰Swift Macros와 같은 편집기 기능이 신뢰할 수 있고 예측 가능한 방식으로 동작하도록 보장하는데도 핵심입니다. Xcode와 Xcode 프로젝트는 편리성을 위해 암시성을 유효한 설계로 채택했기 때문에, Swift Package Manager도 이 원칙을 계승하였으며, Xcode를 사용할 때의 어려움이 Swift Package Manager에서도 나타납니다.

TUIST의 역할

Tuist의 역할은 프로젝트의 암시적 정의를 방지하고 명시성을 활용해 더 나은 개발자 경험 (예: 검증, 최적화) 을 제공하는 툴로 요약할 수 있습니다. Bazel과 같은 툴은 이를 한단계 더 발전시켜 빌드 시스템 수준까지 확장합니다.

이 문제는 커뮤니티에서 거의 언급이 되지 않지만, 중요한 문제입니다. Tuist를 작업하면서, 많은 조직과 개발자들이 현재 직면한 문제를 Swift Package Manager에 의해 해결할 수 있다고 생각하는 것을 발견했지만, 깨닫지 못하는 점은 Swift Package Manager도 동일한 원칙을 기반으로 구축되기 때문에, 잘 알려진 Git 충돌은 해결할 수 있지만, 다른 영역에서 개발자의 경험을 저하시키고 프로젝트 최적화를 어렵게 만든다는 점입니다.

다음 섹션에서 암시적 방식이 개발자 경험과 프로젝트에 어떤 영향을 끼치는지 실제 예제를 통해 다룰 예정입니다. 이 내용이 모든 것을 다루지는 않지만, Xcode 프로젝트나 Swift Package를 작업할 때 직면하는 문제에 대한 해결책을 위한 좋은 아이디어를 제시할 것입니다.

편리함이 장애물이 되는 경우

공유된 빌드 결과물 디렉토리

Xcode는 Derived Data 디렉토리 내에 각 결과물의 디렉토리를 사용합니다. 그 안에는 컴파일된 바이너리, dSYM 파일, 그리고 로그와 같은 빌드 산출물이 저장됩니다. 프로젝트의 모든 결과물이 동일한 디렉토리에 저장되며, 기본적으로 다른 타겟에서 보이기 때문에, 타겟들이 서로 암시적으로 의존성을 가질 수 있습니다. 타겟이 적을 경우 문제가 되지 않지만, 프로젝트가 커지면 이것은 빌드 실패로 이어지고 디버깅하기 어려운 상황이 생길 수 있습니다.

이 설계의 결과로 많은 프로젝트는 명확하게 정의되지 않은 그래프로 컴파일 됩니다.

TUIST의 암시적 의존성 감지

Tuist는 암시적 의존성을 감지하기 위한 명령어를 제공합니다. 해당 명령어를 사용하여 모든 의존성이 명시적으로 선언되었는지 CI에서 검증할 수 있습니다.

스킴에서 암시적 의존성 찾기

프로젝트 규모가 커질수록 Xcode에서 의존성 그래프를 정의하고 유지하기 어려워집니다. 어려운 이유는 빌드 단계와 빌드 설정이 .pbxproj 파일에 코드화 되어 있고, 그래프를 시각화하고 작업할 수 있는 툴이 없으며, 그래프의 변경 사항 (예: 미리 컴파일된 새로운 동적 프레임워크 추가) 이 상위 구성에서의 변경 (예: 번들에 프레임워크를 복사하기 위한 새로운 빌드 단계 추가) 을 요구할 수 있기 때문입니다.

Apple은 어느 시점에 그래프 모델을 더 관리하기 쉬운 형태로 발전시키는 대신에, 빌드 시에 암시적 의존성을 해결하는 옵션을 추가하는 것이 더 합리적이라고 결정하였습니다. 이는 다시 의문점을 남기는데, 빌드 시간이 더 길어지거나 예측할 수 없는 빌드 결과가 나올 수 있기 때문입니다. 예를 들어, 빌드가 로컬에서는 Derive Data의 특정 상태 때문에 정상 동작할 수 있지만, 이 상태는 singleton처럼 동작하므로, 상태가 다른 CI에서는 컴파일이 실패할 수 있습니다.

TIP

우리는 프로젝트 스킴에서 이 기능을 비활성화 하고, 의존성 그래프 관리가 용이한 Tuist 같은 툴을 사용하길 권장합니다.

SwiftUI 프리뷰와 정적 라이브러리/프레임워크

SwiftUI 프리뷰나 Swift Macro와 같은 일부 편집기 기능은 수정된 파일에 의존성 그래프의 컴파일을 필요로 합니다. 편집기의 이런 통합은 빌드 시스템이 모든 암시성을 해결하도록 요구하고 해당 기능이 제대로 동작하도록 올바른 산출물을 출력하도록 요구합니다. 당연히 그래프가 더 암시적이면 빌드 시스템의 작업은 더 어려워지고, 이러한 기능 대부분이 제대로 동작하지 않는 것은 놀라운 일이 아닙니다. 개발자들이 SwiftUI 프리뷰가 제대로 동작하지 않아 오래전에 사용을 중지했다는 얘기를 자주 듣습니다. 대신 해당 기능을 사용하기 위해 예제 앱을 사용하거나, 정적 라이브러리나 스크립트 빌드 단계를 사용하는 것을 피해 기능을 사용하고 있습니다.

Mergeable libraries

작업을 더 유연하고 쉽게 하는 동적 프레임워크는 앱의 실행 시간에 안좋은 영향을 줍니다. 반면에, 정적 라이브러리는 더 빠른 실행 시간을 가지지만, 컴파일 시간에 영향을 주고 복잡한 그래프 환경에서 작업하기 어렵게 만듭니다. 구성에 따라 둘 중 하나로 변경할 수 있다면 좋지 않을까요? 아마 Apple은 Mergeable libraries 작업을 하면서 그 생각을 가졌을 것입니다. 하지만 다시 한 번 Apple은 더 많은 빌드 시간 추론을 빌드 시간으로 옮겼습니다. 의존성 그래프에 대해 추론해야 한다면 타겟의 정적 또는 동적 특성이 일부 타겟의 빌드 설정을 기반으로 빌드 시간이 결정된다고 상상해 봅시다. SwiftUI 프리뷰 기능이 안정적으로 동작하게 하는 것은 쉽지 않습니다.

많은 사용자가 Mergeable libraries를 사용하기 위해 Tuist를 찾지만 우리의 대답은 항상 같습니다. 그럴 필요가 없습니다. 생성 시점에 타겟의 정적 또는 동적 특성을 제어할 수 있으며, 이를 통해 컴파일 전에 의존성 그래프를 미리 알 수 있는 프로젝트를 만들 수 있습니다. 빌드 시점에 해결해야 될 변수는 없습니다.

bash
# The value of TUIST_DYNAMIC can be read from the project {#the-value-of-tuist_dynamic-can-be-read-from-the-project}
# to set the product as static or dynamic based on the value. {#to-set-the-product-as-static-or-dynamic-based-on-the-value}
TUIST_DYNAMIC=1 tuist generate

명시적, 명시적, 그리고 명시적

Xcode로 개발을 확장하려는 모든 개발자나 조직에게 권장하는 중요한 비공식 원칙이 있다면, 그것은 명시성을 채택하는 것입니다. 그리고 명시성을 본래 Xcode 프로젝트에서 관리하기 어려워진다면, TuistBazel과 같은 다른 방법을 고려해볼 필요가 있습니다. 그러면 신뢰성, 예측 가능성, 그리고 최적화가 가능해 집니다.

미래

Apple의 위의 문제를 해결하기 위해 어떤 조치를 취할지는 알 수 없습니다. Xcode와 Swift Package Manager에 반영된 Apple의 지속적인 내용은 Apple은 해당 문제를 해결할 의향이 없음을 시사합니다. 암시적 구성을 유효한 상태로 허용하면, 기존 호환성을 깨는 변경을 도입하지 않으면 이 상태를 벗어나기 어렵습니다. 기본 원칙으로 돌아가 툴의 설계를 재구성하면, 여러 해동안 우연히 컴파일이 되던 많은 Xcode 프로젝트가 더이상 정상적으로 동작 하지 않을 수 있습니다. 이런 일이 일어난다면, 커뮤니티에 일어날 소란을 상상해 보시기 바랍니다.

Apple은 닭이 먼저냐, 달걀이 먼저냐의 문제에 직면해 있습니다. 편리함은 개발자가 빠르게 시작하고 Apple 생태계에서 더 많은 앱을 만들 수 있도록 도와줍니다. 하지만 편리한 경험을 위한 결정이 Xcode의 일부 기능이 신뢰성 있게 동작하기 어렵게 만듭니다.

미래는 알 수 없기 때문에, 우리는 업계 표준과 Xcode 프로젝트에 최대한 부합하도록 노력합니다. 우리는 위의 문제를 방지하고, 우리가 가진 지식을 활용해 더 나은 개발자 경험을 제공합니다. 이론적으로는 프로젝트 생성 할 필요가 없지만, Xcode와 Swift Package Manager의 확장성 부족으로 인해 프로젝트 생성이 유일한 방법입니다. 그리고 이런 방법이 Tuist 프로젝트에 문제가 생기면 Xcode 프로젝트도 문제가 생기기 때문에 안전한 방법입니다.

이론적으로는 빌드 시스템이 더 확장 가능해야 하지만, 암묵적인 방식과 결합되는 플러그인/확장을 두는 것이 더 나쁜 아이디어가 아닐까요? 이것은 좋은 생각이 아닌 것 같습니다. 그래서 더 나은 개발자 경험을 제공하기 위해 Tuist나 Bazel과 같은 외부 툴이 필요합니다. 아니면 Apple이 우리 모두를 놀라게 하고 Xcode를 더 확장 가능하고 명시적으로 만들어 줄지도 모릅니다...

그 일이 일어나기 전까지는, Xcode의 편리함을 받아들이고 그에 따르는 부채를 감수할지, 아니면 더 나은 개발자 경험을 제공하기 위해 우리를 믿고 따라올지 결정해야 합니다. 우린 당신을 실망시키지 않습니다.

Released under the MIT License.