Gradle 使用 Spring Boot 时使用 JaCoCo 代码覆盖率构建多项目失败
Gradle Multi-Project Build with JaCoCo Code Coverage fails when using Spring Boot
从 https://docs.gradle.org/current/samples/sample_jvm_multi_project_with_code_coverage.html (i.e., the code here https://github.com/gradle/gradle/tree/master/subprojects/docs/src/samples/java/jvm-multi-project-with-code-coverage 中的示例开始)并通过将 application/build.gradle
更改为
来简单地添加 Spring 引导
plugins {
id 'myproject.java-conventions'
id 'application'
}
dependencies {
implementation project(':list')
implementation project(':utilities')
implementation 'org.springframework.boot:spring-boot-starter-web:2.3.8.RELEASE' // <-- this line is new
}
application {
mainClass = 'org.gradle.sample.Main'
}
并将application/src/main/java/org/gradle/sample/app/Main.java
更改为
package org.gradle.sample.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.gradle.sample.list.LinkedList;
import static org.gradle.sample.utilities.StringUtils.join;
import static org.gradle.sample.utilities.StringUtils.split;
import static org.gradle.sample.app.MessageUtils.getMessage;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
LinkedList tokens;
tokens = split(getMessage());
System.out.println(join(tokens));
SpringApplication.run(Main.class, args);
}
}
将打破示例(这里是完整的工作,或者更确切地说是非工作示例:https://github.com/SamuelBucheliZ/gradle-jvm-multi-project-with-code-coverage/tree/57e57b8bf24ef4208d9a03a361714c916701e599)。
调用时
./gradlew clean build codeCoverage -stacktrace
它会失败
> Task :code-coverage-report:codeCoverageReport FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':code-coverage-report:codeCoverageReport'.
> Error while creating report
* Try:
Run with --info or --debug option to get more log output. Run with --scan to get full insights.
* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':code-coverage-report:codeCoverageReport'.
[...]
Caused by: java.io.IOException: Error while analyzing log4j-api-2.13.3.jar@org/apache/logging/log4j/util/ProcessIdUtil.class.
at org.jacoco.core.analysis.Analyzer.analyzerError(Analyzer.java:162)
at org.jacoco.core.analysis.Analyzer.analyzeClass(Analyzer.java:134)
at org.jacoco.core.analysis.Analyzer.analyzeClass(Analyzer.java:157)
at org.jacoco.core.analysis.Analyzer.analyzeAll(Analyzer.java:193)
at org.jacoco.core.analysis.Analyzer.analyzeZip(Analyzer.java:265)
at org.jacoco.core.analysis.Analyzer.analyzeAll(Analyzer.java:196)
at org.jacoco.ant.ReportTask.createBundle(ReportTask.java:573)
at org.jacoco.ant.ReportTask.createReport(ReportTask.java:545)
at org.jacoco.ant.ReportTask.execute(ReportTask.java:496)
... 251 more
Caused by: java.lang.IllegalStateException: Can't add different class with same name: org/apache/logging/log4j/util/ProcessIdUtil
at org.jacoco.core.analysis.CoverageBuilder.visitCoverage(CoverageBuilder.java:106)
at org.jacoco.core.analysis.Analyzer.visitEnd(Analyzer.java:99)
at org.objectweb.asm.ClassVisitor.visitEnd(ClassVisitor.java:378)
at org.jacoco.core.internal.flow.ClassProbesAdapter.visitEnd(ClassProbesAdapter.java:100)
at org.objectweb.asm.ClassReader.accept(ClassReader.java:722)
at org.objectweb.asm.ClassReader.accept(ClassReader.java:401)
at org.jacoco.core.analysis.Analyzer.analyzeClass(Analyzer.java:116)
at org.jacoco.core.analysis.Analyzer.analyzeClass(Analyzer.java:132)
... 258 more
* Get more help at https://help.gradle.org
BUILD FAILED in 8s
19 actionable tasks: 19 executed
我怀疑这可能与https://github.com/jacoco/jacoco/issues/407 . However, my question is whether there is any way to fix this problem. Unfortunately, the official documentation at https://docs.gradle.org/current/userguide/jacoco_plugin.html没有提供关于这个主题的任何指导。
// Task to gather code coverage from multiple subprojects
def codeCoverageReport = tasks.register('codeCoverageReport', JacocoReport) {
additionalClassDirs(configurations.runtimeClasspath) // <-- this line here
additionalSourceDirs(sourcesPath.incoming.artifactView { lenient(true) }.files)
executionData(coverageDataPath.incoming.artifactView { lenient(true) }.files.filter { it.exists() })
reports {
// xml is usually used to integrate code coverage with
// other tools like SonarQube, Coveralls or Codecov
xml.enabled true
// HTML reports can be used to see code coverage
// without any external tools
html.enabled true
}
}
删除它将使构建再次工作。但是,代码覆盖率报告是空的。有没有办法调整它,让它只包含项目本身的代码,而不包含外部 jar 等?
只要这样做就可以了(所有外部 类 都将被排除):
additionalClassDirs(configurations.runtimeClasspath.get().filter{it.path.contains(rootProject.name) })
这适用于 Kotlin DSL,Groovy 您可能需要稍微更改代码,也许这样:
additionalClassDirs(configurations.runtimeClasspath.filter{it.path.contains(rootProject.name) })
一种可能的方法是定义
def sourcesOutputPath = configurations.create("sourcesOutputPath") {
visible = false
canBeResolved = true
canBeConsumed = false
extendsFrom(configurations.implementation)
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION))
attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, 'source-output-folders'))
}
}
然后在
中使用它
additionalClassDirs(sourcesOutputPath.incoming.artifactView { lenient(true) }.files)
而不是
additionalClassDirs(configurations.runtimeClasspath)
可在 https://github.com/SamuelBucheliZ/gradle-jvm-multi-project-with-code-coverage
中找到完整的工作设置
从 https://docs.gradle.org/current/samples/sample_jvm_multi_project_with_code_coverage.html (i.e., the code here https://github.com/gradle/gradle/tree/master/subprojects/docs/src/samples/java/jvm-multi-project-with-code-coverage 中的示例开始)并通过将 application/build.gradle
更改为
plugins {
id 'myproject.java-conventions'
id 'application'
}
dependencies {
implementation project(':list')
implementation project(':utilities')
implementation 'org.springframework.boot:spring-boot-starter-web:2.3.8.RELEASE' // <-- this line is new
}
application {
mainClass = 'org.gradle.sample.Main'
}
并将application/src/main/java/org/gradle/sample/app/Main.java
更改为
package org.gradle.sample.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.gradle.sample.list.LinkedList;
import static org.gradle.sample.utilities.StringUtils.join;
import static org.gradle.sample.utilities.StringUtils.split;
import static org.gradle.sample.app.MessageUtils.getMessage;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
LinkedList tokens;
tokens = split(getMessage());
System.out.println(join(tokens));
SpringApplication.run(Main.class, args);
}
}
将打破示例(这里是完整的工作,或者更确切地说是非工作示例:https://github.com/SamuelBucheliZ/gradle-jvm-multi-project-with-code-coverage/tree/57e57b8bf24ef4208d9a03a361714c916701e599)。
调用时
./gradlew clean build codeCoverage -stacktrace
它会失败
> Task :code-coverage-report:codeCoverageReport FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':code-coverage-report:codeCoverageReport'.
> Error while creating report
* Try:
Run with --info or --debug option to get more log output. Run with --scan to get full insights.
* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':code-coverage-report:codeCoverageReport'.
[...]
Caused by: java.io.IOException: Error while analyzing log4j-api-2.13.3.jar@org/apache/logging/log4j/util/ProcessIdUtil.class.
at org.jacoco.core.analysis.Analyzer.analyzerError(Analyzer.java:162)
at org.jacoco.core.analysis.Analyzer.analyzeClass(Analyzer.java:134)
at org.jacoco.core.analysis.Analyzer.analyzeClass(Analyzer.java:157)
at org.jacoco.core.analysis.Analyzer.analyzeAll(Analyzer.java:193)
at org.jacoco.core.analysis.Analyzer.analyzeZip(Analyzer.java:265)
at org.jacoco.core.analysis.Analyzer.analyzeAll(Analyzer.java:196)
at org.jacoco.ant.ReportTask.createBundle(ReportTask.java:573)
at org.jacoco.ant.ReportTask.createReport(ReportTask.java:545)
at org.jacoco.ant.ReportTask.execute(ReportTask.java:496)
... 251 more
Caused by: java.lang.IllegalStateException: Can't add different class with same name: org/apache/logging/log4j/util/ProcessIdUtil
at org.jacoco.core.analysis.CoverageBuilder.visitCoverage(CoverageBuilder.java:106)
at org.jacoco.core.analysis.Analyzer.visitEnd(Analyzer.java:99)
at org.objectweb.asm.ClassVisitor.visitEnd(ClassVisitor.java:378)
at org.jacoco.core.internal.flow.ClassProbesAdapter.visitEnd(ClassProbesAdapter.java:100)
at org.objectweb.asm.ClassReader.accept(ClassReader.java:722)
at org.objectweb.asm.ClassReader.accept(ClassReader.java:401)
at org.jacoco.core.analysis.Analyzer.analyzeClass(Analyzer.java:116)
at org.jacoco.core.analysis.Analyzer.analyzeClass(Analyzer.java:132)
... 258 more
* Get more help at https://help.gradle.org
BUILD FAILED in 8s
19 actionable tasks: 19 executed
我怀疑这可能与https://github.com/jacoco/jacoco/issues/407 . However, my question is whether there is any way to fix this problem. Unfortunately, the official documentation at https://docs.gradle.org/current/userguide/jacoco_plugin.html没有提供关于这个主题的任何指导。
// Task to gather code coverage from multiple subprojects
def codeCoverageReport = tasks.register('codeCoverageReport', JacocoReport) {
additionalClassDirs(configurations.runtimeClasspath) // <-- this line here
additionalSourceDirs(sourcesPath.incoming.artifactView { lenient(true) }.files)
executionData(coverageDataPath.incoming.artifactView { lenient(true) }.files.filter { it.exists() })
reports {
// xml is usually used to integrate code coverage with
// other tools like SonarQube, Coveralls or Codecov
xml.enabled true
// HTML reports can be used to see code coverage
// without any external tools
html.enabled true
}
}
删除它将使构建再次工作。但是,代码覆盖率报告是空的。有没有办法调整它,让它只包含项目本身的代码,而不包含外部 jar 等?
只要这样做就可以了(所有外部 类 都将被排除):
additionalClassDirs(configurations.runtimeClasspath.get().filter{it.path.contains(rootProject.name) })
这适用于 Kotlin DSL,Groovy 您可能需要稍微更改代码,也许这样:
additionalClassDirs(configurations.runtimeClasspath.filter{it.path.contains(rootProject.name) })
一种可能的方法是定义
def sourcesOutputPath = configurations.create("sourcesOutputPath") {
visible = false
canBeResolved = true
canBeConsumed = false
extendsFrom(configurations.implementation)
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION))
attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, 'source-output-folders'))
}
}
然后在
中使用它additionalClassDirs(sourcesOutputPath.incoming.artifactView { lenient(true) }.files)
而不是
additionalClassDirs(configurations.runtimeClasspath)
可在 https://github.com/SamuelBucheliZ/gradle-jvm-multi-project-with-code-coverage
中找到完整的工作设置