기술 운영 기록

클라우드 Mac CI의 Xcode 최초 실행 초기화

클라우드 Mac CI의 Xcode 최초 실행 초기화

클라우드 Mac에 SSH로 접속할 수 있고 xcodebuild -version도 정상적으로 버전을 출력하더라도, 첫 번째 무인 작업은 라이선스 승인이나 추가 구성 요소 설치, 도구 경로 전환 단계에서 멈출 수 있습니다. 특히 이런 문제는 새 노드를 배포하거나 Xcode를 업그레이드하거나 runner를 다시 구축한 뒤에야 드러나는 경우가 많습니다. 그래픽 인터페이스에서 Xcode를 한 번 수동으로 실행하면 문제가 일시적으로 가려지기도 합니다. 더 안정적인 방법은 최초 실행 초기화를 정식 빌드 도중 환경을 수정하는 절차가 아니라, 별도의 노드 프로비저닝 단계로 취급하는 것입니다.

노드 초기화와 빌드 작업 분리하기

노드 초기화는 시스템 상태를 변경할 수 있고 관리자 권한이 필요하며, Xcode 버전마다 한 번만 실행해야 합니다. 반면 빌드 작업은 반복 실행이 가능하고 비대화형으로 동작해야 하며, 권한 상승 명령은 가능한 한 사용하지 않아야 합니다. 두 절차를 하나의 CI 스크립트에 섞으면 여러 작업이 동시에 구성 요소를 설치하고, 무인 sudo가 입력을 기다리며, 실패 원인이 코드 오류인지 미완료된 환경 설정인지 구분하기 어려워지는 세 가지 문제가 발생합니다.

runner 수명 주기는 다음 세 단계로 나누는 것이 좋습니다.

  1. Xcode를 설치하거나 사용할 버전으로 전환한 뒤 라이선스 승인과 최초 구성 요소 초기화를 완료합니다.
  2. 읽기 전용 사전 검사를 실행하고 실제로 사용하는 Developer 디렉터리와 도구 버전을 기록합니다.
  3. 사전 검사를 통과한 뒤에만 runner가 작업을 받을 수 있는 상태로 전환됩니다.

“Xcode를 실행할 수 있다”는 인수 기준이 아닙니다. 실제 인수 기준은 runner 사용자가 비대화형 Shell에서 컴파일 도구를 찾고 SDK를 조회하며 최소 프로젝트를 한 번 정상적으로 해석할 수 있는지 여부입니다.

실제로 사용할 Xcode 경로 고정하기

한 시스템에 여러 Xcode 버전이 설치되어 있다면 전역 xcode-select에만 의존하지 마십시오. 같은 물리 노드에서 실행되는 다른 작업에도 영향을 주며, 버전을 전환한 뒤에는 “로그에는 한 버전이 표시되지만 실제로는 다른 버전이 호출되는” 착시가 생기기 쉽습니다. CI에서는 작업 단위로 DEVELOPER_DIR을 고정하는 방식이 더 적합합니다.

set -euo pipefail

export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"

printf 'developer_dir=%s
' "$DEVELOPER_DIR"
xcodebuild -version
xcrun --find clang
xcrun --find simctl
xcodebuild -showsdks

버전 번호가 포함된 애플리케이션 디렉터리를 사용한다면 여러 스크립트에 경로를 복사하지 말고 runner 설정이나 버전 관리 대상 환경 파일에 기록하십시오. Xcode를 업데이트할 때는 단일 진입점만 수정하고 사전 검사에서 해당 경로를 출력하도록 합니다. 그러면 로그를 분석할 때 프로젝트 자체를 살펴보기 전에 먼저 사용된 도구 체인을 정확히 확인할 수 있습니다.

GUI 상태를 Shell 검증 대신 사용하지 않기

원격 데스크톱 세션의 환경 변수, 기본 Shell, 권한 컨텍스트는 runner 서비스와 다를 수 있습니다. 모든 검사는 실제 빌드를 실행하는 사용자와 동일한 계정으로 수행해야 합니다. 특히 애플리케이션 디렉터리의 존재 여부만 확인하지 말고, xcrun --find 결과가 예상한 DEVELOPER_DIR 아래에 있는지 검사해야 합니다.

최초 실행 절차를 한 번만 완료하기

노드 준비 단계에서는 먼저 최초 실행 상태를 확인한 다음, 관리자 권한이 있는 초기화 절차를 통해 라이선스를 승인하고 구성 요소를 준비합니다.

set -euo pipefail

export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"

if ! xcodebuild -checkFirstLaunchStatus; then
  sudo xcodebuild -license accept
  sudo xcodebuild -runFirstLaunch
fi

xcodebuild -checkFirstLaunchStatus

여기서 핵심은 명령 자체가 아니라 실행 위치입니다. 이 절차를 커밋마다 실행되는 빌드 스크립트에 넣어서는 안 됩니다. runFirstLaunch는 공유 구성 요소를 변경할 수 있습니다. 두 작업이 동시에 실행하면 시간이 낭비될 뿐만 아니라, 한 작업이 아직 완료되지 않은 상태를 읽을 수도 있습니다.

라이선스 승인 역시 대화 상자에서 버튼을 클릭하는 방식에 의존해서는 안 됩니다. 자동화 환경에서는 명시적으로 명령을 실행하고 종료 코드를 검사하며, 실패하면 해당 노드가 작업을 받지 못하게 해야 합니다. 관리자 명령에 대화형 입력이 필요하다면 노드 프로비저닝 단계에서 처리해야 하며, 백그라운드 runner에 입력을 기다리는 절차를 남겨 두어서는 안 됩니다.

원자적 잠금으로 동시 초기화 방지하기

자동 확장 환경이나 여러 서비스가 동시에 시작되는 환경에서는 한 번만 실행하도록 설계한 초기화 스크립트도 동시에 호출될 수 있습니다. macOS 기본 도구만으로도 디렉터리 생성을 이용해 원자적 잠금을 구현할 수 있습니다.

set -euo pipefail

lock_dir="/tmp/xcode-bootstrap.lock"

if ! mkdir "$lock_dir" 2>/dev/null; then
  echo "Xcode bootstrap is already running" >&2
  exit 75
fi

cleanup() {
  rmdir "$lock_dir" 2>/dev/null || true
}
trap cleanup EXIT

export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
sudo xcodebuild -license accept
sudo xcodebuild -runFirstLaunch
xcodebuild -checkFirstLaunchStatus

mkdir는 동일한 파일 시스템에서 원자적으로 동작하므로 하나의 프로세스만 성공할 수 있습니다. 종료 코드 75는 오케스트레이션 계층에서 일시적 실패로 처리한 뒤 나중에 다시 시도할 수 있습니다. 따라서 노드를 영구적인 빌드 장애 상태로 표시하지 않아도 됩니다.

비정상 종료로 남은 오래된 잠금도 처리해야 합니다. 노드 시작 절차에서 초기화 프로세스가 실행 중이지 않은지 확인한 뒤 제거하거나, 재부팅 후 내용이 비워지는 위치에 잠금 디렉터리를 둘 수 있습니다. 잠금이 보인다는 이유만으로 바로 삭제해서는 안 됩니다. 아직 실행 중인 초기화가 손상될 수 있습니다.

정식 작업 수신 전 사전 검사 게이트 구성하기

최초 실행 상태 검사를 통과하면 시스템을 변경하지 않는 인수 검사를 한 번 더 수행합니다. 최소한 Xcode 식별 정보, SDK 조회, 시뮬레이터 도구, 프로젝트 진입점을 검사해야 합니다.

set -euo pipefail

export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"

xcodebuild -checkFirstLaunchStatus
xcodebuild -version
xcodebuild -showsdks >/tmp/xcode-sdks.txt
xcrun simctl list runtimes
xcodebuild -list -workspace "Example.xcworkspace"

마지막 줄은 저장소에서 실제로 사용하는 workspace 또는 project로 바꿔야 합니다. 전체 archive를 바로 실행하는 것보다 빠르면서도 프로젝트 진입점이 없거나, 의존성 해석을 위한 전제 조건이 충족되지 않았거나, runner의 작업 디렉터리가 잘못된 문제를 미리 발견할 수 있습니다.

실패 시 보존해야 할 진단 정보

진단 로그에는 최소한 DEVELOPER_DIR, xcodebuild -version, xcrun --find clang, xcrun simctl list runtimes, 실패한 명령의 종료 코드를 남겨야 합니다. 환경 변수 전체를 로그에 출력해서는 안 됩니다. 토큰, 서명 암호, 저장소 자격 증명이 포함되어 있을 수 있기 때문입니다.

일반적인 진단 순서는 먼저 경로를 확인하고, 최초 실행 상태를 검사한 다음, 필요한 SDK와 runtime을 확인한 뒤 마지막으로 프로젝트 구성을 살펴보는 것입니다. Xcode 버전을 전환했다면 이전 버전의 초기화가 끝난 상태여도 새로운 Developer 디렉터리를 대상으로 이 절차를 다시 실행해야 합니다. 완료 후에는 초기화 스크립트 버전과 Xcode 버전을 함께 기록하십시오. 그러면 runner를 재구축할 때 누군가 어떤 그래픽 인터페이스를 수동으로 열었던 사실에 의존하지 않고 동일한 순서로 환경을 복원할 수 있습니다.

자주 묻는 질문

화면에서 Xcode가 열리는데 CI는 왜 최초 실행 오류를 냅니까?

대화형 세션과 runner가 서로 다른 Xcode 설치 경로나 환경을 사용할 수 있습니다. CI에 지정한 DEVELOPER_DIR로 checkFirstLaunchStatus를 실행해야 합니다.

모든 CI 작업에서 sudo xcodebuild -runFirstLaunch를 실행해도 됩니까?

권장하지 않습니다. 노드 프로비저닝 또는 Xcode 버전 변경 때 한 번만 실행하고, 일반 작업에서는 시스템을 변경하지 않는 검사만 수행해야 합니다.

Xcode 버전을 바꾸면 초기화를 다시 해야 합니까?

그렇습니다. 새 Developer 디렉터리를 기준으로 라이선스 상태, 도구 구성 요소와 Simulator runtime을 각각 다시 확인해야 합니다.

MacVPSGo 클라우드 Mac

독립형 Apple Silicon 빌드 머신이 필요하신가요?

일·주·월·분기 단위로 독립형 물리 노드를 대여해 Xcode 빌드, iOS CI, 원격 개발 및 자동화 작업에 활용할 수 있습니다.

구성 선택 및 주문