美文网首页iOS收藏常用技术收集iOS日常
详解Shell脚本实现iOS自动化编译打包提交

详解Shell脚本实现iOS自动化编译打包提交

作者: zackzheng | 来源:发表于2016-02-27 23:25 被阅读27290次

    本文始发于我的博文详解Shell脚本实现iOS自动化编译打包提交,现转发至此。

    目录

    • 前言
    • Shell脚本涉及的工具
      • xcodebuild和xcrun
      • altool
      • fir-cli
      • PlistBuddy
    • 一些概念的区别
    • 具体实现
      • xcodebuild和xcrun
      • 准备Plist文件
      • 获取命令行参数
      • 清理构建目录
      • 编译打包成Archive
      • 将Archive导出
      • 上传到Fir
      • 验证并上传到App Store
      • 邮件通知相关同事
      • 上传符号表到Bugly
    • 简单例子
    • 对比实验
      • 三种方式的对比
      • xcodebuild+xcrun和仅xcodebuild的比较
      • 命令到底做了什么
    • 总结

    前言

    现在涉及到编译打包的工作主要是以下两个:

    1. 提交测试版本给测试同事
    2. 提交App Store审核

    两个流程分别是:

    • 修改证书和配置文件,然后「Product -> Archive」编译打包,之后在自动弹出的 「Organizer」 中进行选择,根据需要导出 ad hoc enterprise 类型的 ipa 包。等待导出之后再提交到Fir上,等Fir提交完成就需要告知测试同事。整个流程下来一般都要半个多小时,而且需要人工监守操作。
    • 第二个也是差不多,打包完之后需要操作几个步骤然后上传到App Store,上传时间较长,而且中间可能会有错误需要处理。上传后等待苹果处理二进制包,苹果处理后上去选择构建包,点击提交审核。

    所以研究下自动化编译打包,提高下效率,减少人工操作成本。

    主要有两种实现途径,AppleScript和Shell脚本,AppleScript没怎么研究,网上说是很强大的脚本语言。

    下面主要讲Shell脚本的实现,网上也有人实现了并托管在github上,可以参考下。

    https://github.com/webfrogs/xcode_shell

    Shell脚本涉及的工具

    主要是以下几个工具:

    1. xcodebuild
    2. xcrun
    3. altool(提交到App Store使用)
    4. fir-cli(上传到fir时使用)
    5. Python的smtplib(之前已经写过python的发邮件了,所以就直接用没有用Shell写。)
    6. PlistBuddy
    7. BuglySymboliOS(Bugly的符号表工具包)

    xcodebuild和xcrun

    xcodebuildxcrun都是来自Command Line Tools,Xcode自带,如果没有可以通过以下命令安装:

    xcode-select --install
    

    或者在下面的链接下载安装:

    https://developer.apple.com/downloads/

    安装完可在以下路径看到这两个工具:

    /Applications/Xcode.app/Contents/Developer/usr/bin/

    • xcodebuild
      主要是用来编译,打包成Archive和导出ipa包。

    https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/xcodebuild.1.html

    可以执行 xcodebuild -help 查看,主要展示了几种用法、一些可选项,最后是比较重要的exportOptionsPlist文件的一些可选key,这个文件在后面导出ipa包会用到。

    主要下面三个查看的命令比较重要:

    -showsdks                           display a compact list of the installed SDKs
    -showBuildSettings                  display a list of build settings and values
    -list                               lists the targets and configurations in a project, or the schemes in a workspace
    

    后面两个需要在Xcode的project或者workspace目录下才能用。

    • xcrun
    xcrun -h
    

    主要是打包,看网上比较多是用这个工具打包各种渠道包。

    altool

    这个工具在网上搜索几乎没有什么结果,大概国内直接用命令行工具提交App Store的比较少。后来在StackOverflow上才找到相关的文档:

    https://itunesconnect.apple.com/docs/UsingApplicationLoader.pdf

    在上面的文档第38页讲述了如何使用altool上传二进制文件。

    这个工具实际上是ApplicationLoader,打开Xcode-左上角Xcode-Open Developer Tool-Application Loader 可看到。有个“交付您的应用”操作,网上看到有人是直接用这个工具上传的。

    altool的路径是:

    /Applications/Xcode.app/Contents/Applications/Application\ Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Support/altool

    使用时会提示下面的错误:

    altool[] *** Error: Exception while launching iTunesTransporter: 
    Transporter not found at path: /usr/local/itms/bin/iTMSTransporter. 
    You should reinstall the application.
    

    建立个软链接可解决(类似于Windows的快捷方式):

    ln -s /Applications/Xcode.app/Contents/Applications/Application\ Loader.app/Contents/itms /usr/local/itms
    

    fir-cli

    安装时会提示各种权限不允许,可以执行下面命令:

    echo 'gem: --bindir /usr/local/bin' >> ~/.gemrc
    sudo 'gem install fir-cli
    

    fir有提供Android Studio、Eclipse、gradle插件,可以看下。

    http://fir.im/tools

    这是�它的github地址,其中讲到有对?xcodebuild?原生指令进行了封装。

    https://github.com/FIRHQ/fir-cli/blob/master/README.md

    PlistBuddy

    Plist在Mac OSX系统中起着举足轻重的作用,系统和程序使用Plist文件来存储自己的安装/配置/属性等信息。而PlistBuddy是Mac里一个用于命令行下读写plist文件的工具,在/usr/libexec/下。可以通过它读取或修改plist文件的内容。

    这里我仅通过它来获取内部版本号、外部版本号。在一些文章中见过用来修改plist文件的信息来导出出不同需要的包。

    一些概念的区别

    Workspace、Project、Scheme、Target的区别。

    下面是官方文档:

    https://developer.apple.com/library/ios/featuredarticles/XcodeConcepts/Concept-Targets.html#//apple_ref/doc/uid/TP40009328-CH4-SW1

    下面从上往下大概说下,具体看文档比较好:

    • Workspace
      Workspace是最大的集合,可以包含多个Project,可以管理不同的Project之间的关系。Workspace是以xcworkspace的文件形式存在的。(这点和Project一致)。Workspace的存在是为了解决原来仅有Project的时候不同的Project之间的引用和调用困难的问题。同时,一个WorkspaceProject共用一个编译路径。比如使用CocoaPod、或者使用其他开发库/框架。

    • Project
      Project是一个仓库,包含编译一个或多个product所需的文件、资源和信息,保持和聚合这些元素间的关系。(每个Target能指定自己的Build Settings来覆盖Project的)

    • Source code, including header files and implementation files
    • Libraries and frameworks, internal and external
    • Resource files
    • Image files
    • Interface Builder (nib) files
    • Scheme
      Scheme包含了一些要构建的Scheme,一些构建时用到的设置,一些要运行的测试。同时只能有一个Scheme是有效的。

    • Target
      Target是对应了具体一个想要构建的Product,包含了一些构建这个Product所需的配置和文件(build settingsbuild phases)。一个Project可以包含多个Target

    具体实现

    看起来有两种实现方法:

    • 网上可以查到的文章,大多数都是用xcodebuildxcrun实现的,比如:
    xcodebuild -workspace XXX -scheme XXX -configuration Release
    xcrun -sdk iphoneos PackageApplication -v "/XXX/XXX.app" -o "/XXX/XXX"
    

    这些文章都是相对比早期的,大多数用于打包不同渠道包。

    • 另一种是xcodebuildarchive-exportArchive,只有一两篇文章是用这个,而且也过时了,因为现在最新是需要用-exportOptionsPlist这个选项。

    我用的是第二种,并用上-exportOptionsPlist选项,后面我会简单给下这两种的结果比较。脚本流程是:

    1. 准备两个Plist文件,用于导出不同ipa包时使用。
    2. 获取命令行参数,区分上传到Fir还是App Store
    3. 清理构建目录
    4. 编译打包
    5. 导出包
    6. 上传到Fir或者验证并上传到App Store
    7. 发邮件通知

    准备Plist文件

    根据xcodebuild -help提供的可选key可以知道,compileBitcodeembedOnDemandResourcesAssetPacksInBundleiCloudContainerEnvironmentmanifestonDemandResourcesAssetPacksBaseURLthinning这几个key用于非App Store导出的;uploadBitcodeuploadSymbols用于App Store导出;methodteamID共用。

    method的可选值为:

    app-store, package, ad-hoc, enterprise, development, and developer-id

    所以我建了两个文件:AppStoreExportOptions.plistAdHocExportOptions.plist

    AppStoreExportOptions.plist:method=app-store,uploadBitcode=YES,uploadSymbols=YES

    AdHocExportOptions.plist:method=ad-hoc,compileBitcode=NO

    获取命令行参数

    Shell内置的getopts命令,这属于Shell的范畴就不多讲了:

    if [ $# -lt 1 ];then
        echo "Error! Should enter the archive type (AdHoc or AppStore)."
        echo ""
        exit 2
    fi
    while getopts 't:' optname
    do
        case "$optname" in
        t)
            if [ ${OPTARG} != "AdHoc" ] && [ ${OPTARG} != "AppStore" ];then
                echo "invalid parameter of $OPTARG"
                echo ""
                exit 1
            fi
            type=${OPTARG}
            ;;
        *)
            echo "Error! Unknown error while processing options"
            echo ""
            exit 2
            ;;
        esac
    done
    

    清理构建目录

    就如在Xcode操作「Product -> Clean」。

    log_path="/XXX/XXX"
    configuration="Release"
    xcodebuild clean -configuration "$configuration" -alltargets >> $log_path
    

    log_path是一个文档路径,只是用来记录命令的输出,因为都打在终端会很多,另外也方便后面分析。后面的命令也是如此。这里面带的选项可以根据需要参考xcodebuild -help的信息。

    编译打包成Archive

    就如在Xcode操作「Product -> Archive」

    workspaceName="XXX.xcworkspace"
    scheme="XXX"
    configurationBuildDir="XXX/build"
    codeSignIdentity="iPhone Distribution: XXX, Ltd. (xxxxxxxxxx)"
    adHocProvisioningProfile="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    appStoreProvisioningProfile="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    configuration="Release"
    archivePath="/xxx/XXX.xcarchive"
    
    xcodebuild archive -workspace "$workspaceName" -scheme "$scheme" -configuration "$configuration" -archivePath "$archivePath" CONFIGURATION_BUILD_DIR="$configurationBuildDir" CODE_SIGN_IDENTITY="$codeSignIdentity" PROVISIONING_PROFILE="$provisioningProfile" >> $log_path
    

    这里的CONFIGURATION_BUILD_DIR是中间文件生成的路径,可以不指定;CODE_SIGN_IDENTITY是证书名(在对应TARGETSBuild Settings中选择完Code Sinning,再点击选择Other...,就可以得到这串东西);PROVISIONING_PROFILE是配置文件(获取方法同CODE_SIGN_IDENTITY,格式一般是xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)。还可以添加其他参数,不设置的都是默认使用项目Build Settings里面的配置,包括CODE_SIGN_IDENTITYPROVISIONING_PROFILE

    如果是workspace就用-workspace,就像编译带有CocoaPods的项目,如果是普通项目则用-project

    执行完会生成一个.xcarchive文件和build文件夹如下:

    .xcarchive
    build文件夹
        |------.a
        |------.app
        |------.app.dSYM
        |------.swiftmodule文件夹
            |------arm.swiftdoc
            |------arm.swiftmodule
            |------arm64.swiftdoc
            |------arm64.swiftmodule
    

    将Archive导出

    xcodebuild -exportArchive -archivePath "$archivePath" -exportOptionsPlist "$exportOptionsPlist" -exportPath "/XXX/XXX" >> $log_path
    

    其中$exportOptionsPlist是对应使用的Plist的完整路径(包括文件名)。

    然后就会在指定的exportPath路径下生成.ipa文件。

    上传到Fir

    firApiToken="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    ipaPath="/xxx/xxx.ipa"
    fir publish "$ipaPath" -T "$firApiToken" >> $log_path
    

    firApiToken在登录Fir后,右上角-API token看到。

    验证并上传到App Store

    altoolPath="/Applications/Xcode.app/Contents/Applications/Application\ Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/altool"
    ${altoolPath} --validate-app -f ${ipaPath} -u xxxxxx -p xxxxxx -t ios --output-format xml >>
    ${altoolPath} --upload-app -f ${ipaPath} -u xxxxxx -p xxxxxx -t ios --output-format xml
    

    在上面的PDF文档第38页讲明了用法和各个可选项,具体可以看下PDF。需要说明的是,生成的结果是xml打印在终端,可以保存到文档再解析出key来判断是否成功,目前这步还没做。

    这是成功的结果:

    <?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>os-version</key>
        <string>10.11.2</string>
        <key>success-message</key>
        <string>No errors validating archive at /XXX/XXX.ipa</string>
        <key>tool-version</key>
        <string>1.1.902</string>
        <key>xcode-versions</key>
        <array>
            <dict>
                <key>path</key>
                <string>/Applications/Xcode.app</string>
                <key>version.plist</key>
                <dict>
                    <key>BuildVersion</key>
                    <string>7</string>
                    <key>CFBundleShortVersionString</key>
                    <string>7.2</string>
                    <key>CFBundleVersion</key>
                    <string>9548</string>
                    <key>ProductBuildVersion</key>
                    <string>7C68</string>
                    <key>ProjectName</key>
                    <string>IDEFrameworks</string>
                    <key>SourceVersion</key>
                    <string>9548000000000000</string>
                </dict>
            </dict>
        </array>
    </dict>
    </plist>
    

    这是失败的结果(找不到iTMSTransporter的情况,用前面说的ln -s解决):

    <?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>os-version</key>
        <string>10.11.2</string>
        <key>product-errors</key>
        <array>
            <dict>
                <key>code</key>
                <integer>-10001</integer>
                <key>message</key>
                <string>Transporter not found at path: /usr/local/itms/bin/iTMSTransporter.  You should reinstall the application.</string>
                <key>userInfo</key>
                <dict>
                    <key>MZUnderlyingException</key>
                    <string>Transporter not found at path: /usr/local/itms/bin/iTMSTransporter.  You should reinstall the application.</string>
                    <key>NSLocalizedDescription</key>
                    <string>Transporter not found at path: /usr/local/itms/bin/iTMSTransporter.  You should reinstall the application.</string>
                    <key>NSLocalizedFailureReason</key>
                    <string>Transporter not found at path: /usr/local/itms/bin/iTMSTransporter.  You should reinstall the application.</string>
                </dict>
            </dict>
        </array>
        <key>tool-version</key>
        <string>1.1.902</string>
        <key>xcode-versions</key>
        <array>
            <dict>
                <key>path</key>
                <string>/Applications/Xcode.app</string>
                <key>version.plist</key>
                <dict>
                    <key>BuildVersion</key>
                    <string>7</string>
                    <key>CFBundleShortVersionString</key>
                    <string>7.2</string>
                    <key>CFBundleVersion</key>
                    <string>9548</string>
                    <key>ProductBuildVersion</key>
                    <string>7C68</string>
                    <key>ProjectName</key>
                    <string>IDEFrameworks</string>
                    <key>SourceVersion</key>
                    <string>9548000000000000</string>
                </dict>
            </dict>
        </array>
    </dict>
    </plist>
    

    可见,成功会有个success-message的key,而失败会有product-errors的key。

    邮件通知相关同事

    发邮件时可能会想带上当前版本的一些信息,如版本号、内部版本号等,可以用PlistBuddy实现读取甚至修改Plist文件。

    appInfoPlistPath="`pwd`/xxx/xxx-Info.plist"
    bundleShortVersion=$(/usr/libexec/PlistBuddy -c "print CFBundleShortVersionString" ${appInfoPlistPath})
    bundleVersion=$(/usr/libexec/PlistBuddy -c "print CFBundleVersion" ${appInfoPlistPath})
    

    之后便是发邮件:

    python sendEmail.py "测试版本 iOS ${bundleShortVersion}(${bundleVersion})上传成功" "赶紧下载体验吧!http://fir.im/meijia"
    

    或者

    python sendEmail.py "正式版本 iOS ${bundleShortVersion}(${bundleVersion})提交成功" "iOS ${bundleShortVersion} 提交成功!"
    

    python主要用smtplib,网上的文章大多都是旧的,特别是讲到SSL时特别复杂,其实具体看下smtplib的接口文档就可以实现了。另外有可能出现标题、内容乱码的现象。整合了下面的链接解决了:

    下面是实现了SSL Smtp登录的。

    #!/usr/bin/env python3
    #coding: utf-8
    
    # sendEmail title content
    import sys
    import smtplib
    from email.mime.text import MIMEText
    from email.header import Header
    
    sender = 'xxxxxx@qq.com;'
    receiver = 'xxx@qq.com;'
    smtpserver = 'smtp.qq.com'
    #smtpserver = 'smtp.exmail.qq.com'
    
    username = sender
    password = 'xxxxxx'
    
    def send_mail(title, content):
    
        try:
            msg = MIMEText(content,'plain','utf-8')
            if not isinstance(title,unicode):
                title = unicode(title, 'utf-8')
            msg['Subject'] = title
            msg['From'] = sender
            msg['To'] = receiver
            msg["Accept-Language"]="zh-CN"
            msg["Accept-Charset"]="ISO-8859-1,utf-8"
    
            smtp = smtplib.SMTP_SSL(smtpserver,465)
            smtp.login(username, password)
            smtp.sendmail(sender, receiver, msg.as_string())
            smtp.quit()
            return True
        except Exception, e:
            print str(e)
            return False
    
    if send_mail(sys.argv[1], sys.argv[2]):
        print "done!"
    else:
        print "failed!"
    

    可以赋值给msg['CC']实现抄送,经过测试,抄送的人过多会有一部分不成功,网上查了是这个库的bug。发送多个人用分号,另外末尾也要用分号。

    上传符号表到Bugly

    用于分析解决崩溃bug挺好用的,而且他们的客服也很及时。
    发现他们的2.4.1版本有问题,反馈后他们给了2.4.3版本,经测试没问题。

    1. 在Bugly官网下载符号表工具

    2. 设置settings.txt

    3. 调用命令

    java -jar buglySymboliOS.jar -d -i $dSYM -u -id "xxxxxxxxx" -key "xxxxxxxxxxx" -package "com.xxx.xxx" -version "$version" ­-o "xxx.zip"
    

    注意版本号之类的要设置对。

    简单例子

    清理构建目录:

    xcodebuild clean -configuration Release -alltargets
    

    归档(其他参数不指定的话,默认用的是.xcworkspace或.xcodeproj文件里的配置)

    xcodebuild archive -workspace xxx.xcworkspace -scheme xxx -configuration Release -archivePath ./xxx.xcarchive
    

    导出IPA

    xcodebuild -exportArchive -archivePath ./xxx.xcarchive -exportOptionsPlist ./AdHocExportOptions.plist -exportPath ./
    

    上传FIR

    fir publish ./xxx.ipa -T xxxxxx
    

    提交AppStore

    /Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/altool --validate-app -f ./xxx.ipa -u xxx -p xxx -t ios --output-format xml
    /Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/altool --upload-app -f ./xxx.ipa -u xxx -p xxx -t ios --output-format xml
    

    发邮件

    python sendEmail.py "邮件内容" "用户名" "密码"
    

    上传符号表

    java -jar buglySymboliOS.jar -d -i $dSYM -u -id "xxxxxxxxx" -key "xxxxxxxxxxx" -package "com.xxx.xxx" -version "$version" ­-o "xxx.zip"
    

    对比实验

    为了了解一些区别,我做了几个对比。我这里定义下三种方式,方便下面说明。

    • xcodebuild+xcrun(xcodebuild build和xcrun)
    • 只用xcodebuild(archive和exportArchive),
    • Xcode。

    三种方式的对比

    我使用xcodebuild+xcrun、仅xcodebuild、Xcode三种分别对相同代码和配置进行操作,根据结果做比较:

    • xcodebuild+xcrun

    ipa:40.7MB,.app:93.3MB,编译耗时:8m31s,打包耗时:15s。

    • 仅xcodebuild

    ipa:37.3MB,.app:74MB,.xcarchive:227.3MB,编译耗时:8m24s,打包耗时:26s。

    • Xcode

    ipa:37.3MB,.app:74MB,.xcarchive:227.3MB,编译耗时:8m40s,打包耗时:30s。

    Xcode生成的.xcarchive文件可以在以下路径看到:

    /Users/double/Library/Developer/Xcode/Archives

    可以看出,<u>仅使用xcodebuild的结果和使用Xcode编译打包的结果是一致的</u>,并且最终的ipa也可以正常安装使用。而第一种xcodebuild+xcrun的结果略大些,但是ipa也是可以正常使用的。这时需要了解下他们的区别。

    xcodebuild+xcrun和仅xcodebuild的比较

    • 使用xcrun打包方式二产生的.xcarchive中的.app

    打包生成的.ipa文件大小同样为37.3MB,与方式二使用Xcodebuild -exportArchive的结果一致!这样说明:使用xcrun的打包方法是正常的,和xcodebuild -exportArchive的结果一致,而且.ipa包仅和.app有关。那么说明,<u>这两种方式的不同仅在于xcodebuild build和xcodebuild archive之间的不同</u>。

    • 删除.xcarchive中其他文件然后exportArchive

    这时命令提示错误,但是上面我们已经得出结论.ipa的生成只和.app有关,所以可能的原因是,这个exportArchive命令会检查.archive的完整性和正确性,防止生成的.archive不完整或者是伪造的。下面做个实验看下。

    命令到底做了什么

    根据命令运行时输出的内容,看下中间做了什么

    • xcrun -sdk iphoneos PackageApplication -v xxx.app -o xxx.ipa
    Packaging application: '/xxx/xxx.app'
    Arguments: output=/xxx/xxx.ipa  verbose=1  
    Environment variables:
    SDKROOT = /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS9.2.sdk
    ......
    SHELL = /bin/bash
    
    Output directory: '/xxx/xxx.ipa'
    Temporary Directory: '/var/folders/21/6s9bb23j0s1343pm7ltnlgpm0000gn/T/taOIiK9AyK'  (will NOT be deleted on exit when verbose set)
    + /bin/cp -Rp /xxx/xxx.app /var/folders/21/6s9bb23j0s1343pm7ltnlgpm0000gn/T/taOIiK9AyK/Payload
    Program /bin/cp returned 0 : []
    ### Checking original app
    + /usr/bin/codesign --verify -vvvv /xxx/xxx.app
    Program /usr/bin/codesign returned 0 : [/xxx/xxx.app: valid on disk
    /xxx/xxx.xcarchive/Products/Applications/xxx.app: satisfies its Designated Requirement
    ]
    Done checking the original app
    + /usr/bin/zip --symlinks --verbose --recurse-paths /Users/double/Desktop/1.ipa .
    Program /usr/bin/zip returned 0 : [  adding: Payload/   (in=0) (out=0) (stored 0%)
      adding: Payload/xxx.app/  (in=0) (out=0) (stored 0%)
      ......
    

    主要检查了环境变量,然后验证签名,然后压缩(看到了吗,居然是/usr/bin/zip),后面adding的基本都是.nib和.png等的压缩。看起来.archive只是一种压缩形式,包含了.app、.dSYM、.plist和其他一些文件。

    这里的codesign工具就是签名相关的,可以查看说明:

    SYNOPSIS
         codesign -s identity [-i identifier] [-r requirements] [-fv] [path ...]
         codesign -v [-R requirement] [-v] [path|pid ...]
         codesign -d [-v] [path|pid ...]
         codesign -h [-v] [pid ...]
    

    -s是签名,-v是验证。所以可以在.app生成后再签名。

    • xcodebuild clean

    清理工作,根据参数删除指定的workplace、target、configuration(release或debug) 的中间文件,都是工程目录下的build文件夹。

    • xcodebuild archive

    下面是里面主要的步骤:

    1. Create product structure 创建.app文件
    2. CompileC 编译文件(clang编译,指定了编译的SDK版本和指令集)
    3. Ld
    4. CreateUniversalBinary (lipo)
    5. CompileStoryboard (ibtool )
    6. CompileAssetCatalog (actool )
    7. ProcessInfoPlistFile (builtin-infoPlistUtility )
    8. GenerateDSYMFile (dsymutil )
    9. LinkStoryboards(ibtool )
    10. Strip
    11. ProcessProductPackaging (builtin-productPackagingUtility )
    12. CodeSign (codesign --force --sign)
    13. Validate (builtin-validationUtility )

    总结

    呼呼写了这么多,终于到总结部分了。这个过程学到了很多东西,脚本成果确实方便了很多,减少了编译打包过程中人工监守、人工操作的成本,并且测试和提交到appStore的包都验证过可用。

    -END-
    欢迎到我的博客交流:http://zackzheng.info

    相关文章

      网友评论

      • Auditore:作者nb
      • a69fde05b737:请问下Sign in with the app-specific password you generated. If you forgot the app-specific password or need to create a new one, go to appleid.apple.com这里需要一个app专用密码。我该如何配置?
        zackzheng:这是哪里需要用到的呢
      • TinXie:2018-03-29 16:10:30.359 xcodebuild[74788:3099110] [MT] IDEDistribution: -[IDEDistributionLogging _createLoggingBundleAtPath:]: Created bundle at path '/var/folders/hd/0spxw34558z9yf2wt3s6lf1c0000gn/T/yes123Enterprise_2018-03-29_16-10-30.357.xcdistributionlogs'.
        2018-03-29 16:10:32.717 xcodebuild[74788:3099110] [MT] IDEDistribution: Step failed: <IDEDistributionOptionsStep: 0x7f8a9d52e090>: Error Domain=IDEFoundationErrorDomain Code=1 "exportOptionsPlist error for key 'uploadBitcode': cannot upload bitcode because bitcode is imbalanced" UserInfo={NSLocalizedDescription=exportOptionsPlist error for key 'uploadBitcode': cannot upload bitcode because bitcode is imbalanced}
        error: exportArchive: exportOptionsPlist error for key 'uploadBitcode': cannot upload bitcode because bitcode is imbalanced

        Error Domain=IDEFoundationErrorDomain Code=1 "exportOptionsPlist error for key 'uploadBitcode': cannot upload bitcode because bitcode is imbalanced" UserInfo={NSLocalizedDescription=exportOptionsPlist error for key 'uploadBitcode': cannot upload bitcode because bitcode is imbalanced}
        zackzheng:查一下plist文件哈
      • HANSAHACKER:楼主,xcodebuild -Archive 不成功你看看这个什么问题。
        错误日志:
        ** ARCHIVE FAILED **
        The following build commands failed:
        ProcessPCH /Users/jackey_gjt/Library/Developer/Xcode/DerivedData/WCG-bewejmejkdpojiapmawgecokpsyr/Build/Intermediates.noindex/ArchiveIntermediates/WCG_AdHoc/PrecompiledHeaders/WCGlobal-cshnassxeymwxvezjifusxeveyfm/WCGlobal.h.pch WCG/Defines/WCGlobal.h normal armv7 objective-c com.apple.compilers.llvm.clang.1_0.compiler
        (1 failure)
      • e000a9279be3:有完整的shell脚本么 我只用的xcodebuild 上传fir
      • 7a01235bca30:楼主你好,我所有的设置都可以成功的,和你的做法稍有不同,我是用Jenkins远程操作的,都没问题,可是有一点很不爽 执行 altool 命令的时候完全看不到进度,我在终端也尝试了也是没有进度,这样就很尴尬,不知道具体完成了多少。请问有什么办法可以看到上传的进度吗?
        zackzheng:这个不清楚哦…
      • 95c797dbbaa6:导出Api这步骤出了问题,什么原因呢?
        error: Couldn't load -exportOptionsPlist: The file “AdHocExportOptions.plist” couldn’t be opened because there is no such file.

        Error Domain=NSCocoaErrorDomain Code=260 "The file “AdHocExportOptions.plist” couldn’t be opened because there is no such file." UserInfo={NSFilePath=/Users/sunset/Desktop/ECM/AdHocExportOptions.plist, NSUnderlyingError=0x7fa7556cc4c0 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}
        zackzheng:不是提示找不到那个文件了吗?
      • 夜雨GG:楼主,想问一下,我achive的文件65M,ipa 4.9M 都不大,打包时间5m,但是构建时间却一直很长,最短也要80m+ 能不能给点建议
        zackzheng:我们项目比较大,而且是OC和Swift混编
        夜雨GG:可能是我搞错了,你写的是几分几秒是吧,那我这没有那么慢我这基本上是80-200s 编译 6-8s打包~~
      • fb69e982796d:楼主,我的项目中有widget,请问这些的配置在xcodebuild中怎么体现呢?
        fb69e982796d:@zackzheng iOS10新推出的Widget
        zackzheng:@披头五毛 widget是?
      • LV大树:mark。。。
      • 就是一个春天的花朵:楼主能把你写的这个sh分享一下吗
        就是一个春天的花朵:@zackzheng 还有个问题还想请教,我导出IPA的时候提示我error: exportArchive: No valid iOS Distribution signing identities belonging to team EDE2RLY994 were found. 还想请问这个是什么地方的原因
        zackzheng:@ozill 自己实现吧,就上面两三个命令而已,不难哈
      • 9ab6214fa360:您好,
        根据我的理解,如果是选择“Automatically manager signing”,
        是不是就不需要指定以下参数:
        codeSignIdentity="iPhone Distribution: XXX, Ltd. (xxxxxxxxxx)"
        adHocProvisioningProfile="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
        appStoreProvisioningProfile="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

        如果如果是选择“Automatically manager signing”,怎么知道打出来的是adhoc包还是appstore包呢?我使用xcodebuild + xcrun。

        求指教,谢谢
        zackzheng:@大佬杰 不大清楚,可能exportArchive的时候加些参数指定下。
      • 崔可一:我的工程添加了 Notification Service Extension ,使用脚本打包,可以安装上,但是推送就是不好使的。但是使用xcode去打包的话,就好使的。楼主知道怎么弄吗?持续关注。
      • 灵儿菇凉:楼主,(用最新的xcodebuild -exportArchive)打App Store的包成功了,但是ad-hoc的包失败了,请问有什么办法解决。
        错误日志:
        ** ARCHIVE SUCCEEDED **
        2017-03-03 11:10:25.486 xcodebuild[844:15630028] [MT] IDEDistribution: -[IDEDistributionProvisioning _itemToSigningInfoMap:]: Can't find any applicable signing identities for items: (
        "<IDEDistributionItem: 0x7fad0d5dcd00 'com.xianzaishi.normandie' '<DVTFilePath:0x7fad0d4e3bc0:'/Users/zouhaiqing/Desktop/XianZaiShi2017-03-03_11.09.06/XianZaiShi1.1.7.xcarchive/Products/Applications/XianZaiShi.app'>'>"
        )
        error: exportArchive: The operation couldn’t be completed. (IDEDistributionErrorDomain error 3.)
        ** EXPORT FAILED **
        但是用xcode打adhoc是成功了的!!!
        灵儿菇凉:@walter211 搞定了。就是adhoc的plist设置 compikebitcode 那个为no method 为 adhoc。就可以了。其它的参数不用加。就好了。也是奇怪
        zackzheng:看起来时证书的原因。我工程开了自动配置证书后也出现了,后面去掉自动,下载好证书,就没问题了。
        walter211:请问你搞定了吗
      • Ashen:博主你好,看了你的博文 学到好多,有一个问题想要咨询下你,ad-hoc的ipa为什么比app-store的包小很多呢
        zackzheng:这就没研究过呢~
      • 6010e5ed588d:error: the archive at path 'xxx.xcarchive' is not a single-bundle archive
        请问这是什么问题呢
        zackzheng:您是按我的文档打包的.scarchive吗?
      • 翀鹰精灵:博主你好,我想问下,邮件通知相关同事这个具体怎么配置,能加下QQ请教下吗 ? (QQ: 2807338860)
        zackzheng:参考下我那个python文件哦
      • MrSong:打包出来没有推送消息,不知道怎么回事
        zackzheng:嗯嗯是的~
        MrSong:@MrSong Xcode 现在是自动配置这些证书的,这个怎么控制,手动设置吗?
        zackzheng:推送消息应该只跟证书和配置文件有关哦~打包时设置对了吗?
      • 若非长得丑怎会做逗比:altoolPath="/Applications/Xcode.app/Contents/Applications/Application\ Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/altool"
        ${altoolPath} --validate-app -f ${ipaPath} -u xxxxxx -p xxxxxx -t ios --output-format xml >>
        ${altoolPath} --upload-app -f ${ipaPath} -u xxxxxx -p xxxxxx -t ios --output-format xml

        报错 没有这个路径 altoolPath 我看了确实是有的
        zackzheng:@若非长得丑怎会做逗比 这……把错误信息贴一下
        zackzheng:@若非长得丑怎会做逗比 直接用路径呢?
      • 7851bf1a6778:我用xcodebuild archive 出来跟 xcode archive出来的包是一样大小的, 但导出的ipa会大3M多,是什么问题呢?
        zackzheng:@云之国度 exportArchive给了什么参数呢
        7851bf1a6778:@zackzheng ad-hoc/enterprise 我都只写了method这个参数 :flushed:
        zackzheng:@7851bf1a6778 exportOptionsPlist文件里面是什么呢?
      • XcqRomance:您好,我用xcodebuild -exportArchive命令出现以下错误error: exportArchive: exportOptionsPlist error for key 'method': expected one of {}, but found ad-hoc ;我的exportOptionsPlist.plist添加的key是method,value是ad-hoc
        zackzheng:@xcq 感觉应该还是exportOptionsPlist.plist文件有问题,你用xcode打开后看到root是Dictionary,method在root下面,是String,Value是ad-hoc吗?
        XcqRomance:@zackzheng 是的string 类型,就添加了这么一个
        zackzheng:@xcq string吗?
      • 81876d66e26a:我这边用
        xcodebuild archive -workspace "${work_space_name}" -scheme "${scheme}" -configuration "${configuration}" -archivePath "${archivePath}" CONFIGURATION_BUILD_DIR="$configurationBuildDir" CODE_SIGN_IDENTITY="${codeSignIdentity}" PROVISIONING_PROFILE="adHocProvisioningProfile" >> "${log_path}"

        打包 Shell报错了

        ** ARCHIVE FAILED **


        The following build commands failed:
        CompileC /Users/liujun/Library/Developer/Xcode/DerivedData/FunHotel-bcsfrrpnikqxqnaggsmjipxfshzj/Build/Intermediates/ArchiveIntermediates/FunHotel/IntermediateBuildFilesPath/FunHotel.build/DailyRelease-iphoneos/FunHotel.build/Objects-normal/armv7/NearThreeHotelCollectionViewCell.o FunHotel/Classes(实现类)/Hotel(酒店)/View/NearThreeHotelCollectionViewCell.m normal armv7 objective-c com.apple.compilers.llvm.clang.1_0.compiler

        我觉得应该是说这个文件的路径找不到
        zackzheng:@大水壶哥哥 看不出什么错误,还有其他错误信息吗/你有同时存在两个Xcode吗?
        81876d66e26a:@zackzheng 就是Xcode可以编译通过 是Xcode8
        zackzheng:@大水壶哥哥 Xcode可以编译通过吗?
      • 扫地僧练级:用Jenkins构建 加参数-exportOptionsPlist "$exportOptionsPlist" 报错Error Domain=IDEDistributionErrorDomain Code=14 "No applicable devices found." UserInfo={NSLocalizedDescription=No applicable devices found.}

        可是终端执行脚本是ok的 这是什么问题
        zackzheng:@kboy08 之前用Xcode 8之后遇到过一个问题,忘了是不是这个错误,用了下面命令解决。
        在执行之前加:
        [[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm"
        rvm use system
      • Mzxer:博主,我用这个命令打包一个新建的项目出来的包是20多M, xcodebuild -exportArchive -exportFormat ipa -archivePath "barchive" -exportPath "build/test1.ipa" -exportProvisioningProfile "zsy_test_podfile"uild/test1.xc. 但是用xcode就是2点多m.是哪个参数出问题吗
        Mzxer:@zackzheng 就是我用同一个项目(就一个全新的项目,啥都没有),然后一个是通过xcodebuild来打包,一个是通过xcode来打包,然后xcodebuild打包出来的20多m.

        xcodebuild命令为:
        1.xcodebuild clean archive -archivePath build/ios_test -scheme ios_test
        2.xcodebuild -configuration Debug -exportArchive -exportFormat ipa -archivePath "build/ios_test.xcarchive" -exportPath "build/ios_test.ipa" -exportProvisioningProfile "xxxx_podfile"
        zackzheng:@米庄小二 是说打包出来的ipa文件比xcode导出来的大吗?那么打包前的文件一样大吗?
        Mzxer:请指教,谢谢
      • 落影loyinglin:赞。这个可节省好多时间
        zackzheng:谢谢!!

      本文标题:详解Shell脚本实现iOS自动化编译打包提交

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