툴과 개발 환경7 분 읽기

Xcode를 올렸더니 앱 20개가 같은 에러로 멈췄습니다

46개 앱을 실빌드로 훑었더니 20개가 컴파일에 실패했습니다. 원인은 제 코드가 아니라 SDK 안에서 컴파일러가 자동 합성한 생성자였습니다.

#ios#build#tooling#verification#gotchas
개념 도식: 46개 앱 중 20개가 같은 에러로 실패하고, 그중 리포에 흔적을 남겨야 하는 건 2개뿐임을 보여주는 도식
글 내용을 요약한 개념 도식.

Xcode를 최신 메이저 버전으로 올렸습니다. 그리고 플릿 전체 46개 앱을 실빌드로 훑었습니다.

20개가 컴파일 실패였습니다. 전부 같은 에러, 같은 원인이었습니다.

error: invalid redeclaration of synthesized memberwise 'init(stringRepresentation:)'

툴체인을 올린 뒤, 당신은 앱 몇 개를 실제로 빌드해봅니까? 아니면 하나만 열어보고 넘어갑니까?

원인은 제 코드가 아니었습니다

에러는 결제 SDK 안의 페이월 색상 타입에서 났습니다.

그 구조체의 생성자가 private extension 안에 있었습니다. 그래서 컴파일러는 "본문에 생성자가 없다"고 보고 memberwise 생성자를 자동으로 합성합니다. 내부 필드가 Optional이라, 합성된 생성자가 우연히 extension에 정의된 생성자와 같은 이름·같은 인자 모양이 됐습니다. 같은 선언이 두 번 있는 셈입니다.

이전 컴파일러는 이걸 통과시켰고, 새 컴파일러가 더 엄격하게 잡으면서 드러났습니다.

그리고 SDK는 이미 그 생성자를 구조체 본문으로 옮긴 버전을 내놓은 상태였습니다. 핀을 올리기만 하면 되는 문제였는데, 그걸 몰라서 앱 20개가 막혀 있었습니다.

처음 센 숫자는 16이었습니다

"몇 개나 막혔나"를 처음 셀 때 앱 폴더 1단계 깊이에서만 의존성 잠금 파일을 찾았습니다. 16개가 나왔습니다.

실제로는 프로젝트 구조가 한 단계 더 깊은 앱이 4개 더 있었습니다. 20개였습니다. 집계 자체가 과소평가였습니다.

find . -name Package.resolved -not -path '*/SourcePackages/*' -not -path '*/checkouts/*'

깨진 버전과 고쳐진 버전의 경계는 문서가 아니라 태그가 붙은 소스를 직접 열어서 확인했습니다.

git -C <SDK checkout> show <tag>:Sources/Paywalls/PaywallColor.swift \
  | awk '/MARK: - Public constructors/{exit}1' \
  | grep -c 'private init(stringRepresentation: String, underlyingColor'
# 1 이면 고쳐진 버전, 0 이면 깨진 버전

여기서 선택이 갈립니다

20개 앱의 핀을 로컬에서 올리면 전부 빌드가 됩니다. 끝난 것 같습니다.

그런데 이 SDK 버전 핀은 Xcode의 로컬 워크스페이스 상태 파일에 저장되고, 그 파일은 .gitignore로 무시되고 있었습니다. 이 머신에서 고쳐도 리포에는 아무 흔적이 안 남습니다.

당신이라면 20개 전부를 리포에 커밋하겠습니까, 아니면 한 개도 안 하겠습니까?

정답은 둘 다 아니었습니다. 대부분 앱의 버전 선언이 "메이저 버전 내 최신"을 허용하는 느슨한 형태라, 새로 클론하면 자동으로 최신을 받아 저절로 낫습니다. 문제는 버전 선언이 "마이너 버전 내 최신"으로 더 좁게 걸려 있던 2개 앱입니다. 이 둘은 어느 머신에서 다시 받아도 깨진 채로 고정됩니다.

리포에 반드시 남겨야 하는 수정은 그 2개뿐이었습니다. 20개를 전부 커밋하면 소음이고, 0개를 커밋하면 다음 클론이나 CI에서 다시 깨집니다.

실패 개수를 부풀리는 함정

워치 앱이 딸린 프로젝트를 시뮬레이터 대상으로 강제 빌드하면 "WatchKit이 없다"는 별개 에러가 납니다. 이건 빌드 대상을 잘못 지정한 제 호출 오류지 이번 회귀와 무관합니다.

둘을 같은 바구니에 세면 실패 개수가 부풀려지고, 원인이 하나가 아닌 것처럼 보입니다.

자가진단

  • 의존성 잠금 파일을 셀 때, 중첩된 프로젝트 구조까지 훑고 있습니까?
  • 판정을 "핀 값을 읽는 것"으로 끝내지 않고 실제 빌드 산출물이 나오는지로 합니까? 핀만 읽으면 로컬 워크스페이스 상태를 놓칩니다.
  • 당신이 고친 것 중 리포에 안 남는 수정이 몇 개입니까?

솔직한 부분

46개 중 20개 실패, 그중 리포에 흔적을 남긴 건 2개입니다. 나머지 18개는 "고쳤다"기보다 "다음에 클론하면 저절로 낫는다"에 가깝습니다 — 그리고 그 판단이 맞는지는 실제로 클론해서 빌드해보기 전까지는 제 추론일 뿐입니다.

툴체인을 올린 뒤 앱 하나만 열어보고 넘어갔다면, 나머지 19개는 다음 릴리스 때 발견했을 겁니다.

당신 플릿에서 가장 오래 안 빌드해본 앱을 지금 하나 열어서 빌드해보세요.

관련 글