Skip to content

Gradle 常用命令

安装

macOS 用户

下载指定版本

bash
# 下载该文件:https://github.com/Homebrew/homebrew-core/blob/master/Formula/gradle.rb
# 修改第四行内容为指定版本, 同时修改第五行的 sha 值, 对应版本 sha 请在这里查看 https://services.gradle.org/distributions
# 保存后,执行本地的 gradle.rb
brew install /path/to/file/gradle.rb

默认下载最新版本的 Gradle

bash
brew install gradle

NOTE

推荐使用的官方版本: gradle-4.10.3 版本 官网下载地址: https://gradle.org/releases

版本信息

bash
# 查看当前系统环境的 gradle 版本
gradle -v

NOTE

项目根目录下的 gradlew 文件,一般跟着项目中的 gradle 配置走的。推荐使用 gradlew 执行命令行命令, 不需要频繁改动系统的 gradle 版本。

oh-my-zsh (Linux/macOS)

linux/mac 用户安装 oh-my-zsh 后执行可略去 ./

如果启用了 gradle plugin 可以直接使用 gradle 命令,oh-my-zsh 会自动使用当前目录下的 gradlew + wrapper 的方式运行。

bash
./gradlew -v
# 如果项目下没有 gradlew wrapper 包,使用如下命令生成 gradle wrapper
gradle wrapper --gradle-version {$gradleVersion}
# example: gradle wrapper --gradle-version 4.10.3

编译和构建

bash
# 构建 gradle 项目, 组装指定的所有 output 文件并执行 check
gradle build
# 编译单独模块
gradle ${moduleName}:build
# example: gradle demo-hello-api:build
# 清理构建出的 output 文件
gradle clean
# 运行所有子模块下的 test task
gradle test

打包

bash
# springboot 插件执行的打包,将所有模块打进一个包里
gradle bootRepackage
# 打出可执行 jar, 打包位置在子模块的 build/libs 下
gradle jar

查看项目信息

bash
# 查看所有子模块
gradle projects
# 查看所有将在编译中被执行的 task, 实际不执行 task
gradle build --dry-run
# 查看项目下所有 tasks 类型和描述
gradle tasks --all
# build 时排除某项任务
gradle build -x {$taskName}
# example: gradle build -x test
# 查看某类 task 的描述
gradle help --task {$taskName}
# example: gradle help --task processResources
# 查看某类 task 的细节
gradle -q help --task {$taskName}
# example: gradle -q help --task test

项目依赖

bash
# 打印模块依赖树
gradle ${moduleName}:dependencies
# 打印特定 configuration 下的模块的依赖树(推荐)
gradle ${moduleName}:dependencies --configuration {$option}
# example: gradle demo-hello-api:dependencies --configuration compile

--configuration {$option} 可选的 options 包含:

Option说明
compile编译 + Runtime,如果你的 jar 包/依赖代码在编译的时候需要依赖,在运行的时候也需要,那么就用 compile;声明的依赖属于传递性依赖
compileOnly仅编译时需要的依赖
testCompile测试文件的编译 + 运行时需要的依赖
testCompileOnly测试文件中仅编译时需要
api与 compile 相同,声明的依赖属于传递性依赖
implementation编译 + Runtime 需要的依赖,但是声明的是非传递性依赖,可以提高项目的构建速度
testImplementation测试文件中需要的编译 + Runtime 的非传递性依赖
runtime仅运行时需要的依赖

发布

bash
# 发布到本地 maven 仓库
gradle publishToMavenLocal

Gradle 依赖问题

服务端自己引入依赖时 transitiveexclude 的语义比较明确,本文重点探讨 SDK 管理依赖时若使用上述 exclusion 声明可能产生的影响。

以下几种用法效果都是在 SDK 发布的 pom 文件中添加 exclusion 声明:

groovy
transitive(false)                                         // exclude 全部传递的依赖
exclude group: "*"                                        // 等同于 transitive(false)
exclude group: "*", module: "*"                           // 等同于 transitive(false)
exclude group: "希望 exclude 的制品 groupId"                // 排除指定 groupId
exclude group: "希望 exclude 的制品 groupId", module: "希望 exclude 的制品 artifactId" // 排除指定 groupId + artifactId

一、最佳实践

乍一看结论貌似有些突兀和难以理解,若对 maven、gradle 不够熟悉且希望一探究竟可阅读后续章节。

SDK(或 contract)管理依赖时,不要非必要地使用 transitiveexclude,比如单纯觉得另一个包的依赖太乱就 exclude 掉。

如果某些情况为保证功能正确性必须 exclude 的话,推荐 case by case 地分析,可咨询技术支持。

二、详细说明

本节基于几个 case 帮助读者快速理解相关机制,假设存在以下四个依赖:

考虑 project 通过以下几种方式声明依赖时,最终是否包含 test-D 依赖:

groovy
// 依赖 test-A、test-C
api("com.example.test.group:test-A")
api("com.example.test.group:test-C")

// 依赖 test-C、test-A
api("com.example.test.group:test-C")
api("com.example.test.group:test-A")

// 依赖 test-A、test-B
api("com.example.test.group:test-A")
api("com.example.test.group:test-B")

// 依赖 test-B、test-A
api("com.example.test.group:test-B")
api("com.example.test.group:test-A")

2.1 使用 io.spring.dependency-management 插件

使用该插件时版本管理行为可以理解为与 maven 保持一致,也是我们推荐的最佳实践。

众所周知 maven 版本仲裁是最短路径优先,进行 exclusion 判断也是同理:

  1. 找到 test-D 的最短路径,若存在相同长度的路径,则声明顺序靠前的优先
  2. 判断该路径是否声明了需要 exclude test-D,以此决定 project 是否应该包含 test-D 依赖
Case声明最短路径是否包含 test-D
test-A、test-Capi("com.example.test.group:test-A")
api("com.example.test.group:test-C")
test-C ==> test-Dyes
test-C、test-Aapi("com.example.test.group:test-C")
api("com.example.test.group:test-A")
test-A、test-Bapi("com.example.test.group:test-A")
api("com.example.test.group:test-B")
test-A (exclude test-D) > test-C ==> test-Dno
test-B、test-Aapi("com.example.test.group:test-B")
api("com.example.test.group:test-A")
test-B > test-C > test-Dyes

2.2 Gradle 原始实现,不使用 spring 插件

Gradle 进行版本仲裁时并不会仅寻找一个特定路径,而是结合所有依赖路径进行 exclusion 判断,当且仅当所有路径中 test-D 均被 exclude 时才会真正排除依赖:

Case声明所有路径是否包含 test-D
test-A、test-Capi("com.example.test.group:test-A")
api("com.example.test.group:test-C")
test-C ==> test-D
test-A (exclude test-D) > test-C ==> test-D
yes
test-C、test-Aapi("com.example.test.group:test-C")
api("com.example.test.group:test-A")
test-A、test-Bapi("com.example.test.group:test-A")
api("com.example.test.group:test-B")
test-B > test-C > test-D
test-A (exclude test-D) > test-C ==> test-D
yes
test-B、test-Aapi("com.example.test.group:test-B")
api("com.example.test.group:test-A")

2.3 小结

综上所述可以解释一下为何我们会推荐 SDK 或 contract 声明依赖时都不要非必要地使用 transitiveexclude,显然当我们发布的 pom 中包含 exclusion 声明时,服务端使用时是否包含其传递的依赖是不稳定的,会受多种因素的影响,对于依赖本身就复杂的项目来说迭代过程中可能会产生非预期地的依赖变更(比如调整了一下依赖声明的顺序,之前有的依赖可能就没有了)。

三、参考

Move fast and break things