편리함의 대가
소규모부터 대규모 프로젝트까지 사용할 수 있는 코드 편집기를 설계하는 것은 어려운 과제입니다. 많은 도구는 솔루션을 계층화하고 확장성을 제공함으로써 이 문제에 접근합니다. 최하위 계층은 매우 저수준이며 기본 빌드 시스템과 밀접하게 연결되어 있고, 최상위 계층은 사용하기 편리하지만 유연성이 낮은 고수준 추상화입니다. 이를 통해 간단한 작업은 쉽게 만들고, 나머지 모든 작업도 가능하게 합니다.
그러나 Apple은 Xcode에서 다른 접근 방식을 택했습니다. 그 이유는 명확하지 않지만, 대규모 프로젝트의 과제를 최적화하는 것이 그들의 목표였던 적은 없을 가능성이 높습니다. 그들은 소규모 프로젝트의 편의성에 과도하게 투자했고, 유연성은 거의 제공하지 않았으며, 도구를 기본 빌드 시스템과 강하게 결합했습니다. 이러한 편의성을 달성하기 위해 쉽게 대체할 수 있는 합리적인 기본값을 제공하고, 규모 확장 시 많은 문제의 원인이 되는 암시적인 빌드 시간 해결 동작을 많이 추가했습니다.
명시성과 규모#
대규모로 작업할 때 명시성이 핵심입니다. 이는 빌드 시스템이 사전에 프로젝트 구조와 종속성을 분석하고 이해하여, 그렇지 않으면 불가능했을 최적화를 수행할 수 있게 해줍니다. 동일한 명시성은 SwiftUI 미리보기 또는 Swift 매크로와 같은 편집기 기능이 안정적이고 예측 가능하게 작동하도록 보장하는 데에도 핵심적입니다. Xcode와 Xcode 프로젝트는 편의성을 달성하기 위해 암시성을 유효한 설계 선택으로 수용했으며, 이는 Swift Package Manager가 계승한 원칙이기 때문에, Xcode 사용의 어려움은 Swift Package Manager에서도 그대로 나타납니다.
Tuist의 역할을 암시적으로 정의된 프로젝트를 방지하고 명시성을 활용하여 더 나은 개발자 경험(예: 검증, 최적화)을 제공하는 도구라고 요약할 수 있습니다. Bazel과 같은 도구는 이를 빌드 시스템 수준까지 가져가며 한 단계 더 나아갑니다.
이는 커뮤니티에서 거의 논의되지 않지만 중요한 문제입니다. Tuist를 작업하면서 많은 조직과 개발자가 현재 직면한 과제가 Swift Package Manager에 의해 해결될 것이라고 생각하는 것을 발견했지만, 그들이 깨닫지 못하는 점은 동일한 원칙 위에 구축되었기 때문에 잘 알려진 Git 충돌을 완화하더라도 다른 영역에서 개발자 경험을 저하시키고 프로젝트를 최적화 불가능한 상태로 유지한다는 것입니다.
다음 섹션에서는 암시성이 개발자 경험과 프로젝트 상태에 어떻게 영향을 미치는지 실제 예시를 몇 가지 논의하겠습니다. 목록이 포괄적이지는 않지만, Xcode 프로젝트 또는 Swift 패키지로 작업할 때 직면할 수 있는 과제에 대한 좋은 아이디어를 제공할 것입니다.
방해가 되는 편리함#
공유 빌드 제품 디렉터리#
Xcode는 각 제품에 대해 Derived Data 디렉터리 내부의 디렉터리를 사용합니다. 여기에는 컴파일된 바이너리, dSYM 파일 및 로그와 같은 빌드 아티팩트가 저장됩니다. 프로젝트의 모든 제품이 다른 타겟에서 기본적으로 링크 대상으로 보이는 동일한 디렉터리에 들어가므로, 서로 암시적으로 종속되는 타겟이 발생할 수 있습니다. 타겟이 몇 개뿐일 때는 문제가 되지 않을 수 있지만, 프로젝트가 성장하면 디버깅이 어려운 빌드 실패로 나타날 수 있습니다.
이러한 설계 결정의 결과로 많은 프로젝트가 잘 정의되지 않은 그래프로 우연히 컴파일됩니다.
Tuist는 암시적 종속성을 감지하는 명령어를 제공합니다. 이 명령어를 사용하여 CI에서 모든 종속성이 명시적인지 검증할 수 있습니다.
스킴에서 암시적 종속성 찾기#
프로젝트가 커질수록 Xcode에서 종속성 그래프를 정의하고 유지하는 것이 더 어려워집니다. 이는 빌드 단계와 빌드 설정으로 .pbxproj파일에 코딩되어 있고, 그래프를 시각화하고 작업할 수 있는 도구가 없으며, 그래프의 변경 사항(예: 새로운 동적 사전 컴파일된 프레임워크 추가)이 상류 구성 변경(예: 프레임워크를 번들에 복사하는 새로운 빌드 단계 추가)을 필요로 할 수 있기 때문입니다.
Apple은 어느 시점에서 그래프 모델을 더 관리 가능한 형태로 발전시키는 대신, 빌드 시간에 암시적 종속성을 해결하는 옵션을 추가하는 것이 더 합리적이라고 결정했습니다. 이는 다시 한번 questionable한 설계 선택인데, 빌드 시간이 느려지거나 예측 불가능한 빌드가 발생할 수 있기 때문입니다. 예를 들어, 빌드는 Derived Data의 일부 상태로 인해 로컬에서는 통과될 수 있는데, 이는 싱글톤처럼 작동하지만, 상태가 다르기 때문에 CI에서는 컴파일에 실패할 수 있습니다.
프로젝트 스킴에서 이를 비활성화하고, 종속성 그래프 관리를 용이하게 하는 Tuist와 같은 도구를 사용하는 것을 권장합니다.
SwiftUI 미리보기와 정적 라이브러리/프레임워크#
SwiftUI 미리보기 또는 Swift 매크로와 같은 일부 편집기 기능은 편집 중인 파일로부터 종속성 그래프의 컴파일을 필요로 합니다. 편집기 간의 이러한 통합은 빌드 시스템이 모든 암시성을 해결하고 해당 기능이 작동하는 데 필요한 올바른 아티팩트를 출력하도록 요구합니다. 상상할 수 있듯이, 그래프가 더 암시적일수록 빌드 시스템의 작업은 더 어려워지며, 따라서 이러한 많은 기능이 안정적으로 작동하지 않는 것은 놀라운 일이 아닙니다. 개발자들로부터 SwiftUI 미리보기가 너무 불안정해서 오래전에 사용을 중단했다는 이야기를 자주 듣습니다. 대신 예제 앱을 사용하거나, 정적 라이브러리 사용 또는 스크립트 빌드 단계와 같이 기능을 손상시키는 특정 사항을 피하고 있습니다.
병합 가능 라이브러리#
동적 프레임워크는 더 유연하고 작업하기 쉽지만, 앱의 시작 시간에 부정적인 영향을 미칩니다. 반면 정적 라이브러리는 시작 속도가 빠르지만 컴파일 시간에 영향을 주며, 특히 복잡한 그래프 시나리오에서는 작업하기가 조금 더 어렵습니다. 설정에 따라 둘 중 하나를 선택할 수 있다면 좋지 않을까요? Apple이 병합 가능 라이브러리를 개발하기로 결정했을 때 이런 생각을 했을 것입니다. 하지만 다시 한번, 그들은 더 많은 빌드 시간 추론을 빌드 시간으로 이동시켰습니다. 종속성 그래프를 추론해야 한다고 상상해 보세요. 대상의 정적 또는 동적 특성이 일부 대상의 빌드 설정에 따라 빌드 시간에 해결되는 상황에서 이를 수행해야 합니다. SwiftUI 미리보기와 같은 기능이 손상되지 않도록 하면서 이를 안정적으로 작동시키는 것은 쉽지 않을 것입니다.
많은 사용자가 병합 가능 라이브러리를 사용하려는 목적으로 Tuist를 찾지만, 우리의 답변은 항상 동일합니다. 그럴 필요가 없습니다. 생성 시점에 대상의 정적 또는 동적 특성을 제어할 수 있어, 컴파일 전에 그래프가 알려진 프로젝트를 만들 수 있습니다. 빌드 시간에 해결해야 하는 변수가 없습니다.
# 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 프로젝트로 명시성을 관리하기 어렵다면, Tuist나 Bazel과 같은 다른 대안을 고려해야 합니다. 그때야 비로소 신뢰성, 예측 가능성 및 최적화가 가능해집니다.
미래#
Apple이 위의 모든 문제를 방지하기 위해 무언가를 할지는 알 수 없습니다. Xcode와 Swift Package Manager에 내재된 그들의 지속적인 결정은 그들이 그렇게 할 것임을 시사하지 않습니다. 일단 암시적 구성을 유효한 상태로 허용하면, 중단 변경을 도입하지 않고는そこから 벗어나기 어렵습니다. 기본 원칙으로 돌아가 도구의 설계를 재고하면 수년간 우연히 컴파일되어 온 많은 Xcode 프로젝트가 중단될 수 있습니다. 만약 그런 일이 발생한다면 커뮤니티의 반발을 상상해 보세요.
Apple은 일종의 닭이 먼저냐 달걀이 먼저냐 하는 문제에 직면해 있습니다. 편의성은 개발자가 빠르게 시작하고 생태계를 위해 더 많은 앱을 구축하는 데 도움이 됩니다. 그러나 그 규모에서 경험을 편리하게 만들기 위한 그들의 결정은 일부 Xcode 기능이 안정적으로 작동하도록 보장하기 어렵게 만들고 있습니다.
미래가 불확실하기 때문에, 우리는 업계 표준과 Xcode 프로젝트에 최대한 가깝게 유지하려고 노력합니다. 우리는 위의 문제를 방지하고, 더 나은 개발자 경험을 제공하기 위해 우리가 가진 지식을 활용합니다. 이상적으로는 이를 위해 프로젝트 생성에 의존하지 않아도 되겠지만, Xcode와 Swift Package Manager의 확장성 부족으로 인해 이것이 유일한 실현 가능한 옵션입니다. 또한 이는 안전한 옵션이기도 한데, Tuist 프로젝트를 중단시키려면 Xcode 프로젝트를 중단시켜야 하기 때문입니다.
이상적으로는 빌드 시스템이 더 확장 가능했다면 좋았겠지만, 암시성의 세계와 계약하는 플러그인/확장 기능을 갖는 것은 나쁜 아이디어가 아닐까요? 좋은 아이디어로 보이지 않습니다. 따라서 더 나은 개발자 경험을 제공하려면 Tuist나 Bazel과 같은 외부 도구가 필요해 보입니다. 아니면 Apple이 우리 모두를 놀라게 하고 Xcode를 더 확장 가능하고 명시적으로 만들지도 모릅니다...
그런 일이 일어나기 전까지, 여러분은 Xcode의 편의성을 수용하고 그에 따른 부채를 감수할지, 아니면 더 나은 개발자 경험을 제공하기 위한 이 여정에서 우리를 신뢰할지 선택해야 합니다. 우리는 여러분을 실망시키지 않을 것입니다.