当前位置: 移动技术网 > 移动技术>移动开发>IOS > 详解Shell脚本实现iOS自动化编译打包提交

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

2018年09月14日  | 移动技术网移动技术  | 我要评论

详解shell脚本实现ios自动化编译打包提交。

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

提交测试版本给测试同事

提交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脚本涉及的工具

主要是以下几个工具:

xcodebuild

xcrun

altool(提交到app store使用)

fir-cli(上传到fir时使用)

python的smtplib(之前已经写过python的发邮件了,所以就直接用没有用shell写。)

plistbuddy

buglysymbolios(bugly的符号表工具包)

xcodebuild和xcrun

xcodebuild和xcrun都是来自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包会用到。

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

-showsdksdisplay a compact list of the installed sdks

-showbuildsettingsdisplay 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插件,可以看下。

https://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之间的引用和调用困难的问题。同时,一个workspace的project共用一个编译路径。比如使用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 settings和build phases)。一个project可以包含多个target。

具体实现

看起来有两种实现方法:

网上可以查到的文章,大多数都是用xcodebuild和xcrun实现的,比如:

xcodebuild -workspace xxx -scheme xxx -configuration release

xcrun -sdk iphoneos packageapplication -v "/xxx/xxx.app" -o "/xxx/xxx"

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

另一种是xcodebuild的archive和-exportarchive,只有一两篇文章是用这个,而且也过时了,因为现在最新是需要用-exportoptionsplist这个选项。

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

准备两个plist文件,用于导出不同ipa包时使用。

获取命令行参数,区分上传到fir还是app store

清理构建目录

编译打包

导出包

上传到fir或者验证并上传到app store

发邮件通知

准备plist文件

根据xcodebuild -help提供的可选key可以知道,compilebitcode、embedondemandresourcesassetpacksinbundle、icloudcontainerenvironment、manifest、ondemandresourcesassetpacksbaseurl、thinning这几个key用于非app store导出的;uploadbitcode、uploadsymbols用于app store导出;method、teamid共用。

method的可选值为:

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

所以我建了两个文件:appstoreexportoptions.plist、adhocexportoptions.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是证书名(在对应targets的build settings中选择完code sinning,再点击选择other...,就可以得到这串东西);provisioning_profile是配置文件(获取方法同code_sign_identity,格式一般是xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)。还可以添加其他参数,不设置的都是默认使用项目build settings里面的配置,包括code_sign_identity和provisioning_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来判断是否成功,目前这步还没做。

这是成功的结果:

os-version

10.11.2

success-message

no errors validating archive at /xxx/xxx.ipa

tool-version

1.1.902

xcode-versions

path

/applications/xcode.app

version.plist

buildversion

7

cfbundleshortversionstring

7.2

cfbundleversion

9548

productbuildversion

7c68

projectname

ideframeworks

sourceversion

9548000000000000

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

os-version

10.11.2

product-errors

code

-10001

message

transporter not found at path: /usr/local/itms/bin/itmstransporter. you should reinstall the application.

userinfo

mzunderlyingexception

transporter not found at path: /usr/local/itms/bin/itmstransporter. you should reinstall the application.

nslocalizeddescription

transporter not found at path: /usr/local/itms/bin/itmstransporter. you should reinstall the application.

nslocalizedfailurereason

transporter not found at path: /usr/local/itms/bin/itmstransporter. you should reinstall the application.

tool-version

1.1.902

xcode-versions

path

/applications/xcode.app

version.plist

buildversion

7

cfbundleshortversionstring

7.2

cfbundleversion

9548

productbuildversion

7c68

projectname

ideframeworks

sourceversion

9548000000000000

可见,成功会有个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})上传成功" "赶紧下载体验吧!https://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版本,经测试没问题。

在bugly官网下载符号表工具

设置settings.txt

调用命令

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

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

xcodebuild+xcrun和仅xcodebuild的比较

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

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

删除.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

下面是里面主要的步骤:

create product structure 创建.app文件

compilec 编译文件(clang编译,指定了编译的sdk版本和指令集)

ld

createuniversalbinary (lipo)

compilestoryboard (ibtool )

compileassetcatalog (actool )

processinfoplistfile (builtin-infoplistutility )

generatedsymfile (dsymutil )

linkstoryboards(ibtool )

strip

processproductpackaging (builtin-productpackagingutility )

codesign (codesign --force --sign)

validate (builtin-validationutility )

总结

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网