You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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变量引用:
    1. 在Xcode Build Settings中添加SHOP_NAME、SHOP_DOMAIN等用户定义变量
    2. 在Info.plist中用$(SHOP_NAME)、$(PRODUCT_BUNDLE_IDENTIFIER)等变量引用对应值
    3. 修改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}"
      
    4. 在Fastlane中直接读取环境变量:
      bundle_identifier = ENV["BUNDLE_IDENTIFIER"]
      shop_name = ENV["SHOP_NAME"]
      # 其他参数同理
      

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 21:45:07