天天看点

【项目质量管理】Jenkins任务实现任意maven项目的质量检查(sonar+jacoco)

须知:

  1. 编写java代码后,通过单元测试可以了解代码覆盖率等指标
  2. maven可以配置jacoco对项目进行扫描,生成报告文件jacoco.xml
  3. sonar可以识别jacoco.xml文件,对报告进行更好的UI展示,跟踪质量晋升曲线等。

起因:

  1. 使用jacoco插件来生成mvn test报告,需要在每个项目代码中增加配置。
  2. 其中的配置是有共性的,不需要每个项目的开发者都掌握配置方法,这个重复工作可以交给项目管理来做,Jenkins单任务,流水线之类都可以。

计划:

  1. 我们主要通过jenkins的Shell来动态的识别、修改pom.xml文件,然后执行mvn clean test命令。最终,不管是哪个项目,不用做任何配置,执行这个脚本就可以生成对应的sonar报告
  2. maven项目单个module和多个module的jacoco配置是不一样的,我们Shell中大概分为两种分支逻辑

脚本思路:

  1. 代码仓可能不是maven项目的跟目录,先用find目录找到所有pom.xml位置,移动到最顶层的目录,对应cd2pom方法。
  2. 根据pom.xml的数量,确认是单module还是多module,分别执行singleModule和multiModule方法。
  3. singleModule给pom.xml添加jacoco插件
<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>prepare-agent</goal>
            </goals>
        </execution>
        <execution>
            <id>jacoco-site</id>
            <phase>test</phase>
            <goals>
                <goal>report</goal>
            </goals>
            <configuration>
            	<!-- 指定jacoco.xml的位置,让它跟多module的生成位置一致,方便后续sonar配置 -->
                <outputDirectory>dps-aggregate/target/site/jacoco</outputDirectory>
            </configuration>
        </execution>
    </executions>
</plugin>
           
  1. multiModule,相比单module,<goal>report</goal>不需要了,而是需要增加一个子模块,来聚合所有待测的子模块。除了增加jacoco插件配置,还需要生成一个子模块,在其pom中添加dependency依赖,在父pom中增加子module。这部分比较麻烦,主要通过执行一次mvn validate命令,来获取所有jar子模块。
  2. 最后插件、聚合子模块等都配置好了,执行mvn clean test命令,可能会需要-P指定profile,通过sed过滤出profile,默认使用第一个,没有profile就不加-P命令。

完整shell:

#!/bin/bash
# 说明:单module只需在pom.xml中增加jacoco插件,配置prepare-agent和report目标
# 开始

# 如果当前目录没有pom.xml,向下找一层,重复5次
function cd2pom() {
    i=5
    while ((i > 0))
    do
    next=`find ./ -name pom.xml|cut -d / -f 2|uniq|head -1`
    [ ! -e ./pom.xml ] && [ -n "$next" ] && cd $next || break
    ((i--))
    done
    [ ! -e ./pom.xml ] && echo '没有找到pom.xml文件' && return 1
    return 0
}

function addTags() {
    # 排除profile下build的干扰,找到<build>行数
    buildLineNum=`cat -n pom.xml|sed '/<profiles>/,/<\/profiles>/d'|grep '<build>'|awk '{printf $1}'`
    [ -z "$buildLineNum" ] && echo '添加build标签' && sed -i ''$buildLineNum'a <build>\n</build>' pom.xml
    # 排除pluginManagement下plugins的干扰,找到build下的plugins行号
    pluginsLineNum=`cat -n pom.xml|sed '/<profiles>/,/<\/profiles>/d'|sed -n '/<build>/,/<\/build>/p'|sed '/<pluginManagement>/,/<\/pluginManagement>/d'|grep '<plugins>'|awk '{printf $1}'`
    [ -z "$pluginsLineNum" ] && echo '添加plugins标签' && sed -i ''$pluginsLineNum'a <plugins>\n</plugins>' pom.xml
    # 查看是否有jacoco插件,没有则,插入
    jacocoExist=`cat -n pom.xml|sed '/<profiles>/,/<\/profiles>/d'|sed -n '/<build>/,/<\/build>/p'|sed '/<pluginManagement>/,/<\/pluginManagement>/d'|grep "jacoco-maven-plugin"|wc -l`
    [ $jacocoExist -eq 0 ] && sed -i '/<plugins>/r jacoco' pom.xml
}

function singleModule() {

tmpPath=dps-aggregate
mkdir $tmpPath

cat > jacoco << EOF
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>jacoco-site</id>
                        <phase>test</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>dps-aggregate/target/site/jacoco</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
                <configuration>
                    <excludes>
                        <exclude>**/src/test/java/**/*,**/*.xml</exclude>
                    </excludes>
                </configuration>
            </plugin>
EOF

addTags
}


# 说明:多module需两步
# 1、在父pom.xml中增加jacoco插件,配置prepare-agent目标
# 2、增加一个聚合子模块,其中依赖待测的子模块,然后增加report-aggregate目标
# 开始

function multiModule() {

tmpPath=dps-aggregate
mkdir $tmpPath

# 父pom.xml修改-------------------------------------------------------------------------------
cat > jacoco << EOF
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>**/src/test/java/**/*</exclude>
                    </excludes>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
EOF

addTags

# 创建子module,用于聚合报告-------------------------------------------------------------------

# 通过mvn validate的日志获取jar类型的子项目
mvn validate|grep -B2 '\[ jar \]'|sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g" > $tmpPath/sub

# 排除profile下modules的干扰,找到<modules>行数
modulesLineNum=`cat -n pom.xml|sed '/profiles/,/profiles/d'|grep '<modules>'|awk '{printf $1}'`
[ -n "$modulesLineNum" ] && echo '添加module标签' && sed -i ''$modulesLineNum'a <module>'$tmpPath'</module>' pom.xml

cat > $tmpPath/pom.xml << EOF
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <groupId>dps.tmp</groupId>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>dps-aggregate</artifactId>
    <version>1.0</version>

    <dependencies>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>test</phase>
                        <goals>
                            <goal>report-aggregate</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>target/site/jacoco</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
EOF

# 为以上pom.xml添加依赖模块
echo '' > $tmpPath/dependency
subName=(`cat $tmpPath/sub|grep ':'|cut -d ' ' -f 3`)
subVersion=(`cat $tmpPath/sub|grep 'Building'|cut -d ' ' -f 4`)
for((j=${#subName[@]};j>=0;j--))
do
    # post传输时+会变成空格,此处用-代替
    ((i=${#subName[@]}-j))
    groupId=`echo ${subName[$i]}|cut -d : -f 1`
    artifactId=`echo ${subName[$i]}|cut -d : -f 2`

    [ -z "${groupId}" ] && break

    echo '<dependency>' >> $tmpPath/dependency
    echo '<groupId>'${groupId}'</groupId>' >> $tmpPath/dependency
    echo '<artifactId>'${artifactId}'</artifactId>' >> $tmpPath/dependency
    echo '<version>'${subVersion[$i]}'</version>' >> $tmpPath/dependency
    echo '</dependency>' >> $tmpPath/dependency
done

# 将依赖加入到pom.xml中
echo `sed '/<dependencies>/r '$tmpPath'/dependency' $tmpPath/pom.xml` > $tmpPath/pom.xml

}

cd2pom
if [ $? -eq 0 ]
then
    [ `find ./ -name pom.xml|wc -l` -gt 1 ] && multiModule || singleModule
    
    profiles=`sed -n '/<profiles>/,/<\/profiles>/p' pom.xml |sed -e '/activation/,/activation/d' -e '/build/,/build/d' -e '/modules/,/modules/d' -e '/repositories/,/repositories/d' -e '/pluginRepositories/,/pluginRepositories/d' -e '/dependencies/,/dependencies/d' -e '/reports/,/reports/d' -e '/reporting/,/reporting/d' -e '/dependencyManagement/,/dependencyManagement/d' -e '/distributionManagement/,/distributionManagement/d' -e '/properties/,/properties/d'|grep -v 'profile'|sed -e 's/<id>//g' -e 's/<\/id>//g'`
    
    flag=0
    for p in ${profiles[@]}
    do 
        [ -n "$p" ] && mvn clean test -P $p && flag=1 && break
    done
    [ $flag -eq 0 ] && mvn clean test || echo ''

fi
           

注:

  • Jenkins Shell命令是分步执行的,与-xe参数有关,可以搜索了解一下。通过开头的“#!/bin/bash”可以防止中间过程失败导致脚本退出的情况。
  • pom.xml文件中<dependency>等标签可能出现在多个位置:<dependencies>和<dependencyManagement>,需要根据maven标签的定义xml排除一些干扰。
  • 本地测试脚本的时候,cd命令在.sh子脚本中不生效,使用source *.sh可以防止子脚本生成,cd命令才有效。
  • 对于没有父maven的项目,这个脚本是不支持的。
  • 写的不详细,感兴趣可以留言~