持续集成指的是,频繁地(一天多次)将代码集成到主干。它的好处主要有两个。
(1)快速发现错误。每完成一点更新,就集成到主干,可以快速发现错误,定位错误也比较容易。
(2)防止分支大幅偏离主干。如果不是经常集成,主干又在不断更新,会导致以后集成的难度变大,甚至难以集成。
持续集成的目的,就是让产品可以快速迭代,同时还能保持高质量。它的核心措施是,代码集成到主干之前,必须通过自动化测试。只要有一个测试用例失败,就不能集成。
Martin Fowler(重构:改善既有代码的设计的作者)说过,"持续集成并不能消除Bug,而是让它们非常容易发现和改正。
"持续交付(Continuous delivery)指的是,频繁地将软件的新版本,交付给测试和用户,以供测试和评审。如果通过,代码就进入生产阶段。
持续部署(continuous deployment)是持续交付的下一步,指的是代码通过以后,自动部署到生产环境。
主要的工具就是Jenkins+fastlane (但是我个人觉得Jenkins 维护成本高个人, 个人觉得它主要比fastlane就多一个好处,能自动检测gitlab你的代码的上传然后调用脚本 ,其实业务不复杂的话直接用fastlane就很不错,所以重点讲一下fastlane 吧)
1 Jenkins的安装
首先你要先在本地电脑把Java环境配置好 ,https://www.jianshu.com/p/b518ce7e2bce我直接贴出来地址吧
我们来开始安装Jenkins。从官网https://jenkins.io/ 上下载最新的pkg安装包。
一直点击继续 直到安装完成。安装完成之后,Safari可能会自动打开,如果没有自动打开,打开浏览器,输入http://localhost:8080,会出现下图的重设初始密码的界面。
按照提示,找到/Users/Shared/Jenkins/Home/ 这个目录下(也有可能不是这个目录,只需要按照它提示的目录就行),这个目录虽然是共享目录,但是有权限的,非Jenkins用户/secrets/目录是没有读写权限的。
打开initialAdminPassword文件,复制出密码,就可以填到网页上去重置密码了。如下图
点击 install suggested plugins 然后等待安装完成 如下图
一路安装过来,输入用户名,密码,邮件这些,就算安装完成了。
还是继续登录localhost:8080 ,选择“系统管理”——“管理插件”,我们要先安装一些辅助插件。
安装GitLab插件
因为我们用的是GitLab来管理源代码,Jenkins本身并没有自带GitLab插件,所以我们需要依次选择系统管理->管理插件,在“可选插件”中选中“GitLab Plugin”和“Gitlab Hook Plugin”这两项,然后安装。
安装Xcode插件
同安装GitLab插件的步骤一样,我们依次选择系统管理->管理插件,在“可选插件”中选中“Xcode integration”安装。
安装完了这个,我们就可以配置一个构建项目了。
输入项目名字,点击新建好的项目,进来配置一下General参数
接着设置源码管理。
由于现在我用到的是GitLab,先配置SSH Key,在Jenkins的证书管理中添加SSH。在Jenkins管理页面,选择“Credentials”,然后选择“Global credentials (unrestricted)”,点击“Add Credentials”,如下图所示,我们填写自己的SSH信息,然后点击“Save”,这样就把SSH添加到Jenkins的全局域中去了。
如果正常的配置正确的话,是不会出现下图中的那段红色的警告。如果有下图的提示,就说明Jenkins还没有连通GitLab或者SVN,那就请再检查SSH Key是否配置正确。
构建触发器设置这里是设置自动化测试的地方。
Poll SCM(poll source code management) 轮询源码管理
需要设置源码的路径才能起到轮询的效果。一般设置为类似结果: 0/5 * * * * 每5分钟轮询一次
Build periodically(定时build)
一般设置为类似: 00 20 * * * 每天 20点执行定时build 。当然两者的设置都是一样可以通用的。
还有一些关于钥匙串和证书,描述文件的配置,但是我们主要用fastlane 脚本打包 ,所以先说怎么安装 fastlane吧
fastlane安装
确保Xcode Command Line Tools 安装了最新版
xcode-select --install
如果你单独安装过ruby(如果你能看得懂这句),去掉sudo。如果使用系统自带的ruby,需要sudo权限
[sudo] gem install fastlane
进到项目目录。在xcodeproj文件同级目录下,执行
fastlane init
如果是第一次使用 fastlane ,会要求输入你的苹果开发者账号,期间会让你输入 Apple ID 账号密码(这个信息会存在钥匙串中,后续使用无需再输入密码),会检测当前的 app identifier 是否在 Apple Dev Center 中,会检测当前 app 是否在 iTunes Connect 中,如果已经在 Apple Dev Center 和 iTunes Connect 中创建相应的信息,那么过程会很顺利
成功之后,会在你工程的根目录下创建fastlane文件夹里面内容如下,最重要的两个文件就是Appfile和Fastfile,:
其中:
Appfile, 用于存放 app ID 和你的 Apple ID。 Fastfile, 用于管理你所创建的 lane,lane 则会调用 action。
我们先看 Fastfile文件,说到Fastfile文件就要先介绍一下 fastlane组件。fastlane其实是一个工具集,包含了我们日常开发中上线时需要的大部分操作。比如gym/deliver等。主要组件包括:
deliver:自动上传截图,APP的元数据,二进制(ipa)文件到iTunes Connect
snapshot:自动截图(基于Xcode7的UI test)
frameit:可以把截的图片自动套上一层外边框
pem:自动生成、更新推送配置文件
sigh:用来创建、更新、下载、修复Provisioning Profile的工具
produce:如果你的产品还没在iTunes Connect(iTC)或者Apple Developer Center(ADC)建立,produce可以自动帮你完成这些工作
cert:自动创建管理iOS代码签名证书
pilot:管理TestFlight的测试用户,上传二进制文件
boarding:建立一个添加测试用户界面,发给测试者,可自行添加邮件地址,并同步到iTunes Connect(iTC)
gym:自动化编译打包工具
match:证书和配置文件管理工具
scan:自动运行测试工具,并且可以生成漂亮的HTML报告
Fastfile文件
Fastfile文件的主要结构如下所示:
fastlane_version "2.14.2"
default_platform :ios
platform :ios do
before_all do
cocoapods
end
lane :test do
end
lane :beta do
end
lane :release do
end
after_all do |lane|
end
error do |lane, exception|
end
end
说明:
(1)fastlane_version:指定fastlane使用的最小版本
(2)default_platform:指定当前默认的平台,可以选择ios/android/mac
(3)before_all:在执行每一个lane之前都会调用这部分的内容
(4)after_all:在每个lane执行完成之后都会执行这部分的内容
(5)error:每个lane执行出错就会执行这部分的内容
(6)desc:对lane的描述,fastlane会自动将desc的内容生成说明文档
(7)lane:定义一个lane(任务),可以理解为一个函数,我们在执行的时候使用fastlane [ios] lane名称
下面是官方提供的一个示例:
lane :beta do
increment_build_number
cocoapods
match
testflight
sh "./customScript.sh"
slack
end
像increment_build_number、cocoapods这样的一条命令都是一个action,由这样的一个个action组成了一个lane(lane中可以调用其他的lane)。
比如我需要完成一套发布流程:
#发布到AppStore
lane :release do
#增加build版本号,需要先配置build setting
increment_build_number
#pod资源更新
cocoapods
#打包
gym
#发布到AppStore
deliver(force: true)
#发布testflight测试
testflight
end
我们在项目目录下,用终端执行如下命令即可:
fastlane release
场景
随着业务的发展,产品线的增加,我们需要将APP拆分为若干个基础组件和业务组件,以便跨APP使用,并且方便管理维护(这又是另外一个大的议题,就不在此赘述了)。每个组件都由一个私有Pod来管理,Pod的发布和更新也成为了我们日常工作的一部分,对于这些Pod,一般我们团队内部的原则是:谁制作,谁管理,谁发布,Pod的负责人我们内部称之为库管,这件事也就分担到了每个库管身上,库管发布一个Pod的流程大约如下:
增加Podspec中的版本号
执行pod lib lint命令进行库验证
Git Commit代码
Git Push代码到远端
打一个Git Tag
将Tag Push到远端
执行pod repo push命令发布库到私有仓库
如果只有两三个库的话,并且库的更新频率较低的时候,每次手动来处理还好。但是当库逐渐增多的时候这件事就变得相当麻烦,尤其是当顶层的库依赖底层库的时候,那么升级一个库,影响面将远远超过其本身,通过人工的方式处理的话,整个过程会变得相当痛苦。
那我们可以 在fastlane 中 这些写
desc "Release new private pod version"
lane :do_release_lib do |options|
target_version = options[:version]
project = options[:project]
path = "#{project}.podspec"
git_pull
ensure_git_branch # 确认 master 分支
pod_install
pod_lib_lint(verbose: true, allow_warnings: true, sources: SOURCES, use_bundle_exec: true, fail_fast: true)
version_bump_podspec(path: path, version_number: target_version) # 更新 podspec
git_commit_all(message: "Bump version to #{target_version}") # 提交版本号修改
add_git_tag(tag: target_version) # 设置 tag
push_to_git_remote # 推送到 git 仓库
pod_push(path: path, repo: "GMSpecs", allow_warnings: true, sources: SOURCES) # 提交到 CocoaPods
end
我们在项目目录下,用终端执行如下命令即可:
fastlane do_release_lib project:GMUtil version:0.1.4
还有在安卓由于国内Android市场众多上经常会遇到 多渠道打包,这里我们也使用Fastlane如何进行处理:
Fastlane的Action机制
Fastlane本身包含两大模块,一个是其内核部分,另外一个就是Action了。Action是Fastlane自动化流程中的最小执行单元,直观上来讲就是Fastfile脚本中的一个个命令,比如:git_pull,deliver,pod_install等等,而这些命令背后都对应一个用Ruby编写的脚本。Fastlane已经为我们提供了现成的模板,即使你对Ruby的语法不熟悉,也没有关系,Fastlane是开源的嘛,可以直接下载源码看看别人的Action是怎么写的就知道了,我们可以在这个目录下找到所有的Action文件:
fastlane/fastlane/lib/fastlane/actions/
针对pod的执行创建一个action来针对下面三种情况的执行
pod install --no-repo-update (避免master repo的每次更新耗时)
pod update --no-repo-update (避免master repo的每次更新耗时)
pod repo update XXX (私有repo的更新)
自定义Action的流程大约如下,首先,我们在终端中执行命令:
fastlane new_action
后根据提示,在命令行中敲入action的名字pod,然后Fastlane会在当前目录的actions文件夹中帮我们创建了一个pod.rb的Ruby文件,内容大致如下(省略了非重点部分):
module Fastlane
module Actions
class PodLibLintAction < Action
def self.run(params)
UI.message "Parameter API Token: #{params[:api_token]}"
end
......
def self.available_options
[
FastlaneCore::ConfigItem.new(key: :api_token,
env_name: "FL_POD_LIB_LINT_API_TOKEN", # The name of the environment variable
description: "API Token for PodLibLintAction", # a short description of this parameter
verify_block: proc do |value|
UI.user_error!("No API token for PodLibLintAction given, pass using `api_token: 'token'`") unless (value and not value.empty?)
end),
......
]
end
end
end
可以看到,自定义的Action都是隶属于Fastlane/Actions这个module,并且继承自Action这个父类。虽然模板中的内容还挺多,不过不用担心,大部分内容都是一些简单的文本描述,对于我们来说只需要重点关注这两个方法就行:
1.self.run方法:这里放置的是实际的业务处理代码。
2.self.available_options方法:这里声明需要对外暴露出的参数,没有声明的参数在执行过程中无法使用。
在self.available_options中进行声明,在self.run方法中编写最终的业务逻辑,同时将上面的options通过params暴露出去,这样在运行pod这个action的时候,我们就可以传入对应的参数,从而Fastlane可以执行携带各种选项的完整命令,
module Fastlane
module Actions
module SharedValues
POD_INSTALL_CUSTOM_VALUE = :POD_INSTALL_CUSTOM_VALUE
end
class PodInstallAction < Action
def self.run(params)
repo = "-no-repo-update"
command = []
command << "pod install"
if params[:repo_update]
repo = "--repo-update"
end
command << repo
if params[:verbose]
command << "--verbose"
end
result = Actions.sh(command.join(' '))
UI.success(command.join(' ') + " Successfully ")
return result
end
def self.description
"pod install action"
end
def self.details
"verbose / repo-update"
end
def self.available_options
[
FastlaneCore::ConfigItem.new(key: :verbose,
description: "Allow output detail in console",
optional: true,
is_string: false,
default_value: false),
FastlaneCore::ConfigItem.new(key: :repo_update,
description: "Allow output detail in console",
optional: true,
is_string: false,
default_value: false)
]
end
def self.output
end
def self.return_value
end
def self.authors
["yang"]
end
def self.is_supported?(platform)
platform == :ios
end
end
end
end
最后,我们将pod.rb拷贝到iOS项目下的fastlane/actions文件夹中,然后在该项目目录下,执行如下命令:
fastlane action pod
首先,我们自定义一个Action:add_channels_to_apk,这个Action的作用就是:
拷贝最终打包生成的apk文件,并修改文件名为渠道名,如gengmei_qq_630.apk
然后将一个渠道名写入到apk文件的META-INFO目录中
其次,新建一个txt文件,里面写入所有需要打包的渠道名,如:QQ,360,Baidu...等等,渠道名之间用逗号隔开。
最后,在Fastfile中定义一个Lane来进行最终的集成处理:
desc "Package a new app version with different channels"
lane :do_package_apk do |options|
project = "#{options[:project]}"
target_version = options[:version]
git_pull
gradle(task: "clean")
gradle(task: "assembleRelease")
add_channels_to_apk(channels: './channels.txt')
end
接下来的事就简单多了,每次需要打包的时候,只要执行如下的命令即可:
fastlane do_package_apk project:Gengmei version:6.3.0
无论是5个渠道,还是50个渠道,1分钟内全部搞定,非常的方便。