如何生成多项目合一 Gradle 项目?
How to generate all-projects-in-one Gradle project?
我有一个 gradle 具有太多依赖项的单体项目。
我想将它分解成许多子项目并发布所有子项目(build + sources + javadoc)+ 一个额外的项目是所有子项目的合并。
这个额外的项目应该像一个虚拟工件,像今天一样将我的所有项目都放在一个 jar 中,因为我不想对我的用户进行太大的更改。
jar 不能包含依赖项(它不是 uber-jar)但结果 pom.xml 必须包含所有子项目的依赖项(maven 工件的生成 pom.xml 必须包含所有依赖项)。
为了遵守 Maven Central 约定,虚拟工件也将包括 javadoc 和源代码的合并。
当前状态:
- 主要项目,生成
- pom.xml
- main.jar
- 主要-sources.jar
- 主要-javadoc.jar
预期状态:
- 子项目A,生成
- A-pom.xml
- A.jar
- A-sources.jar
- A-javadoc.jar
- 子项目B,生成
- B-pom.xml
- B.jar
- B-sources.jar
- B-javadoc.jar
- virtal-Project Main,生成
- pom.xml=A-pom.xml+B-pom.xml
- main.jar=A.jar+B.jar
- main-sources.jar=A-sources.jar+B-sources.jar
- main-javadoc.jar=A-javadoc.jar+B-javadoc.jar
我该如何管理它?
这不能通过 maven-publish
直接完成,但可以添加单独的 java-library
模块并将每个模块与源代码和文档打包在一起。使用 Gradle 这将是一个简单的 jar
任务,但是当工件公开可用时......这种传递依赖性最好由元包提供;只有 Maven (Local/Central) 依赖项,而不是嵌入式 JARS。在这种情况下,这只是另一个模块(显然只有在发布其他模块后才会构建)。
关于这个概念,它需要任何“合并的”JavaDocs ...
https://central.sonatype.org/pages/requirements.html#supply-javadoc-and-sources
当它们在 *.pom
中被引用 (Maven Central) 时,Gradle 将能够找到它们。
只需使用存储库 mavenLocal()
而不是 mavenCentral()
进行测试。
一段时间以来,我们一直处于完全相同的情况。我们希望发布一个单一的工件供我们的客户依赖,尽管该产品在内部是通过几个单独的组件项目开发的。我最终完成了它(有妥协),这是我学到的:
合并 jar 并不像看起来那么简单,因为 jar 中可能存在诸如资源文件之类的东西,它们不是
始终命名空间。您的两个罐子可能有
具有相同名称的资源文件,在这种情况下,您将不得不
合并这些文件的内容。
如果不访问原始源,Javadoc 很难合并
文件,因为它有摘要页(索引页)。
所以我的建议是:
三思而后行,也许您真正想要的是不是一个单独的 jar,而是 一个单独的依赖关系客户?这些是不同的。你可以轻松拥有一个只有pom的神器。依赖于此 pom only artifact 将简单地转换为依赖于组件子项目的各个 artifacts。实际上,对于您的客户而言,没有任何改变。 Spring 引导采用这种方法。为此,您可以创建一个空的 java-library 项目,使所有组件项目成为其 api
依赖项。在这个项目中你甚至不需要任何源代码。
如果你真的想合并成一个jar,你可以尝试构建一个自定义的fat jar。自定义不是为了引入 3rd 方依赖项。
我们使用 Gradle Shadow 插件来合并 jar。它最初的目的是构建一个 fat jar,其中将包含所有传递依赖项。但它还有一个特殊的“shadow
”配置,如果你希望将依赖项导出到 POM 而不是捆绑,你可以在其中添加依赖项。那么你需要做什么:
- 定义一个 non-transitive 配置(比如 bundler),您将在其中添加您的子项目作为依赖项。这将是 Gradle Shadow 插件的目标配置。
- 定义一个 transitive 配置(bundlerTransitive),它从非传递配置扩展而来。这将被手动解决以找到第 3 方依赖项
- 在您的 build.gradle 中,注册一个
afterEvaluate
闭包,您可以在其中找到 已解决[=58] 的 二级 依赖项=] 传递配置,将它们添加到 shadow
配置中。二级的原因是一级依赖项将是您的子项目工件。
- 经过以上,shadowJar任务生成的神器就是要上传到maven的神器了。您将需要配置 shadowJar 任务以删除分类器(默认为 shadow)
这是一个完整的示例 (build.gradle),它在 io.vertx 组中捆绑了 vertx-web 及其所有依赖项:
plugins {
id 'java'
id 'maven-publish'
id 'com.github.johnrengelman.shadow' version '5.2.0'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
configurations {
bundler {
transitive = false
}
bundlerTansitive {
extendsFrom bundler
transitive = true
}
}
dependencies {
bundler "io.vertx:vertx-web:4.0.0"
bundler "io.vertx:vertx-web-common:4.0.0"
bundler "io.vertx:vertx-core:4.0.0"
bundler "io.vertx:vertx-auth-common:4.0.0"
bundler "io.vertx:vertx-bridge-common:4.0.0"
}
shadowJar {
configurations = [project.configurations.bundler]
classifier ''
}
publishing {
publications {
shadow(MavenPublication) { publication ->
project.shadow.component(publication)
}
}
}
project.afterEvaluate {
// this is needed because your sub-projects might have inter-dependencies
def isBundled = { ResolvedDependency dep ->
return configurations.bundler.dependencies.any {
dep.moduleGroup == it.group && dep.moduleName == it.name
}
}
logger.lifecycle '\nBundled artifacts and their 1st level dependencies:'
// level one dependencies
configurations.bundlerTansitive.resolvedConfiguration.firstLevelModuleDependencies.forEach {
logger.lifecycle "+--- ${it.getName()}"
// level two dependencies
it.children.findAll({ ResolvedDependency dep -> !isBundled(dep) })
.forEach { ResolvedDependency dep ->
logger.lifecycle "| +--- ${dep.name}"
project.dependencies.add('shadow', [group: dep.moduleGroup, name: dep.moduleName, version: dep.moduleVersion])
}
}
logger.lifecycle '\nExported Dependencies:'
configurations.shadow.getResolvedConfiguration().getFirstLevelModuleDependencies().forEach {
project.logger.lifecycle "+--- ${it.getName()}"
}
}
对于 javadoc 如果你不关心索引(妥协,正如我所说),那么它只是一个带有复制规范的 jar 任务:
configurations {
javadoc {
transitive = false
}
}
dependencies {
javadoc 'com.my:component-a:1.1.0:javadoc'
javadoc 'com.my:component-b:1.1.0:javadoc'
javadoc 'com.my:component-c:1.1.0:javadoc'
javadoc 'com.my:component-d:1.1.0:javadoc'
}
task javadocFatJar(type: Jar) {
archiveClassifier.set('javadoc')
from {
configurations.javadoc.collect { it.isDirectory() ? it : zipTree(it) }
}
with jar
}
我有一个 gradle 具有太多依赖项的单体项目。
我想将它分解成许多子项目并发布所有子项目(build + sources + javadoc)+ 一个额外的项目是所有子项目的合并。
这个额外的项目应该像一个虚拟工件,像今天一样将我的所有项目都放在一个 jar 中,因为我不想对我的用户进行太大的更改。
jar 不能包含依赖项(它不是 uber-jar)但结果 pom.xml 必须包含所有子项目的依赖项(maven 工件的生成 pom.xml 必须包含所有依赖项)。
为了遵守 Maven Central 约定,虚拟工件也将包括 javadoc 和源代码的合并。
当前状态:
- 主要项目,生成
- pom.xml
- main.jar
- 主要-sources.jar
- 主要-javadoc.jar
预期状态:
- 子项目A,生成
- A-pom.xml
- A.jar
- A-sources.jar
- A-javadoc.jar
- 子项目B,生成
- B-pom.xml
- B.jar
- B-sources.jar
- B-javadoc.jar
- virtal-Project Main,生成
- pom.xml=A-pom.xml+B-pom.xml
- main.jar=A.jar+B.jar
- main-sources.jar=A-sources.jar+B-sources.jar
- main-javadoc.jar=A-javadoc.jar+B-javadoc.jar
我该如何管理它?
这不能通过 maven-publish
直接完成,但可以添加单独的 java-library
模块并将每个模块与源代码和文档打包在一起。使用 Gradle 这将是一个简单的 jar
任务,但是当工件公开可用时......这种传递依赖性最好由元包提供;只有 Maven (Local/Central) 依赖项,而不是嵌入式 JARS。在这种情况下,这只是另一个模块(显然只有在发布其他模块后才会构建)。
关于这个概念,它需要任何“合并的”JavaDocs ...
https://central.sonatype.org/pages/requirements.html#supply-javadoc-and-sources
当它们在 *.pom
中被引用 (Maven Central) 时,Gradle 将能够找到它们。
只需使用存储库 mavenLocal()
而不是 mavenCentral()
进行测试。
一段时间以来,我们一直处于完全相同的情况。我们希望发布一个单一的工件供我们的客户依赖,尽管该产品在内部是通过几个单独的组件项目开发的。我最终完成了它(有妥协),这是我学到的:
合并 jar 并不像看起来那么简单,因为 jar 中可能存在诸如资源文件之类的东西,它们不是 始终命名空间。您的两个罐子可能有 具有相同名称的资源文件,在这种情况下,您将不得不 合并这些文件的内容。
如果不访问原始源,Javadoc 很难合并 文件,因为它有摘要页(索引页)。
所以我的建议是:
三思而后行,也许您真正想要的是不是一个单独的 jar,而是 一个单独的依赖关系客户?这些是不同的。你可以轻松拥有一个只有pom的神器。依赖于此 pom only artifact 将简单地转换为依赖于组件子项目的各个 artifacts。实际上,对于您的客户而言,没有任何改变。 Spring 引导采用这种方法。为此,您可以创建一个空的 java-library 项目,使所有组件项目成为其
api
依赖项。在这个项目中你甚至不需要任何源代码。如果你真的想合并成一个jar,你可以尝试构建一个自定义的fat jar。自定义不是为了引入 3rd 方依赖项。
我们使用 Gradle Shadow 插件来合并 jar。它最初的目的是构建一个 fat jar,其中将包含所有传递依赖项。但它还有一个特殊的“shadow
”配置,如果你希望将依赖项导出到 POM 而不是捆绑,你可以在其中添加依赖项。那么你需要做什么:
- 定义一个 non-transitive 配置(比如 bundler),您将在其中添加您的子项目作为依赖项。这将是 Gradle Shadow 插件的目标配置。
- 定义一个 transitive 配置(bundlerTransitive),它从非传递配置扩展而来。这将被手动解决以找到第 3 方依赖项
- 在您的 build.gradle 中,注册一个
afterEvaluate
闭包,您可以在其中找到 已解决[=58] 的 二级 依赖项=] 传递配置,将它们添加到shadow
配置中。二级的原因是一级依赖项将是您的子项目工件。 - 经过以上,shadowJar任务生成的神器就是要上传到maven的神器了。您将需要配置 shadowJar 任务以删除分类器(默认为 shadow)
这是一个完整的示例 (build.gradle),它在 io.vertx 组中捆绑了 vertx-web 及其所有依赖项:
plugins {
id 'java'
id 'maven-publish'
id 'com.github.johnrengelman.shadow' version '5.2.0'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
configurations {
bundler {
transitive = false
}
bundlerTansitive {
extendsFrom bundler
transitive = true
}
}
dependencies {
bundler "io.vertx:vertx-web:4.0.0"
bundler "io.vertx:vertx-web-common:4.0.0"
bundler "io.vertx:vertx-core:4.0.0"
bundler "io.vertx:vertx-auth-common:4.0.0"
bundler "io.vertx:vertx-bridge-common:4.0.0"
}
shadowJar {
configurations = [project.configurations.bundler]
classifier ''
}
publishing {
publications {
shadow(MavenPublication) { publication ->
project.shadow.component(publication)
}
}
}
project.afterEvaluate {
// this is needed because your sub-projects might have inter-dependencies
def isBundled = { ResolvedDependency dep ->
return configurations.bundler.dependencies.any {
dep.moduleGroup == it.group && dep.moduleName == it.name
}
}
logger.lifecycle '\nBundled artifacts and their 1st level dependencies:'
// level one dependencies
configurations.bundlerTansitive.resolvedConfiguration.firstLevelModuleDependencies.forEach {
logger.lifecycle "+--- ${it.getName()}"
// level two dependencies
it.children.findAll({ ResolvedDependency dep -> !isBundled(dep) })
.forEach { ResolvedDependency dep ->
logger.lifecycle "| +--- ${dep.name}"
project.dependencies.add('shadow', [group: dep.moduleGroup, name: dep.moduleName, version: dep.moduleVersion])
}
}
logger.lifecycle '\nExported Dependencies:'
configurations.shadow.getResolvedConfiguration().getFirstLevelModuleDependencies().forEach {
project.logger.lifecycle "+--- ${it.getName()}"
}
}
对于 javadoc 如果你不关心索引(妥协,正如我所说),那么它只是一个带有复制规范的 jar 任务:
configurations {
javadoc {
transitive = false
}
}
dependencies {
javadoc 'com.my:component-a:1.1.0:javadoc'
javadoc 'com.my:component-b:1.1.0:javadoc'
javadoc 'com.my:component-c:1.1.0:javadoc'
javadoc 'com.my:component-d:1.1.0:javadoc'
}
task javadocFatJar(type: Jar) {
archiveClassifier.set('javadoc')
from {
configurations.javadoc.collect { it.isDirectory() ? it : zipTree(it) }
}
with jar
}