| // Copyright 2014 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.test; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.devtools.build.lib.actions.ActionAnalysisMetadata; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.analysis.AnalysisEnvironment; |
| import com.google.devtools.build.lib.analysis.FileProvider; |
| import com.google.devtools.build.lib.analysis.RuleContext; |
| import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; |
| import com.google.devtools.build.lib.analysis.config.BuildConfiguration; |
| import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSet; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| import com.google.devtools.build.lib.collect.nestedset.Order; |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; |
| import com.google.devtools.build.lib.packages.BuildType; |
| import com.google.devtools.build.lib.util.FileType; |
| import com.google.devtools.build.lib.util.FileTypeSet; |
| import com.google.devtools.build.lib.util.Pair; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| import javax.annotation.Nullable; |
| |
| /** |
| * A helper class for collecting instrumented files and metadata for a target. |
| */ |
| public final class InstrumentedFilesCollector { |
| |
| /** |
| * Forwards any instrumented files from the given target's dependencies (as defined in {@code |
| * dependencyAttributes}) for further export. No files from this target are considered |
| * instrumented. |
| * |
| * @return instrumented file provider of all dependencies in {@code dependencyAttributes} |
| */ |
| public static InstrumentedFilesInfo forward( |
| RuleContext ruleContext, String... dependencyAttributes) { |
| return collect( |
| ruleContext, |
| new InstrumentationSpec(FileTypeSet.NO_FILE).withDependencyAttributes(dependencyAttributes), |
| /* localMetadataCollector= */ null, |
| /* rootFiles= */ null, |
| /* reportedToActualSources= */ NestedSetBuilder.create(Order.STABLE_ORDER)); |
| } |
| |
| public static InstrumentedFilesInfo collectTransitive( |
| RuleContext ruleContext, InstrumentationSpec spec) { |
| return collect( |
| ruleContext, |
| spec, |
| NO_METADATA_COLLECTOR, |
| ImmutableList.of(), |
| /* reportedToActualSources= */ NestedSetBuilder.create(Order.STABLE_ORDER)); |
| } |
| |
| public static InstrumentedFilesInfo collectTransitive( |
| RuleContext ruleContext, |
| InstrumentationSpec spec, |
| NestedSet<Pair<String, String>> reportedToActualSources) { |
| return collect( |
| ruleContext, |
| spec, |
| NO_METADATA_COLLECTOR, |
| ImmutableList.of(), |
| NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER), |
| NestedSetBuilder.<Pair<String, String>>emptySet(Order.STABLE_ORDER), |
| false, |
| reportedToActualSources); |
| } |
| |
| public static InstrumentedFilesInfo collect( |
| RuleContext ruleContext, |
| InstrumentationSpec spec, |
| LocalMetadataCollector localMetadataCollector, |
| Iterable<Artifact> rootFiles) { |
| return collect( |
| ruleContext, |
| spec, |
| localMetadataCollector, |
| rootFiles, |
| /* reportedToActualSources= */ NestedSetBuilder.create(Order.STABLE_ORDER)); |
| } |
| |
| public static InstrumentedFilesInfo collect( |
| RuleContext ruleContext, |
| InstrumentationSpec spec, |
| LocalMetadataCollector localMetadataCollector, |
| Iterable<Artifact> rootFiles, |
| NestedSet<Pair<String, String>> reportedToActualSources) { |
| return collect( |
| ruleContext, |
| spec, |
| localMetadataCollector, |
| rootFiles, |
| NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER), |
| NestedSetBuilder.<Pair<String, String>>emptySet(Order.STABLE_ORDER), |
| false, |
| reportedToActualSources); |
| } |
| |
| /** |
| * Collects transitive instrumentation data from dependencies, collects local source files from |
| * dependencies, collects local metadata files by traversing the action graph of the current |
| * configured target, collect rule-specific instrumentation support file sand creates baseline |
| * coverage actions for the transitive closure of source files (if <code>withBaselineCoverage |
| * </code> is true). |
| */ |
| public static InstrumentedFilesInfo collect( |
| RuleContext ruleContext, |
| InstrumentationSpec spec, |
| LocalMetadataCollector localMetadataCollector, |
| Iterable<Artifact> rootFiles, |
| NestedSet<Artifact> coverageSupportFiles, |
| NestedSet<Pair<String, String>> coverageEnvironment, |
| boolean withBaselineCoverage) { |
| return collect( |
| ruleContext, |
| spec, |
| localMetadataCollector, |
| rootFiles, |
| coverageSupportFiles, |
| coverageEnvironment, |
| withBaselineCoverage, |
| /* reportedToActualSources= */ NestedSetBuilder.create(Order.STABLE_ORDER)); |
| } |
| |
| public static InstrumentedFilesInfo collect( |
| RuleContext ruleContext, |
| InstrumentationSpec spec, |
| @Nullable LocalMetadataCollector localMetadataCollector, |
| @Nullable Iterable<Artifact> rootFiles, |
| NestedSet<Artifact> coverageSupportFiles, |
| NestedSet<Pair<String, String>> coverageEnvironment, |
| boolean withBaselineCoverage, |
| NestedSet<Pair<String, String>> reportedToActualSources) { |
| Preconditions.checkNotNull(ruleContext); |
| Preconditions.checkNotNull(spec); |
| |
| if (!ruleContext.getConfiguration().isCodeCoverageEnabled()) { |
| return InstrumentedFilesInfo.EMPTY; |
| } |
| |
| NestedSetBuilder<Artifact> instrumentedFilesBuilder = NestedSetBuilder.stableOrder(); |
| NestedSetBuilder<Artifact> metadataFilesBuilder = NestedSetBuilder.stableOrder(); |
| NestedSetBuilder<Artifact> baselineCoverageInstrumentedFilesBuilder = |
| NestedSetBuilder.stableOrder(); |
| NestedSetBuilder<Artifact> coverageSupportFilesBuilder = |
| NestedSetBuilder.<Artifact>stableOrder() |
| .addTransitive(coverageSupportFiles); |
| NestedSetBuilder<Pair<String, String>> coverageEnvironmentBuilder = |
| NestedSetBuilder.<Pair<String, String>>compileOrder() |
| .addTransitive(coverageEnvironment); |
| |
| |
| // Transitive instrumentation data. |
| for (TransitiveInfoCollection dep : |
| getAllPrerequisites(ruleContext, spec.dependencyAttributes)) { |
| InstrumentedFilesInfo provider = dep.get(InstrumentedFilesInfo.SKYLARK_CONSTRUCTOR); |
| if (provider != null) { |
| instrumentedFilesBuilder.addTransitive(provider.getInstrumentedFiles()); |
| metadataFilesBuilder.addTransitive(provider.getInstrumentationMetadataFiles()); |
| baselineCoverageInstrumentedFilesBuilder.addTransitive( |
| provider.getBaselineCoverageInstrumentedFiles()); |
| coverageSupportFilesBuilder.addTransitive(provider.getCoverageSupportFiles()); |
| coverageEnvironmentBuilder.addTransitive(provider.getCoverageEnvironment()); |
| } |
| } |
| |
| // Local sources. |
| NestedSet<Artifact> localSources = NestedSetBuilder.emptySet(Order.STABLE_ORDER); |
| if (shouldIncludeLocalSources( |
| ruleContext.getConfiguration(), ruleContext.getLabel(), ruleContext.isTestTarget())) { |
| NestedSetBuilder<Artifact> localSourcesBuilder = NestedSetBuilder.stableOrder(); |
| for (TransitiveInfoCollection dep : |
| getAllPrerequisites(ruleContext, spec.sourceAttributes)) { |
| if (!spec.splitLists && dep.get(InstrumentedFilesInfo.SKYLARK_CONSTRUCTOR) != null) { |
| continue; |
| } |
| for (Artifact artifact : dep.getProvider(FileProvider.class).getFilesToBuild().toList()) { |
| if (artifact.isSourceArtifact() && |
| spec.instrumentedFileTypes.matches(artifact.getFilename())) { |
| localSourcesBuilder.add(artifact); |
| } |
| } |
| } |
| localSources = localSourcesBuilder.build(); |
| } |
| instrumentedFilesBuilder.addTransitive(localSources); |
| if (withBaselineCoverage) { |
| // Also add the local sources to the baseline coverage instrumented sources, if the current |
| // rule supports baseline coverage. |
| // TODO(ulfjack): Generate a local baseline coverage action, and then merge at the leaves. |
| baselineCoverageInstrumentedFilesBuilder.addTransitive(localSources); |
| } |
| |
| // Local metadata files. |
| if (localMetadataCollector != null) { |
| localMetadataCollector.collectMetadataArtifacts(rootFiles, |
| ruleContext.getAnalysisEnvironment(), metadataFilesBuilder); |
| } |
| |
| // Baseline coverage actions. |
| NestedSet<Artifact> baselineCoverageFiles = baselineCoverageInstrumentedFilesBuilder.build(); |
| |
| // Create one baseline coverage action per target, but for the transitive closure of files. |
| NestedSet<Artifact> baselineCoverageArtifacts = |
| BaselineCoverageAction.create(ruleContext, baselineCoverageFiles); |
| return new InstrumentedFilesInfo( |
| instrumentedFilesBuilder.build(), |
| metadataFilesBuilder.build(), |
| baselineCoverageFiles, |
| baselineCoverageArtifacts, |
| coverageSupportFilesBuilder.build(), |
| coverageEnvironmentBuilder.build(), |
| reportedToActualSources); |
| } |
| |
| /** |
| * Return whether the sources included by {@code target} (a {@link TransitiveInfoCollection} |
| * representing a rule) should be instrumented according the --instrumentation_filter and |
| * --instrument_test_targets settings in {@code config}. |
| */ |
| public static boolean shouldIncludeLocalSources(BuildConfiguration config, |
| TransitiveInfoCollection target) { |
| return shouldIncludeLocalSources(config, target.getLabel(), |
| target.getProvider(TestProvider.class) != null); |
| } |
| |
| /** |
| * Return whether the sources of the rule in {@code ruleContext} should be instrumented based on |
| * the --instrumentation_filter and --instrument_test_targets config settings. |
| */ |
| public static boolean shouldIncludeLocalSources( |
| BuildConfiguration config, Label label, boolean isTest) { |
| return ((config.shouldInstrumentTestTargets() || !isTest) |
| && config.getInstrumentationFilter().isIncluded(label.toString())); |
| } |
| |
| /** |
| * The set of file types and attributes to visit to collect instrumented files for a certain rule |
| * type. The class is intentionally immutable, so that a single instance is sufficient for all |
| * rules of the same type (and in some cases all rules of related types, such as all {@code foo_*} |
| * rules). |
| */ |
| @Immutable |
| public static final class InstrumentationSpec { |
| private final FileTypeSet instrumentedFileTypes; |
| |
| /** The list of attributes which should be checked for sources. */ |
| private final Collection<String> sourceAttributes; |
| |
| /** The list of attributes from which to collect transitive coverage information. */ |
| private final Collection<String> dependencyAttributes; |
| |
| /** Whether the source and dependency lists are separate. */ |
| private final boolean splitLists; |
| |
| public InstrumentationSpec(FileTypeSet instrumentedFileTypes, |
| String... instrumentedAttributes) { |
| this(instrumentedFileTypes, ImmutableList.copyOf(instrumentedAttributes)); |
| } |
| |
| public InstrumentationSpec(FileTypeSet instrumentedFileTypes, |
| Collection<String> instrumentedAttributes) { |
| this(instrumentedFileTypes, instrumentedAttributes, instrumentedAttributes, false); |
| } |
| |
| private InstrumentationSpec(FileTypeSet instrumentedFileTypes, |
| Collection<String> instrumentedSourceAttributes, |
| Collection<String> instrumentedDependencyAttributes, |
| boolean splitLists) { |
| this.instrumentedFileTypes = instrumentedFileTypes; |
| this.sourceAttributes = ImmutableList.copyOf(instrumentedSourceAttributes); |
| this.dependencyAttributes = |
| ImmutableList.copyOf(instrumentedDependencyAttributes); |
| this.splitLists = splitLists; |
| } |
| |
| /** |
| * Returns a new instrumentation spec with the given attribute names replacing the ones |
| * stored in this object. |
| */ |
| public InstrumentationSpec withAttributes(String... instrumentedAttributes) { |
| return new InstrumentationSpec(instrumentedFileTypes, instrumentedAttributes); |
| } |
| |
| /** |
| * Returns a new instrumentation spec with the given attribute names replacing the ones |
| * stored in this object. |
| */ |
| public InstrumentationSpec withSourceAttributes(String... instrumentedAttributes) { |
| return new InstrumentationSpec(instrumentedFileTypes, |
| ImmutableList.copyOf(instrumentedAttributes), dependencyAttributes, true); |
| } |
| |
| /** |
| * Returns a new instrumentation spec with the given attribute names replacing the ones |
| * stored in this object. |
| */ |
| public InstrumentationSpec withDependencyAttributes(String... instrumentedAttributes) { |
| return new InstrumentationSpec(instrumentedFileTypes, |
| sourceAttributes, ImmutableList.copyOf(instrumentedAttributes), true); |
| } |
| } |
| |
| /** |
| * The implementation for the local metadata collection. The intention is that implementations |
| * recurse over the locally (i.e., for that configured target) created actions and collect |
| * metadata files. |
| */ |
| public abstract static class LocalMetadataCollector { |
| /** |
| * Recursively runs over the local actions and add metadata files to the metadataFilesBuilder. |
| */ |
| public abstract void collectMetadataArtifacts( |
| Iterable<Artifact> artifacts, AnalysisEnvironment analysisEnvironment, |
| NestedSetBuilder<Artifact> metadataFilesBuilder); |
| |
| /** |
| * Adds action output of a particular type to metadata files. |
| * |
| * <p>Only adds the first output that matches the given file type. |
| * |
| * @param metadataFilesBuilder builder to collect metadata files |
| * @param action the action whose outputs to scan |
| * @param fileType the filetype of outputs which should be collected |
| */ |
| protected void addOutputs(NestedSetBuilder<Artifact> metadataFilesBuilder, |
| ActionAnalysisMetadata action, FileType fileType) { |
| for (Artifact output : action.getOutputs()) { |
| if (fileType.matches(output.getFilename())) { |
| metadataFilesBuilder.add(output); |
| break; |
| } |
| } |
| } |
| } |
| |
| /** |
| * An explicit constant for a {@link LocalMetadataCollector} that doesn't collect anything. |
| */ |
| public static final LocalMetadataCollector NO_METADATA_COLLECTOR = null; |
| |
| private static Iterable<TransitiveInfoCollection> getAllPrerequisites( |
| RuleContext ruleContext, Collection<String> attributeNames) { |
| List<TransitiveInfoCollection> prerequisites = new ArrayList<>(); |
| for (String attr : attributeNames) { |
| if (ruleContext.getRule().isAttrDefined(attr, BuildType.LABEL_LIST) || |
| ruleContext.getRule().isAttrDefined(attr, BuildType.LABEL)) { |
| prerequisites.addAll(ruleContext.getPrerequisites(attr, Mode.DONT_CHECK)); |
| } |
| } |
| return prerequisites; |
| } |
| } |