Gradle的依赖管理引擎称为变体感知 . 在像Apache Maven™这样的传统依赖管理引擎中,依赖关系绑定到在GAV坐标处发布的组件. 这意味着组件的传递依赖项集仅由该组件的GAV坐标确定. 实际解决什么工件都没有关系,依赖关系的集合始终是相同的 . 另外,为组件选择其他工件(例如,使用jdk7工件)很麻烦,因为它需要使用分类器 . 该模型的一个问题是它不能保证全局图的一致性,因为没有与分类器关联的通用语义. 这意味着没有什么可以阻止在类路径上同时具有单个模块的jdk7jdk8版本,因为引擎不知道分类器名称与什么语义相关联.

component model maven
图1. Maven组件模型

除了在GAV坐标处发布的模块的概念外,Gradle还介绍了此模块的变体的概念. 变体对应于在相同GAV坐标处发布的组件的不同"视图". 在Gradle模型中,工件被附加到变量而不是模块. 实际上,这意味着不同的工件可以具有不同的依赖关系集:

component model gradle
图2. Gradle组件模型

这个中间级别将工件和依赖项与变量关联,而不是直接与组件相关联,使Gradle可以正确地建模每个工件的用途.

但是,这引发了有关如何选择变体的问题:当存在多个变体时,Gradle如何知道要选择哪个变体? 实际上,由于使用了属性 ,因此选择了变体,这些属性为变体提供了语义,并有助于引擎获得一致的解析结果 .

由于历史原因,Gradle区分两种组件:

  • 从源构建的本地组件,其变体映射到传出配置

  • 外部组件(在存储库上发布),在这种情况下,该模块要么与Gradle Module Metadata一起发布,并且本机支持变体,要么该模块使用Ivy / Maven元数据,而变体则从元数据派生 .

In both cases, Gradle performs 变体意识选择.

Configuration and variant attributes

本地组件将变体作为传出配置公开,这是消耗性配置 . 发生依赖性解析时,引擎将通过选择其_consumable配置之一来选择传出组件的一种变体.

此规则有2个明显的例外:

  • 每当生产者公开任何消耗品配置

  • 每当使用者明确选择目标配置时

在这种情况下, 绕过变体感知分辨率 .

Attributes are used on both 可解析的配置 (also known as a consumer) and 耗材配置 (on the producer). Adding attributes to other kinds of configurations simply has no effect, as attributes are not inherited between configurations.

依赖项解析引擎的作用是在给定消费者表示的约束的情况下,找到生产者的合适变体 .

这就是属性发挥作用的地方:它们的作用是执行组件的正确变体的选择.

变体与配置

For external components, the terminology is to use the word variants, not configurations. Configurations are a super-set of variants.

这意味着外部组件提供了variant ,它们也具有属性. 但是,有时由于历史原因或由于您使用也具有这种配置概念的Ivy,术语配置可能会泄漏到DSL中.

Visualizing variant information

Gradle提供了一个名为outgoingVariants的报告任务,该任务显示项目的变体及其功能,属性和工件. 从概念上讲,它与dependencyInsight 报告任务相似.

默认情况下, outgoingVariants打印有关所有变体的信息. 它提供了可选参数--variant <variantName>以选择要显示的单个变量. 它还接受-all标志以包含有关旧配置和不建议使用的配置的信息.

这是新生成的java-library项目上的outgoingVariants任务的输出:

> Task :outgoingVariants
--------------------------------------------------
Variant apiElements
--------------------------------------------------
Description = API elements for main.

Capabilities
    - [default capability]
Attributes
    - org.gradle.category            = library
    - org.gradle.dependency.bundling = external
    - org.gradle.jvm.version         = 8
    - org.gradle.libraryelements     = jar
    - org.gradle.usage               = java-api

Artifacts
    - build/libs/variant-report.jar (artifactType = jar)

Secondary variants (*)
    - Variant : classes
       - Attributes
          - org.gradle.category            = library
          - org.gradle.dependency.bundling = external
          - org.gradle.jvm.version         = 8
          - org.gradle.libraryelements     = classes
          - org.gradle.usage               = java-api
       - Artifacts
          - build/classes/java/main (artifactType = java-classes-directory)

--------------------------------------------------
Variant runtimeElements
--------------------------------------------------
Description = Elements of runtime for main.

Capabilities
    - [default capability]
Attributes
    - org.gradle.category            = library
    - org.gradle.dependency.bundling = external
    - org.gradle.jvm.version         = 8
    - org.gradle.libraryelements     = jar
    - org.gradle.usage               = java-runtime

Artifacts
    - build/libs/variant-report.jar (artifactType = jar)

Secondary variants (*)
    - Variant : classes
       - Attributes
          - org.gradle.category            = library
          - org.gradle.dependency.bundling = external
          - org.gradle.jvm.version         = 8
          - org.gradle.libraryelements     = classes
          - org.gradle.usage               = java-runtime
       - Artifacts
          - build/classes/java/main (artifactType = java-classes-directory)
    - Variant : resources
       - Attributes
          - org.gradle.category            = library
          - org.gradle.dependency.bundling = external
          - org.gradle.jvm.version         = 8
          - org.gradle.libraryelements     = resources
          - org.gradle.usage               = java-runtime
       - Artifacts
          - build/resources/main (artifactType = java-resources-directory)


(*) Secondary variants are variants created via the Configuration#getOutgoing(): ConfigurationPublications API which also participate in selection, in addition to the configuration itself.

从中可以看到Java库公开的两个主要变体apiElementsruntimeElements . 注意,主要区别在于org.gradle.usage属性,其值是java-apijava-runtime . 正如他们所指出的,这是消费者的编译类路径上需要的内容与运行时类路径上需要的内容之间的区别.

它还显示了次级变体,这些变体是Gradle项目专有的,未发布. 例如,来自apiElements的辅助变体classes使Gradle在针对java-library项目进行编译时可以跳过JAR创建.

Variant aware matching

让我们以一个lib库的示例为例,该库公开了2个变体:其API(通过名为exposedApi的变体)和运行时(通过名为exposedRuntime Runtime的变体).

关于生产者变体

此处存在变体名称 ,主要用于调试目的并在错误消息中获得更好的显示. 特别是,名称不参与变体的ID :只有其属性参与. 也就是说,要搜索特定的变体, 必须依靠其属性而不是其名称.

组件可以公开的变体数量没有限制. 传统上,一个组件会公开一个API和一个实现,但是例如,我们可能也想公开一个组件的测试装置. 也可以为不同的使用者公开不同的API (考虑一下不同的环境,例如Linux与Windows).

消费者需要解释它需要什么变体,这是通过在消费者上设置属性来完成的.

属性由名称对组成. 例如,Gradle附带了一个名为org.gradle.usage的标准属性,专门用于处理根据使用者的使用情况(编译,运行时...)选择组件的正确变体的概念. 但是可以定义任意数量的属性. 作为生产者,我们可以通过将(org.gradle.usage,JAVA_API)属性附加到变量来表示消耗性配置表示组件的API. 作为使用者,我们可以通过将(org.gradle.usage,JAVA_API)属性附加到它来表示需要可解析配置的依赖项的API. 为此,Gradle可以通过查看配置属性来自动选择适当的变体

  • 消费者想要org.gradle.usage=JAVA_API

  • 生产者lib 2种不同的变体. 一个使用org.gradle.usage=JAVA_API ,另一个使用org.gradle.usage=JAVA_RUNTIME .

  • Gradle选择生产者的org.gradle.usage=JAVA_API变体,因为它与消费者属性匹配

换句话说:属性用于基于属性的值执行选择.

一个更详尽的示例涉及多个属性. 通常,Gradle中的Java库项目将涉及4个不同的属性,在生产者和消费者方面都可以找到:

  • org.gradle.usage ,说明变体是组件的API还是其实现

  • org.gradle.dependency.bundling ,它声明如何捆绑组件的依赖项(例如,如果工件是一个胖子,那么捆绑是EMBEDDED

  • org.gradle.libraryelements ,用于解释该变体包含库的哪些部分 (类,资源或所有内容)

  • org.gradle.jvm.version ,用于解释此变体针对的最低 Java 版本 .

现在,假设我们的库具有两种不同的风格:

  • 一个用于JDK 8

  • 一个用于JDK 9+

通常,在Maven中,这是通过产生两种不同的工件("主"工件和"分类"工件)来实现的. 但是,在Maven中,使用者无法表达其需要基于运行时的最合适版本的库这一事实.

使用Gradle,可以通过让生产者声明2个变体来优雅地解决此问题:

  • 一个具有org.gradle.jvm.version=8 ,适用于至少在JDK 8上运行的使用者

  • 从JDK 9开始的用户,其org.gradle.jvm.version=9 9

请注意,这两个变体的工件会有所不同,但它们的依存关系也可能会有所不同. 通常,JDK 8变体可能需要JDK 9+的"反向端口"库才能工作,只有在JDK 8上运行的使用者才能使用.

在使用者方面, 可解析配置将在上面设置所有四个属性,并根据运行时将其org.gradle.jvm.version设置为8或更高.

有关变体兼容性的说明

如果使用者将org.gradle.jvm.version设置为7怎么办?

然后解析将失败,并显示一条错误消息,说明生产者没有匹配的变体. 这是因为Gradle认识到消费者需要Java 7兼容的库,但是生产者上可用的Java的最低版本是8.如果另一方面,消费者需要11 ,则Gradle知道89变体都可以.可以,但是它将选择9,因为它是最高兼容版本.

Variant selection errors

在标识组件的正确变体的过程中,两种情况将导致分辨率错误:

  • 生产者的多个变体与消费者属性相匹配,存在变体歧义

  • 生产者的任何变体都不符合消费者属性

Dealing with ambiguous variant selection errors

模棱两可的变量选择看起来如下所示:

> Could not resolve all files for configuration ':compileClasspath'.
   > Could not resolve project :lib.
     Required by:
         project :ui
      > Cannot choose between the following variants of project :lib:
          - feature1ApiElements
          - feature2ApiElements
        All of them match the consumer attributes:
          - Variant 'feature1ApiElements' capability org.test:test-capability:1.0:
              - Unmatched attribute:
                  - Found org.gradle.category 'library' but wasn't required.
              - Compatible attributes:
                  - Required org.gradle.dependency.bundling 'external' and found compatible value 'external'.
                  - Required org.gradle.jvm.version '11' and found compatible value '11'.
                  - Required org.gradle.libraryelements 'classes' and found compatible value 'jar'.
                  - Required org.gradle.usage 'java-api' and found compatible value 'java-api'.
          - Variant 'feature2ApiElements' capability org.test:test-capability:1.0:
              - Unmatched attribute:
                  - Found org.gradle.category 'library' but wasn't required.
              - Compatible attributes:
                  - Required org.gradle.dependency.bundling 'external' and found compatible value 'external'.
                  - Required org.gradle.jvm.version '11' and found compatible value '11'.
                  - Required org.gradle.libraryelements 'classes' and found compatible value 'jar'.
                  - Required org.gradle.usage 'java-api' and found compatible value 'java-api'.

可以看到,显示了所有兼容的候选变体及其属性. 然后将它们分为两个部分:

  • 首先介绍不匹配的属性,因为它们可能是选择适当变体时缺少的部分.

  • 其次显示兼容的属性,因为它们指示消费者想要什么以及这些变体如何匹配该请求.

不能有任何不匹配的属性,因为那时变体将不是候选者. 同样,显示的变体集也排除了已消除歧义的变体.

在上面的示例中,解决方法不在于属性匹配,而在于功能匹配 ,这些功能显示在变量名称旁边. 因为这两个变体有效地提供了相同的属性和功能,所以它们不会被歧义. 因此,在这种情况下,此修补程序最有可能在生产者端( project :lib )提供不同的功能,并在消费者方面( project :ui )表达功能选择.

Dealing with no matching variant errors

没有匹配的变体错误如下所示:

> No variants of project :lib match the consumer attributes:
  - Configuration ':lib:compile':
      - Incompatible attribute:
          - Required artifactType 'dll' and found incompatible value 'jar'.
      - Other attribute:
          - Required usage 'api' and found compatible value 'api'.
  - Configuration ':lib:compile' variant debug:
      - Incompatible attribute:
          - Required artifactType 'dll' and found incompatible value 'jar'.
      - Other attributes:
          - Found buildType 'debug' but wasn't required.
          - Required usage 'api' and found compatible value 'api'.
  - Configuration ':lib:compile' variant release:
      - Incompatible attribute:
          - Required artifactType 'dll' and found incompatible value 'jar'.
      - Other attributes:
          - Found buildType 'release' but wasn't required.
          - Required usage 'api' and found compatible value 'api'.

可以看出,显示了所有候选变体及其属性. 然后将它们分为两个部分:

  • 首先介绍不兼容的属性,因为它们通常是理解为什么无法选择变体的关键.

  • 其次显示其他属性,其中包括必需的兼容的属性,以及消费者不要求的所有其他生产者属性.

与模棱两可的变量错误类似,目标是了解要选择哪个变量,并查看可以针对使用者调整哪些属性或功能以实现此目的.

Mapping from Maven/Ivy to variants

既不的Maven也不常春藤具有变体 ,其仅由本机模块摇篮元数据支持的概念. 但是,由于不同的策略,它不会阻止Gradle与他们合作.

与Gradle模块元数据的关系

Gradle模块元数据是在Maven,Ivy或其他类型的存储库上发布的模块的元数据格式. 它类似于pom.xmlivy.xml文件,但是这种格式可以识别出variants . 这意味着,如果您的项目产生其他变体,则这些变体将作为模块元数据的一部分提供并发布,从而极大地改善用户体验.

有关更多信息,请参见{metadata-file-spec} [Gradle模块元数据规范].

Mapping of POM files to variants

在Maven存储库中发布的模块将转换为可识别变体的模块. Maven模块的特殊之处在于,无法知道发布了哪种组件. 特别是,没有办法在代表平台的BOM和用作超级POM的BOM之间进行区分.有时,POM文件甚至有可能同时充当平台库.

因此,Maven模块分为6个不同的变体,使Gradle用户可以准确地解释它们所依赖的内容:

  • 2个"库"变体(属性org.gradle.category = library

    • compile变量映射<scope>compile</scope>依赖项. 此变体等效于Java库插件apiElements变体. 此范围的所有依赖项均被视为API依赖项 .

    • runtime变量同时映射<scope>compile</scope><scope>runtime</scope>依赖项. 此变体等效于Java库插件runtimeElements变体. 这些作用域的所有依赖关系都被视为运行时依赖关系 .

      • 在这两种情况下, <dependencyManagement>依赖项都不会转换为约束

  • <dependencyManagement>块派生的4个"平台"变体(属性org.gradle.category = platform ):

    • platform-compile变体将<scope>compile</scope>依赖管理依赖项映射为依赖项约束 .

    • platform-runtime变体将<scope>compile</scope><scope>runtime</scope>依赖管理依赖关系都映射为依赖关系约束 .

    • enforced-platform-compile类似于platform-compile但是所有约束都是强制的

    • enforced-platform-runtime类似于platform-runtime但是所有约束都是强制性的

通过查看手册的" 导入BOM表"部分,您可以了解有关平台和强制平台变体用法的更多信息. 默认情况下,每当您声明对Maven模块的依赖关系时,Gradle都会查找library变体. 但是,使用platformenforcedPlatform关键字,Gradle现在正在寻找"平台"变体之一,该变体允许您从POM文件而不是从依赖关系中导入约束.

Mapping of Ivy files to variants

Maven相反,默认情况下没有为Ivy文件实现派生策略. 这样做的原因是,与pom相反,Ivy是一种灵活的格式,它允许您发布任意多个定制的配置 . 因此,通常在Ivy中没有编译/运行时范围或编译/运行时变体的概念. 仅当您使用Gravy发行版ivy-publish插件发布ivy文件时,您才能获得与pom文件类似的结构. 但是,由于不能保证构建所使用的所有常春藤元数据文件都遵循此模式,因此Gradle无法基于该模式实施派生策略.

但是,如果要为Ivy的编译运行时变体实现派生策略,则可以使用组件元数据rule来实现 . 组件元数据规则API允许您访问ivy配置并基于它们创建变体. 如果您知道您使用的所有ivy模块都已通过Gradle发布,而没有进一步自定义ivy.xml文件,则可以在构建中添加以下规则:

例子1.导出Ivy元数据的编译和运行时变体
build.gradle
class IvyVariantDerivationRule implements ComponentMetadataRule {
    @Inject ObjectFactory getObjects() { }

    void execute(ComponentMetadataContext context) {
        context.details.addVariant("runtimeElements", "default") {
            attributes {
                attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, getObjects().named(LibraryElements, LibraryElements.JAR))
                attribute(Category.CATEGORY_ATTRIBUTE, getObjects().named(Category, Category.LIBRARY))
                attribute(Usage.USAGE_ATTRIBUTE, getObjects().named(Usage, Usage.JAVA_RUNTIME))
            }
        }
        context.details.addVariant("apiElements", "compile") {
            attributes {
                attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, getObjects().named(LibraryElements, LibraryElements.JAR))
                attribute(Category.CATEGORY_ATTRIBUTE, getObjects().named(Category, Category.LIBRARY))
                attribute(Usage.USAGE_ATTRIBUTE, getObjects().named(Usage, Usage.JAVA_API))
            }
        }
    }
}

dependencies {
    components { all(IvyVariantDerivationRule) }
}
build.gradle.kts
open class IvyVariantDerivationRule : ComponentMetadataRule {
    @Inject open fun getObjects(): ObjectFactory = throw UnsupportedOperationException()

    override fun execute(context: ComponentMetadataContext) {
        context.details.addVariant("runtimeElements", "default") {
            attributes {
                attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, getObjects().named(LibraryElements.JAR))
                attribute(Category.CATEGORY_ATTRIBUTE, getObjects().named(Category.LIBRARY))
                attribute(Usage.USAGE_ATTRIBUTE, getObjects().named(Usage.JAVA_RUNTIME))
            }
        }
        context.details.addVariant("apiElements", "compile") {
            attributes {
                attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, getObjects().named(LibraryElements.JAR))
                attribute(Category.CATEGORY_ATTRIBUTE, getObjects().named(Category.LIBRARY))
                attribute(Usage.USAGE_ATTRIBUTE, getObjects().named(Usage.JAVA_API))
            }
        }
    }
}

dependencies {
    components { all<IvyVariantDerivationRule>() }
}

规则创建apiElements变体基于所述compile结构和runtimeElements变体基于所述default每个常春藤模块的结构. 对于每个变体,它都设置了相应的Java生态系统属性 . 变体的依赖性和伪影来自基础配置. 如果不是所有消耗的常春藤模块都遵循此模式,则可以调整规则或仅将规则应用于选定的一组模块.

对于没有体都常春藤模块,摇篮回落到原有配置选择(即摇篮执行这些模块的变体意识到分辨率). 这意味着将选择default配置或在对相应模块的依赖性中显式定义的配置. (请注意,只能从构建脚本或常春藤元数据中进行显式配置选择,应避免选择变体.)