【问题标题】:Archive & Distribute iOS app to App Store via command line通过命令行归档和分发 iOS 应用到 App Store
【发布时间】:2014-03-18 14:40:31
【问题描述】:

通常在将 iOS 应用程序提交到 App Store 时,我会从 Xcode 执行 Product -> Archive,然后选择分发到 App Store。我可以通过以下方式成功归档构建:

xcodebuild -scheme "myScheme" archive -archivePath /my/path/myArchive

但是如何使用正确的配置文件进行签名过程并通过命令行分发?

对于临时构建,我在归档后生成我的 ipa:

xcodebuild -exportArchive -exportFormat IPA -archivePath myArchive.xcarchive -exportPath /my/path/myFile.ipa -exportProvisioningProfile 'my adhoc profile name'

但是在分发到应用商店时,我什至需要生成 ipa 吗?无论哪种方式,我如何使用正确的配置文件进行签名并通过命令行分发?

【问题讨论】:

    标签: ios xcode app-store


    【解决方案1】:

    请参阅答案底部的 Xcode 8 更新。

    首先要回答问题的最后一部分 - 是的,需要 App Store Provisioning Profile 才能通过 iTunes Connect 提交您的应用。除非它具有正确的配置文件,否则它不会通过预验证步骤。您需要在会员中心创建 App Store 分发配置文件

    选择“App Store”并点击继续

    问题的第一部分有点困难,因为使用命令行工具创建、签名和分发档案和 IPA 文件的文档很少。实施脚本解决方案充满了陷阱,因为在某些情况下工具的行为与预期不符,并且需要更详细地了解您的开发者帐户、钥匙串、签名证书和配置文件之间的关系。

    这里是一个脚本示例,可用于创建具有嵌入式 Ad Hoc 配置文件的存档,为 Ad Hoc 分发创建 IPA。作为奖励,创建了 DSYMs zip 文件以上传到 TestFlight。然后提供了另外两个脚本。第一个将从现有的 xcarchive 创建 IPA 的 App Store 版本,第二个将展示如何修改 xcarchive,以便第三方可以将其退出以进行 Enterprise In House 分发。

    此自动构建脚本假定 Provisioning Profiles 位于名为 ProvisioningProfiles 的目录中,该目录与源代码一起签入。它还假设解锁持有签名证书的钥匙串的密码存储在构建用户主目录中的受保护文件中。

    #!/bin/sh
    
    # SETME
    # set to name of signing certification usually starts something like "iPhone Distribution: ...."
    # (the associated private key must be available in the key store)
    #
    # use the command "security find-identity" to list all the possible values available
    #
    codeSignIdentity="iPhone Distribution"
    
    # SETME
    # set to location of Ad Hoc provisioning profile
    # (this profile must have the codeSignIdentity specified above included in it)
    #
    provisioningProfile=ProvisioningProfiles/MyAppAdHocDistribution.mobileprovision
    
    # The keychain needs to be unlocked for signing, which requires the keychain
    # password. This is stored in a file in the build account only accessible to
    # the build account user
    if [ ! -f $HOME/.pass ] ; then
        echo "no keychain password file available"
        exit 1
    fi
    
    case `stat -L -f "%p" $HOME/.pass`
    in
        *400) ;;
        *)
            echo "keychain password file permissions are not restrictive enough"
            echo "chmod 400 $HOME/.pass"
            exit 1
            ;;
    esac
    
    #
    # turn off tracing if it is on for security command
    # to prevent logging of password
    #
    case `set -o | grep xtrace`
    in
        *on) xon=yes ;;
        *) xon=no ;;
    esac
    
    #
    # unlock the keychain, automatically lock keychain on script exit
    #
    [ $xon == yes ] && set +x
    security unlock-keychain -p `cat $HOME/.pass` $HOME/Library/Keychains/login.keychain
    [ $xon == yes ] && set -x
    trap "security lock-keychain $HOME/Library/Keychains/login.keychain" EXIT
    
    #
    # Extract the profile UUID from the checked in Provisioning Profile.
    #
    uuid=`/usr/libexec/plistbuddy -c Print:UUID /dev/stdin <<< \
            \`security cms -D -i $provisioningProfile\``
    
    #
    # Copy the profile to the location XCode expects to find it and start the build,
    # specifying which profile and signing identity to use for the archived app
    #
    cp -f $provisioningProfile \
            "$HOME/Library/MobileDevice/Provisioning Profiles/$uuid.mobileprovision"
    
    #
    # Build the xcarchive - this will only be done once, will will then
    # distribute it for Ad Hoc, App Store and Enterprise In House scenarios
    # (profile must be specified by UUID for this step)
    #
    xcodebuild \
        -workspace MyApp.xcworkspace \
        -scheme MyApp \
        -archivePath build/MyApp.xcarchive \
        archive \
        PROVISIONING_PROFILE="$uuid" \
        CODE_SIGN_IDENTITY="$codeSignIdentity"
    
    #
    # Create a zip of the DSYMs for TestFlight
    #
    /usr/bin/zip -r MyApp.dSYM.zip build/MyApp.xcarchive/dSYMs/MyApp.app.dSYM
    
    #
    # now distribute the xcarchive using an Ad Hoc profile
    # (for QA testing for example)
    #
    profileName=`/usr/libexec/plistbuddy -c Print:Name /dev/stdin <<< \
            \`security cms -D -i $provisioningProfile\``
    
    #
    # The profile must be specified by name for this step
    #
    xcodebuild \
            -exportArchive \
            -exportFormat IPA \
            -archivePath build/MyApp.xcarchive \
            -exportPath MyAppForAdHoc.ipa \
            -exportProvisioningProfile "$profileName"
    

    要使用 App Store Distribution 配置文件重新分发 xcarchive,请使用新配置文件重新导出 xcarchive(Ad Hoc 和 App Store 配置文件的签名身份相同)。

    # SETME
    # set to location of App Store provisioning profile
    #
    appStoreProvisioningProfile=ProvisioningProfiles/MyAppAppStoreDistribution.mobileprovision
    
    #
    # Extract the App Store profile UUID from the checked in Provisioning Profile.
    #
    uuid=`/usr/libexec/plistbuddy -c Print:UUID /dev/stdin <<< \
            \`security cms -D -i $appStoreProvisioningProfile\``
    
    #
    # Copy the profile to the location XCode expects to find it and start the export,
    # specifying which profile to use for the archived app
    # (Profile must match with signing identity used to create xcarchive)
    #
    cp -f $appStoreProvisioningProfile \
            "$HOME/Library/MobileDevice/Provisioning Profiles/$uuid.mobileprovision"
    
    #
    # Extract the enterprise profile name from the checked in App Store Provisioning Profile.
    # and redistribute the xcarchive as an App Store ready IPA
    #
    profileName=`/usr/libexec/plistbuddy -c Print:Name /dev/stdin <<< \
            \`security cms -D -i $appStoreProvisioningProfile\``
    
    #
    # Profile must be specified by name for this step
    #
    xcodebuild \
        -exportArchive \
        -exportFormat IPA \
        -archivePath build/MyApp.xcarchive \
        -exportPath MyAppForStore.ipa \
        -exportProvisioningProfile "$profileName"
    

    最后只是为了完整,如果您想使用新的身份和配置文件退出 xcarchive 怎么办?如果您将 xcarchive 分发给第三方公司以进行内部分发,则可能会发生这种情况。收件人需要使用他们的企业证书签署您的 xcarchive 以进行分发。 xcodebuild 无法强制覆盖 xcarchive 中已有的代码签名,因此必须直接使用 codesign。

    # SETME
    # set to name of enterprise signing certification usually starts something like
    # "iPhone Distribution: ...."
    #
    # use the command "security find-identity" to list all the possible values available
    #
    enterpriseCodeSignIdentity="iPhone Distribution: Acme Ltd"
    
    # SETME
    # set to location of Enterprise In-House provisioning profile
    # (this profile must be associated with the enterprise code signing identity)
    #
    enterpriseProvisioningProfile=ProvisioningProfiles/MyAppInHouseDistribution.mobileprovision
    
    # SETME
    # A resigning of the app with a different certificate requires a new bundle ID
    # that is registered by the Enterprise and is included in the In-House distribution
    # profile (This could be automatically extracted from the Enterprise In-House distribution
    # profile, I leave that as an ETTR)
    enterpriseBundleId="com.enterprise.myapp"
    
    #
    # Extract the enterprise profile UUID from the checked in Provisioning Profile.
    #
    euuid=`/usr/libexec/plistbuddy -c Print:UUID /dev/stdin <<< \
            \`security cms -D -i $enterpriseProvisioningProfile\``
    
    #
    # Copy the profile to the location XCode expects to find it and start the build,
    # specifying which profile and signing identity to use for the archived app
    #
    cp -f $enterpriseProvisioningProfile \
            "$HOME/Library/MobileDevice/Provisioning Profiles/$euuid.mobileprovision"
    
    #
    # Copy, modify and resign the xcarchive ready for Enterprise deployment
    # (has to be resigned as the production certificate is different for enterprise)
    #
    cp -Rp build/MyApp.xcarchive build/MyAppEnterprise.xcarchive
    
    #
    # Remove old code signature
    #
    rm -rf build/MyAppEnterprise.xcarchive/Products/Applications/MyApp.app/_CodeSignature
    
    #
    # copy in the enterprise provisioning profile
    #
    cp $enterpriseProvisioningProfile \
            build/MyAppEnterprise.xcarchive/Products/Applications/MyApp.app/embedded.mobileprovision
    
    #
    # Modify the bundle id to that of the enterprise bundle id
    #   
    /usr/libexec/plistbuddy -c "Set:CFBundleIdentifier $enterpriseBundleId" \
            build/MyAppEnterprise.xcarchive/Products/Applications/MyApp.app/Info.plist
    
    #
    # resign the xcarchive with the enterprise code signing identity
    #
    /usr/bin/codesign -f -v -s $enterpriseCodeSignIdentity \
            build/MyAppEnterprise.xcarchive/Products/Applications/MyApp.app 
    
    #
    # Update the DSYM bundle id and create a zip of the DSYMs for TestFlight (if applicable)
    #
    /usr/libexec/plistbuddy -c "Set:CFBundleIdentifier com.apple.xcode.dsym.${enterpriseBundleId}" \
            build/MyAppEnterprise.xcarchive/dSYMs/MyApp.app.dSYM/Contents/Info.plist
    /usr/bin/zip -r MyAppEnterprise.dSYM.zip build/MyAppEnterprise.xcarchive/dSYMs/MyApp.app.dSYM
    
    #
    # Extract the enterprise profile Name from the checked in Provisioning Profile.
    #
    enterpriseProfileName=`/usr/libexec/plistbuddy -c Print:Name /dev/stdin <<< \
            l\`security cms -D -i $enterpriseProvisioningProfile\``
    
    #
    # Profile must be specified by name for this step
    #
    xcodebuild \
        -exportArchive \
        -exportFormat IPA \
        -archivePath build/MyAppEnterprise.xcarchive \
        -exportPath MyAppEnterprise.ipa \
        -exportProvisioningProfile "$enterpriseProfileName"
    

    如果脚本作为 launchd 守护进程运行,请参阅此答案 https://stackoverflow.com/a/9482707/2351246 以解决从 launchd 守护进程访问登录钥匙串的问题。

    OSX Mavericks 和 Yosemite 的更新

    在 OSX Mavericks (v10.9.5) 和 OSX Yosemite 上,您可能会看到代码签名错误:

    Codesign check fails : ...../MyApp.app: resource envelope is obsolete
    

    在此处查看此帖子以了解原因xcodebuild - codesign -vvvv says"resource envelope is obsolete"

    要实施 Apple 支持在引用的帖子中建议的更改,请运行以下命令:

     sudo perl -pi.bak -e 's/--verify"./--verify", "--no-strict",/ if /codesign.*origApp/;' `xcrun -sdk iphoneos -f PackageApplication`
    

    Xcode8 更新

    在 Xcode8 中,我之前的回答中描述的过程不再适用于新的 自动管理签名 功能,因此您需要选择手动签名才能使用此方法。

    如果您希望使用自动签名,以下是基于我们尝试使其与 IBM Jazz 和 Jenkins a CI 环境一起工作的一些观察结果。

    1. 如果您有一台 CI 机器来使自动代码签名工作,这是可能的。我发现您必须在 CI 机器上为 Xcode 实例创建并分配一个开发人员帐户。这是一个手动步骤,我发现无法从命令行导入开发人员配置文件。

    2. 如果您使用具有多台构建机器的分布式 CI 环境,它就无法正常工作。首先,您遇到上述问题,您必须手动将开发人员帐户添加到所有 Xcode 实例,其次,每个帐户必须是不同的 Apple ID,否则您会遇到通用构建帐户的证书生成问题(所有机器正在共享一个导致开发者证书冲突的帐户,因为它与特定的机器绑定)。

    我们运行分布式 Jenkins CI 环境,所以我们坚持手动签名,但导出 IPA 的方法发生了变化,现在必须使用 -exportOptionsPlist 选项。

    更改归档命令:

    #
    # Build the xcarchive - this will only be done once, will will then
    # distribute it for Ad Hoc, App Store and Enterprise In House scenarios
    #
    xcodebuild \
        -workspace MyApp.xcworkspace \
        -scheme MyApp \
        -archivePath build/MyApp.xcarchive \
        archive 
    

    存档使用与构建帐户关联的 iOS 开发者证书进行签名(因此请确保它在钥匙串中安装了一个)。现在可以使用 xcodebuild 的 -exportOptionsPlist 选项将存档导出为 Ad-hoc、Enterprise 和 App Store 的 IPA 格式。

    使用以下内容创建一个名为 exportAppStore.plist 的文件并将其保存在您的顶级项目目录中。

    <?xml version="1.0" encoding="UTF-8"?>                                                                                                                                                                                                                                                                                                     
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <key>method</key>
        <string>app-store</string>
    </dict>
    </plist>
    

    请参阅输出 xcodebuild -help 以获取可用于 -exportOptionsPlist 选项的键的完整列表。

    现在修改导出存档命令以使用新的导出选项 plist 文件

    xcodebuild \
        -exportArchive \
        -archivePath build/MyApp.xcarchive \
        -exportOptionsPlist exportAppStore.plist \
        -exportPath MyAppForStore.ipa
    

    【讨论】:

    • @BitByeDog:能解释一下你是如何生成 $HOME/.pass 的吗?我在优胜美地,找不到这样的文件。
    • @Hamid $HOME/.pass 文件只是一个包含构建用户密码的文本文件。使用文件权限保护文件免受未经授权的访问。以下两个命令将完成此操作 1. echo -n "" > $HOME/.pass 2. chmod 400 $HOME/.pass 。请记住用实际帐户密码代替
    猜你喜欢
    • 2014-01-01
    • 2016-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-15
    • 2015-04-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多