Gradle模块元数据在Gradle 5.3中达到了1.0,这是一件大家都值得兴奋的事情。
Gradle模块元数据的创建是为了解决多年来困扰依赖性管理的许多问题,特别是在Java生态系统中,但不限于此。它特别重要,因为POM文件(或Ivy文件)根本不足以描述当今软件的现实,在这种情况下,你可能需要区分不同平台的二进制文件,或者在有多个API可用时选择一个特定的实现。
我们将在这篇文章的后面描述更多的例子 有些问题可能有变通的方法,但这些变通方法是黑客式的,甚至是容易出错的。 例如,你是否意识到这些问题:为不同的Java版本使用分类器,排除法以避免特定的日志绑定,或者仅仅因为你需要覆盖一个特定的版本而添加第一层依赖?
Gradle Module Metadata 1.0是对这些问题的回答,也是我们整个行业向更好的依赖管理迈出的第一步。
它在实践中允许什么?
当你在classpath上同时拥有guava-jdk5 和guava-jdk8 ,而你的应用程序只是因为幸运的条目排序而工作时,你是否曾经生气地咒骂过? 你是否曾经面临过有不同的SLF4J绑定,而在运行时才注意到的问题? 那是因为这些库有不同的变体,无法通过现有的元数据格式正确描述。一个构建工具甚至应该如何理解一个jdk8 JAR、一个sources 、甚至一个all 之间的区别呢? Gradle模块元数据的设计是为了解释这种区别,使消费者可以表达更精确的需求。例如,消费者可以特别要求他们可以在JDK 8下使用。 而在SLF4J的情况下,构建工具会认识到Log4J的绑定与java.util.logging 的绑定是相互排斥的。
整个想法是支持变体感知的依赖性管理,这是基于对一个组件的变体的描述--如主二进制文件、源码包、平台特定的二进制文件等--以及它们相应的依赖性。
我们的一些合作伙伴已经使用Gradle元数据几个月了。例如,Kotlin native正在使用Gradle Module Metadata来表示当把一个Kotlin项目编译到不同的架构时可以得到的不同的二进制文件。 Google正在使用变量感知的依赖性管理,但缺乏一个 "外部模型"。Gradle模块元数据就是那个外部模型,它将允许正确地发布Android档案(AAR)。
这些只是例子,但这些问题以及更多的问题都可以通过利用Gradle元数据来解决。随着更多的库作者采用Gradle模块元数据,我们的行业将作为一个整体解决更多的问题。
Gradle模块元数据如何影响你
Gradle Module Metadata 1.0为所有Gradle用户实现了精细的依赖关系解决。 从Gradle 5.3开始,如果你是一个消费者,并且你使用的库有Gradle元数据发布,Gradle将自动消费任何发布到Maven或Ivy仓库的Gradle元数据。
然而,Gradle 5.3默认不会自动发布--这将在6.0中出现。你现在就可以发布Gradle模块元数据,但你必须通过使用Maven Publish或Ivy Publish插件来选择发布,并通过在设置脚本中添加以下一行来启用实验性发布功能。
settings.gradle(.kts)
enableFeaturePreview("GRADLE_METADATA")
Gradle模块元数据对Maven或Ant+Ivy的构建有何影响?
对于Maven和Ivy消费者来说,没有任何变化:如果你选择了发布Gradle模块元数据,相应的文件会与POM文件(如果你发布到Maven仓库)或Ivy文件(如果你发布到Ivy仓库)一起发布。请注意,从构建组件到Maven和Ivy元数据的映射是有损失的:例如,你不知道用什么Java版本来构建什么,所以这使得消费者无法事先知道它们是否兼容。另一个例子是,当你使用Gradle的特定功能,如丰富的版本。我们尽力将它们映射到Maven或Ivy的概念中,但由于它们的元数据格式的限制,信息还是会在这个过程中丢失。
请注意,当Gradle 5.2发布插件知道映射对其他构建工具来说是有损失或有问题,它会发出的警告。
链接和参考文献
Gradle模块元数据是一个JSON文件,其扩展名是.module 。每个文件都描述了一个具有零个或多个变体的单一软件组件。下面是一个 "com.acme:client:1.0-SNAPSHOT "组件的元数据文件示例的内容,该组件有几个变体:
{
"formatVersion": "1.0",
"component": {
"group": "com.acme",
"module": "client",
"version": "1.0-SNAPSHOT",
"attributes": {
"org.gradle.status": "integration"
}
},
"createdBy": {
"gradle": {
"version": "5.3",
"buildId": "4wqjtkcv2fbmjjsewyu66wbvfq"
}
},
"variants": [
{
"name": "apiElements",
"attributes": {
"org.gradle.dependency.bundling": "external",
"org.gradle.jvm.version": 11,
"org.gradle.usage": "java-api-jars"
},
"dependencies": [
{
"group": "com.mycompany",
"module": "core",
"version": {
"requires": "2.5"
}
}
],
"files": [
{
"name": "client-1.0-SNAPSHOT.jar",
"url": "client-1.0-SNAPSHOT.jar",
"size": 539,
"sha1": "1f94fe53d33babdc9de537bb3a0108dbc0e25e4b",
"md5": "6364cdd9923e1eda9b328bc80f93969c"
}
]
},
{
"name": "runtimeElements",
"attributes": {
"org.gradle.dependency.bundling": "external",
"org.gradle.jvm.version": 11,
"org.gradle.usage": "java-runtime-jars"
},
"dependencies": [
{
"group": "org.apache.commons",
"module": "commons-lang3",
"version": {
"requires": "3.8"
}
},
{
"group": "com.mycompany",
"module": "core",
"version": {
"requires": "2.5"
}
}
],
"files": [
{
"name": "client-1.0-SNAPSHOT.jar",
"url": "client-1.0-SNAPSHOT.jar",
"size": 539,
"sha1": "1f94fe53d33babdc9de537bb3a0108dbc0e25e4b",
"md5": "6364cdd9923e1eda9b328bc80f93969c"
}
]
},
{
"name": "shadowApiElements",
"attributes": {
"org.gradle.dependency.bundling": "shadowed",
"org.gradle.usage": "java-api"
},
"files": [
{
"name": "client-1.0-SNAPSHOT-all.jar",
"url": "client-1.0-SNAPSHOT-all.jar",
"size": 601730,
"sha1": "9b70e54ffdce0541701d8f855bf75e059857eb0c",
"md5": "3499bb6d9ccf86283854a5550135ea4a"
}
]
},
{
"name": "shadowRuntimeElements",
"attributes": {
"org.gradle.dependency.bundling": "shadowed",
"org.gradle.usage": "java-runtime-jars"
},
"files": [
{
"name": "client-1.0-SNAPSHOT-all.jar",
"url": "client-1.0-SNAPSHOT-all.jar",
"size": 601730,
"sha1": "9b70e54ffdce0541701d8f855bf75e059857eb0c",
"md5": "3499bb6d9ccf86283854a5550135ea4a"
}
]
}
]
}
这个文件声明了4个变体,而属性让构建工具知道它们的用途。特别是你会看到这里有2个 "API "的变体和2个 "运行时 "的变体,而通常你只看到每个变体有一个。原因是这个特殊的组件声明了一个额外的变体,其中的依赖是有阴影的(fat jar)。这让消费者有机会决定它是否需要作为单独的jar的依赖,或者只是库的fatjar变体。
如果你对更多技术细节感兴趣,请参考Gradle模块元数据规范1.0。
我们欢迎早期采用者,请随时提供反馈意见