aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/rules
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/lib/rules
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/lib/rules')
-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
4 files changed, 130 insertions, 10 deletions
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(