修改现有组件并为其添加变体以供发布

Gradle 的发布模型基于插件定义的“组件”概念。例如,Java 库插件定义了一个对应于库的 java 组件,但 Java 平台插件定义了另一种组件,名为 javaPlatform,它实际上是一种不同类型的软件组件(一个“平台”)。

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

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

  • 用作变体源的输出配置

  • 允许您“过滤”将要发布的变体的自定义操作

要使用这些方法,您必须确保您使用的 SoftwareComponent 本身是一个 AdhocComponentWithVariants,Java 插件(Java、Java 库、Java 平台)创建的组件就是这种情况。添加变体非常简单

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()
}
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()
}

在其他情况下,您可能希望修改 Java 插件已添加的变体。例如,如果您激活 Javadoc 和源的发布,它们将成为 java 组件的附加变体。如果您只想发布其中一个,例如只发布 Javadoc 但不发布源,您可以修改 sources 变体使其不被发布

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"])
        }
    }
}
build.gradle
java {
    withJavadocJar()
    withSourcesJar()
}

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

publishing {
    publications {
        mavenJava(MavenPublication) {
            from components.java
        }
    }
}

创建和发布自定义组件

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

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

InstrumentedJarsPlugin.kt
class InstrumentedJarsPlugin @Inject constructor(
    private val softwareComponentFactory: SoftwareComponentFactory) : Plugin<Project> {
InstrumentedJarsPlugin.groovy
private final SoftwareComponentFactory softwareComponentFactory

@Inject
InstrumentedJarsPlugin(SoftwareComponentFactory softwareComponentFactory) {
    this.softwareComponentFactory = softwareComponentFactory
}

声明自定义组件发布“什么”仍然通过AdhocComponentWithVariants API 完成。对于自定义组件,第一步是按照本章中的说明创建自定义输出变体。在此阶段,您应该拥有可用于跨项目依赖项的变体,但我们现在要发布到外部仓库。

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")
}
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")
}

首先我们使用工厂创建一个新的临时组件。然后我们通过 addVariantsFromConfiguration 方法添加一个变体,该方法在上一节中有更详细的描述。

在简单情况下,Configuration 和变体之间存在一对一映射,在这种情况下,您可以发布从单个 Configuration 发出的所有变体,因为它们实际上是相同的。但是,在某些情况下,Configuration 与附加的配置发布相关联,我们也称之为“二级变体”。这种配置在多项目构建中有意义,但在外部发布时则不然。例如,当项目之间共享“文件目录”时,但无法直接将“目录”发布到 Maven 仓库(只能发布像 jar 或 zip 这样的打包内容)。请参阅ConfigurationVariantDetails类,了解如何跳过特定变体的发布。如果已对某个配置调用 addVariantsFromConfiguration,则可以使用 withVariantsFromConfiguration 对结果变体执行进一步修改。

当这样发布临时组件时

  • Gradle 模块元数据将“精确地”表示已发布的变体。特别是,所有输出变体都将继承已发布配置的依赖项、制品和属性。

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

实际上,这意味着以这种方式创建的组件可以像“本地组件”一样被 Gradle 消费。

向发布中添加自定义制品

您不应该再从制品的角度思考,而应该接受 Gradle 的变体感知模型。预计单个模块可能需要多个制品。然而,这很少就此止步,如果附加制品代表一个可选特性,它们也可能具有不同的依赖项等等。

Gradle 通过“Gradle 模块元数据”支持“附加变体”的发布,这些变体使这些制品对依赖项解析引擎可见。请参阅文档的变体感知共享部分,了解如何声明此类变体以及如何发布自定义组件

如果您直接将额外制品附加到发布中,它们将“脱离上下文”发布。这意味着,它们根本不会在元数据中引用,因此只能通过依赖项上的分类器直接寻址。与 Gradle 模块元数据相反,Maven pom 元数据将不包含有关附加制品的信息,无论它们是通过变体还是直接添加,因为变体无法在 pom 格式中表示。

以下部分描述了如果您确定元数据(例如 Gradle 或 POM 元数据)与您的用例无关,如何直接发布制品。例如,如果您的项目不需要被其他项目消费,并且发布唯一需要的结果是制品本身。

一般来说,有两种选择

  • 仅使用制品创建发布

  • 向基于组件的发布添加制品,并附带元数据(不推荐,相反请调整组件或使用临时组件发布,两者都将生成适合您制品的元数据)

要基于制品创建发布,首先定义一个自定义制品并将其附加到您选择的 Gradle 配置。以下示例定义了一个由 rpm 任务(未显示)生成的 RPM 制品,并将该制品附加到 conf 配置

build.gradle.kts
configurations {
    create("conf")
}
val rpmFile = layout.buildDirectory.file("rpms/my-package.rpm")
val rpmArtifact = artifacts.add("conf", rpmFile.get().asFile) {
    type = "rpm"
    builtBy("rpm")
}
build.gradle
configurations {
    conf
}
def rpmFile = layout.buildDirectory.file('rpms/my-package.rpm')
def rpmArtifact = artifacts.add('conf', rpmFile.get().asFile) {
    type = 'rpm'
    builtBy 'rpm'
}

artifacts.add() 方法——来自ArtifactHandler——返回一个类型为PublishArtifact的制品对象,然后可以在定义发布时使用,如下例所示

build.gradle.kts
publishing {
    publications {
        create<MavenPublication>("maven") {
            artifact(rpmArtifact)
        }
    }
}
build.gradle
publishing {
    publications {
        maven(MavenPublication) {
            artifact rpmArtifact
        }
    }
}
  • artifact() 方法接受“发布制品”作为参数——例如示例中的 rpmArtifact——以及Project.file(java.lang.Object)接受的任何类型的参数,例如 File 实例、字符串文件路径或归档任务。

  • 发布插件支持不同的制品配置属性,因此请务必查阅插件文档以获取更多详细信息。classifierextension 属性受到Maven 发布插件Ivy 发布插件的支持。

  • 自定义制品在发布中需要是唯一的,通常通过 classifierextension 的独特组合。有关精确要求,请参阅您正在使用的插件的文档。

  • 如果您将 artifact() 与归档任务一起使用,Gradle 会自动使用该任务的 classifierextension 属性填充制品的元数据。

现在您可以发布 RPM 了。

如果您确实想将制品添加到基于组件的发布中,而不是调整组件本身,您可以组合 from components.someComponentartifact someArtifact 表示法。

限制发布到特定仓库

当您定义了多个发布或仓库时,您通常希望控制哪些发布发布到哪些仓库。例如,考虑以下示例,它定义了两个发布——一个只包含二进制文件,另一个包含二进制文件和相关源——以及两个仓库——一个用于内部使用,一个用于外部消费者

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(layout.buildDirectory.dir("repos/external"))
        }
        maven {
            name = "internal"
            url = uri(layout.buildDirectory.dir("repos/internal"))
        }
    }
}
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 = layout.buildDirectory.dir('repos/external')
        }
        maven {
            name = 'internal'
            url = layout.buildDirectory.dir('repos/internal')
        }
    }
}

发布插件将创建任务,允许您将任一发布发布到任一仓库。它们还将这些任务附加到 publish 聚合任务。但是,假设您想将仅二进制文件的发布限制到外部仓库,并将包含二进制文件和源的发布限制到内部仓库。要做到这一点,您需要使发布“有条件”。

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

build.gradle.kts
tasks.withType<PublishToMavenRepository>().configureEach {
    val predicate = provider {
        (repository == publishing.repositories["external"] &&
            publication == publishing.publications["binary"]) ||
        (repository == publishing.repositories["internal"] &&
            publication == publishing.publications["binaryAndSources"])
    }
    onlyIf("publishing binary to the external repository, or binary and sources to the internal one") {
        predicate.get()
    }
}
tasks.withType<PublishToMavenLocal>().configureEach {
    val predicate = provider {
        publication == publishing.publications["binaryAndSources"]
    }
    onlyIf("publishing binary and sources") {
        predicate.get()
    }
}
build.gradle
tasks.withType(PublishToMavenRepository) {
    def predicate = provider {
        (repository == publishing.repositories.external &&
            publication == publishing.publications.binary) ||
        (repository == publishing.repositories.internal &&
            publication == publishing.publications.binaryAndSources)
    }
    onlyIf("publishing binary to the external repository, or binary and sources to the internal one") {
        predicate.get()
    }
}
tasks.withType(PublishToMavenLocal) {
    def predicate = provider {
        publication == publishing.publications.binaryAndSources
    }
    onlyIf("publishing binary and sources") {
        predicate.get()
    }
}
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——它依赖于所有相关的发布任务来实现这一点

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"]
    })
}
build.gradle
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执行相同的操作。

配置发布任务

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

例如,假设您想更改 generatePomFileForPubNamePublication 任务写入 POM 文件的位置。您可以通过使用TaskCollection.withType(java.lang.Class)方法来做到这一点,如下例所示

build.gradle.kts
tasks.withType<GenerateMavenPom>().configureEach {
    val matcher = Regex("""generatePomFileFor(\w+)Publication""").matchEntire(name)
    val publicationName = matcher?.let { it.groupValues[1] }
    destination = layout.buildDirectory.file("poms/${publicationName}-pom.xml").get().asFile
}
build.gradle
tasks.withType(GenerateMavenPom).all {
    def matcher = name =~ /generatePomFileFor(\w+)Publication/
    def publicationName = matcher[0][1]
    destination = layout.buildDirectory.file("poms/${publicationName}-pom.xml").get().asFile
}

上面的示例使用正则表达式从任务名称中提取发布名称。这样,生成的所有 POM 文件的文件路径之间就不会发生冲突。如果您只有一个发布,那么您不必担心此类冲突,因为只有一个 POM 文件。