Contents

  • 配置和变种属性
  • 可视化变种信息
  • 变种感知匹配
  • 变种选择错误
  • 将Maven / lvy 映射为变种

Gradle 的依赖管理引擎称为变种感知 .. 在传统的依赖管理引擎类似于Apache Maven,依赖是绑定到一个具有GAV 坐标的发布的组件 …这意味着一个组件的传递性依赖集合 是由这个组件的GAV 坐标 完全确定 ..
实际解决了什么工件并不重要,依赖关系集总是相同的。
除此之外,选择一个组件的不同工件(例如 jdk7工件) 是不方便的 因为它需要使用分类标识符 .. 这种模型的缺陷是它不能够保证全图一致性,因为目前没有和分类器有任何联系的语义 …
这就意味着这里没有任何事情将被禁止,例如在类路径上可以同时包含单个模块的jdk7和jdk8的版本 … 因为引擎不知道和分类器名称关联的语义是什么 …
以下是Maven 组件模型:
理解变种选择 - 图1
Gradle 中,除了使用GAV坐标的发布模块的概念之外,引入了模块变种的改变 ..
一个组件的不同视图对应了一个变种它使用相同的GAV坐标发布 …
在Gradle 模型中,工件将依附于变种,不是模块 … 那就意味着事实上不同的工件可以有不同的依赖集合 。。
以下是Gradle 模型:
理解变种选择 - 图2
中间级 用来和工件以及依赖进行关联到变种而不是直接到组件 … 允许gradle 知道模块中的每一个工件用来干什么 ..
然而,这抛出了一个问题 - 变种如何选择: Gradle 如何知道哪一个变种应该被选择(当这里候选有多个的时候) …
事实上,变种选择需要感谢属性的使用,它为变种提供了语义并且帮助引擎达到一致的解析结果 …
由于历史原因,Gradle 区分了两种不同的组件:

  • 本地组件,从源构建,其变体映射到传出配置 .
  • 外部组件,发布到仓库的 ..在这些情况下要么模块使用Gradle 模块元数据进行发布 并且变种是原生支持的,或者 模块使用lvy / Maven 元数据发布 并且 从元数据衍生变种 ..

以上两种情况下,Gradle 执行变种感知选择 …

配置和变种属性

本地组件暴露变种为输出配置,它们是可消费的配置 …(所以它们应该是生产者) …
当依赖解析触发时,引擎将会选择一个输出组件的变种(通过选择它的一个可消费配置) ..

这个规则有两个注意的除外:

  • 每当生产者不公开任何可消费配置时
  • 每当消费者显式的选择一个目标配置

这些情况下,变种感知解析都会被绕过 ..

属性同时用在可解析配置(也称为消费者) 以及可消费配置(称为生产者) ..
增加属性到其他类型的配置简单且无副作用,因为属性不会再配置之间继承 …
依赖解析引擎的角色是发现一个合适的生产者变种 (它们由消费者给定的约定表达)
这就是属性发挥作用的地方:它们的作用是选择组件的正确变体。

变种 vs 配置 对于外部组件,术语是使用变体一词,而不是配置。配置是变体的超集。 这意味着一个外部组件提供变种,同样也拥有属性 …然而某些时候这个属性 有时,由于历史原因,术语配置可能会泄漏到 DSL 中或者因为你使用lvy - 这同样这些配置的概念 …

可视化变种信息

Gradle 提供了一个报告任务 - 叫做 outgoingVariants 用来展示项目的变种 … 包括它的能力,属性以及工件 …
它的概念类似于 dependencyInsight 报告任务 ..
默认来说,outgoingVariants 打印全部变种的信息,它提供了可选的参数 —variant 去选择一个单独的变种进行显示 …
它同样也接收 —all 标志去包括遗留的和废弃的配置信息 ..
例如
使用outogoingVariants 任务 展示java-library项目的变种信息 …

  1. > Task :outgoingVariants
  2. --------------------------------------------------
  3. Variant apiElements
  4. --------------------------------------------------
  5. Description = API elements for main.
  6. Capabilities
  7. - [default capability]
  8. Attributes
  9. - org.gradle.category = library
  10. - org.gradle.dependency.bundling = external
  11. - org.gradle.jvm.version = 8
  12. - org.gradle.libraryelements = jar
  13. - org.gradle.usage = java-api
  14. Artifacts
  15. - build/libs/variant-report.jar (artifactType = jar)
  16. Secondary variants (*)
  17. - Variant : classes
  18. - Attributes
  19. - org.gradle.category = library
  20. - org.gradle.dependency.bundling = external
  21. - org.gradle.jvm.version = 8
  22. - org.gradle.libraryelements = classes
  23. - org.gradle.usage = java-api
  24. - Artifacts
  25. - build/classes/java/main (artifactType = java-classes-directory)
  26. --------------------------------------------------
  27. Variant runtimeElements
  28. --------------------------------------------------
  29. Description = Elements of runtime for main.
  30. Capabilities
  31. - [default capability]
  32. Attributes
  33. - org.gradle.category = library
  34. - org.gradle.dependency.bundling = external
  35. - org.gradle.jvm.version = 8
  36. - org.gradle.libraryelements = jar
  37. - org.gradle.usage = java-runtime
  38. Artifacts
  39. - build/libs/variant-report.jar (artifactType = jar)
  40. Secondary variants (*)
  41. - Variant : classes
  42. - Attributes
  43. - org.gradle.category = library
  44. - org.gradle.dependency.bundling = external
  45. - org.gradle.jvm.version = 8
  46. - org.gradle.libraryelements = classes
  47. - org.gradle.usage = java-runtime
  48. - Artifacts
  49. - build/classes/java/main (artifactType = java-classes-directory)
  50. - Variant : resources
  51. - Attributes
  52. - org.gradle.category = library
  53. - org.gradle.dependency.bundling = external
  54. - org.gradle.jvm.version = 8
  55. - org.gradle.libraryelements = resources
  56. - org.gradle.usage = java-runtime
  57. - Artifacts
  58. - build/resources/main (artifactType = java-resources-directory)

二类变种是通过Configuration#getOutgoing 创建的 …
ConfigurationPublications API 同样会参与选择,除此配置自己本身之外 …
从这里你能够看到两种主要的变种(它们由java 库进行暴露) … apiElements 以及 runtimeElements … 注意到它们的主要不同在于org.gradle.usage 属性 … 分别是
java-api / java-runtime …
正如他们所指出的,这就是消费者的编译类路径上需要的内容与运行时类路径上需要的内容之间的区别所在。
同样它也会展示二类的变种,它们从Gradle 项目中排除了并且没有发布 … 例如 第二类变种来自 apiElements 的 classes 当编译java-library 项目的时候 允许Gradle 跳过Jar 创建 ..

变种感知匹配

上述的例子中lib库它暴露了两个变种: 它的API(名为exposedApi) 以及它的运行时(叫做exposedRuntime)

关于生产者变种 变种的名称一般是用于调试的并且能够很好的在错误消息中显示 … 这个名称,尤其是,并不会参与到变种的id中.. 仅仅它的属性会这样做而已 .. 为了查找一个特殊的变种,那么必须依靠它的属性而不是名称 … 一个组件暴露的变种数量没有限制, 传统情况,一个组件可能会暴露API 和它的实现 … 但是我们可能不止,例如我们想要暴露一个组件的测试装置 … 它同样需要为不同的消费者暴露不同的APIs(例如不同的环境,Linux / Windows)

属性由名称和属性值组成 ..
例如Gradle 具有一个属性名 org.gradle.usage 基于消费者的使用(例如compile / runtime)用来处理一个组件选择正确变种的概念 ..
也有可能定义任意数量的属性 … 作为生产者,我们也能够表示一个可消费的配置(通过抓取org.gradle.usage,JAVA_API)的属性到这个变体上呈现组件的API …
作为消费者,我们能够表达我们想要使用一个可解析配置的依赖的API(通过 依附org.gradle.usage,JAVA_API)到这个变体的属性上 … 做完这些,Gradle已经有能力自动选择合适的变体(通过查询配置的属性) ..

  • 消费者想要 org.gradle.usage = JAVA_API
  • 生产者,lib 暴露两个不同的变种,一个是org.gradle.usage=JAVA_API ,另一个是 org.gradle.usage = JAVA_RUNTIME ..
  • Gradle选择org.gradle.usage=JAVA_API 的生产者的变种,因为它匹配消费者属性 ..

换句话说,属性被用来执行选择(基于属性的值) ..
一个更加全面的例子涉及到多个属性,通常一个Java 库项目将涉及到4个不同的属性 …
消费者和生产者两端同时涉及:

  • org.gradle.usage 阐述了变种是一个组件的API,或者它的实现
  • org.gradle.dependency.bundling 它声明了组件的依赖是如何打包的(例如 工件是一个庞大的jar,那么这个bundling 等于 EMBEDDED)
  • org.gradle.libraryelements,被用来解释这个变种包含库的那一部分…(类 / 资源 或者所有)
  • org.gradle.jvm.version 被用来解释指定这个变种的java 最小语言版本 ..

现在想想我们的库有两个不同的风格:

  • JDK 8
  • JDK 9+

在Maven 中,通过产生两个不同的工件 .. 一个 “main” 工件 以及 一个 “classified” …
然而在Maven 中一个消费者不能够表达这个事实(它基于运行时获取最合适的库版本). ..
在Gradle中,这优雅的通过让生产者声明两个变种即可解决:

  • 一个 org.gradle.jvm.version=8,针对消费者运行至少在 JDK8上 ..
  • 一个 org.gradle.jvm.version = 9,消从JDK9 之上开始的费者

注意到两个变种的工件都所有不同,于是它们的依赖也应该有所不同 …
通常,JDK 8 变体可能需要 JDK 9+ 的“反向移植”库才能工作,那么仅仅运行在JDK8上的消费者才会需要它 ..
消费者这端,可解析的配置将设置上面的所有属性,依赖于运行时,将设置 org.gradle.jvm.version 为 8 或者更高 ..

变体的兼容性注意: 如果设置 org.gradle.jvm.version = 7 ? 那么这个解析将会失败并且有明确的错误消息解释它为什么没有匹配生产者的变种 … 这是因为Gradle 识别到消费者想要Java 7 兼容库,但是生产者的最小Java可用版本都是 8,另一方面,消费者需要11,然后Gradle 知道8和9变体能够工作,但是它将选择9 因为它是最高的兼容版本 …

变种选择错误

识别一个组件的正确变种的过程中, 两种情况将会导致解析错误:

  • 超过一个变种匹配消费者的属性,出现变种歧义 ..
  • 没有生产者的变种匹配消费者的属性 …. m

    处理 选择歧义错误

    1. > Could not resolve all files for configuration ':compileClasspath'.
    2. > Could not resolve project :lib.
    3. Required by:
    4. project :ui
    5. > Cannot choose between the following variants of project :lib:
    6. - feature1ApiElements
    7. - feature2ApiElements
    8. All of them match the consumer attributes:
    9. - Variant 'feature1ApiElements' capability org.test:test-capability:1.0:
    10. - Unmatched attribute:
    11. - Found org.gradle.category 'library' but wasn't required.
    12. - Compatible attributes:
    13. - Provides org.gradle.dependency.bundling 'external'
    14. - Provides org.gradle.jvm.version '11'
    15. - Required org.gradle.libraryelements 'classes' and found value 'jar'.
    16. - Provides org.gradle.usage 'java-api'
    17. - Variant 'feature2ApiElements' capability org.test:test-capability:1.0:
    18. - Unmatched attribute:
    19. - Found org.gradle.category 'library' but wasn't required.
    20. - Compatible attributes:
    21. - Provides org.gradle.dependency.bundling 'external'
    22. - Provides org.gradle.jvm.version '11'
    23. - Required org.gradle.libraryelements 'classes' and found value 'jar'.
    24. - Provides org.gradle.usage 'java-api'

    正如所见,所有兼容的候选变种都会显示,包含它们的属性 … 这些将分类为两个部分:

  • 不匹配(歧义)的属性将首先展示, 它们缺失了对应的属性

  • 兼容属性显示在第二位,因为它们表明消费者想要什么以及这些变体如何匹配该请求。

这不能够存在不匹配(mismatched)的属性,因为这样变种将不会是候选者 … 类似的,一个显示变种的集合同样排除了已经消除歧义的变体 ..
上面的例子中,为了修复这个问题不再依靠属性匹配 而使用能力就匹配,它将展示到变种名称附近 …
因为这两个变种提供了相同的属性和能力,它们不能够消除歧义,因此这种情况下,需要在生产者(project:lib)中提供不同的能力并在消费者(project:ui)中选择表达式一个能力 …

处理不匹配的变种错误

  1. > No variants of project :lib match the consumer attributes:
  2. - Configuration ':lib:compile':
  3. - Incompatible attribute:
  4. - Required artifactType 'dll' and found incompatible value 'jar'.
  5. - Other compatible attribute:
  6. - Provides usage 'api'
  7. - Configuration ':lib:compile' variant debug:
  8. - Incompatible attribute:
  9. - Required artifactType 'dll' and found incompatible value 'jar'.
  10. - Other compatible attributes:
  11. - Found buildType 'debug' but wasn't required.
  12. - Provides usage 'api'
  13. - Configuration ':lib:compile' variant release:
  14. - Incompatible attribute:
  15. - Required artifactType 'dll' and found incompatible value 'jar'.
  16. - Other compatible attributes:
  17. - Found buildType 'release' but wasn't required.
  18. - Provides usage 'api'

一个不匹配的变种错误看起来像上面这样 ,所有候选的变种将会显示包括它的属性,它们分类为两部分:

  • 不兼容的属性首先呈现,因为它们通常是理解为什么变种没被选择的关键 …
  • 其他属性随后呈现,它们包括了需要的以及兼容性的以及一些生产者有的属性但是消费者没有请求的 ..

类似于歧义变种错误,目的都是理解那些变种能够被选择并且查看这些属性或者能力能够在消费者端进行调整 - 为了正确触发这个流程 ..

映射Maven/lvy 到变体

Maven 和 lvy 都没有变种的概念,仅仅Gradle 模块元数据支持 ..
然而它也没有阻碍Gradle 与它们一起工作 - 感谢它们不同的策略 …

与Gradle 模块元数据的关系 Gradle 模块元数据是发布模块到Maven / lvy 或者其他类型仓库的元数据格式 … 类似于pom.xml 或者ivy.xml 文件,但这种格式知道变体 … 这意味着你的项目可以产生不同的变种 .. 然后这些能够可用且发布为模块元数据的一部分 , 这能够极大的提高用户的体验 ..

查看Gradle 模块元数据规范 了解更多信息 ..

将POM 文件映射为 变种

发布到Maven 仓库的模块将转换为 感知变种的模块 … Maven模块的特点是 没有一种方式知道那种类型组件进行了发布 …
没有办法区分代表平台的 BOM 和用作超级 POM 的 BOM。有时,POM 文件甚至可以同时充当平台和库。
因此,Maven 模块衍生为6种明确的变种,Gradle 用户能够完全指定它们依赖于什么:

  • 2个库”变种”(属性 org.gradle.category = library)
    • compile 变种映射compile依赖 … 这个变种等价于 Java 库插件的apiElements 变种,这个范围内的所有依赖考虑为API 依赖 …
    • runtime 变种 映射 compile 以及 runtime依赖 .. 这个变种等价于java 库插件的runtimeElements 变种,这些范围的依赖都认为是运行时依赖 …
      • 上面的两种情况中, 依赖不会转变为约束 ..
  • 4个平台变种 - 根据块衍生(org.gradle.category=platform)
    • platfom-compile 变种映射为 compile 依赖管理依赖作为依赖约束 ..
    • platform-runtime 变种同时映射compile 以及 runtime 依赖管理依赖作为依赖约束 …
    • enforced-platform-compile 类似于 platform-compile,但是所有的约定都是强制性的 …
    • enforced-platform-runtime 类似于platform-runtime ,但是所有的约定都是强制性的 …

你能够理解更多关于使用平台和强制平台变种的更多信息 - 通过查看 importing BOMS.
默认来说,无论何时你在Maven 模块中声明一个以来,Gradle 将查询library 变种,然而使用platform 或者 enforcedPlatform 关键字,Gradle 现在会查询”platform”变种之一 ,这允许你从POM 文件中导入约束,而不是依赖 …

映射lvy文件到 变种

对比Maven,在lvy文件中默认没有衍生的策略实现 … 原因是,对比pom,lvy是一个灵活的格式允许你发布任意多个以及定制化的配置 ..
因此通常lvy中这里没有compile /runtime 标注范围 或者 compile / runtime 变种 …
仅仅如果你使用ivy-publish plugin 使用gradle 去发布ivy 文件,你会得到一个类似于pom文件接口的结构 .. 但是并没有保证所有的元数据文件在一个构建中遵从此模式进行消费 .. Gradle 不能根据它 强制衍生一个策略 ..
但是,如果你想要为lvy 实现comile / runtime 变种实现一个衍生策略,你能够通过component metada rule 这样做 ..
组件元数据规则API 允许你访问 ivy configurations并且基于它们创建变种 …
如果你知道你需要消费的所有ivy 模块 已经通过Gradle 发布而没有深度的ivy.xml文件定制,你能够增加以下的规则到你的构建中 ..

从lvy 元数据衍生 编译 / 运行时 变种

  1. abstract class IvyVariantDerivationRule implements ComponentMetadataRule {
  2. final LibraryElements jarLibraryElements
  3. final Category libraryCategory
  4. final Usage javaRuntimeUsage
  5. final Usage javaApiUsage
  6. @Inject
  7. IvyVariantDerivationRule(ObjectFactory objectFactory) {
  8. jarLibraryElements = objectFactory.named(LibraryElements, LibraryElements.JAR)
  9. libraryCategory = objectFactory.named(Category, Category.LIBRARY)
  10. javaRuntimeUsage = objectFactory.named(Usage, Usage.JAVA_RUNTIME)
  11. javaApiUsage = objectFactory.named(Usage, Usage.JAVA_API)
  12. }
  13. void execute(ComponentMetadataContext context) {
  14. // This filters out any non Ivy module
  15. if(context.getDescriptor(IvyModuleDescriptor) == null) {
  16. return
  17. }
  18. context.details.addVariant("runtimeElements", "default") {
  19. attributes {
  20. attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarLibraryElements)
  21. attribute(Category.CATEGORY_ATTRIBUTE, libraryCategory)
  22. attribute(Usage.USAGE_ATTRIBUTE, javaRuntimeUsage)
  23. }
  24. }
  25. context.details.addVariant("apiElements", "compile") {
  26. attributes {
  27. attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarLibraryElements)
  28. attribute(Category.CATEGORY_ATTRIBUTE, libraryCategory)
  29. attribute(Usage.USAGE_ATTRIBUTE, javaApiUsage)
  30. }
  31. }
  32. }
  33. }
  34. dependencies {
  35. components { all(IvyVariantDerivationRule) }
  36. }

这个角色基于compile 配置创建了apiElements变种并且 基于 每一个ivy模块的default 配置创建了一个runtimeElements 变种 …
对于每一个变种,它设置了相应的Java 生态系统属性 ..
一个变种的依赖和工件 来源于底层的配置 … 如果这个模式没有消费所有的ivy 模块,那么这个规则应该被调整或者仅仅应用到已选择的模块上 …
对于没有变种的所有ivy 模块,Gradle 将会降级处理使用遗留的配置选择(例如Gradle 不会为这些模块执行变种感知解析) …
这意味着要么default 配置或者在依赖中显式定义的配置相关的模块被选择 ..
(注意显式的配置选择仅仅可能来源于构建脚本或者ivy 元数据,并且应该避免有利于变体选择)…