Skip to content

Commit 7fa1dbc

Browse files
committed
fix: iOS CI/CD 빌드 실패 해결 및 동적 프로비저닝 구현 #7
Share Extension 버전 불일치 및 FlutterFire 빌드 오류를 해결하고, 동적 프로비저닝 프로파일 처리 및 Ruby 기반 코드 사이닝 자동화를 구현했습니다. 주요 변경사항: - Share Extension Info.plist에 필수 번들 키 추가 (CFBundleVersion 등) - 동적 프로비저닝 프로파일 이름 추출 (security cms -D, plutil) - ExportOptions.plist 동적 생성으로 유지보수성 향상 - Ruby xcodeproj gem을 사용한 코드 사이닝 자동화 - FlutterFire 빌드 단계 조건부 실행으로 CI 환경 대응 - .gitignore에 동적 생성 파일 추가 해결된 이슈: - Share Extension CFBundleVersion 불일치 에러 (null vs '11') - CI 환경에서 flutterfire 명령 없음 에러 - 프로비저닝 프로파일 갱신 시 수동 수정 필요 문제
1 parent aa58b84 commit 7fa1dbc

File tree

5 files changed

+183
-22
lines changed

5 files changed

+183
-22
lines changed

.claude/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{
22
"permissions": {
33
"allow": [
4-
"Bash(firebase --version:*)"
4+
"Bash(firebase --version:*)",
5+
"Bash(find:*)"
56
]
67
}
78
}

.github/workflows/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml

Lines changed: 159 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -278,26 +278,121 @@ jobs:
278278
p12-file-base64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
279279
p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
280280

281-
- name: Install Provisioning Profiles (Main App + Share Extension)
281+
- name: Install Provisioning Profiles
282+
id: install_profile
282283
run: |
283-
# 메인 앱 프로비저닝 프로파일 설치
284-
echo "${{ secrets.APPLE_PROVISIONING_PROFILE_BASE64 }}" | base64 --decode > profile_main.mobileprovision
285284
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
286-
uuid_main=$(grep -A1 -a "UUID" profile_main.mobileprovision | grep string | sed -e "s/<string>//" -e "s/<\/string>//" -e "s/[[:space:]]//g")
287-
cp profile_main.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/$uuid_main.mobileprovision
288-
echo "✅ 메인 앱 프로파일 설치 완료: ${{ secrets.IOS_PROVISIONING_PROFILE_NAME }}"
289285
290-
# Share Extension 프로비저닝 프로파일 설치
291-
echo "${{ secrets.APPLE_PROVISIONING_PROFILE_SHARE_BASE64 }}" | base64 --decode > profile_share.mobileprovision
292-
uuid_share=$(grep -A1 -a "UUID" profile_share.mobileprovision | grep string | sed -e "s/<string>//" -e "s/<\/string>//" -e "s/[[:space:]]//g")
293-
cp profile_share.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/$uuid_share.mobileprovision
294-
echo "✅ Share Extension 프로파일 설치 완료: ${{ secrets.IOS_PROVISIONING_PROFILE_SHARE_NAME }}"
286+
# 메인 앱 프로파일 설치
287+
echo "${{ secrets.APPLE_PROVISIONING_PROFILE_BASE64 }}" | base64 --decode > profile.mobileprovision
288+
cp profile.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/
295289
296-
- name: Verify ExportOptions.plist
290+
# UUID 추출
291+
uuid=$(grep -A1 -a "UUID" profile.mobileprovision | grep string | sed -e "s/<string>//" -e "s/<\/string>//" -e "s/[[:space:]]//g")
292+
cp profile.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/$uuid.mobileprovision
293+
294+
# 프로파일 이름 추출 (동적)
295+
profile_name=$(security cms -D -i profile.mobileprovision | plutil -extract Name xml1 -o - - | sed -n 's/.*<string>\(.*\)<\/string>.*/\1/p')
296+
297+
echo "✅ Main App Provisioning Profile installed"
298+
echo " UUID: $uuid"
299+
echo " Name: $profile_name"
300+
301+
# 출력 저장
302+
echo "profile_uuid=$uuid" >> $GITHUB_OUTPUT
303+
echo "profile_name=$profile_name" >> $GITHUB_OUTPUT
304+
305+
# Share Extension 프로파일 설치 (선택적)
306+
if [ -n "${{ secrets.APPLE_PROVISIONING_PROFILE_SHARE_BASE64 }}" ]; then
307+
echo "${{ secrets.APPLE_PROVISIONING_PROFILE_SHARE_BASE64 }}" | base64 --decode > share_extension_profile.mobileprovision
308+
cp share_extension_profile.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/
309+
310+
# Share Extension UUID 추출
311+
share_uuid=$(grep -A1 -a "UUID" share_extension_profile.mobileprovision | grep string | sed -e "s/<string>//" -e "s/<\/string>//" -e "s/[[:space:]]//g")
312+
cp share_extension_profile.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/$share_uuid.mobileprovision
313+
314+
# Share Extension 프로파일 이름 추출 (동적)
315+
share_profile_name=$(security cms -D -i share_extension_profile.mobileprovision | plutil -extract Name xml1 -o - - | sed -n 's/.*<string>\(.*\)<\/string>.*/\1/p')
316+
317+
echo "✅ Share Extension Provisioning Profile installed"
318+
echo " UUID: $share_uuid"
319+
echo " Name: $share_profile_name"
320+
321+
# 출력 저장
322+
echo "share_profile_uuid=$share_uuid" >> $GITHUB_OUTPUT
323+
echo "share_profile_name=$share_profile_name" >> $GITHUB_OUTPUT
324+
fi
325+
326+
- name: Create ExportOptions.plist
297327
run: |
298328
cd ios
299-
if [ ! -f "ExportOptions.plist" ]; then
300-
exit 1
329+
330+
# Share Extension 프로파일이 있는지 확인
331+
if [ -n "${{ steps.install_profile.outputs.share_profile_name }}" ]; then
332+
# Share Extension 포함
333+
cat > ExportOptions.plist << EOF
334+
<?xml version="1.0" encoding="UTF-8"?>
335+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
336+
<plist version="1.0">
337+
<dict>
338+
<key>method</key>
339+
<string>app-store</string>
340+
<key>teamID</key>
341+
<string>${{ secrets.APPLE_TEAM_ID }}</string>
342+
<key>provisioningProfiles</key>
343+
<dict>
344+
<key>com.elipair.mapsy</key>
345+
<string>${{ steps.install_profile.outputs.profile_name }}</string>
346+
<key>com.elipair.mapsy.share</key>
347+
<string>${{ steps.install_profile.outputs.share_profile_name }}</string>
348+
</dict>
349+
<key>signingStyle</key>
350+
<string>manual</string>
351+
<key>signingCertificate</key>
352+
<string>Apple Distribution</string>
353+
<key>stripSwiftSymbols</key>
354+
<true/>
355+
<key>uploadBitcode</key>
356+
<false/>
357+
<key>uploadSymbols</key>
358+
<true/>
359+
</dict>
360+
</plist>
361+
EOF
362+
echo "✅ ExportOptions.plist 생성 완료 (Share Extension 포함)"
363+
echo " Main Profile: ${{ steps.install_profile.outputs.profile_name }}"
364+
echo " Share Extension Profile: ${{ steps.install_profile.outputs.share_profile_name }}"
365+
else
366+
# 메인 앱만
367+
cat > ExportOptions.plist << EOF
368+
<?xml version="1.0" encoding="UTF-8"?>
369+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
370+
<plist version="1.0">
371+
<dict>
372+
<key>method</key>
373+
<string>app-store</string>
374+
<key>teamID</key>
375+
<string>${{ secrets.APPLE_TEAM_ID }}</string>
376+
<key>provisioningProfiles</key>
377+
<dict>
378+
<key>com.elipair.mapsy</key>
379+
<string>${{ steps.install_profile.outputs.profile_name }}</string>
380+
</dict>
381+
<key>signingStyle</key>
382+
<string>manual</string>
383+
<key>signingCertificate</key>
384+
<string>Apple Distribution</string>
385+
<key>stripSwiftSymbols</key>
386+
<true/>
387+
<key>uploadBitcode</key>
388+
<false/>
389+
<key>uploadSymbols</key>
390+
<true/>
391+
</dict>
392+
</plist>
393+
EOF
394+
echo "✅ ExportOptions.plist 생성 완료 (메인 앱만)"
395+
echo " Main Profile: ${{ steps.install_profile.outputs.profile_name }}"
301396
fi
302397
303398
- name: Flutter build (no codesign)
@@ -306,19 +401,63 @@ jobs:
306401
--build-name="${{ needs.prepare-build.outputs.version }}" \
307402
--build-number="${{ needs.prepare-build.outputs.build_number }}"
308403
404+
- name: Configure Code Signing in Project
405+
run: |
406+
cd ios
407+
408+
# xcodeproj gem 설치
409+
gem install xcodeproj
410+
411+
# Ruby 스크립트로 프로젝트 설정 수정
412+
ruby <<RUBY_SCRIPT
413+
require 'xcodeproj'
414+
415+
project = Xcodeproj::Project.open('Runner.xcodeproj')
416+
417+
# Runner 타겟 설정
418+
runner_target = project.targets.find { |t| t.name == 'Runner' }
419+
if runner_target
420+
runner_target.build_configurations.each do |config|
421+
if config.name == 'Release'
422+
config.build_settings['CODE_SIGN_STYLE'] = 'Manual'
423+
config.build_settings['CODE_SIGN_IDENTITY'] = 'Apple Distribution'
424+
config.build_settings['PROVISIONING_PROFILE_SPECIFIER'] = '${{ steps.install_profile.outputs.profile_name }}'
425+
puts "✅ Runner target configured with profile: ${{ steps.install_profile.outputs.profile_name }}"
426+
end
427+
end
428+
end
429+
430+
# Share Extension 타겟 설정 (있는 경우)
431+
share_profile = '${{ steps.install_profile.outputs.share_profile_name }}'
432+
if !share_profile.empty?
433+
share_target = project.targets.find { |t| t.name == 'com.elipair.mapsy.share' }
434+
if share_target
435+
share_target.build_configurations.each do |config|
436+
if config.name == 'Release'
437+
config.build_settings['CODE_SIGN_STYLE'] = 'Manual'
438+
config.build_settings['CODE_SIGN_IDENTITY'] = 'Apple Distribution'
439+
config.build_settings['PROVISIONING_PROFILE_SPECIFIER'] = share_profile
440+
puts "✅ com.elipair.mapsy.share target configured with profile: #{share_profile}"
441+
end
442+
end
443+
end
444+
end
445+
446+
project.save
447+
puts "✅ Project signing configuration saved"
448+
RUBY_SCRIPT
449+
309450
- name: Create Archive
310-
env:
311-
IOS_PROVISIONING_PROFILE_NAME: ${{ secrets.IOS_PROVISIONING_PROFILE_NAME }}
312451
run: |
313452
cd ios
453+
echo "📦 Creating Archive with Manual Signing"
454+
314455
xcodebuild -workspace Runner.xcworkspace \
315456
-scheme Runner \
457+
-configuration Release \
316458
-archivePath build/Runner.xcarchive \
317459
-destination 'generic/platform=iOS' \
318-
archive \
319-
CODE_SIGN_STYLE=Manual \
320-
PROVISIONING_PROFILE_SPECIFIER="$IOS_PROVISIONING_PROFILE_NAME" \
321-
CODE_SIGN_IDENTITY="Apple Distribution"
460+
archive
322461
323462
- name: Export IPA
324463
run: |

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ migrate_working_dir/
3333
/build/
3434
/coverage/
3535

36+
# iOS CI/CD generated files
37+
ios/ExportOptions.plist
38+
3639
# Symbolication related
3740
app.*.symbols
3841

ios/Runner.xcodeproj/project.pbxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@
398398
);
399399
runOnlyForDeploymentPostprocessing = 0;
400400
shellPath = /bin/sh;
401-
shellScript = "\n#!/bin/bash\nPATH=\"${PATH}:$FLUTTER_ROOT/bin:${PUB_CACHE}/bin:$HOME/.pub-cache/bin\"\n\nif [ -z \"$PODS_ROOT\" ] || [ ! -d \"$PODS_ROOT/FirebaseCrashlytics\" ]; then\n # Cannot use \"BUILD_DIR%/Build/*\" as per Firebase documentation, it points to \"flutter-project/build/ios/*\" path which doesn't have run script\n DERIVED_DATA_PATH=$(echo \"$BUILD_ROOT\" | sed -E 's|(.*DerivedData/[^/]+).*|\\1|')\n PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\"${DERIVED_DATA_PATH}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run\"\nelse\n PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT=\"$PODS_ROOT/FirebaseCrashlytics/run\"\nfi\n\n# Command to upload symbols script used to upload symbols to Firebase server\nflutterfire upload-crashlytics-symbols --upload-symbols-script-path=\"$PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT\" --platform=ios --apple-project-path=\"${SRCROOT}\" --env-platform-name=\"${PLATFORM_NAME}\" --env-configuration=\"${CONFIGURATION}\" --env-project-dir=\"${PROJECT_DIR}\" --env-built-products-dir=\"${BUILT_PRODUCTS_DIR}\" --env-dwarf-dsym-folder-path=\"${DWARF_DSYM_FOLDER_PATH}\" --env-dwarf-dsym-file-name=\"${DWARF_DSYM_FILE_NAME}\" --env-infoplist-path=\"${INFOPLIST_PATH}\" --default-config=default\n";
401+
shellScript = "\n#!/bin/bash\n\n# CI 환경이거나 flutterfire가 없으면 조건부 스크립트 사용\nif [ -f \"${SRCROOT}/scripts/upload_crashlytics_symbols.sh\" ]; then\n bash \"${SRCROOT}/scripts/upload_crashlytics_symbols.sh\"\nelse\n echo \"⚠️ upload_crashlytics_symbols.sh not found, skipping...\"\nfi\n";
402402
};
403403
2705C21E3BC5614AA64835E3 /* [CP] Check Pods Manifest.lock */ = {
404404
isa = PBXShellScriptBuildPhase;

ios/com.elipair.mapsy.share/Info.plist

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,24 @@
22
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33
<plist version="1.0">
44
<dict>
5+
<key>CFBundleDevelopmentRegion</key>
6+
<string>$(DEVELOPMENT_LANGUAGE)</string>
7+
<key>CFBundleDisplayName</key>
8+
<string>Mapsy Share</string>
9+
<key>CFBundleExecutable</key>
10+
<string>$(EXECUTABLE_NAME)</string>
11+
<key>CFBundleIdentifier</key>
12+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
13+
<key>CFBundleInfoDictionaryVersion</key>
14+
<string>6.0</string>
15+
<key>CFBundleName</key>
16+
<string>$(PRODUCT_NAME)</string>
17+
<key>CFBundlePackageType</key>
18+
<string>XPC!</string>
19+
<key>CFBundleShortVersionString</key>
20+
<string>$(FLUTTER_BUILD_NAME)</string>
21+
<key>CFBundleVersion</key>
22+
<string>$(FLUTTER_BUILD_NUMBER)</string>
523
<key>NSExtension</key>
624
<dict>
725
<key>NSExtensionAttributes</key>

0 commit comments

Comments
 (0)