aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build
diff options
context:
space:
mode:
authorGravatar elenairina <elenairina@google.com>2017-12-05 05:10:52 -0800
committerGravatar Copybara-Service <copybara-piper@google.com>2017-12-05 05:12:52 -0800
commit3d362fb9a122ceee6d781be127dfedbbff8051f8 (patch)
tree638b6a8e8ef65ddfb30a68b3934b4c5507baa5ae /src/main/java/com/google/devtools/build
parent581c197e2da57214f8c4915ba6ca9a78b5af537c (diff)
Add --experimental_java_coverage.
Context: java_import or other custom rules (genrules or Skylark) do not propagate coverage information. Coverage metadata is retrieved from the compilation information and it is passed around through providers as Artifact(s). The problem with the current implementation is that there is no way of retrieving instrumentation metadata from arbitrary jars provided by java_import or other custom rules. --experimental_java_coverage solves the issue presented above ONLY for the java rules (has no effect for android/[]/etc). Implementation details: * For each build jar create a .txt file containing the relative path of each Java file. This file is included in the build jar. It is used for recreating the correct path for each covered file when included in the coverage report. * java_binary/java_test will set 3 environment variables: 1) JACOCO_METADATA_JAR - in experimental mode will be a txt file containing all the jars considered for collecting coverage (JacocoCoverageRunner filters out the ones that don't have .uninstrumented.class files). In non-experimental mode will be a jar containing all the instrumented class files. 2) JACOCO_MAIN_CLASS - The main class to be called for the current coverage run. Previously this information was embedded in the JACOCO_METADATA_JAR's manifest. 3) JACOCO_JAVA_RUNFILES RELNOTES: --experimental_java_coverage is available for testing. PiperOrigin-RevId: 177941471
Diffstat (limited to 'src/main/java/com/google/devtools/build')
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/actions/LazyWritePathsFileAction.java91
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java14
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java15
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java119
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/java/java_stub_template.txt2
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/java/JavaBinary.java8
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java32
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java29
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java71
9 files changed, 339 insertions, 42 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/LazyWritePathsFileAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/LazyWritePathsFileAction.java
new file mode 100644
index 0000000000..be17d7dd2d
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/LazyWritePathsFileAction.java
@@ -0,0 +1,91 @@
+
+// Copyright 2017 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.lib.analysis.actions;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.actions.ActionExecutionContext;
+import com.google.devtools.build.lib.actions.ActionKeyContext;
+import com.google.devtools.build.lib.actions.ActionOwner;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.actions.CommandLineExpansionException;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.util.Fingerprint;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Lazily writes the exec path of the given files separated by newline into a specified output file.
+ */
+public final class LazyWritePathsFileAction extends AbstractFileWriteAction {
+ private static final String GUID = "6be94d90-96f3-4bec-8104-1fb08abc2546";
+
+ private final NestedSet<Artifact> files;
+ private final boolean includeDerivedArtifacts;
+
+ public LazyWritePathsFileAction(
+ ActionOwner owner, Artifact output, NestedSet<Artifact> files,
+ boolean includeDerivedArtifacts) {
+ super(owner, files, output, false);
+ this.files = NestedSetBuilder.fromNestedSet(files).build();
+ this.includeDerivedArtifacts = includeDerivedArtifacts;
+ }
+
+ public LazyWritePathsFileAction(
+ ActionOwner owner, Artifact output,
+ ImmutableSet<Artifact> files,
+ boolean includeDerivedArtifacts) {
+ super(owner, Artifact.NO_ARTIFACTS, output, false);
+ this.files = NestedSetBuilder.<Artifact>stableOrder().addAll(files).build();
+ this.includeDerivedArtifacts = includeDerivedArtifacts;
+ }
+
+ @Override
+ public DeterministicWriter newDeterministicWriter(ActionExecutionContext ctx) {
+ return new DeterministicWriter() {
+ @Override
+ public void writeOutputFile(OutputStream out) throws IOException {
+ out.write(getContents().toString().getBytes(UTF_8));
+ }
+ };
+ }
+
+ /**
+ * Computes the Action key for this action by computing the fingerprint for the file contents.
+ */
+ @Override
+ protected String computeKey(ActionKeyContext actionKeyContext)
+ throws CommandLineExpansionException {
+ Fingerprint f = new Fingerprint();
+ f.addString(GUID);
+ f.addBoolean(includeDerivedArtifacts);
+ f.addString(getContents());
+ return f.hexDigestAndReset();
+ }
+
+ private String getContents() {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (Artifact file : files) {
+ if (file.isSourceArtifact() || includeDerivedArtifacts) {
+ stringBuilder.append(file.getRootRelativePathString());
+ stringBuilder.append("\n");
+ }
+ }
+ return stringBuilder.toString();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
index 6130a0d8f6..e6e6a36b16 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java
@@ -665,6 +665,16 @@ public class BuildConfiguration implements BuildEvent {
public boolean collectCodeCoverage;
@Option(
+ name = "experimental_java_coverage",
+ defaultValue = "false",
+ category = "testing",
+ documentationCategory = OptionDocumentationCategory.OUTPUT_PARAMETERS,
+ effectTags = { OptionEffectTag.AFFECTS_OUTPUTS },
+ help = "If true Bazel will use a new way of computing code coverage for java targets."
+ )
+ public boolean experimentalJavaCoverage;
+
+ @Option(
name = "coverage_support",
converter = LabelConverter.class,
defaultValue = "@bazel_tools//tools/test:coverage_support",
@@ -1908,6 +1918,10 @@ public class BuildConfiguration implements BuildEvent {
return options.collectCodeCoverage;
}
+ public boolean isExperimentalJavaCoverage() {
+ return options.experimentalJavaCoverage;
+ }
+
public boolean isLLVMCoverageMapFormatEnabled() {
return options.useLLVMCoverageMapFormat;
}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java b/src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java
index 34e4311b4a..8a7661f443 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java
@@ -465,8 +465,19 @@ public class TestRunnerAction extends AbstractAction implements NotifyOnActionCa
env.put("COVERAGE_MANIFEST", getCoverageManifest().getExecPathString());
env.put("COVERAGE_DIR", getCoverageDirectory().getPathString());
env.put("COVERAGE_OUTPUT_FILE", getCoverageData().getExecPathString());
- // TODO(elenairina): Remove this after the next blaze release (after 2017.07.30).
- env.put("NEW_JAVA_COVERAGE_IMPL", "True");
+ // TODO(elenairina): Remove this after it reaches a blaze release.
+ if (configuration.isExperimentalJavaCoverage()) {
+ // This value ("released") tells lcov_merger whether it should use the old or the new
+ // java coverage implementation. The meaning of "released" is that lcov_merger will receive
+ // this value only after blaze containing this change will be released.
+ env.put("NEW_JAVA_COVERAGE_IMPL", "released");
+ } else {
+ // This value ("True") should have told lcov_merger whether it should use the old or the new
+ // java coverage implementation. Due to several failed attempts at submitting the new
+ // implementation, this value will be treated still as the old implementation. This
+ // environment variable must be set to a value recognized by lcov_merger.
+ env.put("NEW_JAVA_COVERAGE_IMPL", "True");
+ }
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java
index 3e8b2fc586..de220a7b72 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java
@@ -30,6 +30,7 @@ import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
import com.google.devtools.build.lib.analysis.actions.LauncherFileWriteAction;
import com.google.devtools.build.lib.analysis.actions.LauncherFileWriteAction.LaunchInfo;
+import com.google.devtools.build.lib.analysis.actions.LazyWritePathsFileAction;
import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction;
import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.ComputedSubstitution;
import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Substitution;
@@ -260,6 +261,21 @@ public class BazelJavaSemantics implements JavaSemantics {
Artifact executable,
String javaStartClass,
String javaExecutable) {
+ return createStubAction(
+ ruleContext, javaCommon, jvmFlags, executable, javaStartClass, "",
+ NestedSetBuilder.<Artifact>stableOrder(), javaExecutable);
+ }
+
+ @Override
+ public Artifact createStubAction(
+ RuleContext ruleContext,
+ JavaCommon javaCommon,
+ List<String> jvmFlags,
+ Artifact executable,
+ String javaStartClass,
+ String coverageStartClass,
+ NestedSetBuilder<Artifact> filesBuilder,
+ String javaExecutable) {
Preconditions.checkState(ruleContext.getConfiguration().hasFragment(Jvm.class));
Preconditions.checkNotNull(jvmFlags);
@@ -316,15 +332,43 @@ public class BazelJavaSemantics implements JavaSemantics {
String path =
javaArtifacts.getInstrumentedJar() != null
? "${JAVA_RUNFILES}/"
- + workspacePrefix
- + javaArtifacts.getInstrumentedJar().getRootRelativePath().getPathString()
+ + workspacePrefix
+ + javaArtifacts.getInstrumentedJar().getRootRelativePath().getPathString()
: "";
- arguments.add(
- Substitution.of(
- "%set_jacoco_metadata%",
- ruleContext.getConfiguration().isCodeCoverageEnabled()
- ? "export JACOCO_METADATA_JAR=" + path
- : ""));
+
+ if (ruleContext.getConfiguration().isCodeCoverageEnabled()
+ && ruleContext.getConfiguration().isExperimentalJavaCoverage()) {
+ Artifact runtimeClassPathArtifact = ruleContext.getUniqueDirectoryArtifact(
+ "coverage_runtime_classpath",
+ "runtime-classpath.txt",
+ ruleContext.getBinOrGenfilesDirectory());
+ ruleContext.registerAction(new LazyWritePathsFileAction(
+ ruleContext.getActionOwner(),
+ runtimeClassPathArtifact,
+ javaCommon.getRuntimeClasspath(),
+ true));
+ filesBuilder.add(runtimeClassPathArtifact);
+ arguments.add(Substitution.of(
+ JavaSemantics.JACOCO_METADATA_PLACEHOLDER,
+ "export JACOCO_METADATA_JAR=${JAVA_RUNFILES}/" + workspacePrefix + "/"
+ + runtimeClassPathArtifact.getRootRelativePathString()
+ ));
+ arguments.add(Substitution.of(
+ JavaSemantics.JACOCO_MAIN_CLASS_PLACEHOLDER,
+ "export JACOCO_MAIN_CLASS=" + coverageStartClass));
+ arguments.add(Substitution.of(
+ JavaSemantics.JACOCO_JAVA_RUNFILES_ROOT_PLACEHOLDER,
+ "export JACOCO_JAVA_RUNFILES_ROOT=${JAVA_RUNFILES}/" + workspacePrefix)
+ );
+ arguments.add(
+ Substitution.of("%java_start_class%", ShellEscaper.escapeString(javaStartClass)));
+ } else {
+ arguments.add(Substitution.of(JavaSemantics.JACOCO_METADATA_PLACEHOLDER,
+ ruleContext.getConfiguration().isCodeCoverageEnabled()
+ ? "export JACOCO_METADATA_JAR=" + path : ""));
+ arguments.add(Substitution.of(JavaSemantics.JACOCO_MAIN_CLASS_PLACEHOLDER, ""));
+ arguments.add(Substitution.of(JavaSemantics.JACOCO_JAVA_RUNFILES_ROOT_PLACEHOLDER, ""));
+ }
arguments.add(Substitution.of("%java_start_class%",
ShellEscaper.escapeString(javaStartClass)));
@@ -619,6 +663,18 @@ public class BazelJavaSemantics implements JavaSemantics {
return !attributes.getInstrumentationMetadata().isEmpty();
}
+ @Override
+ public String addCoverageSupport(
+ JavaCompilationHelper helper,
+ JavaTargetAttributes.Builder attributes,
+ Artifact executable,
+ Artifact instrumentationMetadata,
+ JavaCompilationArtifacts.Builder javaArtifactsBuilder,
+ String mainClass) throws InterruptedException {
+ return addCoverageSupport(helper, attributes, executable, instrumentationMetadata,
+ javaArtifactsBuilder, mainClass, false);
+ }
+
// TODO(yueg): refactor this (only mainClass different for now)
@Override
public String addCoverageSupport(
@@ -627,33 +683,36 @@ public class BazelJavaSemantics implements JavaSemantics {
Artifact executable,
Artifact instrumentationMetadata,
JavaCompilationArtifacts.Builder javaArtifactsBuilder,
- String mainClass)
+ String mainClass,
+ boolean isExperimentalCoverage)
throws InterruptedException {
// This method can be called only for *_binary/*_test targets.
Preconditions.checkNotNull(executable);
- // Add our own metadata artifact (if any).
- if (instrumentationMetadata != null) {
- attributes.addInstrumentationMetadataEntries(ImmutableList.of(instrumentationMetadata));
- }
+ if (!isExperimentalCoverage) {
+ // Add our own metadata artifact (if any).
+ if (instrumentationMetadata != null) {
+ attributes.addInstrumentationMetadataEntries(ImmutableList.of(instrumentationMetadata));
+ }
- if (!hasInstrumentationMetadata(attributes)) {
- return mainClass;
- }
+ if (!hasInstrumentationMetadata(attributes)) {
+ return mainClass;
+ }
- Artifact instrumentedJar =
- helper
- .getRuleContext()
- .getBinArtifact(helper.getRuleContext().getLabel().getName() + "_instrumented.jar");
-
- // Create an instrumented Jar. This will be referenced on the runtime classpath prior
- // to all other Jars.
- JavaCommon.createInstrumentedJarAction(
- helper.getRuleContext(),
- this,
- attributes.getInstrumentationMetadata(),
- instrumentedJar,
- mainClass);
- javaArtifactsBuilder.setInstrumentedJar(instrumentedJar);
+ Artifact instrumentedJar =
+ helper
+ .getRuleContext()
+ .getBinArtifact(helper.getRuleContext().getLabel().getName() + "_instrumented.jar");
+
+ // Create an instrumented Jar. This will be referenced on the runtime classpath prior
+ // to all other Jars.
+ JavaCommon.createInstrumentedJarAction(
+ helper.getRuleContext(),
+ this,
+ attributes.getInstrumentationMetadata(),
+ instrumentedJar,
+ mainClass);
+ javaArtifactsBuilder.setInstrumentedJar(instrumentedJar);
+ }
// Add the coverage runner to the list of dependencies when compiling in coverage mode.
TransitiveInfoCollection runnerTarget =
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/java_stub_template.txt b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/java_stub_template.txt
index 8a323d6524..682126d3ee 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/java_stub_template.txt
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/java_stub_template.txt
@@ -255,6 +255,8 @@ export CLASSLOADER_PREFIX_PATH="${RUNPATH}"
# We need to make the metadata jar with uninstrumented classes available for generating
# the lcov-compatible coverage report, and we don't want it on the classpath.
%set_jacoco_metadata%
+%set_jacoco_main_class%
+%set_jacoco_java_runfiles_root%
if [[ -n "$JVM_DEBUG_PORT" ]]; then
JVM_DEBUG_SUSPEND=${DEFAULT_JVM_DEBUG_SUSPEND:-"y"}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaBinary.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaBinary.java
index ed229fee77..2e7f0c5bd0 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaBinary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaBinary.java
@@ -175,7 +175,8 @@ public class JavaBinary implements RuleConfiguredTargetFactory {
executableForRunfiles,
instrumentationMetadata,
javaArtifactsBuilder,
- mainClass);
+ mainClass,
+ ruleContext.getConfiguration().isExperimentalJavaCoverage());
}
} else {
filesBuilder.add(classJar);
@@ -262,6 +263,8 @@ public class JavaBinary implements RuleConfiguredTargetFactory {
jvmFlags,
executableForRunfiles,
mainClass,
+ originalMainClass,
+ filesBuilder,
javaExecutable);
if (!executableToRun.equals(executableForRunfiles)) {
filesBuilder.add(executableToRun);
@@ -526,7 +529,8 @@ public class JavaBinary implements RuleConfiguredTargetFactory {
builder.addTargets(runtimeDeps, RunfilesProvider.DEFAULT_RUNFILES);
semantics.addDependenciesForRunfiles(ruleContext, builder);
- if (ruleContext.getConfiguration().isCodeCoverageEnabled()) {
+ if (ruleContext.getConfiguration().isCodeCoverageEnabled()
+ && !ruleContext.getConfiguration().isExperimentalJavaCoverage()) {
Artifact instrumentedJar = javaArtifacts.getInstrumentedJar();
if (instrumentedJar != null) {
builder.addArtifact(instrumentedJar);
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java
index 64140170cf..75877350ac 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java
@@ -168,14 +168,15 @@ public final class JavaCompilationHelper {
* (null if no sources will be generated).
* @param outputDepsProto the compiler-generated jdeps file to create with the Action
* (null if not requested)
- * @param outputMetadata metadata file (null if no instrumentation is needed).
+ * @param instrumentationMetadataJar metadata file (null if no instrumentation is needed or if
+ * --experimental_java_coverage is true).
*/
public void createCompileAction(
Artifact outputJar,
Artifact manifestProtoOutput,
@Nullable Artifact gensrcOutputJar,
@Nullable Artifact outputDepsProto,
- @Nullable Artifact outputMetadata) {
+ @Nullable Artifact instrumentationMetadataJar) {
JavaTargetAttributes attributes = getAttributes();
@@ -196,6 +197,7 @@ public final class JavaCompilationHelper {
}
JavaCompileAction.Builder builder = createJavaCompileActionBuilder(semantics);
+ builder.setArtifactForExperimentalCoverage(maybeCreateExperimentalCoverageArtifact(outputJar));
builder.setClasspathEntries(attributes.getCompileTimeClassPath());
builder.setBootclasspathEntries(getBootclasspathOrDefault());
builder.setSourcePathEntries(attributes.getSourcePath());
@@ -208,7 +210,7 @@ public final class JavaCompilationHelper {
builder.setGensrcOutputJar(gensrcOutputJar);
builder.setOutputDepsProto(outputDepsProto);
builder.setAdditionalOutputs(attributes.getAdditionalOutputs());
- builder.setMetadata(outputMetadata);
+ builder.setMetadata(instrumentationMetadataJar);
builder.setInstrumentationJars(jacocoInstrumentation);
builder.setSourceFiles(attributes.getSourceFiles());
builder.addSourceJars(attributes.getSourceJars());
@@ -253,6 +255,26 @@ public final class JavaCompilationHelper {
}
/**
+ * Creates an {@link Artifact} needed by {@code JacocoCoverageRunner} when
+ * {@code --experimental_java_coverage} is true.
+ *
+ * <p> The {@link Artifact} is created in the same directory as the given {@code compileJar} and
+ * has the suffix {@code -paths-for-coverage.txt}.
+ *
+ * <p> Returns {@code null} if {@code compileJar} should not be instrumented.
+ */
+ private Artifact maybeCreateExperimentalCoverageArtifact(Artifact compileJar) {
+ if (!shouldInstrumentJar() || !getConfiguration().isExperimentalJavaCoverage()) {
+ return null;
+ }
+ PathFragment packageRelativePath =
+ compileJar.getRootRelativePath().relativeTo(ruleContext.getPackageDirectory());
+ PathFragment path =
+ FileSystemUtils.replaceExtension(packageRelativePath, "-paths-for-coverage.txt");
+ return ruleContext.getPackageRelativeArtifact(path, compileJar.getRoot());
+ }
+
+ /**
* Returns the instrumentation metadata files to be generated for a given output jar.
*
* <p>Only called if the output jar actually needs to be instrumented.
@@ -299,6 +321,10 @@ public final class JavaCompilationHelper {
@Nullable
public Artifact createInstrumentationMetadata(Artifact outputJar,
JavaCompilationArtifacts.Builder javaArtifactsBuilder) {
+ // In the experimental java coverage we don't create the .em jar for instrumentation.
+ if (getConfiguration().isExperimentalJavaCoverage()) {
+ return null;
+ }
// If we need to instrument the jar, add additional output (the coverage metadata file) to the
// JavaCompileAction.
Artifact instrumentationMetadata = null;
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java
index 1dbd8b73c4..304d3f2d44 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompileAction.java
@@ -48,6 +48,7 @@ import com.google.devtools.build.lib.analysis.actions.CommandLine;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.CustomMultiArgv;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.VectorArg;
+import com.google.devtools.build.lib.analysis.actions.LazyWritePathsFileAction;
import com.google.devtools.build.lib.analysis.actions.ParameterFileWriteAction;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
@@ -511,6 +512,7 @@ public final class JavaCompileAction extends SpawnAction {
private Collection<Artifact> additionalOutputs;
private Artifact paramFile;
private Artifact metadata;
+ private Artifact artifactForExperimentalCoverage;
private ImmutableSet<Artifact> sourceFiles = ImmutableSet.of();
private final Collection<Artifact> sourceJars = new ArrayList<>();
private BuildConfiguration.StrictDepsMode strictJavaDeps =
@@ -625,6 +627,11 @@ public final class JavaCompileAction extends SpawnAction {
semantics.getJavaBuilderMainClass(),
pathSeparator);
+ if (artifactForExperimentalCoverage != null) {
+ analysisEnvironment.registerAction(new LazyWritePathsFileAction(
+ owner, artifactForExperimentalCoverage, sourceFiles, false));
+ }
+
// The actual params-file-based command line executed for a compile action.
CommandLine javaBuilderCommandLine =
CustomCommandLine.builder()
@@ -640,7 +647,7 @@ public final class JavaCompileAction extends SpawnAction {
.addAll(instrumentationJars)
.build();
- NestedSet<Artifact> inputs =
+ NestedSetBuilder<Artifact> inputsBuilder =
NestedSetBuilder.<Artifact>stableOrder()
.addTransitive(classpathEntries)
.addTransitive(compileTimeDependencyArtifacts)
@@ -652,8 +659,11 @@ public final class JavaCompileAction extends SpawnAction {
.addAll(sourcePathEntries)
.addAll(extdirInputs)
.add(paramFile)
- .addTransitive(tools)
- .build();
+ .addTransitive(tools);
+ if (artifactForExperimentalCoverage != null) {
+ inputsBuilder.add(artifactForExperimentalCoverage);
+ }
+ NestedSet<Artifact> inputs = inputsBuilder.build();
return new JavaCompileAction(
owner,
@@ -768,9 +778,13 @@ public final class JavaCompileAction extends SpawnAction {
}
}
}
- if (metadata != null) {
+
+ // Chose what artifact to pass to JavaBuilder, as input to jacoco instrumentation processor.
+ // metadata should be null when --experimental_java_coverage is true.
+ Artifact coverageArtifact = metadata != null ? metadata : artifactForExperimentalCoverage;
+ if (coverageArtifact != null) {
result.add("--post_processor");
- result.addExecPath(JACOCO_INSTRUMENTATION_PROCESSOR, metadata);
+ result.addExecPath(JACOCO_INSTRUMENTATION_PROCESSOR, coverageArtifact);
result.addPath(
configuration
.getCoverageMetadataDirectory(targetLabel.getPackageIdentifier().getRepository())
@@ -1006,6 +1020,11 @@ public final class JavaCompileAction extends SpawnAction {
return this;
}
+ public Builder setArtifactForExperimentalCoverage(Artifact artifactForExperimentalCoverage) {
+ this.artifactForExperimentalCoverage = artifactForExperimentalCoverage;
+ return this;
+ }
+
public Builder setRuleKind(String ruleKind) {
this.ruleKind = ruleKind;
return this;
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java
index c4af1dfd93..76e0376a8a 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaSemantics.java
@@ -21,6 +21,7 @@ import static com.google.devtools.build.lib.packages.ImplicitOutputsFunction.fro
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Streams;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.LanguageDependentFragment.LibraryLanguage;
import com.google.devtools.build.lib.analysis.OutputGroupProvider;
@@ -31,6 +32,7 @@ import com.google.devtools.build.lib.analysis.Runfiles;
import com.google.devtools.build.lib.analysis.Runfiles.Builder;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
+import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.ComputedSubstitution;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
@@ -45,8 +47,10 @@ import com.google.devtools.build.lib.rules.java.proto.GeneratedExtensionRegistry
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.util.FileType;
import com.google.devtools.build.lib.vfs.PathFragment;
+import java.io.File;
import java.util.Collection;
import java.util.List;
+import java.util.stream.Collectors;
import javax.annotation.Nullable;
/**
@@ -85,6 +89,9 @@ public interface JavaSemantics {
SafeImplicitOutputsFunction JAVA_ONE_VERSION_ARTIFACT =
fromTemplates("%{name}-one-version.txt");
+ SafeImplicitOutputsFunction JAVA_COVERAGE_RUNTIME_CLASS_PATH_TXT =
+ fromTemplates("%{name}-runtime-classpath.txt");
+
SafeImplicitOutputsFunction JAVA_BINARY_DEPLOY_SOURCE_JAR =
fromTemplates("%{name}_deploy-src.jar");
@@ -212,6 +219,35 @@ public interface JavaSemantics {
String IJAR_LABEL = "//tools/defaults:ijar";
+ String JACOCO_METADATA_PLACEHOLDER = "%set_jacoco_metadata%";
+ String JACOCO_MAIN_CLASS_PLACEHOLDER = "%set_jacoco_main_class%";
+ String JACOCO_JAVA_RUNFILES_ROOT_PLACEHOLDER = "%set_jacoco_java_runfiles_root%";
+
+ /**
+ * Substitution for exporting the jars needed for jacoco coverage.
+ */
+ class ComputedJacocoSubstitution extends ComputedSubstitution {
+ private final NestedSet<Artifact> jars;
+ private final String pathPrefix;
+
+ public ComputedJacocoSubstitution(NestedSet<Artifact> jars, String workspacePrefix) {
+ super(JACOCO_METADATA_PLACEHOLDER);
+ this.jars = jars;
+ this.pathPrefix = "${JAVA_RUNFILES}/" + workspacePrefix;
+ }
+
+ /**
+ * Concatenating the root relative paths of the artifacts. Each relative path entry is prepended
+ * with "${JAVA_RUNFILES}" and the workspace prefix.
+ */
+ @Override
+ public String getValue() {
+ return Streams.stream(jars)
+ .map(artifact -> pathPrefix + "/" + artifact.getRootRelativePathString())
+ .collect(Collectors.joining(File.pathSeparator, "export JACOCO_METADATA_JARS=", ""));
+ }
+ }
+
/**
* Verifies if the rule contains any errors.
*
@@ -293,6 +329,24 @@ public interface JavaSemantics {
String javaExecutable);
/**
+ * Same as {@link #createStubAction(RuleContext, JavaCommon, List, Artifact, String, String)}.
+ *
+ * <p> In *experimental* coverage mode creates a txt file containing the runtime jars names.
+ * {@code JacocoCoverageRunner} will use it to retrieve the name of the jars considered for
+ * collecting coverage. {@code JacocoCoverageRunner} will *not* collect coverage implicitly
+ * for all the runtime jars, only for those that pack a file ending in "-paths-for-coverage.txt".
+ */
+ public Artifact createStubAction(
+ RuleContext ruleContext,
+ JavaCommon javaCommon,
+ List<String> jvmFlags,
+ Artifact executable,
+ String javaStartClass,
+ String coverageStartClass,
+ NestedSetBuilder<Artifact> filesBuilder,
+ String javaExecutable);
+
+ /**
* Returns true if {@code createStubAction} considers {@code javaExecutable} as a substitution.
* Returns false if {@code createStubAction} considers {@code javaExecutable} as a file path.
*/
@@ -345,6 +399,23 @@ public interface JavaSemantics {
throws InterruptedException;
/**
+ * Same as {@link #addCoverageSupport(JavaCompilationHelper, JavaTargetAttributes.Builder,
+ * Artifact, Artifact, JavaCompilationArtifacts.Builder, String)}.
+ *
+ * <p> In *experimental* coverage mode omits dealing with instrumentation metadata and does not
+ * create the instrumented jar.
+ */
+ String addCoverageSupport(
+ JavaCompilationHelper helper,
+ JavaTargetAttributes.Builder attributes,
+ Artifact executable,
+ Artifact instrumentationMetadata,
+ JavaCompilationArtifacts.Builder javaArtifactsBuilder,
+ String mainClass,
+ boolean isExperimentalCoverage)
+ throws InterruptedException;
+
+ /**
* Return the JVM flags to be used in a Java binary.
*/
Iterable<String> getJvmFlags(