iOS多App基于Fastlane+Jenkins打包上传的签名问题求助
问题描述
我有一个可构建多App的Xcode项目,通过不同Bundle ID复用代码,每个App在App Store Connect对应独立应用。用Jenkins+Fastlane搭建了CI/CD流程,通过Jenkinsfile传递各App的详情和Bundle ID。
执行Jenkins任务时,第一个与Xcode项目Build Settings默认Bundle ID一致的App打包正常,但第二个App归档时报错:No provisioning profile found。已确认配置文件已正确获取,推测问题是Jenkins仍使用Xcode项目默认的Bundle ID构建。
目前通过xcconfig文件向Build Settings传递配置,但找不到构建时修改Xcode Build Settings中Bundle ID及对应Provisioning Profile的方法。想解决这个问题,同时确认能否通过同一Jenkins任务+Fastlane流水线实现多App上传。
当前Jenkinsfile脚本
pipeline { agent any environment { LANG = 'en_US.UTF-8' LANGUAGE = 'en_US.UTF-8' LC_ALL = 'en_US.UTF-8' // Other environment variables as needed } stages { stage('Build and Deploy') { steps { script { // Define shop-specific information for all the shops def shopInfo = [ [ shopName: 'shopName', shopDomain: 'shopDomain', shopifyApiKey: 'shopifyApiKey', shopifyStoreToken: 'shopifyStoreToken', bundleIdentifier: 'bundleIdentifier' ], [ shopName: 'shopName', shopDomain: 'shopDomain', shopifyApiKey: 'shopifyApiKey', shopifyStoreToken: 'shopifyStoreToken', bundleIdentifier: 'bundleIdentifier' ] // Add information for other shops here ] def infoPlistPath = 'path to Info.plist' // Update with the correct path // Retrieve the Fastlane username and password from Jenkins credentials def fastlaneUser = '' def fastlanePassword = '' // Iterate through the shop information for (shop in shopInfo) { // Set shop-specific details def shopName = shop.shopName def shopDomain = shop.shopDomain def shopifyApiKey = shop.shopifyApiKey def shopifyStoreToken = shop.shopifyStoreToken def bundleIdentifier = shop.bundleIdentifier // Set shop-specific environment variables including the bundle identifier withEnv([ "SHOP_NAME=${shopName}", "SHOP_DOMAIN=${shopDomain}", "SHOPIFY_API_KEY=${shopifyApiKey}", "SHOPIFY_STORE_TOKEN=${shopifyStoreToken}", "BUNDLE_IDENTIFIER=${bundleIdentifier}", 'FASTLANE_USER=username', 'FASTLANE_PASSWORD=password' ]) { // Change the working directory to the Fastlane directory dir('/Users/shahnasek/Documents/Work/beCo-Store-iOS/beCo-Store/fastlane') { // Run commands to modify Info.plist sh "/usr/libexec/PlistBuddy -c 'Set :ShopName $SHOP_NAME' $infoPlistPath" sh "/usr/libexec/PlistBuddy -c 'Set :ShopDomain $SHOP_DOMAIN' $infoPlistPath" sh "/usr/libexec/PlistBuddy -c 'Set :ShopifyAPIKey $SHOPIFY_API_KEY' $infoPlistPath" sh "/usr/libexec/PlistBuddy -c 'Set :StoreToken $SHOPIFY_STORE_TOKEN' $infoPlistPath" sh "/usr/libexec/PlistBuddy -c 'Set :CFBundleIdentifier $BUNDLE_IDENTIFIER' $infoPlistPath" // Use the Fastlane username and password directly sh 'bundle exec fastlane beta' // Additional steps or cleanup as needed } } } } } } }
当前Fastlane脚本
default_platform(:ios) platform :ios do desc "Description of what the lane does" lane :beta do xcode_select("/Applications/Xcode.app") bundle_identifier = sh("ruby get_bundle_identifier.rb", log: true).strip puts "Fetched bundle identifier: #{bundle_identifier}" latest_build_number = latest_testflight_build_number( app_identifier: bundle_identifier ) UI.message("Latest TestFlight Build Number: #{latest_build_number}") increment_build_number( build_number: latest_build_number + 1 ) match( type: "appstore", app_identifier: bundle_identifier, git_url: "gitURL" ) app_store_connect_api_key( key_id: "key_id", issuer_id: "issuer_id", key_filepath: "key_filepath", duration: 1200, in_house: false ) gym( scheme: "beCo-Store", export_options: { method: "app-store", provisioningProfiles: { bundle_identifier => "match AppStore #{bundle_identifier}" }, signingCertificate: "iPhone Distribution" } ) sync_code_signing( type: "appstore", git_url: "git_url", app_identifier: bundle_identifier ) upload_to_testflight( skip_waiting_for_build_processing: true, # Add this to skip waiting wait_processing_interval: 30, # Increase the interval app_platform: 'ios' ) end end
解决方案
1. 强制覆盖Xcode Build Settings中的Bundle ID
只修改Info.plist不足以覆盖Xcode的构建配置,必须在Fastlane的gym动作中明确指定Build Settings参数,强制替换Bundle ID和签名配置:
gym( scheme: "beCo-Store", # 新增Build Settings覆盖配置 build_settings: { "PRODUCT_BUNDLE_IDENTIFIER" => bundle_identifier, "CODE_SIGN_STYLE" => "Manual", "PROVISIONING_PROFILE_SPECIFIER" => "match AppStore #{bundle_identifier}" }, export_options: { method: "app-store", provisioningProfiles: { bundle_identifier => "match AppStore #{bundle_identifier}" }, signingCertificate: "iPhone Distribution" } )
2. 优化Jenkins流程,避免缓存污染
每次构建不同App前,必须清理Xcode缓存,防止前一次构建的配置残留:
- 在Fastlane lane开头添加
clean_build_artifacts动作,或在Jenkins中执行sh 'xcodebuild clean' - 放弃直接修改Info.plist的方式,改用Xcode用户定义设置+Info.plist变量引用:
- 在Xcode Build Settings中添加
SHOP_NAME、SHOP_DOMAIN等用户定义变量 - 在Info.plist中用
$(SHOP_NAME)、$(PRODUCT_BUNDLE_IDENTIFIER)等变量引用对应值 - 修改Jenkinsfile,去掉PlistBuddy命令,直接传递环境变量到Fastlane:
sh "bundle exec fastlane beta BUNDLE_IDENTIFIER=${bundleIdentifier} SHOP_NAME=${shopName} SHOP_DOMAIN=${shopDomain} SHOPIFY_API_KEY=${shopifyApiKey} SHOPIFY_STORE_TOKEN=${shopifyStoreToken}" - 在Fastlane中直接读取环境变量:
bundle_identifier = ENV["BUNDLE_IDENTIFIER"] shop_name = ENV["SHOP_NAME"] # 其他参数同理
- 在Xcode Build Settings中添加
3. 简化Fastlane签名流程
- 移除重复的
sync_code_signing动作,match已经完成了签名配置的同步 - 确保
match生成的.xcconfig文件被Xcode项目引用,或在gym中指定xcconfig参数
4. 多App上传的可行性确认
完全可以通过同一Jenkins任务+Fastlane流水线实现多App上传,只要满足:
- 每个Bundle ID对应App Store Connect中的独立应用
- 每次循环构建时清理缓存、覆盖Build Settings、使用对应Bundle ID的签名配置
- 按每个App独立递增Build Number(当前脚本的
latest_testflight_build_number已实现此逻辑)
内容的提问来源于stack exchange,提问作者Shana
相关产品推荐
相关产品推荐

