24、Gradle与CI/CD:集成Jenkins/GitHub Actions/GitLab CI、环境变量传递、条件构建

CI/CD,说白了就是让机器替你干活。

我刚开始带团队那会儿,每次发布都要手动跑构建、改配置、传包。有一次凌晨三点上线,我忘了切Profile,结果测试包直接发到了生产环境……嗯,从那以后,我再也不敢相信自己的手了。

这一章,我们就聊聊Gradle怎么和主流的CI/CD工具配合。包括Jenkins、GitHub Actions、GitLab CI,还有环境变量怎么传、条件构建怎么写。

核心要点:CI/CD不是把本地命令搬到服务器上跑,而是要让构建过程可重复、可追溯、可控制。

24.1 为什么Gradle在CI/CD中容易翻车?

很多人在本地构建好好的,一上CI就报错。我遇到过最典型的问题:

  • 本地有缓存,CI没有,依赖下载超时
  • 本地JDK版本和CI不一致,编译报错
  • 本地能访问内网仓库,CI访问不了
  • 环境变量没传进去,构建脚本直接崩

这些问题,其实都可以通过规范化的配置来解决。我们先从最基础的开始。

24.2 Jenkins + Gradle:老牌组合的实战套路

Jenkins我用了很多年。说实话,它配置起来有点繁琐,但胜在稳定、可控。

24.2.1 基础配置

在Jenkins里跑Gradle,核心就三步:

  1. 安装Gradle插件(或者直接用Docker Agent)
  2. 配置构建工具链(JDK版本、Gradle版本)
  3. 写Pipeline脚本

我个人习惯用Pipeline as Code,也就是Jenkinsfile。这样配置跟着代码走,不会出现「本地能跑,Jenkins跑不了」的尴尬。

// Jenkinsfile
pipeline {
    agent any

    tools {
        jdk 'JDK17'
        gradle 'Gradle8.5'
    }

    environment {
        // 定义环境变量,后面可以在Gradle脚本里用
        BUILD_ENV = 'production'
        DEPLOY_HOST = '192.168.1.100'
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('Build') {
            steps {
                // 关键:通过-P传递项目属性
                sh 'gradle clean build -Penv=${BUILD_ENV} -Phost=${DEPLOY_HOST}'
            }
        }

        stage('Test') {
            steps {
                sh 'gradle test --no-daemon'
            }
        }

        stage('Archive') {
            steps {
                archiveArtifacts artifacts: 'build/libs/*.jar'
            }
        }
    }

    post {
        failure {
            // 构建失败时发邮件
            emailext(
                subject: "构建失败: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
                body: "请检查构建日志",
                to: 'team@example.com'
            )
        }
    }
}

我的经验:Jenkins里一定要加--no-daemon参数。Gradle Daemon在CI环境里反而容易出问题,尤其是容器化部署时,Daemon进程不会被正确清理。

24.2.2 环境变量传递的两种方式

在Gradle里读取外部变量,我常用两种方式:

方式 命令示例 Gradle中读取 适用场景
-P参数 gradle build -PmyKey=myValue project.findProperty('myKey') 构建时动态传入
系统环境变量 export MY_KEY=myValue System.getenv('MY_KEY') 全局配置,如API密钥

我曾经踩过一个坑:用-P传密码,结果Jenkins日志里明文显示了。后来我改用Jenkins的Credentials Binding插件,把敏感信息注入到环境变量里,再用System.getenv()读取。

注意:不要在Gradle脚本里硬编码密码或Token。用环境变量或者Gradle的gradle.properties文件(记得加到.gitignore里)。

24.3 GitHub Actions + Gradle:轻量级CI的玩法

GitHub Actions我最近用得比较多。它的优势是配置简单,和GitHub仓库天然集成。

24.3.1 一个标准的Workflow

# .github/workflows/gradle-build.yml
name: Gradle Build

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4

    - name: Set up JDK 17
      uses: actions/setup-java@v4
      with:
        java-version: '17'
        distribution: 'temurin'

    - name: Setup Gradle
      uses: gradle/actions/setup-gradle@v3

    - name: Build with Gradle
      run: ./gradlew build
      env:
        # 通过env传递环境变量
        BUILD_ENV: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}
        API_KEY: ${{ secrets.API_KEY }}

    - name: Upload build artifacts
      uses: actions/upload-artifact@v4
      with:
        name: Package
        path: build/libs

这里有个小技巧:${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }},这是GitHub Actions里的条件表达式。说白了就是:main分支走生产环境,其他分支走预发布环境。

24.3.2 条件构建的写法

在Gradle脚本里,我们可以根据环境变量做条件判断:

// build.gradle.kts
val buildEnv: String? = System.getenv("BUILD_ENV")

tasks.register("deploy") {
    doLast {
        when (buildEnv) {
            "production" -> {
                println("部署到生产环境...")
                // 执行生产部署逻辑
            }
            "staging" -> {
                println("部署到预发布环境...")
                // 执行预发布部署逻辑
            }
            else -> {
                println("未知环境,跳过部署")
            }
        }
    }
}

// 条件性配置:生产环境才启用混淆
if (buildEnv == "production") {
    tasks.named("build") {
        // 启用ProGuard或R8混淆
    }
}

我的建议:条件构建的逻辑不要太复杂。我曾经见过一个项目,build.gradle里写了200多行if-else,最后谁也看不懂。把复杂的条件判断放到CI配置里,Gradle脚本只做简单的分支处理。

24.4 GitLab CI + Gradle:企业级的选择

GitLab CI的配置方式和GitHub Actions类似,但有一些自己的特色。

24.4.1 .gitlab-ci.yml示例

# .gitlab-ci.yml
stages:
  - build
  - test
  - deploy

variables:
  GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.parallel=true"
  BUILD_ENV: "development"

before_script:
  - export GRADLE_USER_HOME=`pwd`/.gradle
  - chmod +x ./gradlew

build:
  stage: build
  script:
    - ./gradlew assemble
  artifacts:
    paths:
      - build/libs/
    expire_in: 1 week
  only:
    - main
    - develop

test:
  stage: test
  script:
    - ./gradlew test
  artifacts:
    reports:
      junit: build/test-results/test/**/TEST-*.xml

deploy:
  stage: deploy
  script:
    - ./gradlew deploy -Penv=$BUILD_ENV
  only:
    - main
  when: manual  # 手动触发,防止误操作

GitLab CI里有个好用的功能:when: manual。部署这种高风险操作,我建议都设成手动触发。我曾经因为自动部署把测试环境覆盖了,从那以后,凡是涉及生产的操作,我都加一道人工确认。

24.5 环境变量传递的最佳实践

不管用哪种CI工具,环境变量传递都有一些通用的原则:

  • 敏感信息用Secrets:Jenkins用Credentials,GitHub用Secrets,GitLab用Variables
  • 非敏感信息用-P参数:比如分支名、构建号、环境标识
  • 统一命名规范:我习惯用大写+下划线,比如BUILD_ENVDEPLOY_HOST
  • 提供默认值:在Gradle脚本里用findProperty()getenv()时,给个兜底值
// 推荐写法:带默认值
val buildEnv = project.findProperty("env") as? String ?: "development"
val apiKey = System.getenv("API_KEY") ?: "default-dev-key"

// 不推荐:直接取值,没有默认值
// val buildEnv = project.property("env") as String  // 没传-Penv就直接崩

24.6 条件构建的高级用法

条件构建不只是if-else。我常用的几个场景:

场景 实现方式 代码示例
按分支执行不同任务 读取CI提供的分支变量 if (System.getenv("CI_COMMIT_BRANCH") == "main")
按构建类型配置 通过-P传递buildType if (project.hasProperty("release"))
跳过特定任务 使用onlyIf条件 tasks.named("lint") { onlyIf { !isCi } }
动态选择依赖仓库 根据环境变量切换URL repositories { maven { url = if (isCi) ciRepo else localRepo } }

避坑指南:我曾经在条件构建里用了System.getenv()来检测CI环境,结果本地开发时因为没设这个变量,整个构建逻辑都变了。后来我加了一个isCi的辅助函数,统一判断:

val isCi: Boolean = System.getenv("CI") != null || 
                     System.getenv("JENKINS_HOME") != null ||
                     System.getenv("GITLAB_CI") != null

24.7 本章小结

Gradle和CI/CD的集成,说白了就是三件事:

  • 环境变量怎么传:-P参数、系统环境变量、Secrets
  • 条件构建怎么写:根据分支、环境、构建类型做分支
  • CI脚本怎么配:Jenkinsfile、GitHub Actions YAML、GitLab CI YAML

嗯,这些套路掌握了,你的构建流水线基本就稳了。记住一点:CI/CD的目的是让构建自动化、可重复,而不是让Gradle脚本变成一团乱麻。保持简单,保持可控。

公众号:蓝海资料掘金营,微信deep3321