须知:
- 编写java代码后,通过单元测试可以了解代码覆盖率等指标
- maven可以配置jacoco对项目进行扫描,生成报告文件jacoco.xml
- sonar可以识别jacoco.xml文件,对报告进行更好的UI展示,跟踪质量晋升曲线等。
起因:
- 使用jacoco插件来生成mvn test报告,需要在每个项目代码中增加配置。
- 其中的配置是有共性的,不需要每个项目的开发者都掌握配置方法,这个重复工作可以交给项目管理来做,Jenkins单任务,流水线之类都可以。
计划:
- 我们主要通过jenkins的Shell来动态的识别、修改pom.xml文件,然后执行mvn clean test命令。最终,不管是哪个项目,不用做任何配置,执行这个脚本就可以生成对应的sonar报告
- maven项目单个module和多个module的jacoco配置是不一样的,我们Shell中大概分为两种分支逻辑
脚本思路:
- 代码仓可能不是maven项目的跟目录,先用find目录找到所有pom.xml位置,移动到最顶层的目录,对应cd2pom方法。
- 根据pom.xml的数量,确认是单module还是多module,分别执行singleModule和multiModule方法。
- 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>
- multiModule,相比单module,<goal>report</goal>不需要了,而是需要增加一个子模块,来聚合所有待测的子模块。除了增加jacoco插件配置,还需要生成一个子模块,在其pom中添加dependency依赖,在父pom中增加子module。这部分比较麻烦,主要通过执行一次mvn validate命令,来获取所有jar子模块。
- 最后插件、聚合子模块等都配置好了,执行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的项目,这个脚本是不支持的。
- 写的不详细,感兴趣可以留言~