From 7ecf87189292308ed38bc15660ef142cf0cf9b3c Mon Sep 17 00:00:00 2001 From: Yue Gan Date: Thu, 1 Dec 2016 14:15:28 +0000 Subject: [PATCH] Add Jacoco related code to buildjar. (series 1/4 of open-sourcing coverage command for java test) -- MOS_MIGRATED_REVID=140723068 --- .../com/google/devtools/build/buildjar/BUILD | 7 +- .../build/buildjar/BazelJavaBuilder.java | 4 +- .../JacocoInstrumentationProcessor.java | 109 ++++++++++++++++++ 3 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 src/java_tools/buildjar/java/com/google/devtools/build/buildjar/instrumentation/JacocoInstrumentationProcessor.java diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/BUILD b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/BUILD index 4d57f6fa1b87cd..e603285c188162 100644 --- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/BUILD +++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/BUILD @@ -47,7 +47,10 @@ java_library( java_library( name = "buildjar", srcs = glob( - ["*.java"], + [ + "*.java", + "instrumentation/*.java", + ], exclude = [ "InvalidCommandLineException.java", "JarOwner.java", @@ -66,6 +69,7 @@ java_library( "//src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins:processing", "//src/main/protobuf:worker_protocol_java_proto", "//third_party:guava", + "//third_party/java/jacoco:core", "//third_party/java/jdk/langtools:javac", ], ) @@ -128,6 +132,7 @@ bootstrap_java_library( "//third_party:bootstrap_guava_and_error_prone-jars", "//third_party:jsr305-jars", "//third_party/protobuf:protobuf-jars", + "//third_party/java/jacoco:core-jars", ], neverlink_jars = ["//third_party/java/jdk/langtools:javac_jar"], srcjars = [ diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/BazelJavaBuilder.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/BazelJavaBuilder.java index c15f49a723ee99..83163030c69161 100644 --- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/BazelJavaBuilder.java +++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/BazelJavaBuilder.java @@ -1,4 +1,4 @@ -// Copyright 2007 The Bazel Authors. All rights reserved. +// Copyright 2016 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; +import com.google.devtools.build.buildjar.instrumentation.JacocoInstrumentationProcessor; import com.google.devtools.build.buildjar.javac.JavacOptions; import com.google.devtools.build.buildjar.javac.plugins.BlazeJavaCompilerPlugin; import com.google.devtools.build.buildjar.javac.plugins.classloader.ClassLoaderMaskingPlugin; @@ -43,6 +44,7 @@ public abstract class BazelJavaBuilder { * The main method of the BazelJavaBuilder. */ public static void main(String[] args) { + AbstractPostProcessor.addPostProcessor("jacoco", new JacocoInstrumentationProcessor()); if (args.length == 1 && args[0].equals("--persistent_worker")) { System.exit(runPersistentWorker()); } else { diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/instrumentation/JacocoInstrumentationProcessor.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/instrumentation/JacocoInstrumentationProcessor.java new file mode 100644 index 00000000000000..a9eee924b401cc --- /dev/null +++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/instrumentation/JacocoInstrumentationProcessor.java @@ -0,0 +1,109 @@ +// Copyright 2016 The Bazel Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.devtools.build.buildjar.instrumentation; + +import com.google.common.io.Files; +import com.google.devtools.build.buildjar.AbstractPostProcessor; +import com.google.devtools.build.buildjar.InvalidCommandLineException; +import com.google.devtools.build.buildjar.jarhelper.JarCreator; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import org.jacoco.core.instr.Instrumenter; +import org.jacoco.core.runtime.OfflineInstrumentationAccessGenerator; + +/** Instruments compiled java classes using Jacoco instrumentation library. */ +public final class JacocoInstrumentationProcessor extends AbstractPostProcessor { + + private String metadataDir; + private String metadataOutput; + + @Override + public void setCommandLineArguments(List args) throws InvalidCommandLineException { + if (args.size() < 2) { + throw new InvalidCommandLineException( + "Number of arguments for Jacoco instrumentation should be 2+ (given " + + args.size() + + ": metadataOutput metadataDirectory [filters*]."); + } + + metadataDir = args.get(1); + metadataOutput = args.get(0); + // ignoring filters, they weren't used in the previous implementation + // TODO(bazel-team): filters should be correctly handled + } + + /** + * Instruments classes using Jacoco and keeps copies of uninstrumented class files in + * jacocoMetadataDir, to be zipped up in the output file jacocoMetadataOutput. + */ + @Override + public void processRequest() throws IOException { + // Clean up jacocoMetadataDir to be used by postprocessing steps. This is important when + // running JavaBuilder locally, to remove stale entries from previous builds. + if (metadataDir != null) { + File workDir = new File(workingPath(metadataDir)); + if (workDir.exists()) { + recursiveRemove(workDir); + } + workDir.mkdirs(); + } + + JarCreator jar = new JarCreator(workingPath(metadataOutput)); + jar.setNormalize(true); + jar.setCompression(shouldCompressJar()); + Instrumenter instr = new Instrumenter(new OfflineInstrumentationAccessGenerator()); + // TODO(bazel-team): not sure whether Emma did anything fancier than this (multithreaded?) + instrumentRecursively(instr, new File(workingPath(getBuildClassDir()))); + jar.addDirectory(workingPath(metadataDir)); + jar.execute(); + } + + /** + * Runs Jacoco instrumentation processor over all .class files recursively, starting with root. + */ + private void instrumentRecursively(Instrumenter instr, File root) throws IOException { + for (File f : Files.fileTreeTraverser().preOrderTraversal(root).filter(Files.isFile())) { + if (f.isDirectory()) { + instrumentRecursively(instr, f); + } else if (f.isFile() && f.getName().endsWith(".class")) { + // TODO(bazel-team): filter with coverage_instrumentation_filter? + // It's not clear whether there is any advantage in not instrumenting *Test classes, apart + // from lowering the covered percentage in the aggregate statistics. + + // We first move the original .class file to our metadata directory, then instrument it and + // output the instrumented version in the regular classes output directory. + File instrumentedCopy = new File(f.getPath()); + File uninstrumentedCopy = + new File( + workingPath(f.getPath().replace(workingPath(getBuildClassDir()), metadataDir))); + uninstrumentedCopy.getParentFile().mkdirs(); + f.renameTo(uninstrumentedCopy); + + try (InputStream input = new BufferedInputStream(new FileInputStream(uninstrumentedCopy)); + OutputStream output = + new BufferedOutputStream(new FileOutputStream(instrumentedCopy))) { + instr.instrument(input, output, f.getName()); + } + } + } + } +}