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)创建的组件AdhocComponentWithVariants ,情况就是如此. 添加变体非常简单:

示例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> {

声明自定义组件发布了哪些仍然通过做AdhocComponentWithVariants API. 对于自定义组件,第一步是按照本章中的说明创建自定义传出变型. 在此阶段,您应该拥有可以在跨项目依赖项中使用的变体,但现在我们将其发布到外部存储库.

例子4.创建一个自定义的adhoc组件
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支持其他变体的发布,这些变体使依赖解析引擎知道这些工件. 请参阅文档的变体感知共享部分,以了解如何声明此类变体并查看如何发布自定义组件 .

If you attach extra artifacts to a publication directly, they are published "out of context". That means, they are not referenced in the metadata at all and can then only be addressed directly through a classifier on a dependency. In contrast to Gradle Module Metadata, Maven pom metadata will not contain information on additional artifacts regardless of whether they are added though a variant or directly, as variants can not be represented in the pom format.

以下部分描述了在确定元数据(例如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 Publish插件Ivy Publish插件均支持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"]
    }
}
Output of 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文件.