Jenkins 实践笔记

2020/06/17

安装 Jenkins

Java JDK 下载

Jenkins 依赖了 Java 环境,所以在安装 Jenkins 前,要确保系统安装了 Java

查看 Java 版本

java --version

使用 brew 安装

brew install jenkins

开启 Jenkins 服务

brew services start jenkins

第一次服务开启成功后,在浏览器中输入http://localhost:8080,会出现一个让你输入Jenkins初始密码的页面

获取 Jenkins 初始密码

defaults write com.apple.finder AppleShowAllFiles YES
vi /Users/jackey/.jenkins/secrets/initialAdminPassword

将获取到的初始密码输入后,重启 Jenkins

brew services restart jenkins

重新输入http://localhost:8080,会进入配置界面。点击安装建议的插件,耐心等待插件安装过程(时间较长)

若过程中有插件安装失败,可查看失败日志。问题可能是 Jenkins 版本过低,所以有些插件不能安装,更新 brew 后安装最新的 Jenkins 版本

通过 url 来关闭/重启 Jenkins

http://localhost:8080/restart
http://localhost:8080/exit

部分汉化问题

网上教程:

  1. 将语言设定为 zh_US,Jenkins 切换为英文。
  2. 调用 restart 重启 Jenkins:http://域名/restart。
  3. 再次语言设定为 zh_CN,刷新即可

局域网无法根据 ip 访问 jenkins

修改 homebrew.mxcl.jenkins.plist 的 httpListenAddress 为 0.0.0.0

路径:

~/Library/LaunchAgents/homebrew.mxcl.jenkins.plist
/usr/local/Cellar/jenkins/版本号/homebrew.mxcl.jenkins.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>homebrew.mxcl.jenkins</string>
    <key>ProgramArguments</key>
    <array>
      <string>/usr/libexec/java_home</string>
      <string>-v</string>
      <string>1.8</string>
      <string>--exec</string>
      <string>java</string>
      <string>-Dmail.smtp.starttls.enable=true</string>
      <string>-jar</string>
      <string>/usr/local/opt/jenkins/libexec/jenkins.war</string>
      <string>--httpListenAddress=0.0.0.0</string>
      <string>--httpPort=8080</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
  </dict>
</plist>

重启 jenkins

Jenkins 插件

Environment Injector Plugin

自定义环境变量注入

  • Job->配置->增加构建步骤->Execute Shell
value="xxxxx"
echo keyname=$value > env.txt
  • 增加构建步骤->Inject environment variables

  • 其它构建步骤中使用变量

# shell 中输出
echo $keyname

description setter plugin

构建描述设置

  • Job->配置->增加构建步骤->Set build description
<a href="${appBuildURL}"><img src="${appQRCodeURL}" width="118" height="118"/></a>

iOS 打包脚本

#!/bin/sh

# ==================== 脚本样式 =====================
# 蓝色样式
__TITLE_LEFT_COLOR="\033[36;1m==== "
__TITLE_RIGHT_COLOR=" ====\033[0m"
# 黄色样式
__OPTION_LEFT_COLOR="\033[33;1m"
__OPTION_RIGHT_COLOR="\033[0m"
# 绿色样式
__LINE_BREAK_LEFT="\033[32;1m"
__LINE_BREAK_RIGHT="\033[0m"
# 红底白字
__ERROR_MESSAGE_LEFT="\033[41m ! ! ! "
__ERROR_MESSAGE_RIGHT=" ! ! ! \033[0m"

# 打印信息
function logMessage() {
  log=$1
  echo "${__TITLE_LEFT_COLOR}${log}${__TITLE_RIGHT_COLOR}"
}
function logWarning() {
  log=$1
  echo "${__OPTION_LEFT_COLOR}${log}${__OPTION_RIGHT_COLOR}"
}
function logSuccess() {
  log=$1
  echo "${__LINE_BREAK_LEFT}${log}${__LINE_BREAK_RIGHT}"
}
function logError() {
  log=$1
  echo "${__ERROR_MESSAGE_LEFT}${log}${__ERROR_MESSAGE_RIGHT}"
}

# 项目名
TARGET_NAME=BubbleTally
# 当前目录
SORCEPATH=${WORKSPACE}

cd ${TARGET_NAME}

# 项目根路径
# root_path=$(cd "$(dirname "$0")";pwd)
root_path=${SORCEPATH}/${TARGET_NAME}

# workspace路径
workspace_path=$(set -- "$root_path/"*.xcworkspace; echo "$1")

# 项目路径
project_path=$(set -- "$root_path/"*.xcodeproj; echo "$1")
# 项目名称
project_name="${project_path##*/}"

# 项目scheme(无后缀的项目名称)
project_scheme="${project_name%.*}"

# info.plist
info_plist="$root_path/$project_scheme/info.plist"

# 配置打包样式:Release/ad-hoc/Debug
configuration="ad-hoc"

# ipa包路径
ipa_path="$root_path/Build/"

# ipa包名称:项目名+版本号+打包类型
versionNum=$(/usr/libexec/PlistBuddy -c "print CFBundleShortVersionString" $info_plist)
cur_date="$(date +'%y%m%d_%H%M')"
ipa_name="ppjz${versionNum}.${cur_date}.${configuration}.ipa"

# 打包配置plist文件路径 (初始化)
plist_path="$root_path/ExportOptions.plist"

# 编译build路径
archive_path="$ipa_path/$project_scheme.xcarchive"

# 项目中不存在 workspace, 则使用project_path

echo "\033[36;1m>>>正在清理工程 \033[0m"

if [[ "$workspace_path" == *"*"* ]]; then
    xcodebuild clean -project $project_path \
                    -scheme $project_scheme \
                    -configuration $configuration || exit
else
    xcodebuild clean -workspace $workspace_path \
                    -scheme $project_scheme \
                    -configuration $configuration || exit
fi


echo "\033[36;1m>>>--正在编译工程:$configuration \033[0m"
if [[ "$workspace_path" == *"*"* ]]; then
    echo "\033[33;1m通过 .xcodeproj 方式打包 \033[0m"
    xcodebuild archive -project $project_path \
                        -scheme $project_scheme \
                        -configuration $configuration \
                        -archivePath $archive_path \
                        -quiet || exit
else
    echo "\033[33;1m通过 .xcworkspace 方式打包 \033[0m"
    xcodebuild archive -workspace $workspace_path \
                        -scheme $project_scheme \
                        -configuration $configuration \
                        -archivePath $archive_path \
                        -quiet || exit
fi

# 检查是否构建成功(build)
if [ -d "$archive_path" ] ; then
    echo "\033[32;1m项目构建成功 \033[0m"
else
    echo "\033[31;1m项目构建失败 \033[0m"
    exit 1
fi

method=$(/usr/libexec/PlistBuddy -c "print method" $plist_path)

echo "\033[36;1m>>>--开始ipa打包:$method \033[0m"
xcodebuild -exportArchive -archivePath $archive_path \
                            -configuration $configuration \
                            -exportPath $ipa_path \
                            -exportOptionsPlist $plist_path \
                            -quiet || exit

if [ -e $ipa_path/$project_scheme.ipa ]; then
    echo "\033[32;1mipa包导出成功 \033[0m"
    rm -rf $archive_path # 删除打包文件
#    open $ipa_path
else
    echo "\033[31;1mipa包导出失败 \033[0m"
fi

# IPA更名
mv "$ipa_path/$project_scheme.ipa" "$ipa_path/$ipa_name"
echo -e "============IPA Name: ${ipa_name}============"

# 输出总用时
echo "\033[36;1m🍺 使用fastBuild打包总用时: ${SECONDS}s \033[0m"

#exit

echo '发布ipa包到 =============蒲公英平台============='

curl -F "file=@${ipa_path}/${ipa_name}" -F "uKey=44568d1ef420e3cfe1dbab18eebe40e1" -F "_api_key=9aff09b124720b2bdb9bb798ef9406eb" -F "updateDescription=${ipa_name}" https://qiniu-storage.pgyer.com/apiv1/app/upload
if [ $? = 0 ];then
echo "=============提交蒲公英成功 ============="
else
echo "=============提交蒲公英失败 ============="
fi

# 输出总用时
echo "\033[36;1m🍺 使用fastBuild打包总用时: ${SECONDS}s \033[0m"

exit

Android 使用 Gradle 打包

Gradle 命令行选项:

assemble:打包(之前已经编译了源文件)
compilemakebuildrebuild都是编译过程:将源代码转换为可执行代码的过程,Java的编译会将java编译为class文件,将非java的文件(一般成为资源文件、比如图片、xmltxtpoperties等文件)原封不动的复制到编译输出目录,并保持源文件夹的目录层次关系。
compile:只编译选定的目标,不管之前是否已经编译过
make:编译选定的目标,但是只编译上次编译变化过的文件,减少重复劳动,节省时间。

Build:是对整个工程进行彻底的重新编译,只针对更改过的文件进行编译。
rebuild:对整个项目重新编译(clean + build),不管之前是否修改过。
Buildrebuild过程往往会生成发布包。

打包命令
gradle assembleRelease

众所周知,现在 App 的竞争已经到了用户体验为王,质量为上的白热化阶段。用户们都是很挑剔的。如果一个公司的推广团队好不容易砸了重金推广了一个 APP,好不容易有了一些用户,由于一次线上的 bug 导致一批的用户在使用中纷纷出现闪退 bug,轻则,很可能前期推广砸的钱都白费了,重则,口碑不好,未来也提升不起用户量来了。静下心来分析一下问题的原因,无外乎就是质量没有过关就上线了。除去主观的一些因素,很大部分的客观因素我觉得可以被我们防范的。根据大神们提出的一套开发规范建议,CI + FDD,就可以帮助我们极大程度的解决客观因素。本文接下来主要讨论 Continuous Integration 持续集成(简称 CI)

为什么我们需要持续集成

谈到为什么需要的问题,我们就需要从什么是来说起。那什么是持续集成呢。

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

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

那么持续集成能给我们带来些什么好处呢?这里推荐一篇文章,文章中把 Continuous integration (CI) and test-driven development (TDD)分成了 12 个步骤。然而带来的好处成倍增加,有 24 点好处。

我来说说用了 CI 以后带来的一些深有体会的优点。

  1. 缩减开发周期,快速迭代版本
    每个版本开始都会估算好开发周期,但是总会因为各种事情而延期。这其中包括了一些客观因素。由于产品线增多,迭代速度越来越快,给测试带来的压力也越来越大。如果测试都在开发完全开发完成之后再来测试,那就会影响很长一段时间。这时候由于集成晚就会严重拖慢项目节奏。如果能尽早的持续集成,尽快进入上图的 12 步骤的迭代环中,就可以尽早的暴露出问题,提早解决,尽量在规定时间内完成任务。

  2. 自动化流水线操作带来的高效
    其实打包对于开发人员来说是一件很耗时,而且没有很大技术含量的工作。如果开发人员一多,相互改的代码冲突的几率就越大,加上没有产线管理机制,代码仓库的代码质量很难保证。团队里面会花一些时间来解决冲突,解决完了冲突还需要自己手动打包。这个时候如果证书又不对,又要耽误好长时间。这些时间其实可以用持续集成来节约起来的。一天两天看着不多,但是按照年的单位来计算,可以节约很多时间!

  3. 随时可部署
    有了持续集成以后,我们可以以天为单位来打包,这种高频率的集成带来的最大的优点就是可以随时部署上线。这样就不会导致快要上线,到处是漏洞,到处是 bug,手忙脚乱弄完以后还不能部署,严重影响上线时间。

  4. 极大程度避免低级错误
    我们可以犯错误,但是犯低级错误就很不应该。这里指的低级错误包括以下几点:编译错误,安装问题,接口问题,性能问题。
    以天为单位的持续集成,可以很快发现编译问题,自动打包直接无法通过。打完包以后,测试扫码无法安装,这种问题也会立即被暴露出来。接口问题和性能问题就有自动化测试脚本来发现。这些低级问题由持续集成来暴露展现出来,提醒我们避免低级错误。

持续化集成工具——Jenkins

Jenkins 是一个开源项目,提供了一种易于使用的持续集成系统,使开发者从繁杂的集成中解脱出来,专注于更为重要的业务逻辑实现上。同时 Jenkins 能实施监控集成中存在的错误,提供详细的日志文件和提醒功能,还能用图表的形式形象地展示项目构建的趋势和稳定性。

根据官方定义,Jenkins 有以下的用途:

  • 构建项目
  • 跑测试用例检测 bug
  • 静态代码检测
  • 部署

关于这 4 点,实际使用中还是比较方便的:

  1. 构建项目自动化打包可以省去开发人员好多时间,重要的是,Jenkins 为我们维护了一套高质量可用的代码,而且保证了一个纯净的环境。我们经常会出现由于本地配置出错而导致打包失败的情况。现在 Jenkins 就是一个公平的评判者,它无法正确的编译出 ipa,那就是有编译错误或者配置问题。开发人员没必要去争论本地是可以运行的,拉取了谁谁谁的代码以后就不能运行了。共同维护 Jenkins 的正常编译,因为 Jenkins 的编译环境比我们本地简单的多,它是最纯净无污染的编译环境。开发者就只用专注于编码。这是给开发者带来的便利。

  2. 这个可以用来自动化测试。在本地生成大批的测试用例。每天利用服务器不断的跑这些用例。每天每个接口都跑一遍。看上去没必要,但是实际上今天运行正常的系统,很可能由于今天的代码改动,明天就出现问题了。有了 Jenkins 可以以天为单位的进行回归测试,代码只要有改动,Jenkins 就把所有的回归测试的用例全部都跑一遍。在项目工期紧张的情况下,很多情况测试都不是很重视回归测试,毕竟很可能测一遍之后是徒劳的“无用功”。然而由于回归测试不及时,就导致到最后发版的时候系统不可用了,这时候回头查找原因是比较耗时的,查看提交记录,看到上百条提交记录,排查起来也是头疼的事情。以天为单位的回归测试能立即发现问题。测试人员每天可以专注按单元测试,一周手动一次回归测试。这是给测试者带来的便利。

  3. 这个是静态代码分析,可以检测出很多代码的问题,比如潜在的内存泄露的问题。由于 Jenkins 所在环境的纯净,还是可以发现一些我们本地复杂环境无法发现的问题,进一步的提高代码质量。这是给质检带来的便利。

  4. 随时部署。Jenkins 在打包完成之后可以设定之后的操作,这个时候往往就是提交 app 到跑测试用例的系统,或者部署到内测平台生成二维码。部署中不能安装等一些低级问题随之立即暴露。测试人员也只需要扫一下二维码即可安装,很方便。这也算是给测试带来的便利。

Search

    Table of Contents