iOS持续CI&CD实践:Jenkins +Fastlane+Pgyer

1,097 阅读7分钟

1. 背景----为什么做?

一个想要持续发展壮大的团队,持续集成构建平台是必需要做的。随着团队项目敏捷开发的快速迭代和业务需求的日益增长,意识到如何减少交付环节的时间消耗和简化开发环节的成本复杂度是项目成熟必不可少的一大痛点。困境和挑战:

  • 资源浪费:开发人力和开发机器的资源,占用生产力。
  • 交付周期长:交付环节时机无法保障,依赖开发人员,经常出现互相等待。
  • 重复性工作:团队配合中存在高度偶和性重复工作。
  • 变动频繁:敏捷开发实践,程序开发周期短,需求变化快。
  • 缺乏执行平台:测试人员或者开发人员存在自动化想法,缺少平台来承载配置执行。

2. 目标----做成什么?

目标就是想让机器自动代替人去做一些事情,实现开发、集成、测试、部署流水线式自动化。简单来讲是要实现:

  • 少:减少人力操作,解放重复性劳动,让交付成果变得简单。
  • 准:避免大部分低级错误,比如编译问题,安装问题,证书问题等等。减少手工错误。
  • 快:更早的获取变更,更早的进入测试。减少等待时间。缩短从开发、集成、测试、部署各个环节的时间。
  • 高:更高的开发测试效率,更高的产品质量。可配置自动化工作流,提供代码质量检测等功能,对不规范或有错误的地方进行标记。

3. 实现----怎么做?

通过Jenkins作为一个触发器,捕捉push等操作或者定时或者手动,触发打包动作;GIT plugin 进行项目源码管理;执行Fastlane脚本进行证书管理和打包;通过pgyer(蒲公英) 平台进行分发。

3.1 Jenkins

3.1.1 环境依赖

3.1.2 安装

  • Jenkins官网地址 下载最新的包双击安装
  • // 使用brew安装
    brew install jenkins
    // 安装完成后启动,直接运行jenkins即可启动服务
    jenkins

3.1.3 配置

  • 默认访问http://localhost:8080/, 可进入jenkins配置页面。 不要关闭终端否则jenkins会停。第一次运行的时候会出现以下界面
    根据图片提示的路径去 将密码输入进去 然后点击 Continue,自己设置账户密码。

http://localhost:8080/如果出现
可能是没有启动jenkins,需要:

  • 主页选择 manage Jenkins --> Manage Plugins 去下载插件

在这个选项中下载需要的插件,一些基本插件在安装jenkins的时候都已经下好了,现在只需要下载需要的就可以了

 GitLab    
 Xcode integration    // 这个其实可以不用
 Keychains and Provisioning Profiles Management // 配置文件管理。这个我也没用,因为我是在项目中用fastLane打包和管理证书的。
 
 Upload to pgyer // 上传蒲公英
 Build Name and Description Setter  // 设置打好包的名字
 description setter   // 设置图片
  • 插件安装好了之后,开始创建项目 回到主页点击新建Item,选择: 自由风格的项目

  • General --> Discard old builds 构建的天数的保持最大的个数。

General --> This project is parameterized --> 添加参数 --> Choice Parameter 这个就是 你在构建的时候可以先择 构建环境和分支,(按需添加!)

名称: BUILD_TYPE
选项: debug
      release
描述: 打包环境
----------------------------------------------
名称: GIT_MAINPROBRANCH
选项: develop
      master		
      3.1.0
      3.0.0
描述: 拉取代码的 git分支
  • 源码管理 --> Git
    使用Git管理源码 可以用gitlab的SSH方式管理和账户密码登录管理

git管理就是为了让jenkins知道我们的源码在哪里 在第一次构建的时候他回去拉取代码放到.jenkins的workspace中进行管理

填写完项目地址,我们点击添加按钮。(branch Specifier 不要忘记修改 这里上面设置的选择参数,拉取代码的分支) 点击添加按钮

如使用SSH方式管理,密钥获取: 首先我们要保证 在git中配置了自己的秘钥。配置方法 --> Mac 配置ssh秘钥

如果你的项目很大,需要的clone时间较长的话,需要设置一下超时时间. jenkins默认的超时时间是10分钟,根据自己项目设置时间,

  • 构建 --> 增加构建步骤 --> Execute shell
    在里面填写如文章最后 4.1 的脚本
  • 然后 保存退出就可以了。其他操作配置方式参数自行探索。
  • 回到首页 --> build with paramters --> 选择环境和分支,开始构建

打包成功了之后,我是要点击去蒲公英的链接然后才能下载。 你也可以选择进入到我们当前项目的配置里面去进行配置操作直接把蒲公英的链接放在构建完成之后。

到最后的构建后操作: --> 增加构建后操作步骤-->Upload to payer with apiV2 
获取二维码构建后操作: --> 增加构建后操作步骤 -->Set build description

3.2 FastLane

3.2.1 环境依赖

(1) Ruby(fastlane需要2.3.0以上)

RVM 是一个命令行工具,可以提供一个便捷的多版本 Ruby 环境的管理和切换。所以先安装RVM。

  • 安装命令
    $ curl -L get.rvm.io | bash -s stable

  • 指定源
    $ source ~/.rvm/scripts/rvm
  • 检查一下是否安装正确,正确会显示版本号
    $ rvm -v

用 RVM 安装 Ruby 环境

  • 列出已知的ruby版本
    rvm list known

  • 可以选择现有的rvm版本来进行安装,如:
    $ rvm install 2.3.0
    可能遇到的问题:提示Error running‘xxx’,并提示查看log文件,这里查询文件提示错误是brew出错。解决办法:$ brew update
    更新的时候可能提示Error:/usr/local must be writable!
    这里需要先更改可写权限:$sudo chown -R 当前Mac登录的用户名 /usr/local
    例如:$sudo chown -R Datacvg /usr/local
    再执行:$ brew update
    成功后再执行$ rvm install 2.3.0 (具体用哪个ruby版本还要看你的cocoapods或者flutter是否有依赖需求)

  • 查看ruby版本,要求2.0及以上版本
    ruby -v

  • 更改ruby的镜像文件路径
    gem sources --remove https://rubygems.org/
    gem sources --add https://gems.ruby-china.com
    gem sources -l

(2) xcode命令行工具

xcode-select --install

3.2.2 安装

fastlane是为iOS和Android应用程序自动部署和发布最简单的方法。🚀可以处理各种繁琐任务,例如生成屏幕截图,处理代码签名和发布应用该程序等。Fastlane整合了一系列移动端开发中签名,编译,发布等工具,堪称打包效率神器。

  • fastlane官网安装教程 docs.fastlane.tools/getting-sta…
  • 安装ruby之后,在命令行输入:
    sudo gem install fastlane -NV
    即可安装,安装完成后查询当前安装版本执行:
    fastlane -v
  • 项目中使用了fastlane打包之后上传蒲公英和Jenkins上传蒲公英,通过蒲公英二维码进行下载安装。
    fastlane add_plugin pgyer

3.2.3 配置

  • cd /到自己的项目目录中,执行fastlane初始化命令:
    localhost:xhj_iOS yanxue$ cd /Users/yanxue/Documents/DEMO/PIBCOCR
    localhost:xhj_iOS yanxue$ fastlane init
  • 会出现一下提示:想要做什么?我这里选择4,如果选择3,需要输入Apple ID等相关信息。
  • 成功之后 打开我们的项目目录发现多了fastlane文件夹,重要的是Fastfile这个文件,用来写lane脚本的。上一步选择4和选择3,这个fastlane文件内的内容是不一样的。不影响使用,请自行探索。下面是我的项目的文件内容:

  • 蒲公英插件: 项目中使用了fastlane打包之后上传蒲公英和Jenkins上传蒲公英,通过蒲公英二维码进行下载安装。
    fastlane add_plugin pgyer
  • 编写Fastfile文件:见4.脚本
  • 运行fastlane 打开终端,cd到自己的项目目录下执行lane
    fastlane betaRelease
    betaRelease其实就是Fastfile中的一个lane功能块。成功之后可以看见并自动在fastlane目录生成package文件夹等,如果有上传蒲公英给的操作则会告知蒲公英上传成功,前往蒲公英查看就有新版本了。

4. 脚本

4.1 Jenkins执行脚本

GIT_MAINPROBRANCH为Jenkins配置的gitLab分支参数名。

export LANGUAGE=en_US.UTF-8
export LC_ALL=en_US.UTF-8
export LANG=zh_CN.gbk

# 工程名
scheme_name="scheme_name"
appName="appName"

cd /Users/yanxue/Documents/enjoyChaning/xhj_iOS

if [ -n "$GIT_MAINPROBRANCH" ]; then
 echo "触发的分支:"$GIT_MAINPROBRANCH
    branch_name=$GIT_MAINPROBRANCH
fi
git checkout .
git fetch
git checkout ${branch_name}

#输出代码分支名字
echo "代码分支名字为:"
echo ${branch_name}

#git pull
git rebase
#如果是gitlab触发的构建,需要过滤无效的push
commit_id=`git rev-parse HEAD`

echo "最近一次提交id是: $commit_id"

echo '最近一次提交id是:$commit_id'

#获取修改记录
#log_arr=$(git log --pretty="%s" --no-merges $commit_id...$GIT_PREVIOUS_SUCCESSFUL_COMMIT)
log_strs=""
for var in ${log_arr[@]};
do
    log_strs="${var}*${log_strs}"
done
echo "拼接后的字符串:"$log_strs
pod install
#pod install --repo-update --verbose
#pod repo update --verbose

if [$BUILD_TYPE == 'release']
then
	fastlane betaRelease desc:开发版release
else 
	fastlane betaDebug desc:开发版Debug
fi

4.2 Fastlane的Fastfile脚本

# 导出路径
$ipa_output_directory = "./fastlane/package"
# 上传蒲公英的包 路径
$ipa_output_upload_directory = "/Users/yanxue/.jenkins/workspace/package"
# jenkins匹配的包名
$jenkins_name = "scheme_name"

default_platform(:ios)
    # 应用名词
    scheme_name = "scheme_name"
    # 获取version
    version = get_info_plist_value(path: "./#{scheme_name}/Info.plist", key: "CFBundleShortVersionString")
    # 获取build版本号
    build = get_info_plist_value(path: "./#{scheme_name}/Info.plist", key: "CFBundleVersion")
    # 默认内测打包方式,目前支持app-store, package, ad-hoc, enterprise, development
    ipa_exportMethod = "ad-hoc"
    ipa_exportMethod_appStore = "app-store"
    
# 计算buildNumber
def updateProjectBuildNumber
    currentTime = Time.new.strftime("%Y%m%d")
    scheme_name = "enjoyChanging"
    build = get_info_plist_value(path: "./#{scheme_name}/Info.plist", key: "CFBundleVersion").to_s
    if build.include?"#{currentTime}."
    # 为当天版本 计算迭代版本号
        lastStr = build[build.length-2..build.length-1]
        lastNum = lastStr.to_i
        lastNum = lastNum + 1
        lastStr = lastNum.to_s
        if lastNum < 10
            lastStr = lastStr.insert(0,"0")
        end
        build = "#{currentTime}.#{lastStr}"
    else
        # 非当天版本 build 号重置
        build = "#{currentTime}.01"
    end
    puts("*************| 更新build #{build} |*************")
        # 更改项目 build 号
        increment_build_number(
            build_number: "#{build}"
        )
end

# 打包成功 jenkins 开始提示上传log
# 使用fastlane上传蒲公英不能直接拿不到二维码展示, 这里使用jenkins上传蒲公英。下面lane里打开注释,也可直接fastlane上传蒲公英,通过蒲公英网站去获取二维码。
def archiveSuccessLog(outputName,buildConfig)
        # 打好的包 移动到 jenkins的 workspace的目录下,用于jenkins上传蒲公英匹配包内容。永远只保留一份
    #     FileUtils.cp_r("#{$ipa_output_directory}" + "/#{outputName}", "#{$ipa_output_upload_directory}" +  "/#{$jenkins_name}.ipa")
        puts" --------------------------------------------------------------------------"
        puts"|                                                                          |"
        puts"|                                                                          |"
        puts"|   🎉🎉🎉 --------->>>>>      #{buildConfig}版本_打包成功     <<<<<-----------🎉🎉🎉  |"
        puts"|                                                                          |"
        puts"|   🎉🎉🎉 --------->>>>>  Jenkins 可以开始上传蒲公英了  <<<<<-----------🎉🎉🎉  |"
        puts"|                                                                          |"
        puts"|                                                                          |"
        puts" --------------------------------------------------------------------------"
end

#
# 所有任务脚本,打包lane
#
platform :ios do

 #
 # *************| 上传到release版本到蒲公英_测试包 |*************
 #
  desc "Push a new betaRelease build to pugongying"
  lane :betaRelease do|option|
    puts "*************| 开始betaRelease打包.ipa文件... |*************"
    # 自动增加build
    # updateProjectBuildNumber
    # 自动生成证书
    # cert
    # 导出名称
    output_name = "#{scheme_name}_#{version}_#{build}_#{option[:desc]}_#{Time.now.strftime('%Y%m%d%H%M%S')}.ipa"
    gym(
          # 指定输出的ipa名称
          output_name: "#{output_name}",
          # 指定项目的scheme
          scheme: "enjoyChanging",
          # 是否清空以前的编译信息 true:是
          clean: true,
          export_method: "#{ipa_exportMethod}",
          export_options: {iCloudContainerEnvironment: 'Production'},
          # 指定打包方式,Release 或者 Debug
          configuration:"Release",
          # 包导出的位置
          output_directory: "#{$ipa_output_directory}",
         )
         
      # 使用fastlane上传蒲公英拿不到二维码展示, 所以使用jenkins上传蒲公英
      # archiveSuccessLog(output_name," Release ")

      # 这里是 fastlane 直接上传蒲公英
      # 如果需要请打开一下注释,并注释掉archiveSuccessLog(output_name," Release ")
      # puts "*************| 开始上传__测试版__本到蒲公英... |*************"
      # 配置蒲公英 api_key 和 user_key
     pgyer(api_key: "pgyer_api_key", user_key: "pgyer_user_key", password: "111111", install_type: "2",update_description: "#{option[:desc]}")
      # puts "*************| 上传__测试版__到蒲公英🎉🎉🎉成功🎉🎉🎉 |*************"
  end


  desc "push a new betaDebug build to pugongying"
  lane :betaDebug do|option|
	 puts "*************| 开始betaDebug打包.ipa文件... |*************"
     # 自动增加build
     updateProjectBuildNumber
     # 自动生成证书
     cert
     # 导出名称
     output_name = "#{scheme_name}_#{version}_#{build}_#{option[:desc]}_#{Time.now.strftime('%Y%m%d%H%M%S')}.ipa"
     gym(
           # 指定输出的ipa名称
           output_name: "#{output_name}",
           # 指定项目的scheme
           scheme: "enjoyChanging",
           # 是否清空以前的编译信息 true:是
           clean: true,
           export_method: "#{ipa_exportMethod}",
           export_options: {iCloudContainerEnvironment: 'Development'},
           # 指定打包方式,Release 或者 Debug
           configuration:"Debug",
           # 包导出的位置
           output_directory: "#{$ipa_output_directory}",
          )
          
       # 使用fastlane上传蒲公英拿不到二维码展示, 所以使用jenkins上传蒲公英
       #archiveSuccessLog(output_name," Debug ")

       # 这里是 fastlane 直接上传蒲公英
       # 如果需要请打开一下注释,并注释掉archiveSuccessLog(output_name," Debug ")
       # puts "*************| 开始上传__测试版__本到蒲公英... |*************"
       # 配置蒲公英 api_key 和 user_key
        pgyer(api_key: "pgyer_api_key", user_key: "pgyer_user_key", password: "111111", install_type: "2",update_description: "#{option[:desc]}")
       # puts "*************| 上传__测试版__到蒲公英🎉🎉🎉成功🎉🎉🎉 |*************"
  end


#
# *************| 上传App Store(没有测试用过,一般都是去手动配置) |*************
#
  desc "push a new releaseApp build to appstore"
  lane :toAppstore do|option|
    project_identifier = "com.aihuishou.enjoyChanging"
        puts "*************| 开始上传AppStore... |*************"
        # 自动增加build
        updateProjectBuildNumber
        # 自动生成证书
        # cert
        # 导出名称
        codesigning_identity = "iPhone Distribution: Jin Song (Shanghai) Network Information Technology Co., Ltd. (Q824G42ZH8)"
        output_name = "#{scheme_name}_#{version}_#{build}_#{ipa_exportMethod_appStore}_#{Time.now.strftime('%Y%m%d%H%M%S')}.ipa"
        gym(
            # 指定输出的ipa名称
            output_name:output_name,
            # 指定项目的scheme
            scheme:"xxx",
            # 是否清空以前的编译信息 true:是
            clean:true,
            # 指定打包方式,Release 或者 Debug
            configuration:"Release",
            # 指定打包方式,目前支持app-store, package, ad-hoc, enterprise, development
            export_method:"app-store",
            # 上传的环境
            export_options: {iCloudContainerEnvironment: 'Production'},
            # 指定输出文件夹
            output_directory:"#{$ipa_output_directory}",
         )

         deliver(
                 # 选择跳过图片和元数据上传,自己去配置
                 skip_screenshots:true,
                 skip_metadata:true,
                 # 上传完成后提交新版本进行审查
                 submit_for_review: false,
                 force:true,
                )
        puts "*************| 上传AppStore成功🎉 |*************"


        puts "*************| 开始上传__测试版__本到蒲公英... |*************"
        # 配置蒲公英 api_key 和 user_key
        pgyer(api_key: "pgyer_api_key", user_key: "pgyer_user_key", update_description: "#{option[:desc]}")
        puts "*************| 上传__测试版__到蒲公英🎉🎉🎉成功🎉🎉🎉 |*************"
        # 发布testflight测试
        # pilot
  end

end