동일한 클라우드 Mac에서 React Native 빌드를 연속으로 실행할 때 가장 진단하기 어려운 문제는 컴파일 오류가 아니라 “코드는 변경됐는데 빌드 결과물에는 반영되지 않은 것처럼 보이는” 현상이다. 로컬에서 다시 실행하면 정상인데 지속적 통합 작업에서는 간헐적으로 이전 모듈을 참조하고, 저장소 전체를 삭제하면 잠시 문제가 사라졌다가 며칠 후 다시 발생하기도 한다. 근본 원인은 대개 Git 작업 트리, Watchman 감시 상태, Metro 변환 캐시, Xcode DerivedData라는 네 가지 상태 계층을 분리하지 않은 데 있다.
캐시 오염이 발생한 계층부터 확인하기
이상이 발생했다고 곧바로 모든 캐시를 삭제하지 않는다. 전체 초기화는 장애 원인을 감출 뿐 아니라 이후 빌드까지 느리게 만든다. 먼저 개발용 패키지에만 포함되는 문자열을 수정하는 것처럼 최소한의 변경으로 문제의 경계를 확인한 다음, 파일 내용과 Metro 모듈 그래프, 최종 애플리케이션 결과물을 각각 점검한다.
작업별 증거 수집하기
각 작업에서 최소한 커밋 해시, 작업 트리의 절대 경로, Node 및 Xcode 버전, Metro 실행 명령, DerivedData 경로를 기록해야 한다. 다음 명령의 출력도 함께 보관한다.
set -euo pipefail
printf 'commit=%s\n' "$(git rev-parse HEAD)"
printf 'workspace=%s\n' "$PWD"
node --version
xcodebuild -version
watchman watch-list || true
find "$PWD" -maxdepth 2 -name metro.config.js -print
커밋과 소스 파일이 모두 올바른데 JavaScript 번들이 여전히 이전 상태라면 Metro부터 확인한다. 아카이브에서 네이티브 코드가 갱신되지 않았다면 DerivedData, 빌드 구성, 실제로 연 작업 공간을 점검한다. Watchman에서 오류가 발생하거나 상위 디렉터리를 감시하고 있다면 감시 경계부터 바로잡는다.
정리는 복구 작업이지 진단 결과가 아니다. 어느 계층이 실패했는지 먼저 확인한 뒤 해당 계층의 상태만 삭제한다.
작업마다 독립된 작업 공간 할당하기
병렬 작업은 동일한 체크아웃 디렉터리에서 브랜치를 전환해서는 안 되며, 여러 작업을 Watchman이 감시하는 하나의 상위 디렉터리 아래에 배치해서도 안 된다. /Users/ci/work/4821/ios-release처럼 파이프라인 번호와 작업 번호가 포함된 경로를 권장한다. 작업이 끝나면 전체 디렉터리를 독립적으로 보관하거나 삭제할 수 있다.
작업 공간은 계속 대상을 바꾸는 심볼릭 링크가 아니라 실제 디렉터리여야 한다. 일부 스크립트는 고정 경로로 “현재 빌드”를 가리키지만, Watchman과 도구 체인에는 이미 해석된 이전 경로가 남아 있을 수 있다. 그 결과 명령은 새 디렉터리에서 실행되는 것처럼 보여도 감시는 여전히 이전 체크아웃을 대상으로 하게 된다.
JOB_ROOT="/Users/ci/work/${PIPELINE_ID}/${JOB_ID}"
WORKSPACE="${JOB_ROOT}/repo"
DERIVED_DATA="${JOB_ROOT}/DerivedData"
METRO_CACHE="${JOB_ROOT}/metro-cache"
mkdir -p "$WORKSPACE" "$DERIVED_DATA" "$METRO_CACHE"
export TMPDIR="${JOB_ROOT}/tmp/"
mkdir -p "$TMPDIR"
디렉터리 이름에는 안정적인 ASCII 문자만 사용하고 공백과 동적 심볼릭 링크는 피한다. 작업마다 루트 디렉터리를 하나씩 사용하면 디스크 사용량과 정리 책임도 더 쉽게 감사할 수 있다.
Watchman 감시 범위 제한하기
작업 공간에 들어간 뒤 watchman watch-project "$WORKSPACE"를 실행하고, 반환된 watch가 예상한 프로젝트 루트인지 확인한다. 공유 상위 디렉터리로 승격된다면 일반적으로 저장소 경계가 명확하지 않거나 상위 디렉터리에 경계 인식에 영향을 주는 설정이 있다는 의미다.
저장소 루트에 .watchmanconfig를 두고 JavaScript 의존성 그래프에 포함되지 않는 대용량 디렉터리를 제외한다.
{
"ignore_dirs": [
".git",
"DerivedData",
"build",
"artifacts"
]
}
이 파일을 수정한 뒤에는 현재 프로젝트의 감시를 다시 등록해야 한다. 동시 작업이 실행되는 머신에서 습관적으로 watchman watch-del-all을 실행하면 다른 작업의 감시까지 삭제되므로 피해야 한다. 안전한 방식은 각 작업 공간을 독립된 감시 루트로 만들고 작업 종료 시 다음 명령만 실행하는 것이다.
watchman watch-del "$WORKSPACE" || true
이 명령에서 대상이 감시 루트가 아니라는 메시지가 표시되면 watchman watch-list를 먼저 확인해야 한다. 경로를 추측해 일괄 삭제하지 않는다.
Metro 및 Xcode 캐시를 명시적으로 관리하기
Metro 캐시는 작업 또는 코드 기준선에 연결해야 하며, 시스템 임시 디렉터리에 출처를 알 수 없는 이전 파일이 남아 있다고 가정해서는 안 된다. 실행 스크립트에서 캐시 위치를 명시적 매개변수로 전달해야 한다. 구체적인 매개변수 이름은 Metro 버전에 따라 달라질 수 있으므로 프로젝트에 고정된 버전의 도움말 출력을 기준으로 삼는다. 명령줄 매개변수를 사용하든 프로젝트 설정을 사용하든, 최종 목표는 여러 작업이 기본 디렉터리를 공유하지 않고 캐시가 ${METRO_CACHE}에 저장되도록 하는 것이다.
--reset-cache는 변환 캐시 손상이 확인된 후 한 번 복구할 때만 사용한다. 모든 작업에서 캐시를 초기화해야 한다면 격리 구성이 아직 완성되지 않은 것이다. Xcode의 DerivedData도 마찬가지로 작업 디렉터리에 고정한다.
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-derivedDataPath "$DERIVED_DATA" \
build
Pods, Swift 패키지, JavaScript 의존성에는 검증된 다운로드 캐시를 사용할 수 있지만, “다운로드 캐시”와 “빌드 상태”는 분리해야 한다. 전자는 잠금 파일의 키를 기준으로 재사용할 수 있지만 후자는 개별 작업이 독점해야 한다. 캐시를 복원한 후에는 최소한 잠금 파일 해시를 확인해야 하며, 브랜치 이름만으로 캐시 적중 여부를 판단해서는 안 된다.
일괄 초기화 대신 계층별 정리 사용하기
이상이 발생하면 영향이 가장 적은 계층부터 처리한다. 먼저 현재 Metro 프로세스를 종료하고 해당 작업의 Metro 캐시를 삭제한 뒤 다시 시작한다. 문제가 계속되면 현재 작업 공간에 대한 Watchman 감시를 제거하고 다시 등록한다. 네이티브 컴파일 결과가 일치하지 않을 때만 해당 작업의 DerivedData를 삭제한다. 저장소를 다시 체크아웃하는 것은 마지막 수단이다.
정리하기 전에 디렉터리를 사용 중인 프로세스가 없는지 확인한다.
pgrep -af "$WORKSPACE" || true
lsof +D "$JOB_ROOT" 2>/dev/null | head -n 50 || true
작업 종료 스크립트에서는 작업 공간 경로가 예상한 루트 디렉터리 아래에 있는지도 검증해야 한다. 그래야 변수가 비어 있을 때 사용자 디렉터리를 실수로 삭제하는 일을 방지할 수 있다. case로 경로 접두사를 허용 목록과 대조한 뒤 rm -rf -- "$JOB_ROOT"를 실행할 수 있다.
검증할 때는 동일한 커밋을 연속으로 두 번 실행한다. 두 번째 실행에서는 다운로드 캐시를 재사용할 수 있지만 작업 공간, Metro 상태, DerivedData에는 계속 작업 단위의 경계를 유지해야 한다. 두 결과물이 다르다면 정리 범위를 즉시 넓히지 말고 먼저 생성된 파일, 환경 변수, 빌드 로그를 비교한다. 이렇게 구축한 절차는 한 번의 캐시 문제를 해결하는 데 그치지 않고 다음 불일치의 원인을 설명할 수 있는 충분한 증거도 남긴다.
자주 묻는 질문
모든 빌드에서 Metro reset-cache를 실행해야 하나요?
아닙니다. 작업별 캐시 디렉터리를 기본으로 사용하고 모듈 그래프나 변환 캐시 손상이 확인된 경우에만 해당 작업의 캐시를 초기화합니다.
여러 작업이 하나의 Watchman 감시 루트를 공유해도 되나요?
권장하지 않습니다. 서로 중첩되지 않는 작업 공간을 사용하고 각 루트를 따로 등록한 뒤 완료된 작업의 감시만 제거해야 합니다.
다음 Xcode 빌드에 사용할 상시 가동 클라우드 Mac을 선택하세요
칩, 메모리, 저장 공간, 결제 주기 및 노드 지역을 확인한 후 구성 절차를 시작하세요. 실제 사용 가능 여부는 콘솔에서 실시간으로 반환되는 상태를 기준으로 합니다.