Java 插件为项目添加了 Java 编译以及测试和捆绑功能。它是许多其他 JVM 语言 Gradle 插件的基础。您可以在构建 Java 项目章节中找到 Java 插件的全面介绍和概述。

如上所示,此插件为使用 JVM 项目添加了基本构建块。它的功能集已被其他插件取代,这些插件根据您的项目类型提供更多功能。与其直接将它应用于您的项目,不如考虑使用 java-libraryapplication 插件,或者其他受支持的替代 JVM 语言插件。

用法

要使用 Java 插件,请在您的构建脚本中包含以下内容

build.gradle.kts
plugins {
    java
}
build.gradle
plugins {
    id 'java'
}

Task

Java 插件为您的项目添加了许多 Task,如下所示。

compileJavaJavaCompile

依赖于:所有为编译 classpath 做出贡献的 Task,包括来自通过项目依赖项位于 classpath 上的项目的 jar Task

使用 JDK 编译器编译生产 Java 源文件。

processResourcesProcessResources

将生产资源复制到生产资源目录中。

classes

依赖于compileJavaprocessResources

这是一个聚合 Task,仅依赖于其他 Task。其他插件可能会向其附加额外的编译 Task。

compileTestJavaJavaCompile

依赖于classes,以及所有为测试编译 classpath 做出贡献的 Task

使用 JDK 编译器编译测试 Java 源文件。

processTestResourcesCopy

将测试资源复制到测试资源目录中。

testClasses

依赖于compileTestJavaprocessTestResources

这是一个聚合 Task,仅依赖于其他 Task。其他插件可能会向其附加额外的测试编译 Task。

jarJar

依赖于classes

基于附加到 main source set 的类和资源,组装生产 JAR 文件。

javadocJavadoc

依赖于classes

使用 Javadoc 为生产 Java 源代码生成 API 文档。

testTest

依赖于testClasses,以及所有生成测试运行时 classpath 的 Task

使用 JUnit 或 TestNG 运行单元测试。

cleanDelete

删除项目构建目录。

cleanTaskNameDelete

删除指定 Task 创建的文件。例如,cleanJar 将删除由 jar Task 创建的 JAR 文件,而 cleanTest 将删除由 test Task 创建的测试结果。

SourceSet Task

对于您添加到项目的每个 source set,Java 插件都会添加以下 Task

compileSourceSetJavaJavaCompile

依赖于:所有为 source set 的编译 classpath 做出贡献的 Task

使用 JDK 编译器编译给定 source set 的 Java 源文件。

processSourceSetResourcesCopy

将给定 source set 的资源复制到资源目录中。

sourceSetClassesTask

依赖于compileSourceSetJavaprocessSourceSetResources

准备给定的 source set 的类和资源以进行打包和执行。某些插件可能会为 source set 添加额外的编译 Task。

生命周期 Task

Java 插件将其某些 Task 附加到 Base Plugin(Java 插件自动应用)定义的生命周期 Task,并且它还添加了一些其他生命周期 Task

assemble

依赖于jar

聚合 Task,用于组装项目中的所有归档文件。此 Task 由 Base Plugin 添加。

check

依赖于test

聚合 Task,用于执行验证 Task,例如运行测试。某些插件会将它们自己的验证 Task 添加到 check。如果您希望它们在完整构建中执行,您还应该将任何自定义 Test Task 附加到此生命周期 Task。此 Task 由 Base Plugin 添加。

build

依赖于checkassemble

聚合 Task,用于执行项目的完整构建。此 Task 由 Base Plugin 添加。

buildNeeded

依赖于build,以及 testRuntimeClasspath 配置中所有作为依赖项的项目的 buildNeeded Task。

执行项目及其所依赖的所有项目的完整构建。

buildDependents

依赖于build,以及在其 testRuntimeClasspath 配置中将此项目作为依赖项的所有项目的 buildDependents Task

执行项目及其所依赖的所有项目的完整构建。

buildConfigName — Task 规则

依赖于:所有生成附加到命名 — ConfigName — 配置的工件的 Task

为指定的配置组装工件。此规则由 Base Plugin 添加。

下图显示了这些 Task 之间的关系。

javaPluginTasks
图 1. Java 插件 - Task

项目布局

Java 插件假定项目布局如下所示。这些目录都不需要存在或包含任何内容。Java 插件将编译它找到的任何内容,并处理任何缺失的内容。

src/main/java

生产 Java 源代码。

src/main/resources

生产资源,例如 XML 和属性文件。

src/test/java

测试 Java 源代码。

src/test/resources

测试资源。

src/sourceSet/java

名为 sourceSet 的 source set 的 Java 源代码。

src/sourceSet/resources

名为 sourceSet 的 source set 的资源。

更改项目布局

您可以通过配置相应的 source set 来配置项目布局。这将在以下部分中更详细地讨论。这是一个简短的示例,它更改了主要的 Java 和资源源目录。

build.gradle.kts
sourceSets {
    main {
        java {
            setSrcDirs(listOf("src/java"))
        }
        resources {
            setSrcDirs(listOf("src/resources"))
        }
    }
}
build.gradle
sourceSets {
    main {
        java {
            srcDirs = ['src/java']
        }
        resources {
            srcDirs = ['src/resources']
        }
    }
}

Source sets

该插件添加了以下 source sets

main

包含项目的生产源代码,这些源代码将被编译并组装成 JAR。

test

包含您的测试源代码,这些源代码将使用 JUnit 或 TestNG 进行编译和执行。这些通常是单元测试,但您可以在此 source set 中包含任何测试,只要它们都共享相同的编译和运行时 classpath。

Source set 属性

下表列出了 source set 的一些重要属性。您可以在 SourceSet 的 API 文档中找到更多详细信息。

name — (只读)String

source set 的名称,用于标识它。

output — (只读)SourceSetOutput

source set 的输出文件,包含其编译后的类和资源。

output.classesDirs — (只读)FileCollection

默认值layout.buildDirectory.dir("classes/java/$name"),例如 build/classes/java/main

用于生成此 source set 的类的目录。可能包含其他 JVM 语言的目录,例如 build/classes/kotlin/main

output.resourcesDir — File

默认值layout.buildDirectory.dir("resources/$name"),例如 build/resources/main

用于生成此 source set 的资源的目录。

compileClasspath — FileCollection

默认值${name}CompileClasspath 配置

编译此 source set 的源文件时要使用的 classpath。

annotationProcessorPath — FileCollection

默认值${name}AnnotationProcessor 配置

编译此 source set 的源文件时要使用的处理器路径。

runtimeClasspath — FileCollection

默认值$output, ${name}RuntimeClasspath 配置

执行此 source set 的类时要使用的 classpath。

java — (只读)SourceDirectorySet

此 source set 的 Java 源文件。仅包含在 Java 源目录中找到的 .java 文件,并排除所有其他文件。

java.srcDirs — Set<File>

默认值src/$name/java,例如 src/main/java

包含此 source set 的 Java 源文件的源目录。您可以将其设置为 本节中描述的任何值。

java.destinationDirectory — DirectoryProperty

默认值layout.buildDirectory.dir("classes/java/$name"),例如 build/classes/java/main

用于生成编译后的 Java 源代码的目录。您可以将其设置为 本节中描述的任何值。

resources — (只读)SourceDirectorySet

此 source set 的资源。仅包含资源,并排除在资源目录中找到的任何 .java 文件。其他插件,例如 Groovy 插件,从此集合中排除其他类型的文件。

resources.srcDirs — Set<File>

默认值[src/$name/resources]

包含此 source set 的资源的目录。您可以将其设置为 本节中描述的任何类型的值。

allJava — (只读)SourceDirectorySet

默认值:与 java 属性相同

此 source set 的所有 Java 文件。某些插件,例如 Groovy 插件,会向此集合添加额外的 Java 源文件。

allSource — (只读)SourceDirectorySet

默认值resourcesjava 属性中所有内容的总和

此 source set 的所有语言的所有源文件。这包括所有资源文件和所有 Java 源文件。某些插件,例如 Groovy 插件,会向此集合添加额外的源文件。

定义新的 source sets

请参阅测试 Java 和 JVM 项目章节中的集成测试示例

其他一些简单的 source set 示例

添加包含 source set 类的 JAR

build.gradle.kts
tasks.register<Jar>("intTestJar") {
    from(sourceSets["intTest"].output)
}
build.gradle
tasks.register('intTestJar', Jar) {
    from sourceSets.intTest.output
}

为 source set 生成 Javadoc

build.gradle.kts
tasks.register<Javadoc>("intTestJavadoc") {
    source(sourceSets["intTest"].allJava)
    classpath = sourceSets["intTest"].compileClasspath
}
build.gradle
tasks.register('intTestJavadoc', Javadoc) {
    source sourceSets.intTest.allJava
    classpath = sourceSets.intTest.compileClasspath
}

添加测试套件以在 source set 中运行测试

build.gradle.kts
tasks.register<Test>("intTest") {
    testClassesDirs = sourceSets["intTest"].output.classesDirs
    classpath = sourceSets["intTest"].runtimeClasspath
}
build.gradle
tasks.register('intTest', Test) {
    testClassesDirs = sourceSets.intTest.output.classesDirs
    classpath = sourceSets.intTest.runtimeClasspath
}

依赖管理

Java 插件为您的项目添加了许多依赖配置,如下所示。诸如 compileJavatest 之类的 Task 然后使用这些配置中的一个或多个来获取相应的文件并使用它们,例如通过将它们放置在编译或运行时 classpath 上。

依赖配置

有关 defaultarchives 配置的信息,请查阅 Base Plugin 参考文档。

有关 apicompileOnlyApi 配置的信息,请查阅 Java Library Plugin 参考文档和 Java 项目的依赖管理

依赖声明配置

implementation

表示项目的 main source set 在编译时和运行时都需要的依赖项(即,仅实现依赖项)。

compileOnly

表示仅在编译时需要且未包含在运行时 classpath 中的依赖项(即,仅编译时依赖项,运行时不使用)。

runtimeOnly

表示仅在运行时需要且未包含在编译 classpath 中的依赖项(即,仅运行时需要的依赖项)。

testImplementation 扩展 implementation

表示项目的 test source set 在编译时和运行时都需要的依赖项(即,仅测试实现依赖项)。

testCompileOnly

表示仅在编译时需要且未包含在运行时 classpath 中的项目的 test source set 的依赖项(即,仅用于编译测试的附加依赖项,运行时不使用)。

testRuntimeOnly 扩展 runtimeOnly

表示仅在运行时需要且未包含在编译 classpath 中的项目的 test source set 的依赖项(即,仅用于运行测试的运行时依赖项)。

annotationProcessor

表示在项目源代码编译期间使用的注解处理器(即,编译期间使用的注解处理器)。

可解析的配置

compileClasspath 扩展 compileOnly, implementation

表示编译主源代码时使用的 classpath,其中包括来自 compileOnlyimplementation 配置的依赖项。由 Task compileJava 使用。

runtimeClasspath 扩展 runtimeOnly, implementation

表示运行主源代码时使用的 classpath,其中包括来自 runtimeOnlyimplementation 配置的依赖项。由 Task run 使用。

testCompileClasspath 扩展 testCompileOnly, testImplementation

表示编译测试源代码时使用的 classpath,其中包括来自 testCompileOnlytestImplementation 配置的依赖项。由 Task compileTestJava 使用。

testRuntimeClasspath 扩展 testRuntimeOnly, testImplementation

表示运行测试源代码时使用的 classpath,其中包括来自 testRuntimeOnlytestImplementation 配置的依赖项。由 Task test 使用。

以下图表分别显示了 maintest source set 的依赖配置。您可以使用此图例来解释颜色

  • 蓝色背景 — 您可以针对配置声明依赖项。

  • 绿色背景 — 该配置供 Task 使用,而不是供您声明依赖项。

  • 灰色背景 — 一个 Task。

java main configurations
图 2. Java 插件 - main source set 依赖配置
java test configurations
图 3. Java 插件 - test source set 依赖配置

对于您添加到项目的每个 source set,Java 插件都会添加以下依赖配置

SourceSet 依赖配置

sourceSetImplementation

给定 source set 的编译时依赖项。由 sourceSetCompileClasspath, sourceSetRuntimeClasspath 使用。

sourceSetCompileOnly

给定 source set 的仅编译时依赖项,运行时不使用。

sourceSetCompileClasspath 扩展 sourceSetCompileOnly, sourceSetImplementation

编译 classpath,用于编译源代码。由 compileSourceSetJava 使用。

sourceSetAnnotationProcessor

在此 source set 编译期间使用的注解处理器。

sourceSetRuntimeOnly

给定 source set 的仅运行时依赖项。

sourceSetRuntimeClasspath 扩展 sourceSetRuntimeOnly, sourceSetImplementation

运行时 classpath 包含实现的元素以及仅运行时元素。

贡献的扩展

Java 插件将 java 扩展添加到项目中。这允许在专用的 DSL 块内配置许多与 Java 相关的属性。

build.gradle.kts
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}
build.gradle
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

以下是在 java 扩展内可用的属性和 DSL 函数列表,并附有简短说明。

工具链和兼容性

toolchain

供使用 JVM 工具(例如编译和执行)的 Task 使用的Java 工具链。默认值:构建 JVM 工具链。

JavaVersion sourceCompatibility

编译 Java 源代码时要使用的 Java 版本兼容性。默认值:来自此扩展的工具链的语言版本。
请注意,在大多数情况下,使用 工具链 优于使用兼容性设置。

JavaVersion targetCompatibility

用于生成类的 Java 版本。默认值:sourceCompatibility
请注意,在大多数情况下,使用 工具链 优于使用兼容性设置。

打包

withJavadocJar()

自动打包 Javadoc 并创建一个变体 javadocElements,其中包含工件 -javadoc.jar,这将是发布的一部分。

withSourcesJar()

自动打包源代码并创建一个变体 sourceElements,其中包含工件 -sources.jar,这将是发布的一部分。

目录属性

String reporting.baseDir

用于生成报告的目录名称,相对于构建目录。默认值:reports

(只读)File reportsDir

用于生成报告的目录。默认值:reporting.baseDirectory

String testResultsDirName

用于生成测试结果 .xml 文件的目录名称,相对于构建目录。默认值:test-results

(只读)File testResultsDir

用于生成测试结果 .xml 文件的目录。默认值:layout.buildDirectory.dir(testResultsDirName)

String testReportDirName

用于生成测试报告的目录名称,相对于报告目录。默认值:tests

(只读)File testReportDir

用于生成测试报告的目录。默认值:reportsDir/testReportDirName

String libsDirName

用于生成库的目录名称,相对于构建目录。默认值:libs

(只读)File libsDir

用于生成库的目录。默认值:layout.buildDirectory.dir(libsDirName)

String distsDirName

用于生成发行版的目录名称,相对于构建目录。默认值:distributions

(只读)File distsDir

用于生成发行版的目录。默认值:layout.buildDirectory.dir(distsDirName)

String docsDirName

用于生成文档的目录名称,相对于构建目录。默认值:docs

(只读)File docsDir

用于生成文档的目录。默认值:layout.buildDirectory.dir(docsDirName)

String dependencyCacheDirName

用于缓存相对于构建目录的源依赖项信息的目录名称。默认值:dependency-cache

其他属性

(只读)SourceSetContainer sourceSets

包含项目的 source sets。默认值:非空 SourceSetContainer

String archivesBaseName

用于归档文件(例如 JAR 或 ZIP 文件)的基本名称。默认值:projectName

Manifest manifest

要包含在所有 JAR 文件中的 manifest。默认值:一个空的 manifest。

约定属性(已弃用)

Java 插件为项目添加了许多约定属性。您可以在构建脚本中使用这些属性,就好像它们是项目对象的属性一样。这些属性已弃用,并由上面描述的扩展取代。有关它们的更多信息,请参阅 JavaPluginConvention DSL 文档。

测试

有关更多详细信息,请参阅 测试 Java 和 JVM 项目 章节。

发布

components.java

用于发布jar Task 创建的生产 JAR 的 SoftwareComponent。此组件包含 JAR 的运行时依赖项信息。

另请参阅 java 扩展

增量 Java 编译

Gradle 配备了复杂的增量 Java 编译器,默认情况下处于活动状态。

这为您带来以下好处

  • 增量构建速度更快。

  • 更改的类文件数量尽可能少。不需要重新编译的类在输出目录中保持不变。一个真正有用的示例场景是使用 JRebel — 更改的输出类越少,JVM 可以更快地使用刷新的类。

为了帮助您了解增量编译的工作原理,以下内容提供了高级概述

  • Gradle 将重新编译所有受更改影响的类。

  • 如果一个类已更改或它依赖于另一个受影响的类,则该类被影响。无论另一个类是在同一项目、另一个项目甚至外部库中定义,这都有效。

  • 类的依赖项是从其字节码中的类型引用或通过编译器插件进行的符号分析来确定的。

  • 由于 source-retention 注解在字节码中不可见,因此对 source-retention 注解的更改将导致完全重新编译。

  • 您可以通过应用良好的软件设计原则(如松耦合)来提高增量编译性能。例如,如果您在具体类及其依赖项之间放置一个接口,则仅当接口更改时才重新编译依赖类,而不是在实现更改时重新编译。

  • 类分析缓存在项目目录中,因此在干净签出后的第一次构建可能会较慢。考虑在您的构建服务器上关闭增量编译器。

  • 类分析也是存储在构建缓存中的输出,这意味着如果从构建缓存中获取编译输出,则增量编译分析也将是,并且下一次编译将是增量的。

已知问题

  • 如果您正在使用读取资源的注解处理器(例如,配置文件),则需要将这些资源声明为编译 Task 的输入。

  • 如果资源文件已更改,Gradle 将触发完全重新编译。

  • 使用自定义 executablejavaHome 会禁用某些优化。编译 Task 在编译错误后或 Java 常量更改时不会立即使用增量构建。如果可能,请改用 工具链

  • 具有与包名称不匹配的源结构,虽然对于编译是合法的,但最终可能会在工具链中引起麻烦。如果涉及注解处理和 缓存,则更是如此。

增量注解处理

从 Gradle 4.7 开始,增量编译器也支持增量注解处理。所有注解处理器都需要选择启用此功能,否则它们将触发完全重新编译。

作为用户,您可以在 --info 日志中查看哪些注解处理器正在触发完全重新编译。如果在编译任务上配置了自定义的 executablejavaHome,则增量注解处理将被禁用。

使注解处理器支持增量编译

请首先查看 增量 Java 编译,因为增量注解处理是基于它构建的。

Gradle 支持两种常见的注解处理器类别的增量编译:“隔离型”和“聚合型”。请查阅以下信息以确定哪个类别适合您的处理器。

然后,您可以使用处理器 META-INF 目录中的文件注册您的处理器以进行增量编译。格式为每行一个处理器,处理器类的完全限定名称及其不区分大小写的类别之间用逗号分隔。

示例:注册增量注解处理器

processor/src/main/resources/META-INF/gradle/incremental.annotation.processors
org.gradle.EntityProcessor,isolating
org.gradle.ServiceRegistryProcessor,dynamic

如果您的处理器只能在运行时决定它是否是增量的,您可以在 META-INF 描述符中将其声明为“动态”类型,并使用 Processor#getSupportedOptions() 方法在运行时返回其真实类型。

示例:动态注册增量注解处理器

processor/src/main/java/org/gradle/ServiceRegistryProcessor.java
@Override
public Set<String> getSupportedOptions() {
    return Collections.singleton("org.gradle.annotation.processing.aggregating");
}

这两个类别都有以下限制

  • 它们只能读取 CLASSRUNTIME 保留策略的注解。

  • 如果用户传递了 -parameters 编译器参数,它们才能读取参数名称。

  • 它们必须使用 Filer API 生成文件。以任何其他方式写入文件将导致稍后出现静默失败,因为这些文件将无法正确清理。如果您的处理器这样做,则它不能是增量的。

  • 它们不得依赖于编译器特定的 API,例如 com.sun.source.util.Trees。Gradle 包装了处理 API,因此尝试强制转换为编译器特定类型将失败。如果您的处理器这样做,除非您有某种回退机制,否则它不能是增量的。

  • 如果它们使用 Filer#createResource,则 location 参数必须是 StandardLocation 中的以下值之一:CLASS_OUTPUTSOURCE_OUTPUTNATIVE_HEADER_OUTPUT。任何其他参数都将禁用增量处理。

“隔离型”注解处理器

这是最快的类别,它们独立地查看每个注解元素,为其创建生成的文件或验证消息。例如,EntityProcessor 可以为每个使用 @Entity 注解的类型创建一个 <TypeName>Repository

示例:隔离型注解处理器

processor/src/main/java/org/gradle/EntityProcessor.java
Set<? extends Element> entities = roundEnv.getElementsAnnotatedWith(entityAnnotation);
for (Element entity : entities) {
    createRepository((TypeElement) entity);
}

“隔离型”处理器还有以下附加限制

  • 它们必须基于从其 AST 可访问的信息,为注解类型做出所有决策(代码生成、验证消息)。这意味着您可以分析类型的超类、方法返回类型、注解等,甚至是传递地分析。但是,您不能基于 RoundEnvironment 中不相关的元素做出决策。这样做将导致静默失败,因为稍后将重新编译的文件太少。如果您的处理器需要基于不相关元素的组合做出决策,请将其标记为“聚合型”。

  • 对于使用 Filer API 生成的每个文件,它们必须提供恰好一个源元素。如果提供零个或多个源元素,Gradle 将重新编译所有源文件。

当源文件被重新编译时,Gradle 将重新编译从中生成的所有文件。当源文件被删除时,从中生成的文件也会被删除。

“聚合型”注解处理器

这些处理器可以将多个源文件聚合到一个或多个输出文件或验证消息中。例如,ServiceRegistryProcessor 可以创建一个单独的 ServiceRegistry,其中每个使用 @Service 注解的类型都有一个方法。

示例:聚合型注解处理器

processor/src/main/java/org/gradle/ServiceRegistryProcessor.java
JavaFileObject serviceRegistry = filer.createSourceFile("ServiceRegistry");
Writer writer = serviceRegistry.openWriter();
writer.write("public class ServiceRegistry {");
for (Element service : roundEnv.getElementsAnnotatedWith(serviceAnnotation)) {
    addServiceCreationMethod(writer, (TypeElement) service);
}
writer.write("}");
writer.close();

Gradle 将始终重新处理(但不会重新编译)处理器注册的所有注解文件。Gradle 将始终重新编译处理器生成的任何文件。

许多常用的注解处理器都支持增量注解处理(见下表)。请直接与注解处理器项目联系,以获取最新的信息和文档。
注解处理器 支持起始版本 详情

不适用

不适用

部分支持。

不适用

不适用

DataBinding

隐藏在功能开关之后

Dagger

2.18 功能开关支持,2.24 默认启用

kapt

隐藏在功能开关之后

Toothpick

2.0

不适用

Glide

不适用

Android-State

不适用

Parceler

不适用

Dart 和 Henson

不适用

不适用

不适用

不适用

Requery

不适用

不适用

EclipseLink

不适用

不适用

Immutables

不适用

2.2.0 功能开关支持,2.3.0-alpha02 默认启用

不适用

不适用

DBFlow

不适用

AndServer

不适用

不适用

2.0

不适用

不适用

不适用

隐藏在功能开关之后

不适用

编译避免

如果依赖项目以 ABI 兼容的方式进行了更改(仅其私有 API 发生了更改),则 Java 编译任务将是最新的。这意味着如果项目 A 依赖于项目 B,并且 B 中的一个类以 ABI 兼容的方式更改了(通常,仅更改了方法体),则 Gradle 不会重新编译 A

一些不影响公共 API 并被忽略的更改类型

  • 更改方法体

  • 更改注释

  • 添加、删除或更改私有方法、字段或内部类

  • 添加、删除或更改资源

  • 更改类路径中 jar 包或目录的名称

  • 重命名参数

由于实现细节对于注解处理器很重要,因此必须在注解处理器路径上单独声明它们。Gradle 会忽略编译类路径上的注解处理器。

build.gradle.kts
dependencies {
    // The dagger compiler and its transitive dependencies will only be found on annotation processing classpath
    annotationProcessor("com.google.dagger:dagger-compiler:2.44")

    // And we still need the Dagger library on the compile classpath itself
    implementation("com.google.dagger:dagger:2.44")
}
build.gradle
dependencies {
    // The dagger compiler and its transitive dependencies will only be found on annotation processing classpath
    annotationProcessor 'com.google.dagger:dagger-compiler:2.44'

    // And we still need the Dagger library on the compile classpath itself
    implementation 'com.google.dagger:dagger:2.44'
}

变体感知选择

整套 JVM 插件利用 变体感知解析 来处理使用的依赖项。它们还安装了一组属性兼容性和消除歧义规则,以 配置 JVM 生态系统特有的 Gradle 属性