修改现有组件并为其添加用于发布的变体
Gradle 的发布模型基于组件的概念,这些组件由插件定义。例如,Java 库插件定义了一个 java
组件,对应于一个库;而 Java 平台插件定义了另一种组件,名为 javaPlatform
,它实际上是一种不同类型的软件组件(一个平台)。
有时我们希望向现有组件添加更多变体或修改其现有变体。例如,如果你为一个不同平台添加了 Java 库的一个变体,你可能只需将这个附加变体声明在 java
组件本身上。总的来说,声明附加变体通常是发布附加制品的最佳解决方案。
为了执行此类添加或修改,AdhocComponentWithVariants
接口声明了两个方法,名为 addVariantsFromConfiguration
和 withVariantsFromConfiguration
,它们接受两个参数:
-
一个自定义动作,允许你过滤哪些变体将被发布
要使用这些方法,必须确保你使用的 SoftwareComponent
本身是 AdhocComponentWithVariants
的实例,Java 插件(Java、Java 库、Java 平台)创建的组件就是这种情况。添加变体非常简单:
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()
}
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
变体使其不被发布:
java {
withJavadocJar()
withSourcesJar()
}
val javaComponent = components["java"] as AdhocComponentWithVariants
javaComponent.withVariantsFromConfiguration(configurations["sourcesElements"]) {
skip()
}
publishing {
publications {
create<MavenPublication>("mavenJava") {
from(components["java"])
}
}
}
java {
withJavadocJar()
withSourcesJar()
}
components.java.withVariantsFromConfiguration(configurations.sourcesElements) {
skip()
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
}
}
}
创建和发布自定义组件
在上一个示例中,我们演示了如何扩展或修改现有组件,例如 Java 插件提供的组件。但 Gradle 也允许你构建一个自定义组件(不是 Java 库,不是 Java 平台,也不是 Gradle 原生支持的组件)。
要创建一个自定义组件,首先需要创建一个空的adhoc组件。目前,这只能通过插件实现,因为你需要获取 SoftwareComponentFactory 的句柄:
class InstrumentedJarsPlugin @Inject constructor(
private val softwareComponentFactory: SoftwareComponentFactory) : Plugin<Project> {
private final SoftwareComponentFactory softwareComponentFactory
@Inject
InstrumentedJarsPlugin(SoftwareComponentFactory softwareComponentFactory) {
this.softwareComponentFactory = softwareComponentFactory
}
声明自定义组件发布什么仍然通过 AdhocComponentWithVariants API 完成。对于自定义组件,第一步是创建自定义传出变体,按照本章中的说明进行。在此阶段,你应该拥有可用于跨项目依赖的变体,但我们现在要将它们发布到外部仓库。
// 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")
}
// 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")
}
首先,我们使用工厂创建一个新的 adhoc 组件。然后,我们通过 addVariantsFromConfiguration
方法添加一个变体,该方法在上一节中有更详细的描述。
在简单的情况下,Configuration
和变体之间存在一对一的映射关系,此时可以发布来自单个 Configuration
的所有变体,因为它们实际上是同一个东西。但是,在某些情况下,Configuration
会关联额外的配置发布,我们也称之为辅助变体。此类配置在多项目构建中有意义,但在外部发布时没有。例如,当项目之间共享一个文件目录时,就属于这种情况,但在 Maven 仓库上无法直接发布目录(只能发布打包好的东西,如 jar 或 zip)。有关如何跳过特定变体发布的详细信息,请参阅 ConfigurationVariantDetails 类。如果已经为某个配置调用了 addVariantsFromConfiguration
,可以使用 withVariantsFromConfiguration
对生成的变体进行进一步修改。
像这样发布 adhoc 组件时:
-
Gradle 模块元数据将精确地表示发布的变体。特别是,所有传出变体将继承已发布配置的依赖、制品和属性。
-
Maven 和 Ivy 元数据文件将生成,但你需要通过 ConfigurationVariantDetails 类声明依赖如何映射到 Maven 范围。
实际上,这意味着通过这种方式创建的组件可以被 Gradle 消费,就像它们是“本地组件”一样。
向发布添加自定义制品
与其从制品的角度思考,不如拥抱 Gradle 的变体感知模型。预期的是一个模块可能需要多个制品。然而,这很少止步于此,如果额外的制品代表一个可选功能,它们可能还有不同的依赖等等。
Gradle 通过Gradle 模块元数据支持发布附加变体,这些变体使这些制品对依赖解析引擎可见。请参阅文档的变体感知共享部分,了解如何声明此类变体,并查看如何发布自定义组件。
如果直接将额外制品附加到发布,它们会“脱离上下文”发布。这意味着它们在元数据中完全没有被引用,因此只能通过依赖上的分类器直接引用。与 Gradle 模块元数据不同,Maven pom 元数据不会包含有关附加制品的信息,无论它们是通过变体添加还是直接添加,因为变体无法在 pom 格式中表示。
以下部分描述了如果你确定元数据(例如 Gradle 或 POM 元数据)与你的用例无关时如何直接发布制品。例如,如果你的项目不需要被其他项目消费,并且发布结果只需要制品本身。
总的来说,有两种选择:
-
仅发布制品
-
将制品添加到基于带有元数据的组件的发布中(不推荐,相反,请调整组件或使用adhoc 组件发布,这两种方式都将产生适合你的制品的元数据)
要基于制品创建发布,首先定义一个自定义制品并将其附加到你选择的 Gradle 配置中。以下示例定义了一个由 rpm
任务(未显示)生成的 RPM 制品,并将该制品附加到 conf
配置:
configurations {
create("conf")
}
val rpmFile = layout.buildDirectory.file("rpms/my-package.rpm")
val rpmArtifact = artifacts.add("conf", rpmFile.get().asFile) {
type = "rpm"
builtBy("rpm")
}
configurations {
conf
}
def rpmFile = layout.buildDirectory.file('rpms/my-package.rpm')
def rpmArtifact = artifacts.add('conf', rpmFile.get().asFile) {
type = 'rpm'
builtBy 'rpm'
}
ArtifactHandler 中的 artifacts.add()
方法返回一个 PublishArtifact 类型的制品对象,该对象可用于定义发布,如下例所示:
publishing {
publications {
create<MavenPublication>("maven") {
artifact(rpmArtifact)
}
}
}
publishing {
publications {
maven(MavenPublication) {
artifact rpmArtifact
}
}
}
-
artifact()
方法接受发布制品作为参数(如示例中的rpmArtifact
),以及 Project.file(java.lang.Object) 接受的任何类型参数,例如File
实例、字符串文件路径或归档任务。 -
发布插件支持不同的制品配置属性,因此请始终查阅插件文档以获取更多详细信息。
classifier
和extension
属性由 Maven 发布插件和 Ivy 发布插件都支持。 -
自定义制品在发布中需要独一无二,通常通过
classifier
和extension
的唯一组合来区分。有关精确要求,请参阅你正在使用的插件的文档。 -
如果你使用
artifact()
并指定归档任务,Gradle 会自动使用该任务的classifier
和extension
属性填充制品的元数据。
现在你可以发布 RPM 了。
如果你确实想将制品添加到基于组件的发布中,而不是调整组件本身,可以将 from components.someComponent
和 artifact someArtifact
两种标记法结合使用。
限制发布到特定仓库
当你定义了多个发布或仓库时,你通常希望控制哪些发布被发布到哪些仓库。例如,考虑以下示例,它定义了两个发布——一个仅包含二进制文件,另一个包含二进制文件及相关源码——以及两个仓库——一个用于内部使用,一个用于外部消费者:
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"))
}
}
}
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) 方法根据条件跳过任何你想要的任务。以下示例演示了如何实现我们刚才提到的约束:
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()
}
}
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
——该任务依赖于所有相关的发布任务来实现这一点:
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"]
})
}
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) 方法来完成,如下例所示:
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
}
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 文件。