Unity 프로젝트가 로컬에서는 iOS 프로젝트를 정상적으로 내보내지만, 클라우드 Mac으로 옮기면 에셋 임포트, 네이티브 의존성 또는 링크 단계에서 멈추는 경우가 있습니다. 가장 까다로운 점은 실패 자체가 아니라 모든 단계가 하나의 명령에 묶여 있다는 것입니다. 로그에는 “빌드 실패”만 남아 Unity를 다시 실행해야 하는지, 캐시를 비워야 하는지, 아니면 Xcode 프로젝트를 점검해야 하는지 판단하기 어렵습니다. 더 안정적인 방법은 파이프라인을 두 개의 독립된 관문으로 분리하는 것입니다. 먼저 감사 가능한 Xcode 프로젝트를 생성하고, 그다음 코드 서명 없이 네이티브 컴파일을 검증합니다.
입력과 산출물의 경계를 먼저 정의하기
재현 가능한 빌드를 위해서는 최소한 네 가지 입력을 고정해야 합니다. 커밋 버전, Unity 에디터 버전, 패키지 의존성 잠금 파일, 씬 목록입니다. 스크립트가 특정 개발자 컴퓨터의 에디터 설정을 임시로 읽게 해서는 안 되며, 이전에 내보낸 Xcode 프로젝트를 그대로 덮어쓴 뒤 컴파일을 계속해서도 안 됩니다.
각 작업마다 독립된 디렉터리를 준비하는 것이 좋습니다.
| 경로 | 용도 | 캐시 가능 여부 |
|---|---|---|
Source/ |
현재 커밋의 Unity 프로젝트 | 아니요 |
Library/ |
에셋 임포트 결과 | 조건부 |
Build/iOS/ |
이번 작업에서 내보낸 Xcode 프로젝트 | 아니요 |
.build/DerivedData/ |
네이티브 컴파일 중간 파일 | 조건부 |
Artifacts/ |
로그, 요약 및 검증 결과 | 아니요 |
Library의 캐시 키에는 Unity 버전, 대상 플랫폼, Packages/manifest.json과 Packages/packages-lock.json의 해시가 포함되어야 합니다. 이 중 하나라도 변경되면 에셋을 다시 임포트해야 합니다. DerivedData 역시 서로 다른 Xcode 버전이나 프로젝트 설정 사이에서 그대로 재사용하면 안 됩니다.
캐시는 고정된 입력의 처리 속도만 높일 수 있을 뿐, 버전 고정을 대신할 수는 없습니다. 경계가 불명확한 캐시가 적중하면 전체를 다시 빌드하는 것보다 문제를 추적하기가 더 어려워지는 경우가 많습니다.
배치 모드로 Xcode 프로젝트 내보내기
내보내기 진입점은 Assets/Editor/IosExport.cs에 두고, 씬 목록은 Build Settings에서 활성화된 항목을 기준으로 생성합니다. 이렇게 하면 스크립트와 에디터 UI에서 두 개의 목록을 따로 관리하지 않아도 됩니다.
using System;
using System.Linq;
using UnityEditor;
using UnityEditor.Build.Reporting;
public static class IosExport
{
public static void Run()
{
var scenes = EditorBuildSettings.scenes
.Where(scene => scene.enabled)
.Select(scene => scene.path)
.ToArray();
if (scenes.Length == 0)
throw new InvalidOperationException("No enabled scenes.");
var options = new BuildPlayerOptions
{
scenes = scenes,
locationPathName = "Build/iOS",
target = BuildTarget.iOS,
options = BuildOptions.CleanBuildCache
};
var report = BuildPipeline.BuildPlayer(options);
if (report.summary.result != BuildResult.Succeeded)
throw new InvalidOperationException("Unity export failed.");
}
}
실행할 때는 프로젝트 디렉터리, 로그 경로, 메서드 이름을 명시적으로 전달합니다.
"$UNITY_EDITOR" \
-batchmode \
-nographics \
-quit \
-projectPath "$PWD" \
-executeMethod IosExport.Run \
-logFile "$PWD/Artifacts/unity-export.log"
작업을 시작하기 전에 Build/iOS는 삭제해야 하지만, Library까지 무조건 삭제해서는 안 됩니다. 작업이 끝나면 종료 코드와 로그 파일, Build/iOS/Unity-iPhone.xcodeproj가 모두 존재하는지 확인해야 합니다. 디렉터리가 있다는 사실만으로 내보내기에 성공했다고 볼 수는 없습니다. 실패한 작업도 미완성 산출물을 남길 수 있습니다.
네이티브 의존성과 컴파일 검증 분리하기
Unity 내보내기가 성공했다는 것은 생성 작업이 완료되었다는 뜻일 뿐, Objective-C, Swift, IL2CPP 산출물과 네이티브 라이브러리를 함께 컴파일할 수 있다는 의미는 아닙니다. 먼저 프로젝트에서 정한 네이티브 의존성 설치 단계를 실행한 다음, workspace와 project 중 무엇을 빌드할지 판단해야 합니다. 의존성 도구가 .xcworkspace를 생성했는데도 .xcodeproj를 계속 빌드하면 모듈을 찾지 못하거나 링크할 라이브러리가 누락되는 경우가 많습니다.
set -euo pipefail
cd Build/iOS
rm -rf ../../.build/DerivedData
if [ -d "Unity-iPhone.xcworkspace" ]; then
xcodebuild \
-workspace Unity-iPhone.xcworkspace \
-scheme Unity-iPhone \
-configuration Release \
-sdk iphoneos \
-derivedDataPath ../../.build/DerivedData \
CODE_SIGNING_ALLOWED=NO \
build
else
xcodebuild \
-project Unity-iPhone.xcodeproj \
-scheme Unity-iPhone \
-configuration Release \
-sdk iphoneos \
-derivedDataPath ../../.build/DerivedData \
CODE_SIGNING_ALLOWED=NO \
build
fi
이 단계의 목적은 컴파일과 링크를 검증하는 것이지, 배포 가능한 설치 패키지를 만드는 것이 아닙니다. 코드 서명은 이후의 통제된 단계에서 수행해야 합니다. 일반적인 커밋 검증 작업이 불필요한 민감 정보에 접근하지 않도록 하기 위해서입니다.
추측이 아니라 명확한 규칙으로 캐시 무효화하기
Unity 프로젝트에서 흔히 저지르는 실수는 브랜치 이름만으로 캐시 키를 만드는 것입니다. 두 커밋이 같은 브랜치에 있다고 해서 에셋 임포트 결과까지 같다는 보장은 없습니다. 먼저 입력 해시를 생성할 수 있습니다.
{
printf '%s
' "$UNITY_VERSION"
printf '%s
' "$XCODE_VERSION"
shasum -a 256 Packages/manifest.json
shasum -a 256 Packages/packages-lock.json
find Assets -name '*.asmdef' -o -name '*.rsp' | sort | xargs shasum -a 256
} | shasum -a 256 | awk '{print $1}' > Artifacts/cache-key.txt
작업 디렉터리 전체를 캐시하지 않기
프로젝트 디렉터리 전체를 묶어 캐시하면 오래된 내보내기 산출물, 임시 설정, 이미 삭제된 파일까지 섞이게 됩니다. Library와 DerivedData는 따로 관리하고, 복원한 뒤에도 입력 해시를 검증해야 합니다. 원인을 설명하기 어려운 컴파일 차이가 발생하면 먼저 실패 로그를 보존한 다음 빈 캐시로 한 번 다시 실행합니다. 빈 캐시에서는 성공한다면 비즈니스 로직을 바로 수정할 것이 아니라 캐시 경계 문제로 분류해야 합니다.
재현과 분석에 필요한 최소 증거 보관하기
빌드가 통과해도 성공 표시 하나만 저장해서는 안 됩니다. 최소한 Unity 전체 로그, Xcode 출력, 커밋 해시, 도구 버전, 캐시 키, 내보낸 프로젝트의 요약을 보관해야 합니다. 프로젝트 요약에는 scheme, configuration, 빌드 설정을 기록할 수 있지만 자격 증명, 토큰, 로컬 컴퓨터의 비공개 경로는 제외해야 합니다.
일반적인 실패는 단계별로 빠르게 분류할 수 있습니다.
- Unity 로그에서
IosExport.Run이 실행되지 않음: 메서드 이름, 스크립트 컴파일 오류, 에디터 버전을 확인합니다. - 내보내기 단계에서 씬이 비어 있다고 보고됨: 경로를 임시로 하드코딩하지 말고 Build Settings에서 활성화된 씬을 확인합니다.
- workspace가 있는데도 스크립트가 project를 빌드함: 의존성 설치가 끝난 뒤 감지하도록 검사 시점을 조정합니다.
- IL2CPP 또는 네이티브 라이브러리 링크 실패: 대상 플랫폼, 플러그인 아키텍처, 조건부 임포트 설정을 확인합니다.
- 같은 커밋이 간헐적으로 성공함: 캐시를 비활성화해 다시 실행하고 두 작업의 캐시 키와 도구 버전을 비교합니다.
- 로컬에서는 성공하지만 원격 환경에서는 실패함: 먼저 컴퓨터 성능 탓으로 돌리지 말고 경로의 대소문자, 커밋되지 않은 파일, 환경 변수를 비교합니다.
마지막으로 “내보내기 성공”과 “네이티브 컴파일 성공”을 두 개의 독립된 상태로 정의합니다. 전자가 실패하면 Xcode를 실행할 필요가 없고, 후자가 실패해도 모든 에셋을 반복해서 임포트할 필요가 없습니다. 이렇게 구성하면 파이프라인을 더 쉽게 재시도할 수 있고, 로그가 짧아지며, 책임 경계도 한층 명확해집니다.
자주 묻는 질문
Unity에서 바로 최종 패키지를 만들지 않고 단계를 나누는 이유는 무엇인가요?
Unity 스크립트와 리소스 가져오기 문제를 네이티브 의존성 및 Xcode 컴파일 문제와 분리할 수 있기 때문입니다. 서명 없이 먼저 컴파일하면 자격 증명을 사용하기 전에 코드와 링크 상태를 검증할 수 있습니다.
Library 캐시를 다른 프로젝트나 Unity 버전에서 함께 써도 되나요?
권장하지 않습니다. 캐시 키에는 Unity 버전, 의존성 잠금 파일, 대상 플랫폼을 포함하고 하나라도 달라지면 Library를 다시 생성해야 합니다.
xcodeproj와 xcworkspace 중 무엇을 사용해야 하나요?
네이티브 의존성 설치 후 workspace가 생성되면 xcworkspace를 사용하고, 생성되지 않았을 때만 xcodeproj를 사용합니다. 의존성 준비가 끝난 뒤 파일 존재 여부를 검사하는 방식이 안전합니다.
다음 iOS 빌드를 MiniDebug M4에서 실행하세요.
M4, 16GB RAM, 256GB SSD를 기본으로 제공하며 일·주·월·분기 단위로 대여할 수 있습니다. 싱가포르, 일본 도쿄, 한국 서울, 홍콩, 미국 동부 등 5개 노드 중에서 선택하세요. 실제 이용 가능 여부는 콘솔에서 실시간으로 확인할 수 있습니다.