From e0d10b4420a0d1efc6a0aafc124453a5bceb27c8 Mon Sep 17 00:00:00 2001 From: Farid Zakaria Date: Fri, 16 Aug 2024 07:17:34 -0700 Subject: [PATCH] Add support for transforming Checkstyle XML output to JUnit XML (#283) Bazel will natively create a `test.xml` for all test runners if they do not create one themselves. For the checkstyle test-runner it would create a test.xml that captures the system-out. ```xml Generated test.log (if the file is not UTF-8, then this may be unreadable): ``` JUnit is a well establish format and well known by many CI programs. Having the checkstyle rule itself create a more curated JUnit XML file is in the user's best interest. In this PR, we modify the checkstyle rules to emit a `test.xml` itself, stopping Bazel from implicitly doing so. We have checkstyle emit the XML format _always_ and then use a very minimal Java tool and a provided default XLST (XML stylesheet) to transform the Checkstyle XML to one following JUnit's XML specification. ```xml ``` --- java/BUILD.bazel | 1 + java/checkstyle2junit.xslt | 46 +++++++++++++++++++ java/private/checkstyle.bzl | 32 +++++++++---- .../contrib_rules_jvm/xml/BUILD.bazel | 14 ++++++ .../xml/XSLTTransformer.java | 35 ++++++++++++++ 5 files changed, 118 insertions(+), 10 deletions(-) create mode 100644 java/checkstyle2junit.xslt create mode 100644 java/src/com/github/bazel_contrib/contrib_rules_jvm/xml/BUILD.bazel create mode 100644 java/src/com/github/bazel_contrib/contrib_rules_jvm/xml/XSLTTransformer.java diff --git a/java/BUILD.bazel b/java/BUILD.bazel index 71889967..e446576e 100644 --- a/java/BUILD.bazel +++ b/java/BUILD.bazel @@ -17,6 +17,7 @@ alias( exports_files([ "checkstyle-strict.xml", "google-checks.xml", + "checkstyle2junit.xslt", ]) checkstyle_config( diff --git a/java/checkstyle2junit.xslt b/java/checkstyle2junit.xslt new file mode 100644 index 00000000..4bbdf88e --- /dev/null +++ b/java/checkstyle2junit.xslt @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Line + + : + + + + + + + diff --git a/java/private/checkstyle.bzl b/java/private/checkstyle.bzl index bedec371..b43326b0 100644 --- a/java/private/checkstyle.bzl +++ b/java/private/checkstyle.bzl @@ -9,7 +9,6 @@ Checkstyle rule implementation def _checkstyle_impl(ctx): info = ctx.attr.config[CheckStyleInfo] config = info.config_file - output_format = info.output_format config_dir = paths.dirname(config.short_path) maybe_cd_config_dir = ["cd {}".format(config_dir)] if config_dir else [] @@ -17,15 +16,23 @@ def _checkstyle_impl(ctx): script = "\n".join([ "#!/usr/bin/env bash", "set -o pipefail", - "set -e", + "set +e", "OLDPWD=$PWD", ] + maybe_cd_config_dir + [ - "$OLDPWD/{lib} -f {output_format} -c {config} {srcs} |sed s:$OLDPWD/::g".format( + "$OLDPWD/{lib} -o checkstyle.xml -f xml -c {config} {srcs}".format( lib = info.checkstyle.short_path, - output_format = output_format, config = config.basename, srcs = " ".join(["$OLDPWD/" + f.short_path for f in ctx.files.srcs]), ), + "checkstyle_exit_code=$?", + # Apply sed to the file in place + "sed s:$OLDPWD/::g checkstyle.xml > checkstyle-stripped.xml", + # Run the Java XSLT transformation tool + "$OLDPWD/{xslt_transformer} checkstyle-stripped.xml $OLDPWD/{xslt} > $XML_OUTPUT_FILE".format( + xslt_transformer = ctx.executable._xslt_transformer.short_path, + xslt = ctx.file.xslt.short_path, + ), + "exit $checkstyle_exit_code", ]) out = ctx.actions.declare_file(ctx.label.name + "exec") @@ -35,8 +42,8 @@ def _checkstyle_impl(ctx): ) runfiles = ctx.runfiles( - files = ctx.files.srcs + [info.checkstyle], - ) + files = ctx.files.srcs + [info.checkstyle, ctx.file.xslt], + ).merge(ctx.attr._xslt_transformer[DefaultInfo].default_runfiles) return [ DefaultInfo( @@ -64,10 +71,15 @@ _checkstyle_test = rule( [CheckStyleInfo], ], ), - "output_format": attr.string( - doc = "Output Format can be plain or xml. Defaults to plain", - values = ["plain", "xml"], - default = "plain", + "xslt": attr.label( + doc = "Path to the checkstyle2junit.xslt file", + allow_single_file = True, + default = "@contrib_rules_jvm//java:checkstyle2junit.xslt", + ), + "_xslt_transformer": attr.label( + default = "@contrib_rules_jvm//java/src/com/github/bazel_contrib/contrib_rules_jvm/xml:XSLTTransformer", + executable = True, + cfg = "exec", ), }, executable = True, diff --git a/java/src/com/github/bazel_contrib/contrib_rules_jvm/xml/BUILD.bazel b/java/src/com/github/bazel_contrib/contrib_rules_jvm/xml/BUILD.bazel new file mode 100644 index 00000000..7bc79db5 --- /dev/null +++ b/java/src/com/github/bazel_contrib/contrib_rules_jvm/xml/BUILD.bazel @@ -0,0 +1,14 @@ +load("@rules_java//java:defs.bzl", "java_binary", "java_library") + +java_library( + name = "xml", + srcs = ["XSLTTransformer.java"], + visibility = ["//:__subpackages__"], +) + +java_binary( + name = "XSLTTransformer", + main_class = "XSLTTransformer", + visibility = ["//visibility:public"], + runtime_deps = [":xml"], +) diff --git a/java/src/com/github/bazel_contrib/contrib_rules_jvm/xml/XSLTTransformer.java b/java/src/com/github/bazel_contrib/contrib_rules_jvm/xml/XSLTTransformer.java new file mode 100644 index 00000000..87094d29 --- /dev/null +++ b/java/src/com/github/bazel_contrib/contrib_rules_jvm/xml/XSLTTransformer.java @@ -0,0 +1,35 @@ +import java.io.File; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +public class XSLTTransformer { + public static void main(String[] args) throws Exception { + if (args.length != 2) { + System.err.println("Usage: java XSLTTransformer "); + System.exit(1); + } + + String inputXML = args[0]; + String xsltFile = args[1]; + + // Create transformer factory + TransformerFactory factory = TransformerFactory.newInstance(); + + // Load the XSLT file + StreamSource xslt = new StreamSource(new File(xsltFile)); + + // Create a transformer + Transformer transformer = factory.newTransformer(xslt); + + // Load the input XML file + StreamSource xmlInput = new StreamSource(new File(inputXML)); + + // Set the output file + StreamResult xmlOutput = new StreamResult(System.out); + + // Perform the transformation + transformer.transform(xmlInput, xmlOutput); + } +}