Gradle支持功能变体的概念:在构建库时,通常只有在存在某些依赖项或使用特殊工件时,某些功能才可用.

功能变体使消费者可以选择他们需要的库的哪些功能 :依赖性管理引擎将选择正确的工件和依赖性.

这考虑到了许多不同的情况(列表并不详尽):

Selection of feature variants and capabilities

声明对组件的依赖性通常是通过提供一组坐标(组,工件,版本也称为GAV坐标)来完成的. 这使引擎可以确定我们要寻找的组件 ,但是这种组件可能提供不同的变体 . 通常根据用途选择一个变体 . 例如,我们可以选择其他变体来针对组件进行编译(在这种情况下,我们需要组件的API),或者在执行代码时(在这种情况下,我们需要组件的运行时). 组件的所有变体都提供许多功能 ,这些功能使用GAV坐标类似地表示.

功能由GAV坐标表示,但您必须将其视为特征描述:

  • "我提供了SLF4J绑定"

  • "我为MySQL提供运行时支持"

  • "我提供一个Groovy运行时"

通常,在图中具有两个提供相同内容的组件是一个问题(它们相互冲突).

这是一个重要的概念,因为:

  • 默认情况下,变体提供与其组件的GAV坐标相对应的功能

  • 如果依赖关系图中提供相同的功能,则不允许它们具有不同的组件或组件的不同变体

  • 只要它们提供不同的功能 ,就可以选择同一组件的两个变体

典型的组件将提供具有默认功能的变体. 例如,Java库公开了提供相同功能的两个变体(API和运行时). 结果,在依赖关系图中同时具有单个组件的API运行时都是错误的.

但是,假设您需要组件的运行时测试夹具 . 然后,只要库的运行时测试夹具变体声明了不同的功能,就可以允许它.

如果这样做,那么使用者将必须声明两个依赖项:

  • 一个关于"主"变体的库

  • 通过要求其功能 ,在"测试装置"变体上安装一个

虽然引擎支持独立于生态系统的功能变体,但此功能当前仅可通过Java插件使用,并且正在开发中.

Declaring feature variants

可以通过应用javajava-library插件来声明功能变体. 以下代码说明了如何声明一个名为mongodbSupport的功能:

例子1.声明一个功能变量
build.gradle
group = 'org.gradle.demo'
version = '1.0'

java {
    registerFeature('mongodbSupport') {
        usingSourceSet(sourceSets.main)
    }
}
build.gradle.kts
group = "org.gradle.demo"
version = "1.0"

java {
    registerFeature("mongodbSupport") {
        usingSourceSet(sourceSets["main"])
    }
}

Gradle将以与Java库插件设置配置非常相似的方式为您自动设置许多东西:

  • 配置mongodbSupportApi ,用于声明此功能的API依赖关系

  • 配置mongodbSupportImplementation ,用于声明此功能的实现依赖项

  • 消费者使用的配置mongodbSupportApiElements来获取此功能的工件和API依赖项

  • 消费者使用的配置mongodbSupportRuntimeElements来获取此功能的工件和运行时依赖项

大多数用户只需要关心前两个配置,即可声明此功能的特定依赖性:

例子2.声明一个功能的依赖
build.gradle
dependencies {
    mongodbSupportImplementation 'org.mongodb:mongodb-driver-sync:3.9.1'
}
build.gradle.kts
dependencies {
    "mongodbSupportImplementation"("org.mongodb:mongodb-driver-sync:3.9.1")
}

按照约定,Gradle将功能名称映射到功能,其功能组和版本分别与主要组件的组和版本相同,但功能名称是主要组件名称,后跟一个-然后是kebab大小写的功能名称.

例如,如果组为org.gradle.demo ,组件的名称为provider ,其版本为1.0且功能名为mongodbSupport ,则功能变体为org.gradle.demo:provider-mongodb-support:1.0 .

如果您自己选择功能名称或向变体添加更多功能,建议遵循相同的约定.

Feature variant source set

In the previous example, we’re declaring a feature variant which uses the 主要来源集. This is a typical use case in the Java ecosystem, where it’s, for whatever reason, not possible to split the sources of a project into different subprojects or different source sets. Gradle will therefore declare the configurations as described, but will also setup the compile classpath and runtime classpath of the 主要来源集 so that it extends from the feature configuration. Said differently, this allows you to declare the dependencies specific to a feature in their own "bucket", but everything is still compiled as a single source set. There will also be a single artifact (the component Jar) including support for all features.

但是,通常最好为功能设置单独的源集 . 然后Gradle将执行类似的映射,但不会使主要组件的编译和运行时类路径从已注册功能的依赖项中扩展. 按照约定,它还将使用与功能的kebab-case名称相对应的分类器,创建一个Jar任务来捆绑从该功能源集构建的类:

示例3.使用单独的源集声明功能变体
build.gradle
sourceSets {
    mongodbSupport {
        java {
            srcDir 'src/mongodb/java'
        }
    }
}

java {
    registerFeature('mongodbSupport') {
        usingSourceSet(sourceSets.mongodbSupport)
    }
}
build.gradle.kts
sourceSets {
    create("mongodbSupport") {
        java {
            srcDir("src/mongodb/java")
        }
    }
}

java {
    registerFeature("mongodbSupport") {
        usingSourceSet(sourceSets["mongodbSupport"])
    }
}

Publishing feature variants

根据元数据文件格式,发布功能变体可能是有损的:

  • 使用Gradle模块元数据 ,所有内容都会发布,消费者将获得功能变体的全部好处

  • 使用POM元数据(Maven),特征变体作为可选的依赖项发布,并且特征变体的工件使用不同的分类器发布

  • 使用常春藤元数据,功能变体将作为额外配置发布, default配置不会对其进行扩展

仅使用maven-publishivy-publish插件支持发布功能变体. Java插件(或Java库插件)将为您注册其他变体,因此不需要其他配置,只需常规出版物即可:

例子4.发布带有特征变量的组件
build.gradle
plugins {
    id 'java-library'
    id 'maven-publish'
}
// ...
publishing {
    publications {
        myLibrary(MavenPublication) {
            from components.java
        }
    }
}
build.gradle.kts
plugins {
    `java-library`
    `maven-publish`
}
// ...
publishing {
    publications {
        create("myLibrary", MavenPublication::class.java) {
            from(components["java"])
        }
    }
}

Adding javadoc and sources JARs

主要Javadoc和源JAR相似,您可以配置添加的功能变量,以便它为Javadoc和源产生JAR. 但是,这仅在使用非主要来源集时才有意义.

Example 5. Producing javadoc and sources JARs for feature variants
build.gradle
java {
    registerFeature('mongodbSupport') {
        usingSourceSet(sourceSets.mongodbSupport)
        withJavadocJar()
        withSourcesJar()
    }
}
build.gradle.kts
java {
    registerFeature("mongodbSupport") {
        usingSourceSet(sourceSets["mongodbSupport"])
        withJavadocJar()
        withSourcesJar()
    }
}

Dependencies on feature variants

如前所述,功能变体在发布时可能有损. 因此,消费者只能在以下情况下依赖功能变体:

  • 有项目依赖项(在多项目构建中)

  • 在可用Gradle Module元数据的情况下,即发布者必须已发布它

  • 在常春藤世界中,通过声明对与功能匹配的配置的依赖

消费者可以通过声明所需的功能来指定需要生产者的特定功能. 例如,如果生产者声明了这样的" MySQL支持"功能:

例子6.声明支持MySQL功能的库
build.gradle
java {
    registerFeature('mysqlSupport') {
        usingSourceSet(sourceSets.main)
    }
}

dependencies {
    mysqlSupportImplementation 'mysql:mysql-connector-java:8.0.14'
}
build.gradle.kts
java {
    registerFeature("mysqlSupport") {
        usingSourceSet(sourceSets["main"])
    }
}

dependencies {
    "mysqlSupportImplementation"("mysql:mysql-connector-java:8.0.14")
}

然后,使用者可以通过执行以下操作来声明对MySQL支持功能的依赖:

示例7.在多项目构建中使用特定功能
build.gradle
dependencies {
    // This project requires the main producer component
    implementation(project(":producer"))

    // But we also want to use its MySQL support
    runtimeOnly(project(":producer")) {
        capabilities {
            requireCapability("org.gradle.demo:producer-mysql-support")
        }
    }
}
build.gradle.kts
dependencies {
    // This project requires the main producer component
    implementation(project(":producer"))

    // But we also want to use its MySQL support
    runtimeOnly(project(":producer")) {
        capabilities {
            requireCapability("org.gradle.demo:producer-mysql-support")
        }
    }
}

这将自动在运行时类路径上引入mysql-connector-java依赖项. 如果存在多个依赖关系,则将它们全部带入,这意味着可以使用功能将有助于功能的依赖关系分组在一起.

类似地,如果带有功能变体的外部库与Gradle Module Metadata一起发布,则可能依赖于该库提供的功能:

示例8.使用外部存储库中的特定功能
build.gradle
dependencies {
    // This project requires the main producer component
    implementation('org.gradle.demo:producer:1.0')

    // But we also want to use its MongoDB support
    runtimeOnly('org.gradle.demo:producer:1.0') {
        capabilities {
            requireCapability("org.gradle.demo:producer-mongodb-support")
        }
    }
}
build.gradle.kts
dependencies {
    // This project requires the main producer component
    implementation("org.gradle.demo:producer:1.0")

    // But we also want to use its MongoDB support
    runtimeOnly("org.gradle.demo:producer:1.0") {
        capabilities {
            requireCapability("org.gradle.demo:producer-mongodb-support")
        }
    }
}

Handling mutually exclusive variants

使用功能作为处理要素的主要优势在于,您可以精确处理变量的兼容性. 规则很简单:

不允许有两个组件变体在单个依赖关系图中提供相同的功能.

我们可以利用它来要求Gradle在用户错误配置依赖项时失败. 例如,假设您的库支持MySQL,Postgres和MongoDB,但只允许同时选择其中之一 . 不允许将其直接转换为"提供相同的功能",因此所有三个功能都必须提供一个功能:

例子9.多个互斥的特征的生产者
build.gradle
java {
    registerFeature('mysqlSupport') {
        usingSourceSet(sourceSets.main)
        capability('org.gradle.demo', 'producer-db-support', '1.0')
        capability('org.gradle.demo', 'producer-mysql-support', '1.0')
    }
    registerFeature('postgresSupport') {
        usingSourceSet(sourceSets.main)
        capability('org.gradle.demo', 'producer-db-support', '1.0')
        capability('org.gradle.demo', 'producer-postgres-support', '1.0')
    }
    registerFeature('mongoSupport') {
        usingSourceSet(sourceSets.main)
        capability('org.gradle.demo', 'producer-db-support', '1.0')
        capability('org.gradle.demo', 'producer-mongo-support', '1.0')
    }
}

dependencies {
    mysqlSupportImplementation 'mysql:mysql-connector-java:8.0.14'
    postgresSupportImplementation 'org.postgresql:postgresql:42.2.5'
    mongoSupportImplementation 'org.mongodb:mongodb-driver-sync:3.9.1'
}
build.gradle.kts
java {
    registerFeature("mysqlSupport") {
        usingSourceSet(sourceSets["main"])
        capability("org.gradle.demo", "producer-db-support", "1.0")
        capability("org.gradle.demo", "producer-mysql-support", "1.0")
    }
    registerFeature("postgresSupport") {
        usingSourceSet(sourceSets["main"])
        capability("org.gradle.demo", "producer-db-support", "1.0")
        capability("org.gradle.demo", "producer-postgres-support", "1.0")
    }
    registerFeature("mongoSupport") {
        usingSourceSet(sourceSets["main"])
        capability("org.gradle.demo", "producer-db-support", "1.0")
        capability("org.gradle.demo", "producer-mongo-support", "1.0")
    }
}

dependencies {
    "mysqlSupportImplementation"("mysql:mysql-connector-java:8.0.14")
    "postgresSupportImplementation"("org.postgresql:postgresql:42.2.5")
    "mongoSupportImplementation"("org.mongodb:mongodb-driver-sync:3.9.1")
}

生产者在这里声明3个变体,每个数据库运行时支持一个变体:

  • mysql-support提供db-supportmysql-support功能

  • postgres-support提供db-supportpostgres-support功能

  • mongo-support提供db-supportmongo-support功能

然后,如果使用者尝试像这样同时获得postgres-supportmysql-support (这也可以传递):

例子10.消费者试图同时使用两个不兼容的变体
build.gradle
dependencies {
    implementation(project(":producer"))

    // Let's try to ask for both MySQL and Postgres support
    runtimeOnly(project(":producer")) {
        capabilities {
            requireCapability("org.gradle.demo:producer-mysql-support")
        }
    }
    runtimeOnly(project(":producer")) {
        capabilities {
            requireCapability("org.gradle.demo:producer-postgres-support")
        }
    }
}
build.gradle.kts
dependencies {
    // This project requires the main producer component
    implementation(project(":producer"))

    // Let's try to ask for both MySQL and Postgres support
    runtimeOnly(project(":producer")) {
        capabilities {
            requireCapability("org.gradle.demo:producer-mysql-support")
        }
    }
    runtimeOnly(project(":producer")) {
        capabilities {
            requireCapability("org.gradle.demo:producer-postgres-support")
        }
    }
}

依赖关系解析将失败,并出现以下错误:

Cannot choose between
   org.gradle.demo:producer:1.0 variant mysqlSupportRuntimeElements and
   org.gradle.demo:producer:1.0 variant postgresSupportRuntimeElements
   because they provide the same capability: org.gradle.demo:producer-db-support:1.0