앱스토어 계정에 앱이 46개 있습니다. 그중 라이브(또는 심사대기) 버전이 있는 41개를 뽑아, 각 저장소의 MARKETING_VERSION과 나란히 놓았습니다.
저장소를 매칭한 35개 전부, 커밋된 버전이 라이브보다 뒤였습니다. 일치: 0. 앞선 것: 0.
당신 저장소의 버전 번호는 지금 스토어에 있는 것과 같습니까? 저는 한 번도 확인한 적이 없었습니다.
갭이 "한 릴리스 차이"가 아니었다
한두 칸 밀린 거라면 "릴리스 후 커밋을 깜빡했나" 하고 넘어갈 수 있습니다. 그런데 이렇게 생겼습니다.
Plotta repo 1.0 live 1.9.4
Paxio repo 1.0.0 live 1.4.12
Nomori repo 1.0 live 1.4.3
Cyra repo 1.1.8 live 1.3.0
Aftershift repo 1.0 live 1.3.1
...
Unse repo 1.0 live 1.0.1Plotta는 저장소가 1.0인데 스토어엔 1.9.4입니다. 아홉 번의 마이너 릴리스 동안 커밋된 값이 한 번도 안 움직였습니다. 이건 "깜빡"이 아니라 그 값이 갱신되는 경로가 아예 없다는 뜻입니다.
릴리스 도구를 읽어보니
# ios_release.py
if version:
cmd.append(f"MARKETING_VERSION={version}")
if build_number:
cmd.append(f"CURRENT_PROJECT_VERSION={build_number}")xcodebuild archive 명령 뒤에 MARKETING_VERSION=1.9.4를 커맨드라인 인자로 붙입니다. xcodebuild는 이 오버라이드를 project 파일의 값보다 우선합니다. 그래서 아카이브는 1.9.4로 만들어지고, 업로드도 1.9.4로 됩니다.
그리고 그게 끝입니다. 저장소의 project.pbxproj에 1.9.4를 되쓰는 코드도, 버전 범프를 커밋하는 코드도 없습니다. 다음 릴리스에서 드라이버 스크립트가 또 --version을 넘기고, 또 오버라이드하고, 저장소는 또 아무것도 모릅니다.
버전 번호가 두 곳에 있고(빌드 인자 · project 파일), 그중 하나만 진짜입니다. 같은 값이 코드에 두 번 있으면 하나는 이미 틀렸다의 릴리스 도구 판입니다.
왜 한 번도 안 터졌나
빌드 시점 오버라이드가 매번 이기기 때문입니다. MARKETING_VERSION은 스토어 표시용 문자열일 뿐이고, 애플이 중복을 검사하는 건 빌드 번호(CURRENT_PROJECT_VERSION)입니다. 빌드 번호는 드라이버가 타임스탬프 같은 걸로 매번 새로 넘기니 충돌이 안 납니다.
즉 이 갭은 릴리스를 막지 않습니다. 대신 이런 걸 조용히 부숩니다.
git log로 "이 앱이 언제 1.2로 갔나"를 알 수 없다. 전부1.0이니까.- 저장소만 보고 CI를 짜는 사람은 틀린 값을 읽는다.
- project 파일에서 버전을 읽는 가드가 스테일 값과 비교한다.
딱 한 번 터진 자리
세 번째가 실제로 물었습니다. 릴리스 노트를 뒤지다 이 줄을 찾았습니다.
⚠️
xcodegen generate가 Info.plist 버전 매크로를 되돌려 build "1" DUPLICATE → project.ymlinfo.properties에CFBundleShortVersionString/$(MARKETING_VERSION)+CFBundleVersion/$(CURRENT_PROJECT_VERSION)추가로 해결
xcodegen을 쓰는 앱은 project.yml에서 Xcode 프로젝트를 재생성합니다. 그때 Info.plist가 project.yml에 적힌 값 — 즉 스테일 시드 1 — 으로 되돌아갔고, 그 상태로 아카이브하니 애플이 "빌드 1은 이미 있음"으로 반려했습니다.
오버라이드가 이기는 건 오버라이드가 적용될 때 뿐입니다. project 파일을 재생성하는 도구가 중간에 끼면, 그 도구는 스테일 시드를 진실로 취급합니다.
여기서 당신이라면?
두 갈래입니다.
- 릴리스할 때마다 사람이
MARKETING_VERSION을 손으로 범프하고 커밋한다. - 릴리스 도구가 업로드 성공 후 project 파일에 버전을 되쓰고 커밋한다.
1번은 40개 앱에 40번 잊을 기회입니다. 저장소가 진실을 따라가야 하는데 사람 손을 경유하는 구조는 반드시 어긋납니다.
2번이 맞습니다. xcodebuild가 오버라이드로 받은 값을, 업로드가 200을 받은 직후 project.pbxproj(또는 project.yml)에 써 넣고 chore: bump <app> to X.Y.Z 커밋. 그러면 저장소가 스토어의 거울이 되고, xcodegen 재생성도 진실을 읽습니다.
(이 글은 그 수정을 하기 전에 씁니다 — 40개 앱의 project 파일 포맷이 두 종류라 되쓰기 자체가 테스트가 필요한 변경이고, 지금은 "무엇이 스테일한가"를 먼저 못박아 두는 게 목적입니다.)
자가진단 3개
- 저장소의 버전 번호(
MARKETING_VERSION,package.json,Cargo.toml…)가 지금 프로덕션에 있는 것과 같습니까? 릴리스 도구가 그 값을 되쓰지 않으면 답은 대개 "아니오"입니다. - 빌드 시점에 오버라이드되는 값이 저장소에도 있습니까? 있다면 그 저장소 값은 시드일 뿐이고, 시드를 진실로 읽는 코드가 어딘가 있습니다.
- project 파일을 재생성하는 도구(xcodegen, 코드젠, 템플릿)를 쓰십니까? 그렇다면 그 도구는 오버라이드 이전의 값을 읽습니다 — 그 값이 맞습니까?
솔직한 부분
이걸 몇 달 몰랐던 이유는 릴리스가 계속 성공했기 때문입니다. 갭이 나를 한 번도 막지 않았으니 갭이 있는지 볼 이유가 없었습니다. git log가 전부 1.0이라는 게 이상했지만, 그게 "버그"로 보이진 않았습니다.
교훈 하나. 빌드 시점 오버라이드는 저장소를 진실에서 갈라놓는다. 오버라이드로 넘긴 값은 오버라이드가 없는 모든 경로(재생성 도구, CI 스크립트, 사람이 저장소를 읽는 순간)에서 틀립니다. 오버라이드를 쓸 거면, 그 값을 저장소에 되써서 두 곳을 다시 합쳐야 합니다.
지금 딱 하나만 해보세요. 앱 하나를 골라 저장소 버전과 스토어(또는 배포된) 버전을 나란히 놓아 보시기 바랍니다.