Xcode에서 아카이브가 성공했더라도 업로드 과정에서 푸시 알림 환경, 연결된 도메인 또는 앱 식별자 불일치로 실패할 수 있습니다. 이런 문제는 대개 컴파일이 아니라 프로젝트가 선언한 엔타이틀먼트, 프로비저닝 프로파일이 허용한 엔타이틀먼트, 최종적으로 App 서명에 기록된 엔타이틀먼트가 서로 일치하지 않아 발생합니다. 클라우드 Mac은 스크립트로 반복 사용되는 경우가 많고, 하나의 노드에서 여러 브랜치와 환경을 처리하기도 합니다. 따라서 저장소의 .entitlements 파일만 확인해서는 안 되며, 실제로 배포할 .xcarchive를 직접 검사해야 합니다.
먼저 검증 대상을 확정하기
다음 절차는 이미 생성된 아카이브를 입력으로 사용합니다. 먼저 메인 App의 위치를 확인합니다. DerivedData에 있는 임시 산출물은 최종 서명이 아직 완료되지 않았을 수 있으므로 직접 검사하지 마십시오.
ARCHIVE_PATH="$HOME/builds/MyApp.xcarchive"
APP_PATH="$ARCHIVE_PATH/Products/Applications/MyApp.app"
test -d "$APP_PATH"
test -f "$APP_PATH/embedded.mobileprovision"
codesign --verify --strict --verbose=2 "$APP_PATH"
App에 확장 기능이 포함되어 있다면 각 .appex도 별도로 검증해야 합니다. 메인 App이 검증을 통과했다고 해서 알림 확장, Widget 또는 공유 확장에 호환되는 앱 식별자와 엔타이틀먼트가 사용되었다고 볼 수는 없습니다.
find "$APP_PATH/PlugIns" -type d -name '*.appex' -print0 2>/dev/null |
while IFS= read -r -d '' item; do
codesign --verify --strict --verbose=2 "$item"
done
사전 검증의 판단 기준은 “프로젝트 설정이 올바르게 보이는가”가 아니라 “곧 업로드할 산출물”이어야 합니다. 전자는 입력일 뿐이고, 후자가 실제 결과입니다.
실제 엔타이틀먼트 목록 두 개 추출하기
codesign으로 최종 서명에 포함된 entitlements를 읽을 수 있고, security cms로 App에 내장된 프로비저닝 프로파일을 디코딩할 수 있습니다. 두 결과를 plist로 저장하면 이후 비교 과정에서 Xcode 화면에 의존할 필요가 없습니다.
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "$WORK_DIR"' EXIT
codesign -d --entitlements :- "$APP_PATH" \
> "$WORK_DIR/app-entitlements.plist" 2>/dev/null
security cms -D -i "$APP_PATH/embedded.mobileprovision" \
> "$WORK_DIR/profile.plist"
plutil -lint "$WORK_DIR/app-entitlements.plist"
plutil -lint "$WORK_DIR/profile.plist"
프로비저닝 프로파일의 최상위 필드를 App과 직접 비교해서는 안 됩니다. 허용된 엔타이틀먼트는 Entitlements 딕셔너리에 있습니다. 생성 시간이나 이름 같은 필드는 문제를 추적하는 보조 정보일 뿐이며 서명 결과를 나타내지 않습니다.
핵심 필드 자동 비교하기
아래 스크립트는 자주 발생하는 다섯 가지 문제를 검사합니다. 문자열은 프로비저닝 프로파일에 포함된 * 와일드카드 범위를 지원합니다. 배열형 엔타이틀먼트는 App에 있는 모든 항목을 프로비저닝 프로파일이 허용해야 합니다. 문제가 있으면 스크립트가 0이 아닌 종료 상태를 반환하므로 아카이브 단계 직후에 연결하기 적합합니다.
python3 - "$WORK_DIR/app-entitlements.plist" "$WORK_DIR/profile.plist" <<'PY'
import fnmatch
import plistlib
import sys
with open(sys.argv[1], "rb") as f:
app = plistlib.load(f)
with open(sys.argv[2], "rb") as f:
profile = plistlib.load(f)["Entitlements"]
keys = [
"application-identifier",
"com.apple.developer.team-identifier",
"aps-environment",
"com.apple.developer.associated-domains",
"get-task-allow",
]
def allowed(actual, expected):
if isinstance(actual, list) and isinstance(expected, list):
return all(any(fnmatch.fnmatch(str(v), str(rule)) for rule in expected) for v in actual)
if isinstance(actual, str) and isinstance(expected, str):
return fnmatch.fnmatch(actual, expected)
return actual == expected
failed = []
for key in keys:
if key not in app:
continue
if key not in profile or not allowed(app[key], profile[key]):
failed.append(key)
if failed:
print("Entitlement mismatch:", ", ".join(failed))
raise SystemExit(1)
print("Entitlement preflight passed")
PY
이 스크립트는 App이 실제로 선언한 필드만 비교합니다. 예를 들어 프로덕션 패키지에는 aps-environment가 반드시 있어야 하는 것처럼 프로젝트 정책상 필수인 항목이 있다면 필수 필드 목록을 추가하고, 누락된 경우에도 실패하도록 해야 합니다.
증상에 따라 설정 원인 찾기
| 증상 | 우선 확인할 항목 | 일반적인 원인 |
|---|---|---|
| 푸시 알림 기능 이상 | aps-environment |
아카이브 환경과 프로비저닝 프로파일의 용도가 일치하지 않음 |
| 유니버설 링크가 작동하지 않음 | com.apple.developer.associated-domains |
도메인 항목이 최종 서명에 기록되지 않았거나 확장 기능의 설정이 다름 |
| 업로드 중 식별자 불일치 오류 발생 | application-identifier |
Bundle Identifier, 팀 접두사 또는 타깃 설정이 혼용됨 |
| 배포용 패키지를 계속 디버깅할 수 있음 | get-task-allow |
배포에 적합하지 않은 빌드 설정을 사용함 |
| 확장 기능 설치 실패 | 메인 App과 .appex의 엔타이틀먼트 |
확장 식별자, 앱 그룹 또는 프로비저닝 프로파일 조합이 호환되지 않음 |
실패가 발생하면 먼저 아카이브와 두 plist를 보존한 다음 CODE_SIGN_ENTITLEMENTS, PRODUCT_BUNDLE_IDENTIFIER, 현재 빌드 설정을 확인하십시오. 곧바로 다시 서명해 기존 상태를 덮어쓰면 가장 중요한 차이 증거가 사라지므로 피해야 합니다.
연결된 도메인과 앱 그룹은 배열이며 일반적으로 순서는 중요하지 않으므로 집합의 포함 관계를 비교해야 합니다. 불리언 필드는 실제 불리언 값으로 읽어야 하며 문자열 "false"를 false로 처리해서는 안 됩니다. grep 대신 plistlib을 사용하는 이유도 여기에 있습니다.
클라우드 빌드 인수 검사에 연동하기
스크립트를 Scripts/verify_entitlements.sh로 저장하고 xcodebuild archive가 성공한 뒤, 내보내기 또는 업로드 전에 실행합니다. 각 작업에는 독립된 아카이브 디렉터리를 사용하고 검사 결과, App 서명 요약, 실패한 필드를 빌드 첨부 파일로 보관합니다. 로그에는 필드 이름과 마스킹한 식별자만 기록하고, 개인 키 내용이나 전체 액세스 자격 증명을 출력하지 마십시오.
인수 검사는 패키지 구조 검증, 서명 엔타이틀먼트 추출, 프로비저닝 프로파일 디코딩, 정책 비교 실행의 네 단계로 나누는 것이 좋습니다. 어느 단계에서든 실패하면 이후 배포 절차를 중단하되, 사후 분석을 위해 .xcarchive는 보존합니다. Target이 여러 개인 프로젝트에서는 메인 App과 각 확장 기능에 대해 별도로 실행해야 하며, 메인 App의 검사 결과를 재사용해서는 안 됩니다.
마지막으로 수동 확인도 한 번 수행합니다. 현재 작업에서 의도한 Scheme과 Configuration을 사용했는지, 프로덕션 산출물의 get-task-allow가 false인지, 푸시 알림 환경이 배포 목적에 맞는지, 연결된 도메인에 테스트 주소가 섞이지 않았는지, 모든 확장 기능이 개별 서명 검증을 통과했는지 확인하십시오. 이렇게 하면 기존에는 업로드 단계에서야 드러나던 오류를 반복 가능하고 추적 가능한 빌드 검사 단계에서 차단할 수 있습니다.
자주 묻는 질문
프로젝트의 entitlements 파일만 확인하면 안 되나요?
충분하지 않습니다. 빌드 설정과 프로파일, 서명 과정이 최종 권한을 바꿀 수 있으므로 아카이브 안의 실제 서명 결과를 확인해야 합니다.
어떤 권한부터 자동 검사하는 것이 좋나요?
application-identifier, 팀 식별자, aps-environment, 연결 도메인, get-task-allow를 우선 검사하는 것이 효과적입니다.
다음 빌드를 클라우드 Mac에서 실행하세요.
세 가지 Apple Silicon 구성을 비교하고, 현재 워크플로에 맞는 리전을 5개의 해외 노드 중에서 선택하세요.