介绍 Gradle 模块元数据

Gradle 模块元数据在 Gradle 5.3 中达到 1.0 版本,我们在此解释为什么你应该像我们一样兴奋!

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

我们将在本文后面介绍更多示例。一些问题可能存在解决方法,但这些解决方法可能是笨拙的,甚至容易出错。例如,你是否意识到以下问题:使用分类器来区分不同的 Java 版本、排除以避免特定记录器绑定,或者添加一级依赖项仅仅因为你需要覆盖特定版本?

Gradle 模块元数据 1.0 是对这些问题的答案,也是我们行业中迈向更好依赖管理的第一步。

它在实践中允许什么?

您是否曾经在类路径中同时存在 guava-jdk5guava-jdk8 时咒骂过,而您的应用程序仅因条目幸运排序而工作?您是否曾经遇到过拥有不同 SLF4J 绑定并在运行时才注意到这个问题?这是因为这些库具有不同的 *变体*,而现有的元数据格式无法对其进行适当描述。构建工具如何才能理解 jdk8 JAR、sources JAR 甚至 all JAR 之间的区别?Gradle 模块元数据旨在以一种方式解释这种差异,以便使用者可以表达更精确的要求。例如,使用者可以专门要求他们可以使用 JDK 8 的东西。在 SLF4J 的情况下,构建工具将识别出 Log4J 绑定与 java.util.logging 绑定是互斥的。

整个想法是支持变体感知的依赖项管理,它基于对组件变体的描述——例如主二进制文件、源包、特定于平台的二进制文件等等——以及它们相应的依赖项。

我们的一些合作伙伴已经使用 Gradle 元数据几个月了。例如,Kotlin Native 使用 Gradle 模块元数据来表示在将 Kotlin 项目编译到不同架构时可以获得的不同二进制文件。Google 使用 变体感知依赖项管理,但缺乏一个“外部模型”。Gradle 模块元数据就是那个外部模型,它将允许正确发布 Android 存档 (AAR)。

这些只是例子,但这些问题以及更多问题都可以通过利用 Gradle 元数据来解决。随着越来越多的库作者采用 Gradle 模块元数据,我们的行业将作为一个整体解决更多问题。

Gradle 模块元数据如何影响您

Gradle 模块元数据 1.0 为所有 Gradle 用户提供了细粒度的依赖项解析。从 Gradle 5.3 开始,如果您是使用者,并且您使用的库发布了 Gradle 元数据,Gradle 将自动使用发布到 Maven 或 Ivy 存储库的任何 Gradle 元数据。

但是,Gradle 5.3 默认情况下不会自动发布它——这将在 6.0 中实现。您现在可以发布 Gradle 模块元数据,但您必须选择使用 Maven 发布或 Ivy 发布插件并通过在您的设置脚本中添加以下行来启用实验性发布功能

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 个“runtime”变体,而通常您只看到每个变体一个。原因是这个特定的组件声明了一个额外的变体,其中依赖项被阴影(胖 jar)。这为消费者提供了选择依赖项作为单个 jar 还是库的胖 jar 变体的机会。

如果您有兴趣了解更多技术细节,请参阅Gradle 模块元数据规范 1.0

欢迎早期采用者,请随时提供您的反馈

讨论