【问题标题】:How can I aggregate excludes from modules into the root excludes?如何将模块中的排除项聚合到根排除项中?
【发布时间】:2020-03-17 18:13:18
【问题描述】:

我们有一个多模块 Gradle 项目。所有模块都是根项目目录的直接子目录。

在模块中,我们运行 JUnit 并使用 Jacoco 生成覆盖率。在根中,我们聚合了覆盖范围。这一切都有效,但目前我们必须输入两次排除项:

MODULE1:

apply plugin: "jacoco"
jacoco {
    toolVersion = "0.8.5"
}

def static analysisExcludes() {
    return [
        "com/ourcompany/module1/*Config*",
        "com/ourcompany/module1/endpoint/exception/**",
        "com/ourcompany/module1/v2/**",
        "com/ourcompany/module1/v3/**",
        "src/generated/**",
        "src/*test*/**",
        "wrapper/dists/**"
    ]
}

def execData() {
    return files(fileTree(buildDir).include("jacoco/*.exec"))
}

jacocoTestReport {
    getExecutionData().setFrom(execData())
    afterEvaluate {
        classDirectories.setFrom(files(classDirectories.files.collect {
            fileTree(dir: it, exclude: analysisExcludes())
        }))
    }
}

jacocoTestCoverageVerification {
    getExecutionData().setFrom(execData());
    afterEvaluate {
        classDirectories.setFrom(files(classDirectories.files.collect {
            fileTree(dir: it, exclude: analysisExcludes())
        }))
    }
    violationRules {
        rule {
            limit {
                minimum = 0.93
            }
        }
    }
}

MODULE2:

apply plugin: "jacoco"
jacoco {
    toolVersion = "0.8.5"
}

def static analysisExcludes() {
    return [
        "com/ourcompany/module2/*IgnoreMe*"
        "src/generated/**",
        "src/*test*/**",
        "wrapper/dists/**"
    ]
}

def execData() {
    return files(fileTree(buildDir).include("jacoco/*.exec"))
}

jacocoTestReport {
    getExecutionData().setFrom(execData())
    afterEvaluate {
        classDirectories.setFrom(files(classDirectories.files.collect {
            fileTree(dir: it, exclude: analysisExcludes())
        }))
    }
}

jacocoTestCoverageVerification {
    getExecutionData().setFrom(execData());
    afterEvaluate {
        classDirectories.setFrom(files(classDirectories.files.collect {
            fileTree(dir: it, exclude: analysisExcludes())
        }))
    }
    violationRules {
        rule {
            limit {
                minimum = 0.87
            }
        }
    }
}

ROOT:

apply plugin: "jacoco"
jacoco {
    toolVersion = "0.8.5"
}

// TODO: Eliminate double-entry bookkeeping by getting the excludes from the modules.
def static aggregatedAnalysisExcludes() {
    return [
        "**/com/ourcompany/module1/*Config*",
        "**/com/ourcompany/module1/endpoint/exception/**",
        "**/com/ourcompany/module1/v2/**",
        "**/com/ourcompany/module1/v3/**",
        "**/com/ourcompany/module2/*IgnoreMe*"
        "**/src/generated/**",
        "**/src/*test*/**",
        "**/wrapper/dists/**"
    ]
}

def execData() {
    return files(fileTree(rootDir).include("**/build/jacoco/*.exec"))
}

def includedClasses() {
    return files(fileTree(dir: rootDir, include: "**/build/classes/**", exclude: aggregatedAnalysisExcludes()))
}

task jacocoAggregateReport(type: JacocoReport) {
    getExecutionData().setFrom(execData());
    afterEvaluate {
        classDirectories.setFrom(includedClasses())
    }
}

task jacocoAggregateTestCoverageVerification(type: JacocoCoverageVerification) {
    getExecutionData().setFrom(execData());
    afterEvaluate {
        classDirectories.setFrom(includedClasses())
    }
    violationRules {
        rule {
            limit {
                minimum = 0.91
            }
        }
    }
}

task mergeJacocoExecData(type: JacocoMerge) {
    setExecutionData(execData());
    setDestinationFile(new File("build/jacoco/all.exec"))
}

apply plugin: "org.sonarqube"

def junitResultsDirs() {
    def dirs = []
    rootDir.eachDirRecurse { dir ->
        if (dir.absolutePath.matches("^.*/build/test-results/(test|(component|integration)Test)\$")) {
            dirs << dir
        }
    }
    return dirs;
}

sonarqube {
    properties {
        property "sonar.projectName", "Multimodule Repo"
        property "sonar.projectKey", "multimodule-repo"
        property "sonar.java.coveragePlugin", "jacoco"
        property "sonar.jacoco.reportPath", "build/jacoco/all.exec"
        property "sonar.junit.reportsPath", junitResultsDirs()
        property "sonar.issuesReport.html.enable", true
        property "sonar.issuesReport.console.enable", true
        property 'sonar.exclusions', aggregatedAnalysisExcludes()
    }
}

我们想以某种方式迭代模块以获取它们的排除项,并将它们组合到聚合的排除项中,添加“**/”前缀,使路径相对于根目录。

在伪代码中:

def aggregatedAnalysisExcludes() {
    return rootDir.modules.excludes.withPrefixes("**/")
}

或:

def aggregatedAnalysisExcludes() {
    def excludes = []
    for (Module m : rootDir.modules) {
        excludes << m.excludes.withPrefixes("**/")
    }
    return excludes
}

我们如何真正编写代码?

注意:假设有一种方法可以做我们想做的事,结果列表将包含 src/generated、src/test 和 wrapper/dists 的重复项。我们尝试创建具有重复项的列表以查看这是否重要,但事实并非如此。即使有重复项,排除项也能正常工作。但是,如果有一种干净的声明方式来删除重复项,那将是对解决方案的一个很好的补充。

【问题讨论】:

    标签: gradle groovy multi-module


    【解决方案1】:

    我了解您希望确保 Jacoco 排除在子项目级别上的内容自动传播到聚合级别。

    这可以通过确保不会过早评估文件树来实现,当然还可以询问 JacocoReport 任务它们是如何配置的。我在解决方案中找到了this Gist,并编写了一个小插件来创建一个名为gradle-jacoco-log 的汇总报告(查看内部以找到source code which explicitly performs the aggregation,包括排除项)。

    你基本上添加到你的 build.gradle

    plugins {
        id 'org.barfuin.gradle.jacocolog' version '1.1.0'
    }
    

    然后运行

    gradle jacocoAggregatedReport
    

    这是一个 full example project,其中包含自动汇总的 JacocoReport 排除项的子项目。

    【讨论】:

    • 我一直在用 Java 重新实现我们的自定义插件,以使其更易于使用(例如,调试器实际工作、自动完成和导航工作等)。我以为只有我。关于您的解决方案,您能否解释一下如何/在何处尊重排除项?您的解决方案没有提及排除项。我正在寻找一种方法来引用子项目任务中的排除项,但不知道如何访问它们。假设根项目对子项目中声明的变量和函数具有完全可见性,但我无法让它工作。
    • 对,不用直接引用。在链接代码中,我们调用JacocoReport.getAllClassDirs() 并注意不要解决懒惰的东西。这样,它可以在稍后提供文件时进行更新(例如,在配置阶段还不存在类!)。解决方案仅发生在执行阶段,当所有文件(和配置)都正确存在时。
    • 谢谢。它似乎对我很有效。 github.com/felipegutierrez/explore-spring
    【解决方案2】:

    我想要一个更符合 Groovy 的解决方案(声明式、闭包),感谢@barfuin,我终于能够实现。

    在模块中,在定义排除项后添加:

    ext {
        analysisExcludes = analysisExcludes()
    }
    

    在根目录下添加:

    def aggregatedAnalysisExcludes() {
        evaluationDependsOnChildren()
        def excludes = new HashSet<>()
        subprojects.each {
            subproject -> subproject.property("analysisExcludes").each {
                exclude -> excludes << "**/" + exclude;
            }
        }
        return (String[])excludes.stream().sorted().toArray()
    }
    

    (String[]) 演员表很烦人,但如果没有它,分配给 exclude: 之后的类型将不匹配。更改为 new HashSet&lt;String&gt;() 没有帮助,.toArray(String[]::new) 会出现编译错误。

    【讨论】:

      【解决方案3】:

      排除聚合器的清理版本:

      def aggregatedAnalysisExcludes() {
      evaluationDependsOnChildren()
      def excludes = new HashSet<>()
      subprojects.each {
          subproject ->
              subproject.findProperty("analysisExcludes").each {
                  exclude -> excludes << "**/" + exclude
              }
      }
      return excludes.stream().sorted().toArray({ length -> new String[length] })
      

      }

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-10-11
        • 2021-02-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-05-12
        相关资源
        最近更新 更多