ENGINEERING ARTICLE

클라우드 Mac에서 Swift 컴파일러 충돌 진단하기

클라우드 Mac에서 Swift 컴파일러 충돌 진단하기

동일한 커밋이 개발용 Mac에서는 정상적으로 빌드되지만, 클라우드 Mac의 지속적 빌드 환경에서는 swift-frontend가 비정상 종료될 수 있습니다. 다시 실행하면 간혹 성공하고, 캐시를 비우면 이번에는 다른 파일에서 실패하기도 합니다. 이때 가장 위험한 대응은 소스 코드 수정, 의존성 업그레이드, 전체 캐시 삭제를 연달아 수행하는 것입니다. 여러 조건이 동시에 바뀌면서 컴파일러 충돌을 일으킨 실제 원인까지 사라질 수 있기 때문입니다. 먼저 장애 유형을 확인한 다음, 한 번에 하나의 변수만 바꾸는 원칙에 따라 범위를 좁히는 편이 안전합니다.

먼저 컴파일러 충돌인지 확인하기

CompileSwiftSources 실패가 곧 컴파일러 충돌을 의미하지는 않습니다. 문법 오류, 타입 추론 실패, 누락된 모듈도 이 단계에서 0이 아닌 종료 상태를 반환할 수 있습니다. 다음과 같은 신호가 있을 때 이 글의 진단 절차를 적용할 수 있습니다.

  • 로그에 swift-frontend의 비정상 종료 또는 signal 11이 명시되어 있습니다.
  • 출력에 Stack dump, 내부 assertion 실패 또는 컴파일러 호출 스택이 포함되어 있습니다.
  • ~/Library/Logs/DiagnosticReports/에 발생 시각이 일치하는 swift-frontend 보고서가 생성되어 있습니다.
  • 일회성 원격 세션 중단이 아니라, 동일한 소스와 명령으로 여러 번 재현됩니다.

로그의 마지막 열 줄만 발췌하지 말고, 먼저 툴체인과 호스트 정보를 기록합니다.

mkdir -p diagnostics
{
  date -u
  sw_vers
  uname -m
  xcode-select -p
  xcodebuild -version
  xcrun swiftc --version
} | tee diagnostics/environment.txt

find "$HOME/Library/Logs/DiagnosticReports" \
  -maxdepth 1 -type f -name 'swift-frontend*' \
  -exec cp {} diagnostics/ \;

충돌 보고서, 전체 빌드 명령, 충돌을 유발한 소스 코드는 반드시 같은 재현 실행에서 수집해야 합니다. 서로 다른 실행에서 얻은 자료를 한데 모으면 검증할 수 없는 잘못된 단서가 만들어지기 쉽습니다.

격리된 빌드로 최초 상태 보존하기

재현 전용 DerivedData를 준비하고 터미널 출력을 빠짐없이 저장합니다. 캐시가 충돌 조건에 영향을 미치는지 여부도 검증해야 할 변수이므로, 처음부터 전역 캐시를 삭제하지 마십시오.

set -o pipefail
rm -rf "$PWD/.diagnostics-derived-data"

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Debug \
  -destination 'generic/platform=iOS Simulator' \
  -derivedDataPath "$PWD/.diagnostics-derived-data" \
  -jobs 1 \
  build 2>&1 | tee diagnostics/build-single-job.log

여기서 동시 작업 수를 1로 낮추는 목적은 충돌이 병렬 컴파일과 관련되어 있는지 확인하는 것이며, 장기적으로 이 설정을 사용하라는 의미는 아닙니다. 단일 작업에서도 계속 충돌한다면 해당 로그를 보관합니다. 반대로 문제가 사라진다면 소스와 DerivedData를 고정한 상태에서 -jobs 2와 평소 사용하는 동시 작업 수를 각각 테스트합니다. 각 조건을 최소 세 번 반복하고 성공, 일반 컴파일 오류, 컴파일러 충돌 중 어떤 결과가 발생했는지 기록해야 합니다. 단순히 “실패”라고만 적어서는 안 됩니다.

OVPS 클라우드 Mac에서 재현할 때는 실제로 선택된 Xcode 경로도 기록해야 합니다. UI에 표시되는 버전 이름만 비교해서는 충분하지 않습니다. 같은 메이저 버전에서도 빌드 번호와 Swift 툴체인이 다를 수 있습니다.

단일 변수 비교 매트릭스 만들기

조사는 비용이 가장 낮고 변경 영향이 가장 작은 변수부터 시작해야 합니다. 한 번에 한 항목만 변경하고 매번 새 로그를 별도로 저장합니다.

비교 항목 기준값 변경값 확인할 사항
동시 작업 수 -jobs 1 평소 사용하는 동시 작업 수 병렬 컴파일에서만 발생하는가
DerivedData 격리된 디렉터리 새로 만든 빈 디렉터리 기존 중간 산출물에 의존하는가
컴파일 모드 현재 프로젝트 값 임시 비교값 증분 컴파일 또는 전체 모듈 컴파일 경로와 관련 있는가
최적화 수준 Debug 설정 프로젝트에서 허용되는 비교 설정 최적화 단계에서만 발생하는가
툴체인 현재 고정 버전 검증된 다른 버전 특정 툴체인의 회귀 문제인가

Xcode 변경, 의존성 업데이트, 캐시 삭제를 동시에 수행하지 마십시오. 툴체인을 바꾼 뒤 문제가 사라졌더라도, 이는 장애가 해당 툴체인 조합과 관련 있다는 의미일 뿐 새 버전에서 문제가 수정되었다는 직접적인 증거는 아닙니다. 실패한 파일, 컴파일 모드, 재현 횟수도 동일한 기록표에 함께 정리해야 합니다.

캐시로 인한 착시 구분하기

빈 DerivedData에서는 빌드가 성공하고 기존 디렉터리에서는 계속 실패할 때만 모듈 캐시와 빌드 데이터베이스를 추가로 조사합니다. 문제가 발생한 디렉터리를 먼저 보관한 뒤 필요한 부분만 선별적으로 정리합니다. 처음부터 광범위하게 삭제하면 이전 산출물과 새 산출물을 비교할 기회를 잃게 됩니다.

실패한 파일에서 최소 재현 사례로 축소하기

전체 로그에서 실패한 Swift 컴파일 작업을 찾습니다. 프로젝트와 분리해 문제를 재현할 수 있다면 관련 선언을 Repro.swift로 복사하고, 먼저 타입 검사를 실행해 확인합니다.

xcrun swiftc -typecheck Repro.swift 2>&1 | tee diagnostics/repro.log

소스를 축소할 때는 의존성의 역순으로 제거합니다. 먼저 관련 없는 메서드를 삭제한 뒤 프로토콜 구현, 제네릭 제약 조건, 프로퍼티 래퍼를 차례로 제거합니다. 블록 하나를 삭제할 때마다 판정 스크립트를 실행해야 합니다. 어떤 구문이 “복잡해 보인다”는 인상만으로 원인을 추측하지 마십시오. 컴파일러 충돌은 평범한 기능 두 가지의 조합으로도 자주 발생합니다.

#!/bin/zsh
set -o pipefail

output="$(mktemp)"
xcrun swiftc -typecheck Repro.swift >"$output" 2>&1

if grep -Eiq 'signal 11|segmentation fault|stack dump|swift-frontend.*failed' "$output"; then
  cp "$output" diagnostics/latest-crash.log
  rm -f "$output"
  exit 0
fi

rm -f "$output"
exit 1

이 스크립트는 “대상 충돌이 여전히 발생함”을 성공으로 정의하므로 수동 이분 탐색이나 소스 축소 도구와 함께 사용하기 좋습니다. 소스를 축소할 때마다 오류 시그니처가 동일한지도 확인해야 합니다. 내부 assertion 실패가 일반 타입 오류로 바뀌었다면 핵심 조건을 이미 제거한 것입니다.

단일 파일로 재현할 수 없다면 최소한의 모듈 경계를 유지합니다. 간소화한 프로젝트, 필수 빌드 설정, 고정된 의존성 버전, 실행 명령 하나만 남깁니다. 비즈니스 데이터, 접근 자격 증명, 관련 없는 리소스를 포함해서는 안 됩니다.

재검증 가능한 전달 패키지 만들기

최종 자료는 별도의 설명 없이도 다른 클라우드 Mac에서 검증할 수 있어야 합니다. 권장 디렉터리 구성은 다음 항목으로 제한합니다.

  • README.md: 예상 현상, 실행 명령, 반복 횟수와 실제 결과.
  • environment.txt: 시스템 아키텍처, Xcode 빌드 번호와 Swift 버전.
  • Repro.swift 또는 간소화한 프로젝트: 필요한 소스만 유지.
  • build.log: 잘리지 않은 표준 출력과 표준 오류.
  • 충돌 보고서: 해당 실행 시각과 일치하는 진단 파일.
  • check.sh: 대상 충돌의 재현 여부를 종료 상태로 명확히 표시하는 스크립트.

전달하기 전에 새 디렉터리에서 다시 실행하여 스크립트가 원본 프로젝트의 절대 경로, 사용자 디렉터리 또는 남아 있는 모듈 캐시에 의존하지 않는지 확인합니다. 문제가 특정 동시 작업 수에서만 발생한다면 해당 매개변수와 재현 확률을 명시해야 합니다. 특정 툴체인 빌드 번호에서만 발생한다면 정상적으로 통과하는 비교 버전도 함께 기록합니다. 이렇게 해야 “Swift가 가끔 충돌했다”는 모호한 설명이 아니라, 반복 재현하고 비교하며 후속 수정에 활용할 수 있는 엔지니어링 증거를 확보할 수 있습니다.

자주 묻는 질문

CompileSwiftSources 실패는 항상 컴파일러 충돌을 뜻하나요?

아닙니다. 문법 오류, 타입 검사 실패, 의존성 누락도 같은 단계에서 실패합니다. swift-frontend 비정상 종료, signal, Stack dump 또는 대응하는 충돌 보고서가 있는지 확인해야 합니다.

최소 재현 사례에 전체 Xcode 프로젝트가 필요하나요?

필수는 아닙니다. 단일 Swift 파일과 swiftc 명령으로 안정적으로 충돌한다면 그것이 더 좋은 사례입니다. 모듈 경계나 빌드 설정이 필요할 때만 축소된 프로젝트를 유지합니다.

OVPS CLOUD MAC

다음 빌드는 독점 물리 노드에서 실행하세요

세 가지 Apple Silicon 구성과 현재 판매 중인 여섯 개 노드 중에서 선택할 수 있으며, 실제 이용 가능 여부는 콘솔에 실시간으로 표시되는 상태를 기준으로 합니다.

구성 선택 및 주문