Modifying and adding variants to existing components for publishing

Gradle的发布模型基于组件的概念,该概念由插件定义. 例如,Java Library插件定义了一个与库相对应的java组件,但是Java Platform插件定义了另一种名为javaPlatform的组件,它实际上是另一种软件组件( 平台 ).

有时我们想向现有组件添加更多变体或修改现有组件的现有变体 . 例如,如果您为其他平台添加了Java库的变体 ,则可能只想在java组件本身上声明此附加变体. 通常,声明其他变体通常是发布其他工件的最佳解决方案.

为了执行此类添加或修改, AdhocComponentWithVariants接口声明了两个方法addVariantsFromConfigurationwithVariantsFromConfiguration ,它们接受两个参数:

  • 用作变体源的传出配置

  • 定制操作,使您可以过滤将要发布的变体

要使用这些方法,必须确保使用的SoftwareComponent本身是AdhocComponentWithVariants ,对于Java插件(Java,Java Library,Java Platform)创建的组件,情况就是如此. 添加变体非常简单:

示例1.向现有软件组件添加变体
InstrumentedJarsPlugin.groovy
        AdhocComponentWithVariants javaComponent = (AdhocComponentWithVariants) project.components.findByName("java")
        javaComponent.addVariantsFromConfiguration(outgoing) {
            // dependencies for this variant are considered runtime dependencies
            it.mapToMavenScope("runtime")
            // and also optional dependencies, because we don't want them to leak
            it.mapToOptional()
        }
InstrumentedJarsPlugin.kt
        val javaComponent = components.findByName("java") as AdhocComponentWithVariants
        javaComponent.addVariantsFromConfiguration(outgoing) {
            // dependencies for this variant are considered runtime dependencies
            mapToMavenScope("runtime")
            // and also optional dependencies, because we don't want them to leak
            mapToOptional()
        }

在其他情况下,您可能想要修改已经由其中一个Java插件添加的变体. 例如,如果激活Javadoc和源的发布,则它们将成为java组件的其他变体. 如果只想发布其中之一,例如只发布Javadoc而没有发布源,则可以将sources变体修改为不发布:

例子2.发布一个带有Javadoc但没有源代码的Java库
build.gradle
java {
    withJavadocJar()
    withSourcesJar()
}

components.java.withVariantsFromConfiguration(configurations.sourcesElements) {
    skip()
}

publishing {
    publications {
        mavenJava(MavenPublication) {
            from components.java
        }
    }
}
build.gradle.kts
java {
    withJavadocJar()
    withSourcesJar()
}

val javaComponent = components["java"] as AdhocComponentWithVariants
javaComponent.withVariantsFromConfiguration(configurations["sourcesElements"]) {
    skip()
}

publishing {
    publications {
        create<MavenPublication>("mavenJava") {
            from(components["java"])
        }
    }
}

Creating and publishing custom components

前面的示例中 ,我们演示了如何扩展或修改现有组件,例如Java插件提供的组件. 但是Gradle还允许您构建自定义组件(不是Java库,不是Java平台,不是Gradle本身支持的东西).

要创建自定义组件,您首先需要创建一个空的自组织组件. 目前,这只能通过插件实现,因为您需要获取SoftwareComponentFactory的句柄:

例子3.注入软件组件工厂
InstrumentedJarsPlugin.groovy
    private final SoftwareComponentFactory softwareComponentFactory

    @Inject
    InstrumentedJarsPlugin(SoftwareComponentFactory softwareComponentFactory) {
        this.softwareComponentFactory = softwareComponentFactory
    }
InstrumentedJarsPlugin.kt
class InstrumentedJarsPlugin @Inject constructor(
    private val softwareComponentFactory: SoftwareComponentFactory) : Plugin<Project> {

Declaring what a custom component publishes is still done via the AdhocComponentWithVariants API. For a custom component, the first step is to create custom outgoing variants, following the instructions in this chapter. At this stage, what you should have is variants which can be used in cross-project dependencies, but that we are now going to publish to external repositories.

Example 4. Creating a custom, adhoc component
InstrumentedJarsPlugin.groovy
        // create an adhoc component
        def adhocComponent = softwareComponentFactory.adhoc("myAdhocComponent")
        // add it to the list of components that this project declares
        project.components.add(adhocComponent)
        // and register a variant for publication
        adhocComponent.addVariantsFromConfiguration(outgoing) {
            it.mapToMavenScope("runtime")
        }
InstrumentedJarsPlugin.kt
        // create an adhoc component
        val adhocComponent = softwareComponentFactory.adhoc("myAdhocComponent")
        // add it to the list of components that this project declares
        components.add(adhocComponent)
        // and register a variant for publication
        adhocComponent.addVariantsFromConfiguration(outgoing) {
            mapToMavenScope("runtime")
        }

首先,我们使用工厂创建一个新的即席组件. 然后,我们通过addVariantsFromConfiguration方法添加一个变体,该变体在上一节中进行了详细描述.

在简单的情况下, Configuration和变体之间存在一对一的映射,在这种情况下,您可以发布从单个Configuration发出的所有变体,因为它们实际上是同一件事. 但是,在某些情况下, Configuration与其他配置发布相关联,我们也将这些发布称为辅助变体 . 这样的配置在跨项目发布用例中有意义,但在外部发布时则没有意义. 例如,在项目之间共享文件目录的情况就是这种情况,但是您无法直接在Maven存储库中发布目录 (仅打包文件,如jar或zip). 查看ConfigurationVariantDetails类,以获取有关如何跳过特定变体的发布的详细信息. 如果已经为配置调用了addVariantsFromConfiguration ,则可以使用withVariantsFromConfiguration进行对所得变体的进一步修改.

当发布像这样的临时组件时:

  • Gradle模块元数据将完全代表已发布的变体. 特别是,所有传出的变体都将继承已发布配置的依赖项,工件和属性.

  • 将生成Maven和Ivy元数据文件,但是您需要声明如何通过ConfigurationVariantDetails类将依赖项映射到Maven范围.

实际上,这意味着Gradle可以像使用"本地组件"一样使用这种方式创建的组件.

Adding custom artifacts to a publication

与其考虑工件,不如考虑Gradle的变体感知模型. 预期单个模块可能需要多个工件. 但是,这种情况很少在这里停下来,如果其他工件代表可选功能 ,它们可能也具有不同的依存关系,甚至更多.

Gradle通过Gradle Module Metadata支持支持其他变体的发布,这些变体使依赖解析引擎知道这些工件. 请参阅文档的变量感知共享部分,以了解如何声明此类变量并查看如何发布自定义组件 .

如果您将其他工件直接附加到发布,则它们将"脱离上下文"发布. 这意味着,它们根本不在元数据中引用,因此只能通过依赖项上的分类器直接解决. 与Gradle Module元数据相比,Maven pom元数据将不包含有关其他工件的信息,无论它们是通过变体添加还是直接添加,因为变体无法以pom格式表示.

以下部分描述了在确保元数据(例如Gradle或POM元数据)与用例无关的情况下如何直接发布工件. 例如,如果您的项目不需要被其他项目占用,并且发布所需的唯一内容就是工件本身.

通常,有两种选择:

  • 仅使用工件创建出版物

  • 根据具有元数据的组件将工件添加到发布中(不建议使用,而是调整组件或使用即席组件发布 ,这都会生成适合您工件的元数据)

要基于工件创建发布,请首先定义一个自定义工件并将其附加到您选择的Gradle 配置 . 以下示例定义了由rpm任务(未显示)产生的RPM工件,并将该工件附加到archives配置中:

例子5.为配置定义一个定制工件
build.gradle
def rpmFile = file("$buildDir/rpms/my-package.rpm")
def rpmArtifact = artifacts.add('archives', rpmFile) {
    type 'rpm'
    builtBy 'rpm'
}
build.gradle.kts
val rpmFile = file("$buildDir/rpms/my-package.rpm")
val rpmArtifact = artifacts.add("archives", rpmFile) {
    type = "rpm"
    builtBy("rpm")
}

来自ArtifactHandlerartifacts.add()方法返回PublishArtifact类型的工件对象,然后可将其用于定义发布,如以下示例所示:

例子6.在发布上附加一个定制的PublishArtifact
build.gradle
publishing {
    publications {
        maven(MavenPublication) {
            artifact rpmArtifact
        }
    }
}
build.gradle.kts
publishing {
    publications {
        create<MavenPublication>("maven") {
            artifact(rpmArtifact)
        }
    }
}
  • artifact()方法接受发布的工件作为参数(如rpmArtifact中的rpmArtifact以及Project.file(java.lang.Object)接受的任何类型的参数,例如File实例,字符串文件路径或归档任务.

  • 发布插件支持不同的工件配置属性,因此请始终查看插件文档以获取更多详细信息. Maven发布插件Ivy发布插件均支持classifierextension属性.

  • 自定义工件需要在出版物内是不同的,通常是通过classifierextension的唯一组合. 有关确切要求,请参见所用插件的文档.

  • 如果您对归档任务使用artifact() ,Gradle会自动使用该任务的classifierextension属性来填充工件的元数据.

现在,您可以发布RPM.

如果您确实要基于组件将工件添加到发布中,而不是调整组件本身,则可以将from components.someComponentartifact someArtifact表示法组合在一起.

Restricting publications to specific repositories

当定义了多个发布或存储库时,通常需要控制将哪些发布发布到哪个存储库. 例如,考虑以下示例,该示例定义了两个发布-一个仅由一个二进制文件组成,另一个包含二进制和相关联的资源-两个存储库-一个供内部使用,一个供外部使用者使用:

例子7.添加多个发布和存储库
build.gradle
publishing {
    publications {
        binary(MavenPublication) {
            from components.java
        }
        binaryAndSources(MavenPublication) {
            from components.java
            artifact sourcesJar
        }
    }
    repositories {
        // change URLs to point to your repos, e.g. http://my.org/repo
        maven {
            name = 'external'
            url = "$buildDir/repos/external"
        }
        maven {
            name = 'internal'
            url = "$buildDir/repos/internal"
        }
    }
}
build.gradle.kts
publishing {
    publications {
        create<MavenPublication>("binary") {
            from(components["java"])
        }
        create<MavenPublication>("binaryAndSources") {
            from(components["java"])
            artifact(tasks["sourcesJar"])
        }
    }
    repositories {
        // change URLs to point to your repos, e.g. http://my.org/repo
        maven {
            name = "external"
            url = uri("$buildDir/repos/external")
        }
        maven {
            name = "internal"
            url = uri("$buildDir/repos/internal")
        }
    }
}

发布插件将创建任务,使您可以将任何一个发布发布到任何一个存储库. 他们还将这些任务附加到publish聚合任务. 但是,假设您要将仅限二进制的发布限制为外部存储库,而将带有源的二进制发布限制为内部存储库. 为此,您需要使发布成为条件发布.

Gradle允许您通过Task.onlyIf(org.gradle.api.specs.Spec)方法根据条件跳过所需的任何任务. 下面的示例演示了如何实现我们刚刚提到的约束:

示例8.配置应将哪些工件发布到哪些存储库
build.gradle
tasks.withType(PublishToMavenRepository) {
    onlyIf {
        (repository == publishing.repositories.external &&
            publication == publishing.publications.binary) ||
        (repository == publishing.repositories.internal &&
            publication == publishing.publications.binaryAndSources)
    }
}
tasks.withType(PublishToMavenLocal) {
    onlyIf {
        publication == publishing.publications.binaryAndSources
    }
}
build.gradle.kts
tasks.withType<PublishToMavenRepository>().configureEach {
    onlyIf {
        (repository == publishing.repositories["external"] &&
            publication == publishing.publications["binary"]) ||
        (repository == publishing.repositories["internal"] &&
            publication == publishing.publications["binaryAndSources"])
    }
}
tasks.withType<PublishToMavenLocal>().configureEach {
    onlyIf {
        publication == publishing.publications["binaryAndSources"]
    }
}
gradle publish输出
> gradle publish
> Task :compileJava
> Task :processResources
> Task :classes
> Task :jar
> Task :generateMetadataFileForBinaryAndSourcesPublication
> Task :generatePomFileForBinaryAndSourcesPublication
> Task :sourcesJar
> Task :publishBinaryAndSourcesPublicationToExternalRepository SKIPPED
> Task :publishBinaryAndSourcesPublicationToInternalRepository
> Task :generateMetadataFileForBinaryPublication
> Task :generatePomFileForBinaryPublication
> Task :publishBinaryPublicationToExternalRepository
> Task :publishBinaryPublicationToInternalRepository SKIPPED
> Task :publish

BUILD SUCCESSFUL in 0s
10 actionable tasks: 10 executed

您可能还需要定义自己的聚合任务以帮助您的工作流程. 例如,假设您有多个发布应发布到外部存储库. 一次发布所有这些而不发布内部的可能非常有用.

下面的示例演示如何通过定义聚合任务publishToExternalRepository来完成此任务,该任务依赖于所有相关的发布任务:

例子9.定义自己的发布简写任务
build.gradle
task publishToExternalRepository {
    group = 'publishing'
    description = 'Publishes all Maven publications to the external Maven repository.'
    dependsOn tasks.withType(PublishToMavenRepository).matching {
        it.repository == publishing.repositories.external
    }
}
build.gradle.kts
tasks.register("publishToExternalRepository") {
    group = "publishing"
    description = "Publishes all Maven publications to the external Maven repository."
    dependsOn(tasks.withType<PublishToMavenRepository>().matching {
        it.repository == publishing.repositories["external"]
    })
}

此特定示例通过将TaskCollection.withType(java.lang.Class)PublishToMavenRepository任务类型一起使用,自动处理相关发布任务的引入或删除. 如果要发布到兼容Ivy的存储库,则可以对PublishToIvyRepository进行相同的操作 .

Configuring publishing tasks

在评估了项目之后,发布插件会创建其非聚合任务,这意味着您无法直接从构建脚本中引用它们. 如果要配置这些任务中的任何一个,则应使用延迟的任务配置. 这可以通过项目的tasks集合以多种方式tasks .

例如,假设您想更改generatePomFileFor PubName Publication任务在何处写入其POM文件. 您可以使用TaskCollection.withType(java.lang.Class)方法执行此操作,如本示例所示:

示例10.配置由发布插件创建的动态命名任务
build.gradle
tasks.withType(GenerateMavenPom).all {
    def matcher = name =~ /generatePomFileFor(\w+)Publication/
    def publicationName = matcher[0][1]
    destination = "$buildDir/poms/${publicationName}-pom.xml"
}
build.gradle.kts
tasks.withType<GenerateMavenPom>().configureEach {
    val matcher = Regex("""generatePomFileFor(\w+)Publication""").matchEntire(name)
    val publicationName = matcher?.let { it.groupValues[1] }
    destination = file("$buildDir/poms/$publicationName-pom.xml")
}

上面的示例使用正则表达式从任务名称中提取发布的名称. 这样可以避免所有可能生成的POM文件的文件路径之间发生冲突. 如果只有一个出版物,则不必担心此类冲突,因为只有一个POM文件.