본문 바로가기
C.W.K.
Lesson 04 of 07 · published

빌드 번호: 소스에서 올리고, 마지막에 커밋하고, 24시간 한도 안에서

~15 min · testflight, build-numbers, xcodegen, versioning, upload-limits

Level 0번들 열어본 사람
0 XP0/84 lessons0/17 achievements
0/100 XP to next level100 XP to go0% complete
"번호는 태워 없애는 게 아니라 다시 써."

하는 일이 다른 숫자 둘

iOS 빌드엔 버전이 둘 붙어. CFBundleShortVersionString은 사람들이 보는 마케팅 버전이야. 1.4.0 같은 거. CFBundleVersion은 빌드 문자열이고, App Store Connect는 이 값으로 시스템 전체에서 빌드 하나하나를 구별해. 그래서 이미 쓴 값을 다시 쓴 업로드는 거절돼. XcodeGen 프로젝트에선 둘이 MARKETING_VERSION과 CURRENT_PROJECT_VERSION에서 나와. 공유 확장의 Info.plist는 두 값을 모두 $(…)로 참조하니까, 확장이 자기를 담은 앱과 다른 버전 조합을 가질 수 없어. export 옵션은 manageAppVersionAndBuildNumber를 false로 둬. 기본값으로 두면 Xcode가 업로드하면서 빌드 번호를 바꿔 쓸 수 있고, 그러면 소스에 적힌 번호가 실제로 나간 빌드와 어긋나거든.

번호를 두는 곳

번호는 소스에 둬. 프로젝트를 생성해서 쓰는 앱은 project.yml에 있는 CURRENT_PROJECT_VERSION을 모두 올리고 프로젝트를 다시 생성해. 생성된 .xcodeproj를 직접 고치면 다음 xcodegen generate 때 사라져. 터미널 앱은 다른 방식을 쓰는데, 이것도 맞는 방법이야. 커밋된 git 리비전 개수를 빌드 번호로 쓰는 거지. 이 값도 처음부터 소스에서 나오는 거고.

번호가 의미를 가지려면 번호를 올리기 전후의 순서가 맞아야 해. 파이프라인은 앱 입력에 커밋 안 된 변경이 있으면 시작하지 않아. 그래야 번호가 가리키는 커밋에 실제로 빌드하는 내용이 그대로 들어 있거든. xcodebuild가 끝나면 HEAD가 그대로인지, 입력도 안 바뀌었는지 확인해. 수정 전에 시작해서 수정 뒤에 올라간 빌드는 엉뚱한 이름표를 달게 돼. 설치된 빌드와 코드를 이어주는 건 그 이름표뿐인데 말이야. 올린 번호는 export나 업로드가 성공한 뒤에만 커밋하고 푸시해. 업로드가 거절되면 올린 번호는 커밋하지 않은 채로 남아. 재시도할 땐 같은 번호를 다시 쓰고, 실패한 실행은 기록에 흔적을 하나도 남기지 않아. 한 파이프라인은 일어나지도 않은 업로드의 빌드 번호를 커밋한 적이 있어.

최근 24시간을 세는 한도

App Store Connect는 앱마다 업로드 횟수를 제한하는데, 기준이 되는 시간 창이 고정돼 있지 않고 계속 밀려가. 잘게 나눈 작업이 많았던 긴 하루 동안, 네이티브 Pippa 앱이 빌드 21개를 몰아서 올렸어. 뒤이은 두 개는 export 단계에서 "Upload limit reached"로 거절됐고, 그 뒤의 업로드는 몰아 올린 첫 빌드가 24시간을 넘기자마자 받아들여졌어. UTC−7부터 UTC+7 사이 어느 자정에서든(태평양 자정 포함) 날짜가 새로 시작됐다면, 두 번째로 거절된 업로드는 이미 통과했어야 해. 한도가 달력 날짜가 아니라 계속 밀려가는 24시간 창이라는 게 이걸로 증명된 거야. 실전 규칙은 이래. 업로드마다 시각을 적어두고, 창 안에 남은 가장 오래된 업로드로부터 24시간이 지나야 다음 기회가 열린다고 봐. 재시도는 sleep으로 잠든 셸에 맡기지 말고 launchd StartCalendarInterval로 딱 그 분에 걸어둬. 바쁜 날엔 작업 조각 하나마다 빌드를 올리지 말고, 조각 몇 개를 묶어 빌드 하나로 올려.

Code

bump.zsh: 빌드 번호는 project.yml에서 전부 올리고, 프로젝트는 다시 생성·bash
#!/bin/zsh
# Bump the build number at its source, every occurrence, then regenerate the project.
set -euo pipefail
yml=SparkMobile/project.yml
current=$(sed -n 's/^ *CURRENT_PROJECT_VERSION: *\([0-9][0-9]*\).*$/\1/p' "$yml" | head -1)
[[ "$current" == <-> ]] || { print -u2 "HARD_FAIL no numeric CURRENT_PROJECT_VERSION in $yml"; exit 1; }
next=$((current + 1))
# Every occurrence: the project-wide value and any target's, so an extension never
# carries a different build number from the app that contains it.
# The number must end where the digits end, or a current 1 would turn a sibling 10 into 20.
sed -E -i '' "s/^( *CURRENT_PROJECT_VERSION:) *${current}([^0-9].*)?$/\1 ${next}\2/" "$yml"
(cd SparkMobile && xcodegen generate >/dev/null)
print "build number: $current -> $next"
grep -n 'CURRENT_PROJECT_VERSION' "$yml"   # every line shows the new number
실제로 잰 그대로의 24시간 한도·text
One app, one burst, times in KST, measured:

  day 1  17:05  upload  1  accepted   <- the oldest upload in the window
         ...           ... accepted
  day 2  01:40  upload 21  accepted
  day 2  13:06  upload 22  refused: "Upload limit reached", server code 90382
  day 2  16:21  upload 23  refused   (a calendar-day reset at any UTC-7..UTC+7 midnight would
                                      already have let this one through)
  day 2  17:27  upload 24  accepted  <- upload 1 is now more than 24 hours old

A refused run costs nothing but time: tests, archive and gates all passed, the bump is
reverted, and the same build number is used again on the retry.

External links

Exercise

SparkMobile project.yml의 공유 확장 타깃 설정에 CURRENT_PROJECT_VERSION 줄을 하나 더 넣어. 그리고 bump.zsh를 돌려서 두 줄과 다시 생성된 프로젝트가 모두 새 번호를 갖는지 확인해. 이어서 번호 올리기를 파이프라인 순서대로 감싸봐. 앱 입력 경로에서 git status --porcelain에 변경이 보이면 거부하고, HEAD를 기억해두고, exit 1로 실패하는 export를 흉내 내. 그러고 나서 올린 번호가 커밋되지 않은 채 남고, 되돌리는 방법도 안내되는지 보여줘.
Hint
git status --porcelain -- <경로들>은 검사 범위를 앱의 입력으로만 좁혀줘. 다른 세션이 상관없는 파일을 고치고 있는 Mac에선 이게 중요해. 되돌릴 땐 git checkout -- SparkMobile/project.yml을 쓰고, 레포가 프로젝트 파일도 추적한다면 그것도 같이 되돌려.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인 — 댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.