Gradle模块元数据简介及常用场景概述

242 阅读6分钟

Gradle模块元数据在Gradle 5.3中达到了1.0,这是一件大家都值得兴奋的事情。

Gradle模块元数据的创建是为了解决多年来困扰依赖性管理的许多问题,特别是在Java生态系统中,但不限于此。它特别重要,因为POM文件(或Ivy文件)根本不足以描述当今软件的现实,在这种情况下,你可能需要区分不同平台的二进制文件,或者在有多个API可用时选择一个特定的实现。

我们将在这篇文章的后面描述更多的例子 有些问题可能有变通的方法,但这些变通方法是黑客式的,甚至是容易出错的。 例如,你是否意识到这些问题:为不同的Java版本使用分类器,排除法以避免特定的日志绑定,或者仅仅因为你需要覆盖一个特定的版本而添加第一层依赖?

Gradle Module Metadata 1.0是对这些问题的回答,也是我们整个行业向更好的依赖管理迈出的第一步。

它在实践中允许什么?

当你在classpath上同时拥有guava-jdk5guava-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

我们欢迎早期采用者,请随时提供反馈意见