如何生成多项目合一 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 和源代码的合并。

当前状态:

预期状态:

我该如何管理它?

这不能通过 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() 进行测试。

一段时间以来,我们一直处于完全相同的情况。我们希望发布一个单一的工件供我们的客户依赖,尽管该产品在内部是通过几个单独的组件项目开发的。我最终完成了它(有妥协),这是我学到的:

  1. 合并 jar 并不像看起来那么简单,因为 jar 中可能存在诸如资源文件之类的东西,它们不是 始终命名空间。您的两个罐子可能有 具有相同名称的资源文件,在这种情况下,您将不得不 合并这些文件的内容。

  2. 如果不访问原始源,Javadoc 很难合并 文件,因为它有摘要页(索引页)。

所以我的建议是:

  • 三思而后行,也许您真正想要的是不是一个单独的 jar,而是 一个单独的依赖关系客户?这些是不同的。你可以轻松拥有一个只有pom的神器。依赖于此 pom only artifact 将简单地转换为依赖于组件子项目的各个 artifacts。实际上,对于您的客户而言,没有任何改变。 Spring 引导采用这种方法。为此,您可以创建一个空的 java-library 项目,使所有组件项目成为其 api 依赖项。在这个项目中你甚至不需要任何源代码。

  • 如果你真的想合并成一个jar,你可以尝试构建一个自定义的fat jar。自定义不是为了引入 3rd 方依赖项。

我们使用 Gradle Shadow 插件来合并 jar。它最初的目的是构建一个 fat jar,其中将包含所有传递依赖项。但它还有一个特殊的“shadow”配置,如果你希望将依赖项导出到 POM 而不是捆绑,你可以在其中添加依赖项。那么你需要做什么:

  1. 定义一个 non-transitive 配置(比如 bundler),您将在其中添加您的子项目作为依赖项。这将是 Gradle Shadow 插件的目标配置。
  2. 定义一个 transitive 配置(bundlerTransitive),它从非传递配置扩展而来。这将被手动解决以找到第 3 方依赖项
  3. 在您的 build.gradle 中,注册一个 afterEvaluate 闭包,您可以在其中找到 已解决[=58] 的 二级 依赖项=] 传递配置,将它们添加到 shadow 配置中。二级的原因是一级依赖项将是您的子项目工件。
  4. 经过以上,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
}