折腾了一周,终于用Python语言写好了项目工程的自动编译脚本,虽然最终脚本只有200多行代码,但中间遇到的一些问题还是折磨了我一番, 好了, 现在把过程记录下来, 以便加深自己的印象, 也可供需要的人参考.
一.目标
我写这个脚本是要做到下面几个目标:
1.每次编译都从服务器更新最新代码;
2.每次编译要自动修改info.plist里的bundle version;
3.每次编译都要编译出debug版和可供发布的distribution版, debug供测试人员使用, 测试通过后直接使用对应的distribution版本提交到App Store;
4.编译出的包上传到服务器, 并通过邮件通知相关人员, 邮件内容是安装包的地址, 如果编译失败, 邮件内容是失败信息.
二. 开发环境
我是在Mac下用Python开发的, 其实直接用shell脚本也行, 我是为了操作一些文件更方便, 编辑器用的Sublime text 2.
三. 前提条件
开发证书和发布证书请先安装好, 工程配置里添加Distribution项(复制Release), 并配置好对应的发布证书.
四. check out工程代码.
代码管理我用的是svn, 下载代码其实就是用Python执行shell命令, 代码如下:
def svnCheckout(self):
print "svn checkout start"
os.system('rm -frd %s'%codeDir)
os.system('mkdir %s'%codeDir)
os.system('svn checkout %s --username %s --password %s --non-interactive %s'\
%(svnPath,svnUserName,svnPassword,codeDir))
if os.path.exists('%s/YourProject'%codeDir) == False:
msg = 'checkout code failed'
print msg
self.buildSucceed = False
self.buildErrorDescription.append(msg)
return False
else:
os.chdir(LifeSearchBuild.codeDir)
return True
需要导入os模块, os.system()方法是执行shell命令的, 代码中, codeDir变量是check out代码后存放的本地目录, buildErrorDescription是我定义的一个列表, 用来记录错误的信息的, 供后面发送邮件使用(编译失败时).
五. 修改bundle version
其实这一步是为了Distribution版, 因为提交到App Store的包, bundle version必须是递增的一个数, bundle version在工程的info.plist文件里, 操作plist, 可以直接用Mac自带的PlistBuddy, 代码如下:
def modifyBundleVersion(self,newBundleVersion):
os.system('/usr/libexec/PlistBuddy -c "Set:CFBundleVersion %s" %s'%(newBundleVersion,infoPlistFilePath))
其中infoPlistFilePath是你check out代码后工程里info.plist文件的路径. newBundleVersion需要你自己定义一个规则, 保证和上次编译的包的version是递增的就行, 编译完成后也需要持久化到本地, 以便下次做递增计算.
六. 编译工程
有了代码后, 就开始用xcodebuild命令来编译工程, 编译debug版的代码如下:
def build_debug(self):
print "build debug start"
os.system('rm -frd build')
os.system('xcodebuild clean -configuration Debug')
os.system('xcodebuild -configuration Debug')
if os.path.exists('build/Debug-iphoneos/YourProject.app'):
debugIpaName = 'debug_%s_%s.ipa'%(currAppVersion,self.buildVersion)
os.system('mkdir Payload')
os.system('cp -r ./build/Debug-iphoneos/YourProject.app ./Payload/')
os.system('zip -r %s Payload'%debugIpaName)
os.system('rm -frd build')
os.system('rm -frd Payload')
self.ipaNameList.append(debugIpaName)
else:
self.buildSucceed = False
self.buildErrorDescription.append('build debug ipa failed')
说明:app生成后需要放进一个Payload文件夹, 然后压缩成zip, 然后改后缀为ipa, Distribution的不用, 发布时直接用zip包提交.
代码中ipaNameList是我定义的一个列表, 用来保存编好的包名字的, 以便后续上传服务器时使用.
七. 上传到服务器
我用到了ftp命令, 代码如下:
def uploadToFTP(self):
if len(self.ipaNameList) == 0:
self.buildSucceed = False
print "no ipas to upload"
return
os.system('''
ftp -niv %s << EOF
user %s %s
bin
mkdir %s/%s
cd %s/%s
lcd %s
mput %s
bye
EOF'''%(ftpAddress,
ftpUserName,
ftpPassword,
currAppVersion,
self.buildVersion,
currAppVersion,
self.buildVersion,
codeDir,
' '.join(self.ipaNameList)))
其实大家不用细看这段代码, 这是我项目里直接拷贝过来的, 只要大家能用ftp命令上传到自己的服务器就行了.
八. 邮件通知
发邮件用到了smtplib, 我的邮件内容需是html格式的, 我就不直接拷贝我项目里的代码了, 写个例子代码如下:
smtp = smtplib.SMTP('mail.yourcompany.com')
smtp.login(emailSenderUserName, emailSenderPassword)
msg = MIMEMultipart()
msg['To'] = ";".join(emailAddressList)
msg['From'] = emailSender
msg['Subject']= "test mail"
text = "<p>build successful</p>"
body = MIMEText("%s"%text,_subtype='html',_charset='utf-8')
msg.attach(body)
smtp.sendmail(msg['From'], emailAddressList, msg.as_string())
smtp.quit()
九. dailybuild
编译脚本弄好后, 可以手动运行编译, 也可以添加计划任务来每日定时编译, 这就用到了crontab, 如果你想每天早上8点运行自动编译脚本, 则编辑crontab如下:
0 8 * * * python build.py >buildLog.txt
buildLog.txt文件用来记录日志.
注意!!用crontab的时候, 证书的问题折磨了我整整一天, 因为在crontab, 证书无法读取到, 导致签名失败, 上网查了大量的文章, 好像跟crontab无法读取到用户化境变量有关, 不过, 最终我的解决办法倒是很简单, 就是打开钥匙串, 把证书从"登录"里拷贝到"系统"里就可以了.
ok, 完了, 希望对大家有帮助.