iOS 프로젝트가 개발 브랜치에서는 정상적으로 컴파일되더라도, Archive에는 동적 Framework가 조용히 추가되거나 개발용 Mac의 디렉터리를 가리키는 절대 경로 링크가 포함되거나, 내부에서만 사용할 예정이었던 전역 심볼이 노출될 수 있습니다. 프로젝트 파일 검토만으로는 이런 변화를 모두 확인하기 어렵습니다. 실제로 배포되는 것은 링크가 완료된 Mach-O이기 때문입니다. 더 안정적인 방법은 클라우드 Mac CI에서 아카이브 산출물을 직접 검사하고, 의존성과 심볼의 변화를 검토가 필요한 차이로 만드는 것입니다.
차단할 변경 사항부터 정의하기
이러한 게이트는 특정 라이브러리가 “좋은지 나쁜지” 판단하는 대신, 검증 가능한 다음 세 가지 질문에 답해야 합니다.
- 메인 앱과 내장 Framework가 실제로 무엇에 의존하는가?
@rpath가 가리키는 번들 라이브러리가 App 패키지 안에 실제로 존재하는가?- 새로 추가된 전역 심볼이 의도한 인터페이스에 속하는가?
기준선은 영구적인 허용 목록이 아니라 사람이 검토하고 승인한 산출물의 스냅샷입니다. 도구 체인 업그레이드, 의존성 업데이트 또는 인터페이스 변경 후에는 기준선을 갱신할 수 있지만, 반드시 코드 변경과 함께 검토해야 합니다.
검사는 Release Archive 이후에 수행하는 것이 좋습니다. Debug 빌드에는 진단 라이브러리가 포함되거나 최적화 결과가 다를 수 있으므로 최종 배포 경계를 대표하지 못합니다. 처음 도입할 때는 보고서만 생성하고 즉시 빌드를 차단하지 마십시오. 팀에서 기준선 내용을 확인한 후 차이가 발생하면 실패하도록 전환합니다.
Archive에서 검사 대상 추출하기
먼저 별도의 아카이브를 생성해 개발자의 로컬 Mac에 남아 있는 이전 산출물을 재사용하지 않도록 합니다.
rm -rf out/App.xcarchive
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-destination 'generic/platform=iOS' \
-archivePath "$PWD/out/App.xcarchive" \
clean archive
서명을 후속 독립 작업에서 처리한다면 프로젝트 상황에 맞게 CODE_SIGNING_ALLOWED=NO를 설정할 수 있습니다. 이 재정의 옵션을 모든 프로젝트에 기계적으로 추가해서는 안 됩니다. 특정 capability나 사용자 정의 빌드 단계를 포함한 프로젝트라면 서명 없는 아카이브가 완전한지 먼저 확인해야 합니다.
아카이브의 메인 앱은 일반적으로 Products/Applications에 있습니다. 검사 범위에는 최소한 App의 메인 실행 파일과 Frameworks 디렉터리에 있는 각 Framework의 실행 파일이 포함되어야 합니다. 확장 타깃도 자체적인 의존성 경계를 가지므로 별도로 검사해야 합니다.
| 대상 | 주요 검사 항목 | 일반적인 이상 징후 |
|---|---|---|
| App 메인 실행 파일 | 시스템 라이브러리, 번들 Framework, 전역 심볼 | 시스템 외부의 절대 경로 |
| 동적 Framework | 2차 의존성, 설치 이름, 공개 심볼 | 패키지에 포함되지 않은 의존성 |
| App Extension | 독립적인 의존성 및 심볼 기준선 | 메인 앱에 대한 가정을 잘못 재사용 |
의존성과 심볼 목록 생성하기
다음 스크립트는 메인 실행 파일과 내장 Framework를 읽고, 안정적으로 정렬된 의존성 및 전역 심볼 목록을 각각 출력합니다. 정렬하면 도구의 출력 순서 변화로 인해 의미 없는 차이가 생기는 것을 방지할 수 있습니다.
set -euo pipefail
archive="${1:?archive path required}"
app=$(find "$archive/Products/Applications" -maxdepth 1 -name '*.app' -print -quit)
test -n "$app"
mkdir -p audit/current
main=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$app/Info.plist")
printf '%s
' "$app/$main" > audit/current/binaries.txt
if test -d "$app/Frameworks"; then
while IFS= read -r framework; do
executable=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$framework/Info.plist")
printf '%s
' "$framework/$executable"
done < <(find "$app/Frameworks" -maxdepth 1 -name '*.framework' -type d | sort)
fi >> audit/current/binaries.txt
: > audit/current/dependencies.txt
: > audit/current/symbols.txt
while IFS= read -r binary; do
name=${binary#"$app/"}
otool -L "$binary" |
sed '1d' |
awk -v n="$name" '{$1=$1; print n " " $1}' \
>> audit/current/dependencies.txt
xcrun nm -gU "$binary" |
awk -v n="$name" 'NF {print n " " $NF}' \
>> audit/current/symbols.txt
done < audit/current/binaries.txt
LC_ALL=C sort -u -o audit/current/dependencies.txt audit/current/dependencies.txt
LC_ALL=C sort -u -o audit/current/symbols.txt audit/current/symbols.txt
기준선을 커밋할 때는 상대적인 대상 이름만 보존하고 클라우드 Mac의 작업 디렉터리는 기록하지 마십시오. 이후 파이프라인에서 다음 명령을 실행합니다.
diff -u audit/baseline/dependencies.txt audit/current/dependencies.txt
diff -u audit/baseline/symbols.txt audit/current/symbols.txt
보고서를 신뢰할 수 있는 게이트로 전환하기
전체 텍스트의 차이만 비교하면 지나치게 민감할 수 있습니다. 더 실용적인 전략은 먼저 강제 규칙을 적용한 다음 기준선 변화를 검사하는 것입니다. 의존성이 /System/Library/Frameworks/, /usr/lib/ 또는 통제된 @rpath/로 시작한다면 일반적으로 타당합니다. 그 밖의 절대 경로는 즉시 실패 처리해야 합니다. @rpath/Example.framework/Example의 경우에는 접두사가 올바르다는 이유만으로 허용하지 말고, 패키지 안에 해당 Framework가 실제로 존재하는지도 스크립트에서 확인해야 합니다.
심볼 변화는 사람이 검토하는 방식이 적합합니다. Swift 이름은 맹글링 후 매우 길어질 수 있으므로 문자열 길이만으로 위험 여부를 판단하지 마십시오. 갑자기 대량으로 추가된 전역 심볼, 디버깅 보조 인터페이스, 테스트 진입점, 그리고 숨겨져 있어야 하지만 모듈 간에 노출된 C 또는 Objective-C 심볼을 중점적으로 확인합니다.
오탐 줄이기
Xcode 버전과 Release 구성을 고정하고 매번 동일한 scheme을 비교해야 합니다. 프로젝트에 여러 확장이 포함되어 있다면 모든 결과를 하나의 집합으로 합치지 말고 각 바이너리에 독립적인 기준선을 저장합니다. 도구 체인 업그레이드로 시스템 심볼이 변경되었다면 해당 업그레이드의 병합 요청에서 기준선을 갱신해, 나중에 차이의 원인을 설명할 수 없는 상황을 방지해야 합니다.
실패 시 산출물에서 원인 추적하기
새 의존성이 나타나면 먼저 otool -L을 사용해 어떤 바이너리가 이를 도입했는지 확인한 다음, 해당 타깃의 링크 설정, 조건부 컴파일 및 의존성 임베드 단계를 점검합니다. @rpath 대상이 누락되었다면 Framework가 링크에만 참여하고 패키지에는 복사되지 않았는지 중점적으로 확인합니다. 예상하지 못한 심볼이 나타나면 nm -gU로 대상을 찾은 뒤 가시성 선언, 브리징 코드 및 링커 내보내기 설정을 검사할 수 있습니다.
실패했다고 기준선을 바로 덮어쓰지 마십시오. 올바른 순서는 변경의 출처를 확인하고, 배포 경계에 영향을 미치는지 판단하고, 변경을 수정하거나 승인한 다음, 마지막으로 기준선을 다시 생성하는 것입니다. 이렇게 구축한 게이트는 특정 클라우드 Mac의 일시적인 상태에 의존하지 않으며, 릴리스마다 검증 가능한 바이너리 경계 기록을 남길 수 있습니다.
자주 묻는 질문
프로젝트에 선언된 의존성만 검사하면 왜 부족한가요?
조건부 빌드, 링커 옵션, 의존성 스크립트가 최종 Archive를 바꿀 수 있으므로 실제로 생성된 Mach-O 파일을 검사해야 합니다.
공개 심볼이 바뀌면 항상 배포를 중단해야 하나요?
항상 중단할 필요는 없습니다. 의도한 API 변경이나 도구 체인 갱신인지 검토한 뒤 승인된 기준 파일을 함께 갱신하면 됩니다.
심볼 목록으로 바이너리 안의 비밀 정보도 찾을 수 있나요?
단독으로는 어렵습니다. 심볼 감사와 별도로 소스, 설정, 리소스, 빌드 로그를 포함한 비밀 정보 검사를 수행해야 합니다.
전용 물리 노드
개발 및 빌드 작업을 전용 클라우드 Mac에서 실행하세요
작업에 맞는 모델, 노드 지역 및 대여 기간을 선택하세요. 각 인스턴스는 전용 물리 머신으로 제공되며 가상 머신 환경이 아닙니다.