利用JenKins持续集成iOS项目时遇到的问题

Author Avatar
XibHe 11月 06, 2017
  • 在其它设备中阅读本文章

持续集成(Continuous Integration,简称CI)是一种软件开发实践:许多团队频繁地集成他们的工作,每位成员通常进行日常集成,进而每天会有多种集成。每个集成会由自动的构建(包括测试)来尽可能快地检测错误。许多团队发现这种方法可以显著的减少集成问题并且可以使团队开发更加快捷。

CI是一种开发实践。实践应该包含3个基本模块,一个可以自动构建的过程,自动编译代码,可以自动分发,部署和测试。一个代码仓库,SVN或者Git。最后一个是一个持续集成的服务器。通过持续集成,可以让我们通过自动化等手段高频率地去获取产品反馈并响应反馈的过程。

更新说明

更新记录:

  • 2017 年 11 月,第一版。
  • 2018 年 01 月,替换一些图片,增加上传ipa包到FTP。

持续集成的优点

  1. 缩减开发周期,快速迭代版本
  2. 自动化流水线操作带来的高效
  3. 随时可部署
  4. 极大程度避免低级错误

持续化集成工具—JenKins

Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能。

JenKins的用途:

  1. 构建项目自动化打包可以省去开发人员好多时间,重要的是,Jenkins为我们维护了一套高质量可用的代码,而且保证了一个纯净的环境。
  2. 可以用来自动化测试,在本地生成大批的测试用例,每天利用服务器不断的跑这些用例。
  3. 静态代码分析,可以检测出很多代码的问题,比如潜在的内存泄露的问题。
  4. 随时部署,Jenkins在打包完成之后可以设定之后的操作,这个时候往往就是提交app到跑测试用例的系统,或者部署到内测平台生成二维码。

开始安装JenKins

方法一: 直接下载安装包

注意:此时有两种安装方式是

  • 标准安装,如下图

  • 自定义安装,此时应该取消Start at boot as “jenkins”勾选,如下图

安装完成后在Terminal中输入,

open /Applications/Jenkins/jenkins.war

即可打开Jenkins

方法二: 使用命令行安装
安装JenKins,

$ brew install jenkins

若brew无效?则需要安装homebrew,

$ ruby -e “$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)”

启动JenKins,

$ jenkins

启动JenKins后使用浏览器访问JenKins,

地址为: http://localhost:8080/

使用安装包安装后会自动打开,如果端口冲突那么请修改端口

defaults write /Library/Preferences/org.jenkins-ci httpPort 7070

强烈推荐使用方法二安装JenKins,使用命令行可以避免后面构建项目时,报一些莫名其妙的权限错误。

安装插件

若使用git做源码管理,则需要安装

  • GIT plugin
  • GitHub plugin

这两个插件,为了方便管理打包证书,需要安装插件

  • Keychains and Provisioning Profiles Management

由于需要使用Xcode编译环境,因此必须要安装插件 (当然,也可以不安装Xcode integration插件,使用shell脚本命令进行打包)

  • Xcode integration

若最后构建生成的ipa包,需要上传到FTP,则需要安装上传FTP的插件

  • FTP publisher plugin

我们需要依次选择 系统管理->管理插件,在“可选插件”中选中“Keychains and Provisioning Profiles Management”“FTP publisher plugin”这两项,然后安装。如图,

配置构建环境

在配置构建环境前,我们先来导入一下打包证书,点击系统管理,找到刚才添加的插件Keychains and Provisioning Profiles Management,点击选取文件,导入名为login.keychain的钥匙串文件。

这个Keychain其实在/Users/管理员用户名/Library/keychains/login.keychain,当把这个Keychain设置好了之后,Jenkins会把这个Keychain拷贝到/Users/管理员用户名/.jenkins/workspace/项目目录名称,(.jenkins是隐藏文件)。

如图所示,

其中,Keychains中的Identities输入项,对应的是刚才点击Upload后钥匙串中的测试证书名称,发布证书名称(需要输入证书名称)。

Provisioning Profiles中对应的是刚才上传的证书配置文件。这些配置文件的存放路径为:/Users/管理员用户名/Library/MobileDevice/Provisioning Profiles

这样Adhoc证书和签名文件就在Jenkins中配置好了,接下来我们只需要在项目的构建配置中指定相关文件即可。

注意: 有些升级了最新的Mac OS系统后,/Users/管理员用户名/Library/keychains目录下没有login.keychain文件,有的是一个名为login.keychain-db的文件,这是需要将login.keychain-db文件copy到桌面,修改成文件名为login.keychain。点击”选取文件“,选取并上传这个修改名称后的login.keychain文件。

导入证书后需要在”系统管理“–>”系统设置“里再设置”Xcode Builder“时,访问证书的全局路径。如图,

Keychain path“为钥匙串的本地路径,”Keychain password“为访问钥匙串的密码。设置完成后不要忘记点击”保存“。

新建项目

新建 -> 输入项目名称 -> 选择“构建一个自由风格的软件项目” -> 点击ok就行了。

源码管理

如图所示,

其中,Repository URL对应的是svn的路径,Credentials为登录svn时的用户账号密码,点击”Add”按钮添加即可。Check-out Strategy最好选择每次update最新代码前都revert下,而不是“Use ‘svn update’ as much as possible” ,因为我使用的是CocoaPods管理的第三方,每次打包运行pod install会修改了工程配置文件,如果下次自动打包前不先revert再update的话会出现冲突。

构建触发器

这里是设置自动化测试的地方。涉及的内容很多,暂不做深入研究,这里先不设置,有自动化测试需求的可以好好研究这里的设置。

  • Poll SCM (poll source code management) 轮询源码管理
    需要设置源码的路径才能起到轮询的效果。一般设置为类似结果: 0/5 每5分钟轮询一次

  • Build periodically (定时build)
    一般设置为类似: 00 20 * 每天 20点执行定时build 。当然两者的设置都是一样可以通用的。

构建环境

勾选“Keychains andProvisioning Profiles Management”和“Mobile Provisioning Profiles”。

这里的Code Signing IdentityProvisioning Profile选项中对应上文Keychains and Provisioning Profiles Management插件中导入的钥匙串文件中的证书及证书的配置文件。当然也可以不勾选Mobile Provisioning Profiles。

构建(构建步骤)

点击”增加构建步骤”选项,因为我使用的是CocoaPods管理的第三方,打包前先运行
pod install –verbose –no-repo-update安装下第三方库。

注意:执行pod install命令时,需要cd到pod文件所在的目录,${WORKSPACE}是项目在JenKins中的工作目录,而我的项目由于多套了一层目录BusinessMall,因此需要加上这层目录${WORKSPACE}/BusinessMall,否则,在构建时就会报:

[!] No `Podfile’ found in the project directory.

Build step ‘Execute shell’ marked build as failure

的错误。在执行完pod install后,就可以使用Xcode构建项目打包环境了。

添加构建步骤-> Xcode

在”General build settings“中,点击”Settings“按钮设置相关参数。

Target要与Xcode项目中Target的名字对应

Clean before build设置为YES

勾选”Pack application,build and sign .ipa?“的复选框,会弹出设置生成ipa包的几个参数项。如图,

Export method,ipa的类型(‘development’, ‘ad-hoc’, ‘enterprise’ or ‘app-store’)

.ipa filename pattern,ipa的名称。

Output directory,输出ipa的文件路径。

注意:这里如果Xcode的版本低于9.0,就可以正常输出ipa包。如果Xcode版本不低于9.0,就会由于Xcode 9.0不在允许你访问钥匙串里的内容,而输出ipa失败。具体的解决方法请参照后面”构建项目时遇到的几个问题“中具体的解决方法。

继续设置”Code signing & OS X keychain options“证书信息,如图,

其中,Development Team ID为开发团队ID,可以在钥匙串中的证书详情里查看。勾选Unlock Keychain,显示为之前上传的login.keychain文件。

如果项目使用了cocoaPods,需要配置Advanced Xcode build options,设置

  1. Xcode Schema File,设为Xcode项目中的schema
  2. Xcode Workspace File,这里设置绝对路径,不需要带上.xcworkspace后缀。如果项目中没有workspace后缀,那就在“Xcode Project File”上填.xcodeproj文件的路径。
  3. Build output directory,设为${WORKSPACE}/build/

注意:Xcode Workspace File为绝对路径,也就是Jenkins用于构建项目的workspace目录中,.xcworkspace文件所在目录的路径。

如果路径不正确,则在构建项目时,会报:

open BusinessMall.xcworkspace

The file /Users/zyjk_imac-penghe/.jenkins/workspace/BusinessMall/BusinessMall.xcworkspace does not exist.

Build step ‘Execute shell’ marked build as failure

的错误。

构建后操作

  • 添加构建后操作步骤,使用脚本将生成的ipa包上传到fir或者蒲公英等三方平台,扫码下载安装。

参考http://blog.fir.im/jenkins/使用官方工具fir-plugin-1.9.5.hpi插件上传ipa包到fir。

  • 将ipa包上传到FTP服务器

如图,选择“Publish artiffacts to FTP”,其中,FTP site就是在系统管理->系统设置中,增加的一项名为“FTP repository hosts”的配置里已经设置好的host和端口。Source为构建生成的ipa包的存放路径。

构建项目时遇到的几个问题

1. pod: command not found

pod install
/var/folders/gn/rqsybgtn7f50w67111kj1hhw0000gn/T/hudson3821369083140563198.sh: line 2: pod: command not found
Build step ‘Execute shell’ marked build as failure

解决方法: 在系统管理–>系统设置,增加”全局属性”,勾选Environment variables,增加键值对列表。如图,

其中,PATH是固定的,值是在终端输入:

$echo $PATH

命令获取,将输入命令后得到的值粘贴过来就可以了。

2. ruby_executable_hooks: No such file or directory

Pod: env: ruby_executable_hooks: No such file or directory
Build step ‘Execute shell’ marked build as failure

从jekyll有时也会报这个错误得到启示,可能是由于执行pod命令的路径粗存在问题。在终端使用命令:

$ which pod

查看pod的安装路径为: /Users/zyjk_imac-penghe/.rvm/gems/ruby-2.2.0/bin/pod,而执行pod install的shell的环境变量中可能没有该路径。于是使用命令:

$ gem env

查看SHELL PATH:,发现列表中没有/Users/zyjk_imac-penghe/.rvm/gems/ruby-2.2.0/bin/pod这条路径。查阅了一些资料(What it is and How to Modify the Shell Path in macOS Sierra and OSX using Terminal)后,发现可以在根目录的.bash_profile中增加永久的路径。在终端输入命令:

$ sudo vi ~/.bash_profile

打开根目录下的.bash_profile文件,将路径/Users/zyjk_imac-penghe/.rvm/gems/ruby-2.2.0/bin/pod插入:

export PATH:”/Users/zyjk_imac-penghe/.rvm/gems/ruby-2.2.0/bin/pod:$PATH”
结束编辑esc后,输入:wq保存。在终端输入命令:

$ source ~/.bash_profile

让这个配置文件在修改后立即生效。此时,再查看gem env发现SHELL PATH:列表中,增加了/Users/zyjk_imac-penghe/.rvm/gems/ruby-2.2.0/bin/pod这条路径。

3. `find_spec_for_exe’: can’t find gem cocoapods (>= 0.a) (Gem::GemNotFoundException)

/Users/zyjk_imac-penghe/.rvm/rubies/ruby-2.2.0/lib/ruby/ site_ruby/2.2.0/rubygems.rb:271:in find_spec_for_exe': can't find gem cocoapods (>= 0.a) (Gem::GemNotFoundException) from /Users/zyjk_imac-penghe/.rvm/rubies/ruby-2.2.0/lib/ ruby/site_ruby/2.2.0/rubygems.rb:299:inactivate_bin_path’
from /Users/zyjk_imac-penghe/.rvm/rubies/ruby-2.2.0/bin/pod: 23:in <main>' from /Users/zyjk_imac-penghe/.rvm/gems/ruby-2.2.0/bin/ruby_executable_hooks:15:ineval’
from /Users/zyjk_imac-penghe/.rvm/gems/ruby-2.2.0/bin/ruby_executable_hooks:15:in `


Build step ‘Execute shell’ marked build as failure

子所以在构建项目时出现这个错误,是因为看到一篇文章(cocoapods插件GEM_PATH的配置),说是修改Xcode中CocoaPods插件的GEM_PATH:中的路径与SHELL PATH:中路径一致就能解决:

env: ruby_executable_hooks: No such file or directory

这个错误。于是就在~/.bash_profile文件中增加了一条GEM路径

export GEM_PATH=”/Users/zyjk_imac-penghe/.rvm/gems/ruby-2.2.0/bin/pod”

构建项目后就会报上面的错误,仔细看看,觉得是多了一条无效的执行路径导致的。于是,就删除了那条新添的GEM_PATH,再次构建就不报这个错误了。

4. in `find_spec_for_exe’: can’t find gem cocoapods (>= 0.a)

in `find_spec_for_exe’: can’t find gem cocoapods (>= 0.a)

更新一下gem版本,终端命令如下;

$ sudo gem update –system

有时在输入命令后会出现ssh相关的网络错误提示,可以在终端输入命令:

gem sources -a http://gems.ruby-china.org/

将源地址换为ruby-china。

5. 报xcodebuild: error: The workspace ‘Project’ does not contain a scheme named ‘Project’.的错误

Going to invoke xcodebuild:, scheme: Project, sdk: DEFAULT, workspace: Project, configuration: Debug, clean: YES, archive:NO, symRoot: DEFAULT, configurationBuildDir: /Users/ignat/.jenkins/workspace/Project/build, codeSignIdentity: DEFAULT
[Project] $ /usr/bin/xcodebuild -scheme Project -workspace Project.xcworkspace -configuration Debug clean build CONFIGURATION_BUILD_DIR=/Users/ignat/.jenkins/workspace/Project/build
Build settings from command line:
CONFIGURATION_BUILD_DIR = /Users/ignat/.jenkins/workspace/Project/build

xcodebuild: error: The workspace ‘Project’ does not contain a scheme named ‘Project’.
FATAL: Build directory does not exist at /Users/ignat/.jenkins/workspace/Project/build. Potential configuration issue.
Build step ‘Xcode’ marked build as failure
Finished: FAILURE

在Xcode中,选择Manage Scheme,勾选对应的Shared

6. 构建时报 NSLocalizedRecoverySuggestion=Add a profile to the “provisioningProfiles” dictionary in your Export Options property list.

Error Domain=IDEProvisioningErrorDomain Code=9 “”BusinessMall.app” requires a provisioning profile with the Push Notifications feature.” UserInfo={NSLocalizedDescription=”BusinessMall.app” requires a provisioning profile with the Push Notifications feature., NSLocalizedRecoverySuggestion=Add a profile to the “provisioningProfiles” dictionary in your Export Options property list.}

EXPORT FAILED

最后错误提示,EXPORT FAILED,可知是导出ipa包时出的错。根据提示”provisioningProfiles” dictionary in your Export Options property list.在网上搜了一下这个plist文件的用处,发现原来新版的Xcode 9将不会允许你访问钥匙串里的内容,除非设置allowProvisioningUpdates

具体的解决方式是: 自己动手写脚本替代插件(插件本质是帮助我们生成打包脚本代码)。于是,在JenKins里,点击项目的”配置“选项,在”构建“中,添加xcodebuild -archivePath,
xcodebuild -exportArchive -archivePath的脚本命令,输出ipa包。如图,

具体的脚本命令如下:

xcodebuild -archivePath "/Users/zyjk_imac-penghe/.jenkins/workspace/BusinessMall/BusinessMall/output/debug/BusinessMall.xcarchive" -workspace BusinessMall.xcworkspace -sdk iphoneos -scheme "BusinessMall" -configuration "Release" archive
xcodebuild -exportArchive -archivePath "/Users/zyjk_imac-penghe/.jenkins/workspace/BusinessMall/BusinessMall/output/debug/BusinessMall.xcarchive" -exportPath "/Users/zyjk_imac-penghe/.jenkins/workspace/BusinessMall/BusinessMall/ipa/debug/" -exportOptionsPlist '/Users/zyjk_imac-penghe/.jenkins/workspace/BusinessMall/BusinessMall/ipa/debug/ExportOptions.plist' -allowProvisioningUpdates

脚本中的构建路径为JenKins下对应的archivePath,ipa包的输出路径。其中,需要特别注意的是ExportOptions.plist的存放路径。手写ExportOptions.plist文件过于麻烦,不如让XCode帮我们生成。使用XCode 9打包并导出后的文件夹里就有这样一份文件可以直接拿过来用。修改后的plist文件,如图,

之后就在项目所在的目录下,新建一个名为ipa/debug/的目录,将修改后的ExportOptions.plist文件放在这个目录下。在Jenkins中使用SVN管理源码,因此,我需要将这个新增的文件上传SVN,这样在我每次构建项目,输出ipa时就能直接从SVN上拉取这个plist文件,通过设置ExportOptions.plist’ -allowProvisioningUpdates就能访问钥匙串中的打包证书,输出ipa包了。

注意:设置了allowProvisioningUpdates字段后,在打包过程中会弹出是否允许访问钥匙串内容的弹窗,这时需要多次点击“始终允许”按钮。

7. 构建时报error: exportArchive: The data couldn’t be read because it isn’t in the correct format.

error: exportArchive: The data couldn’t be read because it isn’t in the correct format.
EXPORT FAILED

关掉bitcode重新打包就可以了……

参考https://forums.developer.apple.com/thread/21193

写在最后

中间使用过命令:

sudo gem install -n /usr/local/bin cocoapods –pre

重装过CocoaPods,目前CocoaPods的版本为: 1.4.0.beta.2。也可以输入命令:

$ sudo gem install cocoapods -v 0.39.0

安装特定版本的CocoaPods。

由于给iOS项目构建版本时必须依赖于xcodebuild环境。现在是在一台Windows上部署Jenkins环境,可以配置节点,在另一台Mac电脑上打包。具体操作,可以参考这篇文章—Jenkins自动打包 配置mac slave节点

参考资料

w3cschool Jenkins

How to solve “/usr/bin/env: ruby_executable_hooks: No such file or directory”?

/usr/local/bin/pod No such file or directory

How to delete a gem path?

What it is and How to Modify the Shell Path in macOS Sierra and OSX using Terminal

手把手教你利用Jenkins持续集成iOS项目

cocoapods插件GEM_PATH的配置

cocoaPods安装2017 以及遇到的坑

Xcodebuild fails in jenkins with cocoapods

xcodebuild commands give different results when run from the command line than when run from within Jenkins

Jenkins自动打包 配置mac slave节点

Jenkins+XCode9自动打包错误处理

xcode 9 beta export options not working #9589

Xcode 7 Enterprise Distribution not working

–EOF–

若无特别说明,本站文章均为原创,转载请保留链接,谢谢