写给Android工程师的 Github CI 快速指北

news2024/11/19 22:43:59

背景

关于 CI/CD ,在2023年的今天,基本所有技术团队或多或少都会使用,其很大程度上减轻了我们的冗余重复工作,从而简化我们的工作流程。

不过对于大多数客户端工程师而言,其实 CI 这个词还是比较陌生。当然并不是说,CI/CD 有什么高大上或者门槛很高,因为毕竟不是所有人会去维护开源库或者搞基建,或者说少有场景去接触到。但对于一个 工程师 而言,这严格意义上其实属于 基本技能点 ,或者说在现在这个时代,这种小技能应该没有太多边界之分。

相应的,如果使用 Github Action,这个难度就更低了,其相比传统的 Jenkins ,容易上手了更多,简化了 环境配置 等等。并且,在这个过程中,我们也将逐步接触到一些 cmdpython 等其他工具使用方式或者语法,从而探索出更多可玩性。

写在开始

注意: 本篇不会讲 Github Action 基础语法,这些官网有更详细的文档,没有意义去做二次搬运🙅🏻‍♂️。

本篇更多是围绕要解决的实际问题进行分析,并在中间对关键语法进行解释,从而便于更快的使用 Github CI

个人建议对于这种工具类的技能,不需要学的很详细,只需要了解基本原则:自己要解决什么问题,即可。

故此,学完本篇,你将学会 Github CI 的基础使用,以及一些常见的实用操作,如:

  • 自动化打包以及上传;

  • 自动化版本号与code;

  • 自动化发布release;

  • 逐步解开传统思维陷阱,体会 CI 在日常开发中的妙用;

好了,让我们开始吧 ! 🏃🏻

什么是 CI/CD?

CI/CD 是指持续集成(Continuous Integration)和持续部署/交付(Continuous Deployment/Delivery)的缩写。

  • 持续集成(CI)是一种软件开发实践,指的是将代码集成到主干分支中并进行构建和测试的过程,以便尽早发现和解决问题。CI 工具可以自动执行这个过程,例如 1、Travis CI、CircleCI 等。每次提交代码时,CI 工具会自动构建和运行测试,并给出构建和测试结果的反馈。
  • 持续部署/交付(CD)是指自动化地将代码部署到生产环境或发布到应用商店的过程。持续部署/交付可以让开发团队更加快速和可靠地将新功能交付给用户。CD 工具可以自动化执行部署和发布过程,例如 AnsibleKubernetesDocker 等。在持续部署/交付的过程中,需要进行自动化测试、版本控制、持续监控等操作,以确保代码质量和应用稳定性。

CI/CD 的优点包括加速软件开发、提高代码质量、降低风险、提高工作效率 等。从而可以让开发团队更加专注于代码编写,而不必花费大量时间进行 手动构建测试部署 等重复性工作。

什么是 Github CI?

因为本篇,主要是讲 Github CI 的使用,故还是要简单说一下 Github CI 的简介及功能。

GitHub CI(GitHub Actions)是 GitHub 提供的一项自动化工具,用于 构建测试部署 GitHub 上托管的代码仓库。GitHub CI 提供了一种定义自动化工作流程的方式,可以根据代码仓库的变化自动触发工作流程。一组工作流程可以包括多个步骤,例如编译代码、运行测试、构建镜像、部署应用等。其优点包括与 GitHub 平台紧密集成、易于配置、支持多种语言和环境、提供丰富的集成能力等。它可以帮助开发团队自动化构建和测试过程,提高代码质量和开发效率。

具体运行示例中如下图所示:

image-20230515221921780

CI 可以做什么?

几乎可以简化任何我们能在本地做的所有 人工 操作,甚至自动编码。

为了更好的便于理解,我们切换到 Android工程师 视角,使用一个示例来说明。

比如我们现在有个 下厨房 Android工程,如果在没有 CI 时,我们最基础的流程通常如下:

  • 开发: 本地开发、调试、push;
  • 测试:本地打包、发给测试同学;
  • 打包: 改版本号、打tag、本地打包、发给运营同学;

上面的流程看上去似乎没有什么问题,对于本地开发而言,浓缩下来也就上面三步,这也是小团队常见流程。

但仔细观察的话,其中很多步骤都是冗余,比如每次 本地打包改版本号打tag发给指定人 ,这些步骤都显得很机械(或者比较呆),特别是如果是在 bug fix 阶段,更是繁琐。

那如果借助 CI,我们应该如何优化上述步骤呢?

  • 当我们每次提一个 PR 或者 push 时,就自动去打测试包,并执行一些我们自定义的一些 check,如 代码检查包大小检查自动化测试 等等,并将最后打出的 apk 上传到 fir 或者其他地方。并借助 webhook ,从而实现 飞书钉钉 等方式通知相关同学;
  • 发布新的 release版本 前,改版本号时,也可以支持自动化版本号。比如可以利用 git tag 作为版本号,commit 记录作为code,并与 CI 联动,实现动态指定;
  • 而当我们每次发布 release 版本时,通常情况下,我们都会打一个 tag ,然后 push 。所以我们也可以利用 CI ,发现有新的 tag 时,则触发工作流执行,从而去自动发布一个 release 版本,并且执行一遍打包,将相关产物上传到我们指定的位置;并根据项目的规则总结出相应的 release 更改信息,并更新描述,最后再将版本信息通知到相关运营同学;
  • 在应用包上传的过程中,人工必不可少会出现传错包的情况,此时也可以借助 CI 实现打包完成后自动上传应用商店,比如 华为、小米、Gogole 目前就支持 api 上传

如果上述步骤,你们团队都已经实现了,那就证明对于 Android工程 而言,你们的基础 CI/CD 设施已经做的很不错了,不妨为自己点点赞。

换个角度而言,CI 几乎可以完成大多数重复项工作,从而为我们节约时间。而使用 Github Action 实现上述步骤,如虎添翼,更为方便。

快速入门教程

对于 GitHub Action 而言,官方规定了工作流文件必须存储在代码仓库的 .github/workflows 目录中,文件名必须以 .yml.yaml 结尾,从而便于 Github 识别这是一个工作流程。

创建新的工作流

要创建一个工作流,有两种方式:

  • 在线创建:Github-Reposity-actions 里去创建,创建过程中可以随时添加别的工作流;
  • 本地创建: 在项目目录里创建 .github/workflows 文件夹,并在其中创建你的工作流文件,Github 会自动按照规则识别;

1. 在线创建

我们直接去相应的 Github 仓库底下,点击 Actions ,此时有两种选择:

  • 在现有的工作流模版上进行创建;
  • 新创建自己的工作流文件;

比如下面的示例中,我们搜索 Android ,并选择 Android CI 模版进行创建,如下所示:

New ActionsCreate New Action

在上面的图2里,这是官方给我们的 基础Android CI 模版,具体的逻辑我们下面再解释。图中箭头所指的是一些比较热门的 Action ,可以选择其中一个,快速复制(引用)到我们自己的工作流中,即相当于添加一个新的子步骤。

2. 本地创建

我们以刚才上面截图中的 Android CI 为示例,直接复制到本地新建的 android.yml 中,如下所示:

创建工作流工作流运行效果
local-create-actionaction-run-log

将相应的工作流 push 之后,如图所示,我们会发现,我们新 push 的工作流已经被触发了多次(原因下面解释),而列表最顶部的,也是最新的,即正在运行的工作流。

点击进去看一下,如下图所示:

工作流运行结果工作流具体执行步骤
action-run-infoaction-run-steps

左边的图表示这是本次的运行结果,以及一些工件的上传或者日志输出位置;而右图则代表这个工作流具体执行子步骤列表,我们也可以点击去查看每一个步骤做的结果。

示例工作流分析

如下所示,这是我们上面步骤创建的 Android CI 工作流,其目的是用于每次 push 代码后,执行一次 build ,具体代码如下:

name: Android CI

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - name: set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'
          cache: gradle

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew
      - name: Build with Gradle
        run: ./gradlew build

相应的,其内部的含义如下所示:

  • name:

    表示当前工作流的名字是什么。

  • on:

    表示当前工作流在什么情况下被触发。

    比如这个示例中,我们触发的时机有两个,即 push || pr 时,并且限制了必须是在 main分支 。再解释的通俗点就是:

    当我们在main分支push了代码,或者提了一个新的PR,要合并到main分支时,此时就会触发这个工作流。

    ps: 这也是为什么我们上面的截图里,创建一个新的工作流后,为什么会出现运行多次(首次创建时main分支触发+push行为触发)。

  • jobs:

    指的是当前任务列表。比如上述示例中我们只有一个任务,名为 build,当然也可以使用 name: 进行重命名。

  • runs-on:

    指定当前任务运行的环境。比如上述示例中任务是在 ubuntu-latest 系统下运行。

  • steps:

    指定当前任务的步骤列表。比如当前示例中,我们有多个任务,分别如下:

    • -拉代码;
    • -设置 jdkGradle 环境;
    • -为 Gradle 设置运行权限;
    • -执行 Gradle 命令: build;

    uses:

    用于导入开源的 Action 或者自定义的 Action ,从而在自己的工作流中进行使用;

    run:

    shell 环境中执行一段命令,常用于执行 cmd 命令;

需要注意: yml 文件,严格控制段落间的缩进,所以如果 IDE 提示异常,或者排列不齐时,经常会出现工作流运行时报错。

常用的环境变量

在使用 Github Action 时,我们常常会遇到需要使用一些环境变量的情况,比如最常用的 Github.token 等等,对于每一个工作流,默认提供了以下环境变量用于使用:

  • GITHUB_ACTION

    当前运行的操作的名称,或id步骤的名称;

  • GITHUB_RUN_ID

    当前运行的工作流ID,这个 id 是固定的;

  • 更多环境变量见文档

当然,我们也可以自定义一些环境变量,从而将其保存到 Github Action 里的 secrets 中,从而实现安全的存储与使用,而非硬编码的方式。

如下所示,我们将fir.im 的api token保存到secrets里,并取名为FIR_TOKEN:

image-20230516195702703

在具体使用时,如下所示:

- name: echo token
  run: |
    echo "-token: ${{ secrets.FIR_TOKEN }}"

这里我们尝试去打印一下 token ,但是结果肯定是 [**] 。因为 Github Action 默认会对其进行隐藏,从而防止其被意外泄漏。

小练习

自动化打包

在开发中,我们日常接触最多的无非就是 [fix bug] & [create new bug] 😂,而如果每次如果都要手动打包,再转发给测试同学,无疑是一件及其浪费时间和无聊的事情。

本小节示例代码见:Android CI

这里我们以打包并上传fir 为例,如下所示:

image-20230517224017277

上述流程如下:

  • 拉代码;
  • 安装 && 配置gradle环境;
  • 打debug包;
  • 安装 fir-cli & 上传apk;

上面内部使用的 FIR_TOKEN 正是我们上面在介绍环境变量部分时,自己定义的。

当然做的更详细点,这里还可以加上打包成功后webhook到飞书或者钉钉等,以飞书为例,可以使用我司另一个小伙伴写的这个 xiachufang/action-feishu,碍于篇幅,这里就不做解释了。

自动化版本号

本小节示例代码见:settings.gradle、release.yml

在日常发版本的过程中,我们都有打 tag 的经历,比如每周在发布新的版本之前,打 新版本tag ,同时打新的线上包。这个时候,如果每次都要去再改一次硬编码里的 versionName 以及 versionCode ,无疑有点烦人,而且人工操作,依然存在改错的问题😶。

此时,常见的方式是利用 tag 作为 versionNamegit commit 数作为 versionCode 。这种方式固然好用,但是还是不够严谨,对于常见的团队而言,一般有更统一的名称,如 版本名@版本号,示例:2.1.1@807,当然这都是后话了。

要实现上面的基础需求,需要我们对 GradleGit 有一丢丢使用经验。比如,怎么获取 最新tag 呢?怎么获取 commit 数呢?

解决方式如下所示:

image-20230517224617923

如上所示,我们直接在 settings.gradle 中新增了以下代码。目的是当 Gradle 加载完当前项目信息之后,此时就利用 cmd 去获取一下当前的 最新tagcommit数,并将其设置给 ext,从而便于我们在其他地方引用。

外部使用方式:rootProject.versionCode

此时 build 我们的项目, build 日志部分就会打印下面语句:

versionName:xxx,versionCode:x

ps: 如果你的 versionName 是空,请注意是否打过 tag 🙃。 [git tag xxx]


上面的方法看着似乎没什么问题,但是如果你实际用几次就会发现,如果你 两个tag之间并没有任何变化的话,此时 gitVersionTag() 取出的 tag永远不是最新的那一条(不知道该怎么解决)😅。

这个时候,我们就可以利用 Github Action,获取最新 release.tag,然后将其以 gradle传参 的方式传递到我们本次编译中,从而实现自动化版本号。

如下所示:

image-20230516225353898

我们重新调整下上述的写法,每次优先获取外部传入的参数 versionName 以及 versionCode,同时对其check。如果没传递或者为null,则本地重新利用Git去获取,否则就使用指定的参数。

release.yml

image-20230516225440888

在具体的工作流脚本这里,我们的触发时机选择为每次发布新的 release 时,此时就去获取本次 release 对应的 tag_name,并在打包时,通过 Gradle 命令行传参的方式,将其传递给我们本次的打包流程。

上面的 env:用于设置一个或多个环境变量

比如在这个示例里,我们定义了一个名为 VERSION_NAME 的变量,其的值取自 本次release所对应的tag_name ,而 {{ xx }} 这种取值方式,则是 Github Action 中的一个规范。而在shell里,我们可以不加 {{ }} ,直接 $xx

自动化发布release

本小节示例代码见:create_release.yml

每个版本发布 release 时,我们一般都要去写一遍描述,但如果每个版本都去写一遍,无疑非常呆。

所以,那能不能把两个版本之间的 PR 自动收集下来,然后写到 release 的描述里呢?🤔

回答肯定是可以的,而且 Github 也提供了默认的方式,如下所示:

创建新的release最终结果页
image-20230517230707439image-20230517230911675

看着效果还不错,省事不少。👏


那能不能我每次 push tag 时,就自动触发 release创建 呢?

🤖: 你最好别懒死。😂

当然是可以的,Github Action 工作流提供了很多触发时机,所以我们只需要设置触发时机为 push tag 时,然后再去新建 release 即可。

示例代码如下:

image-20230517234135411

这里使用开源的 action,ncipollo/release-action,从而更简单的实现上述需求,当然也可以选择使用 Github Api

效果如下所示:

Github ActionGithub release
image-20230517234330380image-20230517234357265

一些经验分享

关于 Github Action,因为其本身上手难度很低,所以当我们想解决某个问题时,只需要考虑清下面的几个问题:

  • 当前问题 到底 是什么?
  • 有没有开源的 Action ?
  • 如果问题比较复杂,那能不能拆解为多个步骤呢?
  • Github API 能不能解决,能不能搭配其他方式呢?如 shellpythonjargradle;

当然如果你想再探索一点,此时可以考虑以下:

  • 工作流复用,工作流依赖执行,工作流结果传递,工作流并发等等。

常用的一些资料:

  • Github Action开源库搜索;
  • Github Action文档;

总结

本篇,我们从 CI/CD 是什么开始,叨叨絮絮,一直到解决常见开发中的一些问题。纵观这些问题或者场景,虽然并不是特别繁琐,但也构成了 CI 的基本使用单元。希望通过这些场景,能让大家对于 Github CI 有快速的了解及上手体验。

当然我本人也不是一个熟练的 CI工程师 ,更多是个半吊子,所以文章里肯定也有模糊不清的地方,此时就建议大家多搜多试验,或者评论区问我。但对于这些工具方面,我个人的原则一直是,会用即可。当然更好的是,当问题不能直线解决时,我们能不能拆分步骤去逐个解决。

我们生在一个幸运的时代,很多事情,都能很简单的去解决,比如有问题问 GPT,不懂就翻翻源码,而对于一些繁琐的重复项工作,此时不妨交给CI/CD 或者其他 自动化工具。

对于开发者的我们而言,我们只需要明白一个原则:当下要解决什么问题,即可

把时间浪费在更有意思的事情上,真的 泰库辣 😃

见字如面,我们下篇文章再见 👋

参考

  • GitHub Actions文档

关于我

我是 Petterp ,一个 Android工程师。如果本文,你觉得写的还不错,不妨点个赞或者收藏,你的支持,是我持续创作的最大鼓励!

欢迎关注我的 公众号(Petterp) ,期待与你一同前进 😃

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/553951.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity - 解决TMP FontAssetCreator 在生成 SDF 时 Font 显示 ????? 的问题

文章目录 原因问题解决下载 FontCreator,并打开有问题的字体修改字体属性重新导出字体 返回 Unity 后重新使用 TMP Font Asset Creator 来生成 原因 美术找到一个字体和某个参考的某个游戏的字体是一致 美术同学截图了参考的游戏,和 自己找到的 字体 放…

Mybatis查询语句汇总与小技巧

前言 在之前的学习中,使用到了将查询结果封装到bean对象内,使用到了将查询结果封装的bean对象封装到List集合中,但是如果我们没有对应的bean对象,我们应该将结果封装到哪呢?今天这篇文章就记录一下几种常见查询结果的封…

用蹩脚英语在StackOverflow上飞奔:试看以色列兄弟自荐的Http文件上传工具MgntUtils

上班摸鱼、下班干活,日常埋坑、加班填坑——这是我的搬砖,亦是在座的各位! 文章目录 1.试看MgntUtils来源2.下载MgntUtils源码3.初探源码4.验证API 1.试看MgntUtils来源 ​ ​上篇文章说到,有个以色列大兄弟在回答Stack Overflow…

13岁青少年DAO创始人:Web3治好了我的“丧”

“我看大家都死气沉沉的,大家都站起来活动活动。” 4月,香港Web3嘉年华的一场沙龙,橙色针织帽给黑压压的现场带来一抹亮色,13岁的Carry Zheng戴着它登台,没有“大家好”的寒暄,直接向台下的成年人发出指令&…

训练营-5月

JAVA训练营-5月 一、环境 1、idea安装 idea就是一个开发工具,写代码的地方 目录结构: --- 项目(工程 project) 比如:京东 ---- 模块(module) 比如:订单、购物车、秒杀等 -----…

ALOHA 开源机械臂(Viper 300 Widow X 250 6DOF机械臂 远程操控系统)第四部分

Teleoperation System 远程操作系统 We introduce ALOHA: A Low-cost Open-source Hardware System for Bimanual Teleoperation. With a $20k budget, it is capable of teleoperating precise tasks such as threading a zip tie, dynamic tasks such as juggling a ping p…

【2023 · CANN训练营第一季】进阶班 应用开发深入讲解→端到端案例

1 样例调试 1.1 日志文件 运行应用程序后,若出现报错或异常,需录取日志进一步定位问题。日志文件的默认目录为$HOME/ascend/log。 可通过环境变量指定日志文件的落盘路径 export ASCEND_PROCESS_LOG_PATH/$HOME/xxx但需要确保该目录为任意有读写权限…

外参手算方法

虽然有的slam系统是代外参标定功能,可以在线标定(vins)或者离线进行标定,但外参标定的质量也会与运动激励相关的,例如对于3自由度的小车很难把z方向的外参标定的很好。有些情况车子或者是定位模块是有设计图纸的&#…

Ubuntu22.04下使用Conda安装PyTorch GPU版本

环境 首先,你需要有 GPU 支持。 Ubuntu 22.04 显卡 $ nvidia-smi Mon May 22 11:15:33 2023 --------------------------------------------------------------------------------------- | NVIDIA-SMI 530.30.02 Driver Version: 530.30.02 CUDA…

MySQL在Centos7环境下的安装操作

文章目录 一、卸载不需要的环境二、通过yum安装MySQL1.安装MySQL的yum源2.安装MySQL 三、启动mysql的服务器四、登录MySQL1.方法一2.方法二3.方法三 五、配置my.cnf文件 一、卸载不需要的环境 首先要检查服务器中是否存在mariadb,mariadb是MySQL的一个开源分支&…

栈与C++中的std::stack详解(多图超详细)

文章目录 栈(stack)什么是栈?栈的基本操作和应用入栈(push)出栈(pop)入栈和出栈的复杂度和应用场景 类模板std::satck形参T和Container成员函数元素访问栈的容量栈的修改 用法示例 栈(stack) 什么是栈? 栈是一种线性的数据结构&…

Python爬虫被封ip解决方案

在使用 Python 程序进行网络爬虫开发时,可能因以下原因导致被封 IP 或封禁爬虫程序: 1、频繁访问网站 爬虫程序可能会在很短的时间内访问网站很多次,从而对目标网站造成较大的负担和压力,这种行为容易引起目标网站的注意并被封禁…

2023ACP世界大赛中国总决赛|让世界再多一个微笑

5月21日,正值第三十三次全国助残日,作为公益推行的一份子,恒利联创也呈现出了“仁者爱人”的文化内核。 恒利联创携手微笑明天慈善基金会合作同行,旨在推动公益,促进残疾人事业的全面发展。在前行的道路上&#xff0c…

MQTT入门手册

初识MQTT MQTT 协议简介 概览 MQTT 是一种基于发布/订阅模式的轻量级消息传输协议,专门针对低带宽和不稳定网络环境的物联网应用而设计,可以用极少的代码为联网设备提供实时可靠的消息服务。MQTT 协议广泛应用于物联网、移动互联网、智能硬件、车联网…

​LeetCode解法汇总1080. 根到叶路径上的不足节点

目录链接: 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目: https://github.com/September26/java-algorithms 原题链接:力扣 描述: 给你二叉树的根节点 root 和一个整数 limit ,请你同时删除树中所有 …

Mybatis连接MySQL数据库通过逆向工程简化开发流程

文章目录 一、使用步骤1、建立新项目2、引入pom依赖3、创建逆向工程的配置文件 generatorConfig.xml4、运行逆行工程,生成代码文件 二、案例展示1、建立数据表2、改写对应的配置文件内容1、数据库连接配置,指定自己的数据库2、配置pojo生成的位置3、配置sql映射文件…

新一代数据湖存储技术Apache Paimon入门Demo

目录 前言 1. 什么是 Apache Paimon 一、本地环境快速上手 1、本地Flink伪集群 2、IDEA中跑Paimon Demo 2.1 代码 2.2 IDEA中成功运行 3、IDEA中Stream读写 3.1 流写 3.2 流读(toChangeLogStream) 二、进阶:本地(IDEA&…

【Java EE】Spring介绍

Spring笔记 1.概述1.1 IOC1.2.context上下文和bean1.3.AOP 2.IoC 控制反转2.1. Spring IoC容器和Bean简介2.2. 容器概述2.2.1. 配置元数据2.2.2. 实例化一个容器2.2.3. 使用容器 2.3. Bean 概览2.3.1. Bean 命名2.3.2. 实例化 Bean2.3.3 bean的生命周期 3.AOPAOP 概念 参考资料…

【微博-UITableViewController介绍 Objective-C语言】

一、加载xib文件的另外一种办法 1.我们说,加载xib,一种方式就是, CZFooterView *footerView = [[[NSBundle mainBundle] loadNibNamed:@“CZFooterView” owner:nil options:nil] lastObject]; 吧,这是一种方式, 2.另外一种方式,就是这里这种方式, UINIb *nib = [UI…

关于Jetpack DataStore(Preferences)的八点疑问

前言 DataStore是Android上一种轻量级存储方案,依据官方教程很容易就写出简易的Demo。 本篇主要是分析关于DataStore(Preferences)使用过程中的一些问题,通过问题寻找本质,反过来能更好地指导我们合理使用DataStore。 本篇内容目录&#xff…