의존성을 업그레이드한 뒤 아카이브까지 문제없이 완료했지만, 릴리스 직전에야 새 구성 요소가 오픈소스 라이선스 목록에 포함되지 않았다는 사실을 발견하는 경우가 있습니다. 문제는 대개 빌드 자체가 아니라 의존성 출처가 여러 곳에 흩어져 있다는 데 있습니다. SwiftPM에는 잠금 파일이 있고, CocoaPods는 별도의 디렉터리 구조를 사용합니다. 저장소에 소스 코드를 직접 복사하거나 바이너리 패키지를 추가했을 수도 있습니다. 검사를 클라우드 Mac CI에서 수행하면 릴리스 담당자가 막판에 디렉터리를 수동으로 뒤지는 대신, 고정된 작업 공간에서 동일한 프로젝트를 기준으로 목록을 생성하고 정책과 비교한 뒤 증거를 보관할 수 있습니다.
게이트가 답해야 할 질문부터 정의하기
실제로 실행 가능한 게이트라면 최소한 네 가지 질문에 답해야 합니다. 이번 빌드에서 실제로 해석된 구성 요소는 무엇인지, 버전 또는 커밋은 무엇인지, 라이선스 전문은 어디에 있는지, 해당 조건이 승인되었는지를 확인해야 합니다. 파일 이름만 검색한 뒤 통과시켜서는 안 됩니다. LICENSE 파일이 존재한다고 해서 내용이 변경되지 않았다는 뜻은 아니며, 해당 구성 요소가 현재 대상에 실제로 링크되었다는 의미도 아니기 때문입니다.
결과는 기계 처리용 명세와 사람이 검토할 보고서로 나누는 것이 좋습니다. 기계 처리용 명세는 JSON으로 작성하고 구성 요소 이름, 출처, 고정된 버전, 라이선스 유형, 텍스트 해시, 발견 경로를 기록합니다. 사람용 보고서는 Markdown으로 작성해 추가, 삭제, 변경된 항목을 보여 줍니다. 두 결과물 모두 파이프라인 아티팩트로 보관합니다.
게이트는 의존성의 실제 상태가 사전에 정한 정책과 일치하는지를 검사합니다. 알 수 없는 항목이 발견되면 라이선스 이름만 보고 처리 방식을 자동으로 추측하지 말고, 릴리스를 중단한 뒤 재검토를 요청해야 합니다.
빌드 결과에서 실제 의존성 목록화하기
먼저 Xcode가 의존성 해석을 완료하도록 한 다음, 작업 공간에서 실제로 사용하는 디렉터리를 읽습니다. 이렇게 하면 개발자 컴퓨터에 남아 있는 오래된 캐시까지 스캔하는 문제를 피할 수 있습니다. 각 작업은 별도의 DerivedData를 사용하고, 작업이 끝난 뒤 정리합니다.
set -euo pipefail
ROOT="$(pwd)"
OUT="$ROOT/build/license-audit"
DD="$ROOT/build/DerivedData"
mkdir -p "$OUT"
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-resolvePackageDependencies \
-derivedDataPath "$DD"
find "$DD/SourcePackages/checkouts" \
-maxdepth 3 \
-type f \
\( -iname 'license*' -o -iname 'copying*' -o -iname 'notice*' \) \
-print0 | sort -z > "$OUT/swiftpm-license-files.bin"
find "$ROOT/Pods" \
-maxdepth 4 \
-type f \
\( -iname 'license*' -o -iname 'copying*' -o -iname 'notice*' \) \
-print0 | sort -z > "$OUT/pods-license-files.bin"
바이너리 의존성은 파일 확장자만으로 판단해서는 안 됩니다. 다운로드 주소에 대응하는 내부 구성 요소 식별자, 체크섬, 버전, 패키지에 포함된 고지 사항을 함께 기록해야 합니다. 저장소에 직접 복사한 소스 코드는 compliance/manual-components.json을 관리하고 디렉터리 소유자를 명시합니다. 그러면 매번 스캔할 때마다 모호한 일치 규칙에 의존하지 않아도 됩니다.
잠금 파일도 반드시 함께 기록하기
Package.resolved, Podfile.lock, 수동 구성 요소 목록을 보고서 디렉터리로 복사합니다. 게이트는 목록의 각 구성 요소가 고정된 출처 중 하나에서 확인되는지 검증해야 하며, 반대로 잠금 파일에 있는 직접 의존성이 보고서에서 사라지지 않았는지도 확인해야 합니다. 프로젝트에 여러 작업 공간이 포함되어 있다면 주 애플리케이션만 검사하지 말고 각 작업 공간의 의존성을 모두 해석해야 합니다.
라이선스를 정규화하고 지문 계산하기
라이선스 텍스트는 줄바꿈 문자나 줄 끝 공백만 다른 경우가 많습니다. 먼저 UTF-8과 LF 줄바꿈으로 통일하고 줄 끝 공백을 제거한 뒤 SHA-256을 계산할 수 있습니다. 단, 본문 단락이나 저작권 표시, 연도는 삭제하지 않아야 합니다. 이를 삭제하면 실제 변경 사항이 가려질 수 있습니다.
python3 - <<'PY'
from pathlib import Path
import hashlib
import json
root = Path("build/license-audit/licenses")
rows = []
for path in sorted(root.rglob("*")):
if not path.is_file():
continue
text = path.read_text(encoding="utf-8", errors="strict")
normalized = "\n".join(line.rstrip() for line in text.splitlines()).strip() + "\n"
digest = hashlib.sha256(normalized.encode("utf-8")).hexdigest()
rows.append({"path": str(path), "sha256": digest})
Path("build/license-audit/license-hashes.json").write_text(
json.dumps(rows, ensure_ascii=False, indent=2) + "\n",
encoding="utf-8"
)
PY
라이선스 유형 식별 결과와 텍스트 지문은 별도로 저장해야 합니다. 유형은 정책 일치 여부를 판단하는 데 사용하고, 지문은 업스트림에서 조용히 이루어진 변경을 감지하는 데 사용합니다. 신뢰할 수 있게 식별할 수 없다면 UNKNOWN으로 표시하고, 파일 이름이 익숙해 보인다는 이유만으로 자동 분류해서는 안 됩니다.
정책 파일로 변경 제어하기
정책 파일은 버전 관리에 포함하고 명확한 코드 리뷰 절차를 거쳐 변경해야 합니다. 라이선스 식별자별로 allow, review, deny를 설정하고, 특정 구성 요소에는 승인된 텍스트 해시를 기록할 수 있습니다. 검사 결과는 다음 기준으로 판정하는 것이 좋습니다.
| 상황 | CI 결과 | 후속 조치 |
|---|---|---|
| 알려진 구성 요소이며 버전과 텍스트가 변경되지 않음 | 통과 | 보고서 보관 |
| 새 버전이지만 라이선스 지문은 변경되지 않음 | 검토 | 구성 요소 변경 확인 |
| 새 구성 요소 또는 알 수 없는 라이선스 | 차단 | 출처와 승인 정보 추가 |
| 기존 구성 요소의 라이선스 텍스트가 변경됨 | 차단 | 전문 차이 비교 |
| 잠금 파일에는 구성 요소가 있지만 보고서에는 없음 | 차단 | 발견 규칙 수정 |
허용 목록에 광범위한 라이선스 유형만 기록해서는 안 됩니다. 구성 요소에는 추가 NOTICE, 이중 라이선스 또는 별도의 리소스 라이선스가 포함될 수 있으므로 보고서에 관련 파일을 모두 보관해야 합니다. 바이너리 패키지는 현재 다운로드한 파일의 체크섬이 승인 기록과 일치하는지도 검증해야 합니다.
보고서를 병합 및 릴리스 흐름에 연결하기
일반적인 병합 요청에서는 의존성 해석, 차이 검사, 보고서 생성을 실행할 수 있습니다. 릴리스 작업에서는 아카이브 내용 검사를 추가해 최종 결과물이 동일한 잠금 파일에 대응하는지 확인합니다. 기준선은 compliance/license-baseline.json에 두고, CI에서는 터미널의 색상 출력이 아니라 구조화된 필드만 비교하는 것이 좋습니다.
게이트가 실패하면 로그에 구성 요소, 이전 값, 새 값, 증거 경로를 직접 표시해야 합니다. 단순히 “라이선스 검사 실패”만 반환해서는 안 됩니다. 보고서 디렉터리에는 최소한 잠금 파일 사본, 정규화된 텍스트, 해시 목록, 정책 버전, 차이 요약이 포함되어야 합니다. ArmCLI의 병렬 작업은 각각 독립된 작업 디렉터리를 사용해, 한 작업의 의존성 업데이트가 다른 작업의 스캔 결과를 오염시키지 않도록 해야 합니다.
배포 전 점검 항목
릴리스 전에 의존성 해석 과정에서 기록되지 않은 로컬 경로를 사용하지 않았는지, 모든 직접 및 전이 의존성에 버전 출처가 있는지, 알 수 없는 라이선스 수가 0인지 확인합니다. 또한 수동 구성 요소 목록에 기록된 디렉터리가 여전히 존재하는지, 보고서와 아카이브가 동일한 커밋을 사용하는지, CI 아티팩트에 액세스 토큰이나 비공개 다운로드 주소, 마스킹되지 않은 환경 변수가 포함되지 않았는지도 점검해야 합니다.
이 절차의 가치는 단순히 고지 페이지 하나를 생성하는 데 있지 않습니다. 의존성 변경을 검토 가능한 엔지니어링 이벤트로 전환한다는 점이 핵심입니다. 잠금 파일, 라이선스 지문, 정책 기준선이 계속 동기화되어 있다면 아카이브가 끝난 뒤 구성 요소의 출처를 다시 추적하는 대신, 의존성이 릴리스 브랜치에 들어오는 시점에 문제를 발견할 수 있습니다.
자주 묻는 질문
Package.resolved만 검사하면 충분한가요?
충분하지 않습니다. SwiftPM 버전은 확인할 수 있지만 라이선스 원문 존재 여부, CocoaPods, 복사된 소스 코드와 바이너리 의존성은 별도로 조사해야 합니다.
어떤 변경이 배포를 차단해야 하나요?
라이선스 파일이 없는 새 의존성, 허용 목록 밖의 조건, 잠금 파일과 결과의 불일치, 기존 라이선스 원문 변경은 수동 검토 전까지 차단해야 합니다.
다음 빌드를 위한 독점 물리 Mac 준비
작업에 맞는 모델, 리전과 대여 기간을 선택하세요. 노드는 연중 정상 운영되며, 실제 사용 가능 여부는 콘솔의 실시간 응답을 기준으로 합니다.