엔지니어링 가이드

원격 클라우드 Mac의 Xcode 빌드 캐시 최적화

원격 클라우드 Mac의 Xcode 빌드 캐시 최적화

출시를 이틀 앞두고 원격 Mac 한 대를 급히 추가하면 코드를 내려받는 데는 몇 분밖에 걸리지 않지만, 첫 Xcode 빌드는 좀처럼 끝나지 않을 수 있습니다. 두 번째 빌드는 빨라져도 브랜치를 전환하면 오래된 리소스가 남거나 인덱싱 오류가 발생하고, 의존성 해석이 다시 시작되기도 합니다. 이런 문제는 대개 Mac의 성능보다 DerivedData, Swift 패키지 다운로드, 빌드 결과가 기본 디렉터리에 뒤섞여 있는 데서 비롯됩니다. 이 상태에서는 캐시 적중 여부를 판단하기 어렵고, 대여 기간이 끝나기 전에 필요한 데이터만 골라 반출하기도 어렵습니다.

비교 가능한 빌드 기준선 설정

캐시를 최적화하기 전에 먼저 변수를 고정합니다. xcodebuild -version, macOS 버전, 커밋 해시, Scheme, 빌드 구성, 대상 플랫폼을 기록합니다. 테스트 중에는 의존성을 업그레이드하거나 Xcode 버전을 바꾸거나 컴파일 옵션을 수정하지 마십시오. 이런 변경이 동시에 이루어지면 두 빌드의 소요 시간을 제대로 비교할 수 없습니다.

최소한 다음 세 가지 테스트를 수행합니다.

  1. 프로젝트 전용 DerivedData를 삭제한 뒤 콜드 빌드를 수행합니다.
  2. 코드를 변경하지 않고 동일한 명령으로 웜 빌드를 수행합니다.
  3. 일반 Swift 파일 하나를 수정한 뒤 증분 빌드를 수행합니다.

각 실행의 시작 시각, 종료 시각, 종료 코드, 결과 번들을 저장합니다. Xcode GUI의 진행 표시줄은 상태를 살펴보는 데는 유용하지만 정확한 비교에는 적합하지 않습니다. 자동 측정은 일관되게 xcodebuild를 사용해야 합니다.

캐시의 목적은 특정 빌드 한 번을 가장 빠르게 보이게 만드는 것이 아니라, 동일한 입력에서 예측 가능한 시간과 결과를 얻는 것입니다.

역할에 따라 캐시 분리

작업 디렉터리 아래에 소스 코드, DerivedData, Swift 패키지 다운로드, 결과 및 로그를 위한 네 종류의 경로를 만드십시오. 여러 프로젝트가 하나의 DerivedData를 공유하지 않도록 하고, Debug와 Release가 서로의 데이터를 덮어쓰지 않도록 합니다. 프로젝트가 많다면 Xcode 주 버전과 커밋 또는 브랜치 식별자까지 경로에 포함할 수 있습니다.

디렉터리 저장 내용 권장 전략
Source Git 작업 트리 프로젝트별로 분리
DerivedData 인덱스 및 중간 산출물 버전, Scheme, 구성별로 격리
SourcePackages Swift 패키지 다운로드 및 체크아웃 잠금 파일이 바뀌지 않은 경우 재사용
Results 결과 번들 및 로그 빌드 시각별로 보관

Swift 패키지 디렉터리는 의존성 잠금 파일이 동일한 경우에만 재사용할 수 있습니다. 잠금 파일이 변경되었다면 기존 체크아웃 디렉터리를 새 프로젝트에 그대로 복사하지 말고, 의존성 해석이 정상적으로 완료되도록 하십시오. DerivedData는 툴체인에 더 민감합니다. Xcode 버전을 바꾼 뒤에는 기존 디렉터리를 덮어써서 계속 사용하지 말고 새 디렉터리를 만들어야 합니다.

고정된 명령으로 빌드 실행

아래 스크립트는 주요 경로를 명시적으로 지정합니다. 워크스페이스, Scheme, 대상 플랫폼은 실제 프로젝트 값으로 바꾸십시오. 결과 번들 경로는 실행 전에 존재하지 않아야 하므로 스크립트에서 같은 이름의 디렉터리를 먼저 삭제합니다.

#!/bin/bash
set -euo pipefail

ROOT="${HOME}/BuildWorkspace"
SCHEME="Application"
CONFIGURATION="Release"
DERIVED="${ROOT}/DerivedData/${SCHEME}-${CONFIGURATION}"
PACKAGES="${ROOT}/SourcePackages"
RESULT="${ROOT}/Results/${SCHEME}.xcresult"
LOG="${ROOT}/Results/${SCHEME}.log"

mkdir -p "${DERIVED}" "${PACKAGES}" "${ROOT}/Results"
rm -rf "${RESULT}"

xcodebuild \
  -workspace "${ROOT}/Source/Application.xcworkspace" \
  -scheme "${SCHEME}" \
  -configuration "${CONFIGURATION}" \
  -destination "generic/platform=iOS" \
  -derivedDataPath "${DERIVED}" \
  -clonedSourcePackagesDirPath "${PACKAGES}" \
  -resultBundlePath "${RESULT}" \
  build | tee "${LOG}"

먼저 터미널에서 xcodebuild -list를 실행해 워크스페이스에 대상 Scheme이 실제로 공개되어 있는지 확인합니다. 프로젝트가 워크스페이스가 아닌 Xcode 프로젝트 파일을 사용한다면 -workspace-project로 바꾸십시오. 두 옵션을 동시에 전달하면 안 됩니다.

캐시 디렉터리용 안정적인 식별자 생성

캐시 키에는 최소한 Xcode 버전, 의존성 잠금 파일의 요약값, Scheme, 구성이 포함되어야 합니다. 브랜치 이름을 추가할 수는 있지만 브랜치 이름만 사용해서는 안 됩니다. 같은 브랜치에서도 의존성은 달라질 수 있기 때문입니다.

XCODE_KEY="$(xcodebuild -version | shasum -a 256 | cut -c1-12)"
PACKAGE_KEY="$(shasum -a 256 Package.resolved | cut -c1-12)"
printf '%s-%s-%s-%s
' "${XCODE_KEY}" "${PACKAGE_KEY}" "${SCHEME}" "${CONFIGURATION}"

워크스페이스에 Package.resolved가 여러 개 있다면 실제 빌드에 사용되는 파일을 명확히 선택해야 합니다. 잠금 파일을 찾을 수 없을 때 고정된 빈 키를 생성하지 마십시오. 그러면 서로 다른 의존성 상태가 같은 디렉터리에 저장됩니다.

단계별로 불필요한 재컴파일 진단

웜 빌드도 여전히 느리다면 어느 단계에서 시간이 소요되는지 먼저 확인합니다. 의존성 해석이 반복되면 스크립트가 잠금 파일을 다시 쓰고 있는지, 패키지 디렉터리 권한이 올바른지 점검합니다. 컴파일 단계가 계속 반복되면 컴파일 조건, 자동 생성 파일, 빌드 단계 스크립트를 확인합니다. 링크 단계가 비정상적이라면 버전 정보나 포함되는 콘텐츠가 빌드할 때마다 변경되는지 살펴봅니다.

가장 흔한 원인은 입력과 출력을 선언하지 않은 스크립트 단계입니다. 스크립트가 실행될 때마다 생성 파일의 타임스탬프를 갱신하면 이후 대상은 다시 빌드해야 하는 것으로 판단됩니다. 파일 내용이 바뀌었을 때만 교체하도록 스크립트를 수정하고, Xcode 빌드 단계에 정확한 입력 및 출력 경로를 지정해야 합니다.

‘모든 캐시 삭제’를 고정 절차로 삼지 마십시오. 이 방법으로는 캐시 오염 여부만 확인할 수 있을 뿐, 오염의 원인은 찾을 수 없습니다. 먼저 프로젝트의 DerivedData만 다른 위치로 옮기고, 문제가 계속되면 Swift 패키지 디렉터리를 처리하십시오. 이렇게 하면 진단 근거를 보존하면서 테스트할 때마다 모든 의존성을 다시 내려받는 일을 피할 수 있습니다.

대여 종료 전에 필요한 데이터만 보관

데이터를 반출하기 전에 실행 중인 빌드를 중지하고 로그 기록이 모두 끝났는지 확인합니다. 그런 다음 의존성 잠금 파일, 필요한 Swift 패키지 다운로드, 결과 번들, 프로젝트 스크립트를 패키징합니다. 소스 코드는 저장소에 커밋된 내용을 기준으로 삼고, 작업 트리의 커밋되지 않은 변경 사항은 별도로 점검해야 합니다.

DerivedData 전체를 장기간 보관하는 것은 권장하지 않습니다. 대개 용량이 크고 인덱스, 오브젝트 파일, 특정 툴체인에 종속된 중간 산출물이 포함되어 있기 때문입니다. 다음에 다른 Xcode 버전을 사용한다면 새로 생성하는 편이 더 안정적입니다. 민감한 인증서, 개인 키, 토큰, 임시 환경 파일을 캐시 아카이브에 포함하지 마십시오. 먼저 Shell 기록, 임시 키체인, 프로젝트에서 생성한 자격 증명 파일을 정리한 뒤 반출 목록을 다시 확인합니다.

마지막으로 툴체인 버전, 캐시 키, 마지막 성공 커밋, 빌드 명령, 반출한 경로, 명시적으로 반출하지 않은 경로를 간단히 기록해 두십시오. 다음 클라우드 Mac에서 이 기록을 기준으로 환경을 복원하는 편이 홈 디렉터리 전체를 복사하는 것보다 재현하기 쉽습니다.

자주 묻는 질문

서로 다른 Xcode 버전이 DerivedData를 공유해도 되나요?

공유하지 않는 편이 안전합니다. Xcode 버전, 프로젝트, Scheme, 빌드 설정별로 경로를 나누고 도구 체인을 바꾼 뒤에는 DerivedData를 다시 생성해야 합니다.

대여 종료 전에 어떤 캐시를 우선 반출해야 하나요?

의존성 잠금 파일, 재사용 가능한 Swift 패키지 다운로드, 결과 번들, 문제 분석용 로그를 우선 반출합니다. 객체 파일과 인덱스는 도구 체인 의존성이 높아 다시 만드는 편이 낫습니다.

빌드가 느릴 때 캐시를 전부 지우면 되나요?

캐시 오염 여부를 확인하는 일회성 진단에는 쓸 수 있지만 상시 해결책은 아닙니다. 의존성 해석, 컴파일, 링크, 테스트 시간을 나눠 측정한 뒤 관련 경로만 정리해야 합니다.

ArmMacs Cloud Mac

프로젝트 기간에 맞춰 전용 물리 노드 사용

고정 칩, 메모리, 스토리지, 대여 기간 및 판매 중인 노드를 선택하고, 재고가 있으면 제공 절차를 시작합니다.

모델 선택 및 주문