Skip to content

Commit e6142b7

Browse files
committed
feat: 배포 전 시뮬레이터 빌드를 선행하고 성공했을 때만 배포하도록 개선 및 qa-local-* 형태 태그를 붙였을 때는 Connect에 올라가지 않도록 추가
1 parent 07acdec commit e6142b7

4 files changed

Lines changed: 210 additions & 143 deletions

File tree

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
name: iOS Simulator Build
2+
description: Select an installed iOS simulator runtime and build the app with xcodebuild.
3+
4+
inputs:
5+
scheme:
6+
description: Xcode scheme to build
7+
required: true
8+
9+
runs:
10+
using: composite
11+
steps:
12+
- name: Select iOS Simulator Runtime (installed)
13+
id: pick_ios
14+
shell: bash
15+
run: |
16+
set -euo pipefail
17+
18+
# macOS 메인 버전에 맞는 iOS 버전 중 최신 버전의 iPhone 선택
19+
RESULT=$(python3 - <<'PY'
20+
import re, subprocess, sys
21+
22+
xcode_ver = subprocess.check_output(["xcodebuild", "-version"], text=True).splitlines()[0].strip()
23+
xcode_major = xcode_ver.split()[1].split('.')[0]
24+
try:
25+
xcode_major_num = int(xcode_major)
26+
except ValueError:
27+
xcode_major_num = None
28+
if xcode_major_num is not None and xcode_major_num <= 15:
29+
xcode_major = "26"
30+
31+
text = subprocess.check_output(["xcrun", "simctl", "list", "devices"], text=True)
32+
lines = text.splitlines()
33+
34+
def ver_key(v):
35+
return tuple(int(x) for x in v.split('.'))
36+
37+
# 1) 최신 iOS 버전(해당 mac 메이저) 찾기
38+
latest_ver = None
39+
for line in lines:
40+
header = re.match(r"^-- iOS ([0-9]+(?:\.[0-9]+)*) --$", line.strip())
41+
if not header:
42+
continue
43+
ver = header.group(1)
44+
if not ver.startswith(f"{xcode_major}."):
45+
continue
46+
if latest_ver is None or ver_key(ver) > ver_key(latest_ver):
47+
latest_ver = ver
48+
49+
if latest_ver is None:
50+
print(f"No iOS versions found for Xcode major {xcode_major}", file=sys.stderr)
51+
sys.exit(1)
52+
53+
# 2) 해당 버전 섹션에서 첫 iPhone 찾고 즉시 종료
54+
current_ver = None
55+
for line in lines:
56+
header = re.match(r"^-- iOS ([0-9]+(?:\.[0-9]+)*) --$", line.strip())
57+
if header:
58+
current_ver = header.group(1)
59+
continue
60+
if current_ver != latest_ver:
61+
continue
62+
if "(unavailable)" in line:
63+
continue
64+
if "iPhone" in line:
65+
raw = line.strip()
66+
# key:value 형태면 딕셔너리로 파싱해서 name만 사용
67+
if "platform:" in raw and "name:" in raw and "OS:" in raw:
68+
kv = {}
69+
for part in raw.split(","):
70+
if ":" not in part:
71+
continue
72+
k, v = part.split(":", 1)
73+
kv[k.strip()] = v.strip()
74+
name = kv.get("name", raw)
75+
else:
76+
name = raw
77+
# UUID/상태만 제거하고 모델명 괄호는 유지
78+
name = re.sub(r"\s+\([0-9A-Fa-f-]{36}\)\s+\(.*\)$", "", name)
79+
print(f"{latest_ver}|{name}")
80+
sys.exit(0)
81+
82+
print(f"No iPhone candidates found for iOS {latest_ver}", file=sys.stderr)
83+
sys.exit(1)
84+
PY
85+
)
86+
87+
if [ -z "${RESULT:-}" ]; then
88+
echo "No iPhone simulator devices detected." >&2
89+
exit 1
90+
fi
91+
92+
IFS='|' read -r IOS_VER DEVICE_NAME <<< "$RESULT"
93+
94+
echo "Chosen iOS runtime version (iPhone): $IOS_VER"
95+
echo "Chosen simulator: $DEVICE_NAME"
96+
97+
echo "ios_version=$IOS_VER" >> "$GITHUB_OUTPUT"
98+
echo "device_name=$DEVICE_NAME" >> "$GITHUB_OUTPUT"
99+
100+
- name: Build
101+
shell: bash
102+
run: |
103+
set -euo pipefail
104+
set -x
105+
IOS_VER="${{ steps.pick_ios.outputs.ios_version }}"
106+
DEVICE_NAME="${{ steps.pick_ios.outputs.device_name }}"
107+
SPM_DIR="$GITHUB_WORKSPACE/.spm"
108+
mkdir -p "$SPM_DIR"
109+
110+
xcodebuild -version
111+
112+
echo "Using scheme: ${{ inputs.scheme }}"
113+
114+
echo "Using simulator: $DEVICE_NAME (iOS ${IOS_VER})"
115+
116+
set -o pipefail
117+
set +e
118+
echo "== Resolving Swift Package dependencies =="
119+
xcodebuild \
120+
-scheme "${{ inputs.scheme }}" \
121+
-configuration Debug \
122+
-clonedSourcePackagesDirPath "$SPM_DIR" \
123+
-resolvePackageDependencies
124+
echo "== Starting xcodebuild build =="
125+
xcodebuild \
126+
-scheme "${{ inputs.scheme }}" \
127+
-configuration Debug \
128+
-destination "platform=iOS Simulator,OS=${IOS_VER},name=${DEVICE_NAME}" \
129+
-clonedSourcePackagesDirPath "$SPM_DIR" \
130+
-skipPackagePluginValidation \
131+
-skipMacroValidation \
132+
-showBuildTimingSummary \
133+
build \
134+
| tee build.log
135+
echo "== xcodebuild finished =="
136+
XC_STATUS=${PIPESTATUS[0]}
137+
set -e
138+
139+
if [ -f build.log ]; then
140+
echo "== error: lines =="
141+
if grep -i "error:" build.log; then
142+
if [ "$XC_STATUS" -eq 0 ]; then
143+
XC_STATUS=1
144+
fi
145+
fi
146+
fi
147+
148+
exit $XC_STATUS

.github/workflows/build.yml

Lines changed: 3 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -37,144 +37,10 @@ jobs:
3737
restore-keys: |
3838
${{ runner.os }}-spm-
3939
40-
41-
- name: Select iOS Simulator Runtime (installed)
42-
id: pick_ios
43-
shell: bash
44-
run: |
45-
set -euo pipefail
46-
47-
# macOS 메인 버전에 맞는 iOS 버전 중 최신 버전의 iPhone 선택
48-
RESULT=$(python3 - <<'PY'
49-
import re, subprocess, sys
50-
51-
xcode_ver = subprocess.check_output(["xcodebuild", "-version"], text=True).splitlines()[0].strip()
52-
xcode_major = xcode_ver.split()[1].split('.')[0]
53-
try:
54-
xcode_major_num = int(xcode_major)
55-
except ValueError:
56-
xcode_major_num = None
57-
if xcode_major_num is not None and xcode_major_num <= 15:
58-
xcode_major = "26"
59-
60-
text = subprocess.check_output(["xcrun", "simctl", "list", "devices"], text=True)
61-
lines = text.splitlines()
62-
63-
def ver_key(v):
64-
return tuple(int(x) for x in v.split('.'))
65-
66-
# 1) 최신 iOS 버전(해당 mac 메이저) 찾기
67-
latest_ver = None
68-
for line in lines:
69-
header = re.match(r"^-- iOS ([0-9]+(?:\.[0-9]+)*) --$", line.strip())
70-
if not header:
71-
continue
72-
ver = header.group(1)
73-
if not ver.startswith(f"{xcode_major}."):
74-
continue
75-
if latest_ver is None or ver_key(ver) > ver_key(latest_ver):
76-
latest_ver = ver
77-
78-
if latest_ver is None:
79-
print(f"No iOS versions found for Xcode major {xcode_major}", file=sys.stderr)
80-
sys.exit(1)
81-
82-
# 2) 해당 버전 섹션에서 첫 iPhone 찾고 즉시 종료
83-
current_ver = None
84-
for line in lines:
85-
header = re.match(r"^-- iOS ([0-9]+(?:\.[0-9]+)*) --$", line.strip())
86-
if header:
87-
current_ver = header.group(1)
88-
continue
89-
if current_ver != latest_ver:
90-
continue
91-
if "(unavailable)" in line:
92-
continue
93-
if "iPhone" in line:
94-
raw = line.strip()
95-
# key:value 형태면 딕셔너리로 파싱해서 name만 사용
96-
if "platform:" in raw and "name:" in raw and "OS:" in raw:
97-
kv = {}
98-
for part in raw.split(","):
99-
if ":" not in part:
100-
continue
101-
k, v = part.split(":", 1)
102-
kv[k.strip()] = v.strip()
103-
name = kv.get("name", raw)
104-
else:
105-
name = raw
106-
# UUID/상태만 제거하고 모델명 괄호는 유지
107-
name = re.sub(r"\s+\([0-9A-Fa-f-]{36}\)\s+\(.*\)$", "", name)
108-
print(f"{latest_ver}|{name}")
109-
sys.exit(0)
110-
111-
print(f"No iPhone candidates found for iOS {latest_ver}", file=sys.stderr)
112-
sys.exit(1)
113-
PY
114-
)
115-
116-
if [ -z "${RESULT:-}" ]; then
117-
echo "No iPhone simulator devices detected." >&2
118-
exit 1
119-
fi
120-
121-
IFS='|' read -r IOS_VER DEVICE_NAME <<< "$RESULT"
122-
123-
echo "Chosen iOS runtime version (iPhone): $IOS_VER"
124-
echo "Chosen simulator: $DEVICE_NAME"
125-
126-
echo "ios_version=$IOS_VER" >> "$GITHUB_OUTPUT"
127-
echo "device_name=$DEVICE_NAME" >> "$GITHUB_OUTPUT"
128-
12940
- name: Build
130-
shell: bash
131-
run: |
132-
set -euo pipefail
133-
set -x
134-
IOS_VER="${{ steps.pick_ios.outputs.ios_version }}"
135-
DEVICE_NAME="${{ steps.pick_ios.outputs.device_name }}"
136-
SPM_DIR="$GITHUB_WORKSPACE/.spm"
137-
mkdir -p "$SPM_DIR"
138-
139-
xcodebuild -version
140-
141-
echo "Using scheme: $SCHEME"
142-
143-
echo "Using simulator: $DEVICE_NAME (iOS ${IOS_VER})"
144-
145-
set -o pipefail
146-
set +e
147-
echo "== Resolving Swift Package dependencies =="
148-
xcodebuild \
149-
-scheme "$SCHEME" \
150-
-configuration Debug \
151-
-clonedSourcePackagesDirPath "$SPM_DIR" \
152-
-resolvePackageDependencies
153-
echo "== Starting xcodebuild build =="
154-
xcodebuild \
155-
-scheme "$SCHEME" \
156-
-configuration Debug \
157-
-destination "platform=iOS Simulator,OS=${IOS_VER},name=${DEVICE_NAME}" \
158-
-clonedSourcePackagesDirPath "$SPM_DIR" \
159-
-skipPackagePluginValidation \
160-
-skipMacroValidation \
161-
-showBuildTimingSummary \
162-
build \
163-
| tee build.log
164-
echo "== xcodebuild finished =="
165-
XC_STATUS=${PIPESTATUS[0]}
166-
set -e
167-
168-
if [ -f build.log ]; then
169-
echo "== error: lines =="
170-
if grep -i "error:" build.log; then
171-
if [ "$XC_STATUS" -eq 0 ]; then
172-
XC_STATUS=1
173-
fi
174-
fi
175-
fi
176-
177-
exit $XC_STATUS
41+
uses: ./.github/actions/ios-simulator-build
42+
with:
43+
scheme: ${{ env.SCHEME }}
17844

17945
- name: Comment build failure on PR
18046
if: failure() && github.event.pull_request.head.repo.fork == false

.github/workflows/testflight.yml

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ on:
77
workflow_dispatch:
88

99
env:
10+
SCHEME: DevLog
1011
RUBY_VERSION: "3.2"
1112
XCODE_VERSION: latest
1213
APP_STORE_TEAM_ID: ${{ secrets.APP_STORE_TEAM_ID }}
@@ -22,7 +23,37 @@ permissions:
2223
contents: read
2324

2425
jobs:
26+
validate:
27+
runs-on: macos-latest
28+
timeout-minutes: 30
29+
30+
steps:
31+
- uses: actions/checkout@v4
32+
33+
- name: Set up Xcode
34+
uses: maxim-lobanov/setup-xcode@v1
35+
with:
36+
xcode-version: ${{ env.XCODE_VERSION }}
37+
38+
- name: Cache SwiftPM
39+
uses: actions/cache@v4
40+
with:
41+
path: |
42+
~/.swiftpm
43+
~/Library/Caches/org.swift.swiftpm
44+
~/Library/Developer/Xcode/SourcePackages
45+
.spm
46+
key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
47+
restore-keys: |
48+
${{ runner.os }}-spm-
49+
50+
- name: Validate Debug Simulator Build
51+
uses: ./.github/actions/ios-simulator-build
52+
with:
53+
scheme: ${{ env.SCHEME }}
54+
2555
testflight:
56+
needs: validate
2657
runs-on: macos-latest
2758
timeout-minutes: 45
2859

@@ -47,5 +78,13 @@ jobs:
4778
run: |
4879
printf '%s' "$ASC_KEY_CONTENT" | base64 -D > "$ASC_KEY_PATH"
4980
81+
- name: Build for TestFlight
82+
run: bundle exec fastlane testflight_build_only
83+
84+
- name: Skip TestFlight Upload for Local QA Tag
85+
if: github.event_name == 'push' && startsWith(github.ref_name, 'qa-local-')
86+
run: echo "Skipping TestFlight upload for local QA tag ${GITHUB_REF_NAME}"
87+
5088
- name: Upload to TestFlight
51-
run: bundle exec fastlane deploy_testflight
89+
if: github.event_name != 'push' || !startsWith(github.ref_name, 'qa-local-')
90+
run: bundle exec fastlane upload_testflight_build

0 commit comments

Comments
 (0)