关于 iOS 批量打包的总结

作者: Tsui_YuenHong | 来源:发表于2016-10-31 16:46 被阅读2684次

    关于 iOS 自动化打包的一点看法

    如果你曾经试过做多 target 的项目,到了测试人员要测试包的时候,你就会明白什么叫“生不如死”。虽然 Xcode 打包很方便,但是当你机械重复打 N 次包的时候,就会觉得这纯粹是浪费时间的工作。所以这时候自动化打包就显得尤为重要(其实就算只有一个 target,就算使用 Xcode 打包很方便,也应该构建自动化打包,因为你可以节省大量时间)。

    构建自动化打包脚本

    xcodebuild

    使用 xcodebuild -h 来看看 xcodebuild 到底是干啥的

    Usage: xcodebuild [-project <projectname>] [[-target <targetname>]...|-alltargets] [-configuration <configurationname>] [-arch <architecture>]... [-sdk [<sdkname>|<sdkpath>]] [-showBuildSettings] [<buildsetting>=<value>]... [<buildaction>]...
           xcodebuild [-project <projectname>] -scheme <schemeName> [-destination <destinationspecifier>]... [-configuration <configurationname>] [-arch <architecture>]... [-sdk [<sdkname>|<sdkpath>]] [-showBuildSettings] [<buildsetting>=<value>]... [<buildaction>]...
           xcodebuild -workspace <workspacename> -scheme <schemeName> [-destination <destinationspecifier>]... [-configuration <configurationname>] [-arch <architecture>]... [-sdk [<sdkname>|<sdkpath>]] [-showBuildSettings] [<buildsetting>=<value>]... [<buildaction>]...
           xcodebuild -version [-sdk [<sdkfullpath>|<sdkname>] [<infoitem>] ]
           xcodebuild -list [[-project <projectname>]|[-workspace <workspacename>]] [-json]
           xcodebuild -showsdks
           xcodebuild -exportArchive -archivePath <xcarchivepath> -exportPath <destinationpath> -exportOptionsPlist <plistpath>
           xcodebuild -exportLocalizations -localizationPath <path> -project <projectname> [-exportLanguage <targetlanguage>...]
           xcodebuild -importLocalizations -localizationPath <path> -project <projectname>
    

    这里我只截取了 usage 部分,option 部分太多没有截取。

    这里介绍几条毕竟常用的命令

    1. xcodebuild -list ...

    xcodebuild -list [[-project <projectname>]|[-workspace <workspacename>]] [-json]

    usage: 输出 project 中的 targets 和 configurations,或者 workspace 中 schemes。
    -project-workspace 是输出指定内容,不输入默认输出当前目录下。-json 是以 json 格式输出。

    example:

    $ xcodebuild -list
    Information about project "XX":
        Targets:
            XX
            XXTests
    
        Build Configurations:
            Debug
            Release
    
        If no build configuration is specified and -scheme is not passed then "Release" is used.
    
        Schemes:
            XX
    

    2. xcodebuild -project ...

    xcodebuild [-project <projectname>] [[-target <targetname>]...|-alltargets] [-configuration <configurationname>] [-arch <architecture>]... [-sdk [<sdkname>|<sdkpath>]] [-showBuildSettings] [<buildsetting>=<value>]... [<buildaction>]...

    usage:

    -project: 指定 project 名字,默认首个 project。

    -target: 指定对应的 target ,默认首个 target。

    -configuration: 选择Debug 或 Release,默认 Release,当然如果你有自定义的配置的,就应该选你配置的,上面 -list 中有输出。

    -showBuildSettings: 显示工程的配置。

    <buildsetting>=<value>: 修改工程的配置文件。

    buildaction ... : 如下,默认为 build

              Specify a build action (or actions) to perform on the target.
              Available build actions are:
    
              build       Build the target in the build root (SYMROOT).  This is
                          the default build action.
    
              installsrc  Copy the source of the project to the source root
                          (SRCROOT).
    
              install     Build the target and install it into the target's
                          installation directory in the distribution root
                          (DSTROOT).
    
              clean       Remove build products and intermediate files from the
                          build root (SYMROOT).
    
    

    example:

    • $ xcodebuild -project 你的项目名字.xcodeproj -target 你的 target 名字 -configuration release

    这行命令表示编译 xx.xcodeproj 的 xx target。在 terminal 中会看到编译过程,如果成功最后会输出 ** BUILD SUCCEEDED **。最后会在当前目录下生成 build/Release-iphoneos/xx.app

    • $ xcodebuild -project 你的项目名字.xcodeproj -target 你的 target 名字 -configuration release -showBuildSettings

    这行命令使用 -showBuildSettings 是不会 build 项目的,只是输出工程的配置。这里输出的的内容有(内容过多,只截取部分)

    Build settings for action build and target XX:
        ACTION = build
        AD_HOC_CODE_SIGNING_ALLOWED = NO
        ALTERNATE_GROUP = staff
        ALTERNATE_MODE = u+w,go-w,a+rX
        ALTERNATE_OWNER = TsuiYuenHong
        ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO
        ALWAYS_SEARCH_USER_PATHS = NO
        ALWAYS_USE_SEPARATE_HEADERMAPS = NO
        APPLE_INTERNAL_DEVELOPER_DIR = /AppleInternal/Developer
        APPLE_INTERNAL_DIR = /AppleInternal
        APPLE_INTERNAL_DOCUMENTATION_DIR = /AppleInternal/Documentation
        APPLE_INTERNAL_LIBRARY_DIR = /AppleInternal/Library
        APPLE_INTERNAL_TOOLS = /AppleInternal/Developer/Tools
        APPLICATION_EXTENSION_API_ONLY = NO
        APPLY_RULES_IN_COPY_FILES = NO
        ARCHS = armv7 arm64
        ...
    

    如果要修改配置文件,就直接最命令最后加上你要修改的内容。
    例如在这行命令最后加上指定证书

    • $ xcodebuild -project 你的项目名字.xcodeproj -target 你的 target 名字 -configuration release PROVISIONING_PROFILE="你证书的id"

    其中的字段是上面 -showBuildSettings 显示的字段,也可以看官网介绍

    3. xcodebuild -workspace ...

    xcodebuild -workspace <workspacename> -scheme <schemeName> [-destination <destinationspecifier>]... [-configuration <configurationname>] [-arch <architecture>]... [-sdk [<sdkname>|<sdkpath>]] [-showBuildSettings] [<buildsetting>=<value>]... [<buildaction>]...

    除了 workspace 和 scheme 之外其余选项都和上条命令相同。

    -workspace: 指定 workspace 名字,默认首个 workspace

    -scheme: 指定对应的 scheme ,默认首个 scheme

    4 . xcodebuild -exportArchive ...

    这里顺便介绍一下 archive 命令,因为在下面使用 PackageApplication 会出一个警告说推荐使用 -exportArchive。所以我们就来尝试一下使用 archive 来生成 app。

    首先使用一下命令来生成 .xcarchive 文件
    xcodebuild archive -workspace xx.xcworkspace -scheme xx -archivePath xx.xcarchive
    可以看出添加上 archive 命令和最后加入 -archivePath 生成archivePath的路径即可。
    然后该路径下会生成一个 xx.archivePath,里面包括三个文件,xx.app.dsym文件(可用于bugly等监控bug的平台),info.plist(保存打包的一些信息),还有我们的 xx.app 文件。

    其次使用 -exportArchive 生成 ipa 包

    xcodebuild -exportArchive -archivePath xx.xcarchive -exportPath xx -exportFormat ipa

    -archivePath: xx.archivePath 的路径

    -exportPath: 输出路径

    -exportFormat: 生成类型,这里选择我们需要的 ipa

    这样就利用我们的 xcodebuild 命令来生成 ipa 包

    xcrun

    这里也使用 xcrun 来生成 ipa 包即可

    xcrun -sdk iphoneos PackageApplication build/Release-iphoneos/xx.app -o ~/Desktop/xx.ipa

    但是,在 macos10.12 和 Xcode8 的环境下会出现一个警告

    warning: PackageApplication is deprecated, use xcodebuild -exportArchive instead.

    说明 PackageApplication 已经被弃用了。

    不过其实这一步可以几乎等价于将 xx.app 放入一个 payload 的文件夹下然后压缩文件夹为 xx.ipa,当然这样做缺失一些信息,不过并不影响程序的运行。

    初步小结

    综上,我们有两种方法来生成我们需要的 ipa 包。

    1. 使用 xcodebuild 命令来编译我们的项目生成 app,然后再用 xcrun 将 app 转 ipa。
    2. 使用 xcodebuild archive 命令来直接生成我们需要的 ipa。

    虽然现在网上几乎都是使用 xcodebuild + xcrun 来来生成 ipa 包,不过既然官方说 PackageApplication is deprecated,那还是推荐使用第二种方法,一步到位。

    自动化打包正式开始

    这里从我工作室的一个项目切入,这个项目需要最终生成 18 个 ipa 包,但是他们几乎是共用一套代码的,不同的地方在于bundleName/bundleDisplayName/bundleid 等,以及一些资源文件的不同,例如 icon 等。所以可想而知如果选择手动打包的痛苦,并且当你打包到一半发现某个地方错了要重新打包 ......

    这里说一下自动化打包1.0解决思路:

    1. 使用命令 defaults write 来修改项目中的 plist 文件,来达到修改 bundleName/bundleDisplayName/bundleid... 的目的。
    2. 使用命令 cp 来替换资源文件。
    3. 使用 xcodebuild -workspace .. 编译出 app 包。
    4. 使用 xcrun ... 生成 ipa 。

    这是我最开始想到的思路,最终运行时间大概为每个包2.5m(时间主要浪费在编译),然后一套下来也要半个多小时。虽然比起手动打快了不少,但还是太慢了。毕竟自动化的目的不仅仅是自动,还要速度。

    既然问题出在编译上,那我的思路就往编译一次多次使用这个方向上面思考。然后想到了既然只是资源文件和plist的不同,没有涉及到代码的更换(不过这个项目后期不同 app 会执行不同一套代码,不过也有解决办法),这里就出现了自动化打包2.0的版本。

    1. 使用 xcodebuild -workspace .. 编译出 app 包。
    2. 使用命令 defaults write 来修改项目中的 plist 文件,来达到修改 bundleName/bundleDisplayName/bundleid... 的目的。
    3. 使用命令 cp 来替换资源文件。
    4. 重签名 codesign -f -s "iPhone Distribution: xx co., LTD" --entitlements $Entitlements $ipaPath/Payload/YouXiaoYun.app
    5. 使用 xcrun ... 生成 ipa 。

    和1.0大致相似,不过并不是每次生成 ipa 都需要编译一次。而是编译一次,然后直接修改 app 下内容,不过这里会出现签名错误的问题,因为在编译的最后会用证书帮 app 签名,如果你直接替换资源然后就生成 ipa 的话会导致 ipa 无法安装。

    那这时候神奇的重签名技术就出来(重签名用在正途上的真少见...hhhh,关于重签名的文章 google 一下就会很多),使用 codesign 命令就可以帮修改过资源的 app 重签名。
    最终使用2.0的时间基本是在5-6分钟左右。果然能机器完成的工作绝对不要手动完成,从半天到30分钟到最后的6分钟,节省下来的时间可以让你学习到更多。

    上面说到如果不同 app 间会用到不同的代码。例如 app A 里面的 title 叫 A 部门,app B 里面 title 又叫 B 部门,这样就不会通过命令行直接修改到代码,不过我想到的是维护一个 plist 文件,plist 文件可以这样设计的,每个不同 app 的 bundleName 都设置字典的键,然后字典下就可以是你自定义的内容。然后每次启动 app 就根据 bundleName 来寻找对应的字典,然后 title 就赋值为 plist 下 title 的值。如果不同代码就根据 code1 里面的值来 switch 不同的代码。

    appInfos.plist

    最终代码

    以下是完整的脚本文件,部分信息需要自己替换。
    以下脚本适用于一次打 N 个包,适用情况:

    1. 可以替换 bundle 信息
    2. 替换音频图片资源
    3. 可以执行不同代码
    4. 生成相应的plist文件
    5. 上传到蒲公英分发平台

    当然也可以打一个包,适当删除某些代码即可。

    # 1.Configuration Info
    
    # 项目路径 需修改
    projectDir="你的项目路径"
    
    # 打包生成路径 需修改
    ipaPath="ipa生成路径"
    
    # 图标路径 需修改
    iconPath="~/Desktop/icon"
    
    # Provisioning Profile 需修改 查看本地配置文件
    PROVISIONING_PROFILE="xxxxxxx-xxxx-4bfa-a696-0ec7391b24d8"
    
    ############# 重签名需要文件
    # 以下文件需放在 ipaPath 路径下
    Entitlements=$ipaPath/entitlements.plist
    #############
    
    # 版本号
    bundleVersion="2.0.0"
    
    # 选择打包序号 多选则以空格隔开 如("1" "2" "3")
    appPackNum=("1 2")
    
    # 蒲公英分发参数 不分发可忽略 默认不分发 下面的两个KEY是默认测试的网址对应KEY
    ISUPLOAD=0
    USERKEY="xxx"
    APIKEY="xxx"
    
    # ---------------------------可选 如果需要替换 app 的 icon --------------------------------- #
    
    # 配置App信息数组 格式:"AppName(和工程中appInfo.Plist对应)" "icon"
    #Schemes:
    #        1.app1 app1Icon
    #        2.app2 app2Icon
    #        3.app3 app3Icon
    
    # --------------------------------------------------------------------------------------- #
    
    # 打包个数
    appPackNumLength=${#appPackNum[*]}
    
    appInfos=(
              "app1" "app1Icon" "xxxx"
              "app2" "app2Icon" "xxxx"
              "app3" "app3Icon" "xxxx"
              )
    
    appInfosLength=${#appInfos[*]}
    
    # Scheme Name
    schemeName="xx"
    
    # Code Sign ID
    CODE_SIGN_IDENTITY="xx co., LTD"
    
    # 生成 APP 路径
    buildDir="build/Release-iphoneos"
    
    # 开始时间
    beginTime=`date +%s`
    
    # 创建打包目录
    mkdir ${ipaPath}/AllPack
    
    # 本地存放全部 IPA 的路径
    allIPAPackPath="${ipaPath}/allPack"
    
    # 清除缓存
    rm -rf $projectDir/$buildDir
    
    # Build 生成 APP
    xcodebuild -workspace ${projectDir}/xx.xcworkspace -scheme ${schemeName} -configuration Release clean -sdk iphoneos build CODE_SIGN_IDENTITY="${CODE_SIGN_IDENTITY}" PROVISIONING_PROFILE="${PROVISIONING_PROFILE}" SYMROOT="${projectDir}/build"
    if [[ $? = 0 ]]; then
      echo "\033[31m 编译成功\n \033[0m"
    else
      echo "\033[31m 编译失败\n \033[0m"
    fi
    
    # 先创建 payload 文件夹
    mkdir ~/Desktop/Payload
    # 移动编译生成的 app 到桌面的 Payload 文件夹下
    cp -Rf ${projectDir}/${buildDir}/${schemeName}.app $ipaPath/Payload
    
    # 以下二选一
    # 1.----全部打包----
    #for (( i=0; i<appInfosLength; i+=3 )); do
    
    # 2.----自定义打包----
    for (( j=0; j<$appPackNumLength; j++)); do i=`expr ${appPackNum[$j]} - 1` i=`expr $i \* 3`
    
    # App Bundle Name (CFBundleName)
    appName=${appInfos[${i}]}
    
    # App DisPlay Name
    appDisplayName=${appInfos[${i}]}
    
    # App Icon Name
    appIconName=${appInfos[$i+1]}
    
    # App Download Name
    appDownloadName=${appInfos[$i+2]}
    
    # 创建不同 app ipa 目录
    mkdir $allIPAPackPath/$appName
    rm -rf $allIPAPackPath/$appName/*
    
    echo "\033[31m appName:$appName appIconName:$appIconName appDownloadName:$appDownloadName\n \033[0m"
    
    # 将对应的 icon 复制到需要修改的 app 的目录下
    cp -Rf $iconPath/$appName/* $ipaPath/Payload/YouXiaoYun.app
    
    if [[ $? = 0 ]]; then
     echo "\033[31m 修改 icon 成功\033[0m"
    else
     echo "\033[31m 修改 icon 失败\033[0m"
    fi
    
    # 修改 Plist
    defaults write $ipaPath/Payload/xx.app/info.plist "CFBundleName" $appName
    defaults write $ipaPath/Payload/xx.app/info.plist "CFBundleDisplayName" $appDisplayName
    
    if [[ $? = 0 ]]; then
      echo "\033[31m 修改 Plist 成功\033[0m"
    else
      echo "\033[31m 修改 Plist 失败\033[0m"
    fi
    
    # 重签名
    codesign -f -s "xx co., LTD" --entitlements $Entitlements $ipaPath/Payload/xx.app
    if [[ $? = 0 ]]; then
    echo "\033[31m 签名成功\n \033[0m"
    else
    echo "\033[31m 签名失败\n \033[0m"
    fi
    
    # 生成 ipa
    xcrun -sdk iphoneos -v PackageApplication $ipaPath/Payload/.app -o ${ipaPath}/$appDownloadName.ipa
    
    if [[ $? = 0 ]]; then
      echo "\033[31m \n 生成 IPA 成功 \n\n\n\n\n\033[0m"
    else
      echo "\033[31m \n 生成 IPA 失败 \n\n\n\n\n\033[0m"
    fi
    
    # 创建 Plist
    plist_path=$allIPAPackPath/$appName/$appDownloadName.plist
    
    cat << EOF > $plist_path
    <?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>items</key>
        <array>
            <dict>
                <key>assets</key>
                <array>
                    <dict>
                        <key>kind</key>
                        <string>software-package</string>
                        <key>url</key>
                        <string>https://xxxxxxxxxxxx/$appDownloadName.ipa</string>
                    </dict>
                    <dict>
                        <key>kind</key>
                        <string>display-image</string>
                        <key>url</key>
                        <string>https://xxxxxxxxxxxx/${appIconName}.png</string>
                    </dict>
                    <dict>
                        <key>kind</key>
                        <string>full-size-image</string>
                        <key>url</key>
                        <string>https://xxxxxxxxxxxx/${appIconName}.png</string>
                    </dict>
                </array>
                <key>metadata</key>
                <dict>
                    <key>bundle-identifier</key>
                    <string>你的bundid</string>
                    <key>bundle-version</key>
                    <string>$bundleVersion</string>
                    <key>kind</key>
                    <string>software</string>
                    <key>title</key>
                    <string>$appDownloadName</string>
                </dict>
            </dict>
        </array>
    </dict>
    </plist>
    EOF
    
    # 移动
    mv ${ipaPath}/$appDownloadName.ipa ${allIPAPackPath}/$appName
    
    # 6.上传蒲公英分发平台
    
    if [[ $ISUPLOAD = 1 ]]; then
      echo "正在上传蒲公英..."
      curl -F "file=@$allIPAPackPath/$appName/$appDownloadName.ipa" -F "uKey=$USERKEY" -F "_api_key=$APIKEY" http://www.pgyer.com/apiv1/app/upload
    
    fi
    
    done
    
    # 清除无关文件
    rm -rf $ipaPath/Payload
    
    # 结束时间
    endTime=`date +%s`
    echo -e "打包时间$[ endTime - beginTime ]秒"
    
    

    相关文章

      网友评论

      • 会上树的潴:我这边也是 打包上传到蒲公英 如果我没有修改info.plist文件可以上传到蒲公英并且可以安装成功
      • 汤不理不_:博主,你这个脚本能实现多target打包成功吗?
      • 小小小龙包:xcrun方式生成的ipa可以安装到手机,但-exportArchive方式生成的ipa装不了,这个要怎么配置
      • 骑猪兜风_3355:怎么打测试包,我改了,发现重签的还是发布证书签的
      • 骑猪兜风_3355:cannot read entitlement是怎么问题
      • AgoniNemo: -exportFormat ipa 这样的写法在xcode 8.3 环境下已经不能执行了
      • Doris_Lee:果然好使,虽然我跑了两天才搞懂:smile:
        b143723cb384:@Doris_Lee 能发一个demo 或者一个 脚本文件 给我看看 吗
        Doris_Lee:@醉酒熊猫 就打一个包干嘛用这个啊。。你把博主最后的脚本不用替换的去掉,重签名的时候注意钥匙串里用到的证书不能有多个。
        b143723cb384:我不会啊 能不能教教我啊 我只想打一包 然后替换icon
      • ibingewin:dddddddddd
      • Hengry:这个脚本怎么运行呢
      • Hengry:明天试试看
      • brownfeng:留着. 细细看

      本文标题:关于 iOS 批量打包的总结

      本文链接:https://www.haomeiwen.com/subject/pxeputtx.html