21

我们的产品由许多相互依赖的 Maven 项目组成。所有这些 Maven 项目都在一个交付最终产品的项目中组合在一起。

Maven 项目共享相同的生命周期。换句话说,它们不是由单独的团队管理,这些团队通过明确的<dependency>更改来获取其他项目的更新版本。相反,当有人在其中一个项目中更改某些内容时,结果应该直接进入最终产品而无需额外更改。

我们使用 Jenkins 作为我们的持续集成工具。

我们的主要愿望如下:

  • 无需将所有项目间依赖项复制到 Jenkins 配置:这些应该在一个地方,最好是pom.xml文件。
  • 避免不必要的构建:在 SCM 更改时,仅构建可能受到影响的项目。
  • 菱形依赖关系的情况下(C 依赖于 B1 和 B2,它们都依赖于 A),如果最低的 (A) 发生了变化,那么最终产品 (C) 应该始终使用也用于构建的 A 的版本/测试 B1 和 B2。

问题:使用 Jenkins 执行此操作的最佳方法是什么?

我们目前正在考虑使用 Jenkins Pipeline 插件使用单个作业,该插件分析 Maven 依赖项和 SCM 更改,决定需要构建的内容和顺序,然后实际构建项目。

4

1 回答 1

27

为了满足您的要求,您可以依赖 Maven 多模块构建的许多默认功能和下面解释的附加配置。

从你的问题:

无需将所有项目间依赖项复制到 Jenkins 配置:这些应该在一个地方,最好是pom.xml文件。

使用聚合器/多模块 pom,您可以为 maven 构建提供一个入口点(pom.xml 文件),然后构建所有已定义的模块:

具有模块的项目称为多模块或聚合器项目。模块是这个 POM 列出的项目,并作为一个组执行。一个 pom 打包的项目可以通过将一组项目列为模块来聚合它们的构建,这些模块是这些项目的相对目录。

也就是pom.xml像下面这样的文件:

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.sample</groupId>
    <artifactId>project</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>

    <modules>
        <module>module-1</module>
        <module>module-2</module>
        <module>module-n</module>
    </modules>
</project>

是一个最小的例子:定义将从这个 maven 项目开始构建的模块列表(其他 maven 项目)(一个空项目,其唯一目的是构建其他 maven 项目)。注意拥有模块所需的pom打包,它告诉 maven 这个项目只提供了一个 pom(没有进一步的工件)。

因此,您可以拥有一个根 Maven 聚合器项目,它将其他 maven 项目定义为其模块,并有一个 Jenkins 作业来构建此入口点。


此外,根据您的问题:

避免不必要的构建:在 SCM 更改时,仅构建可能受到影响的项目。

要满足此要求,您可以使用incremental-build-plugin

因为它寻找对一个独特项目的模块的修改,所以 Maven 增量插件只对多模块项目敏感。如果检测到对模块的修改,则删除输出目录。

此插件将验证 pom 文件、资源、源、测试源、测试资源中的任何一个是否会在模块中更改,以及是否删除其输出目录。因此,节省了相关模块的构建时间,并为整个多模块项目(在这种情况下是我们自己的构建)的链中节省了时间。

要启用此机制,您可以在上面的聚合器 pom 中配置以下内容:

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.sample</groupId>
    <artifactId>project</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>
    <modules>
        <module>module-1</module>
        <module>module-2</module>
        <module>module-n</module>
    </modules>

    <properties>
        <skip.incremental>true</skip.incremental>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>net.java.maven-incremental-build</groupId>
                <artifactId>incremental-build-plugin</artifactId>
                <version>1.6</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>incremental-build</goal>
                        </goals>
                        <configuration>
                            <noIncrementalBuild>${skip.incremental}</noIncrementalBuild>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

上面,我们只是简单地incremental-build-plugin在聚合器构建和一个属性中添加skip.incremental

<project>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.sample</groupId>
        <artifactId>project</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <artifactId>simple-module</artifactId>

    <properties>
        <skip.incremental>false</skip.incremental>
    </properties>
</project>

注意:在示例模块的上述 pom 中,我们将聚合器 pom 文件作为父级,因此将maven 继承与聚合(经典用法)结合使用,因此具有多模块构建和提供的通用构建治理由所有声明的模块的公共父级(在这种情况下,公共构建治理提供额外的incremental-build-plugin配置)。此外,每个模块都重新配置skip.incremental属性以不跳过上述插件。这是一个技巧:现在插件只会在模块中执行,而不是在其根目录中执行(这没有意义,在这种情况下实际上会抛出错误)。

显然,在相关的 Jenkins 工作中,在其源代码管理部分中,我们不必在每次构建时配置新的签出,否则将无法检测到任何更改(并且每次都会从零开始,即实际上是发布版本的一个好习惯)。

此外,您的 maven 命令不应调用clean生命周期,这也会target在每次构建时删除 ,因此也无法检测到任何更改。


此外,根据您的问题:

在“钻石依赖”的情况下(C 依赖于 B1 和 B2,两者都依赖于 A),如果更改了最低的 (A),则最终产品 (C) 应始终使用也使用过的 A 版本构建/测试 B1 和 B2。

Maven 将把这个要求作为多模块构建及其反应器机制的一部分来处理:

Maven 中处理多模块项目的机制称为反应器。Maven 核心的这一部分执行以下操作:

  • 收集所有可用的模块来构建
  • 将项目排序为正确的构建顺序
  • 按顺序构建选定的项目

默认情况下,reactor 也会因此创建一个构建顺序,并始终在其依赖/消费者模块之前构建一个模块。这也意味着最后构建的模块实际上将是负责构建最终工件的模块,可交付产品取决于部分或所有其他模块(例如,负责交付war文件的模块可能是最后一个构建一个多模块 webapp 项目)。


有关 Maven 的进一步相关阅读,并在某些内容未更改时跳过操作:

  • 提供默认情况下已启用 maven-jar-pluginforceCreation

    要求 jar 插件构建一个新的 JAR,即使内容似乎都没有改变。默认情况下,此插件会查看输出 jar 是否存在并且输入是否未更改。如果这些条件为真,插件将跳过 jar 的创建。

  • maven-compiler-plugin提供了useIncrementalCompilation,尽管目前无法正常工作
  • maven-war-plugin提供的选项recompressZippedFiles也可用于加速构建并避免重新做某事(false在这种情况下切换到):

    指示是否应再次压缩添加到战争中的 zip 档案(jar、zip 等)。再次压缩可能会导致更小的存档大小,但会显着延长执行时间。
    默认true


更新

Maven 项目共享相同的生命周期。

此要求还将强制使用聚合器/多模块项目,但也可能适用于通过依赖项链接的不同项目。

它们不是由单独的团队管理,这些团队有明确的更改来获取其他项目的更新版本。

这一点也强制使用多模块项目。在多模块项目中,模块之间可能有不同的版本,但通常的做法和准则是共享相同的版本,由父项目(聚合器)定义并在其模块之间级联。因此,其集中化和治理将避免错位和错误。

当有人在其中一个项目中更改某些内容时,结果应该直接进入最终产品而无需额外更改。

同样,这将由多模块项目自动处理。每个使用SNAPSHOT 版本的不同项目也会发生同样的情况,因此无需更改其消费者项目(负责构建最终产品的项目)中的依赖版本。然而,虽然 SNAPSHOT 版本在开发过程中确实很有帮助(并且被推荐),但在交付最终产品时绝对不应该使用它们,因为构建再现性将处于危险之中(也就是说,您以后可能无法重新构建相同的版本on,因为它依赖于 SNAPSHOT 版本,因此不是冻结版本)。因此,SNAPSHOT 不是灵丹妙药,仅应在产品 SDLC 的某些阶段使用,而不是作为最终确定和冻结的解决方案。


Update 2
Worth 还可以查看适用于 Maven 3.3.1+和 Java 7的新Maven Incremental Module Builder

更多详情:

于 2016-04-25T20:51:47.733 回答