blob: f52aae93942a4d2141b22481199227777b343e1f [file] [log] [blame]
// Copyright 2020 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.bazel.rules.ninja.actions;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Maps;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.FileProvider;
import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
import com.google.devtools.build.lib.analysis.RuleConfiguredTargetFactory;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.RunfilesProvider;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.bazel.rules.ninja.file.GenericParsingException;
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.NestedSetVisitor;
import com.google.devtools.build.lib.collect.nestedset.NestedSetVisitor.VisitedState;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.packages.Type;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
/** Configured target factory for {@link NinjaBuildRule}. */
public class NinjaBuild implements RuleConfiguredTargetFactory {
@Override
public ConfiguredTarget create(RuleContext ruleContext)
throws InterruptedException, RuleErrorException, ActionConflictException {
Map<String, List<String>> outputGroupsMap =
ruleContext.attributes().get("output_groups", Type.STRING_LIST_DICT);
NinjaGraphProvider graphProvider =
ruleContext.getPrerequisite("ninja_graph", Mode.TARGET, NinjaGraphProvider.class);
Preconditions.checkNotNull(graphProvider);
List<PathFragment> pathsToBuild =
outputGroupsMap.values().stream()
.flatMap(List::stream)
.map(PathFragment::create)
.collect(Collectors.toList());
ImmutableSortedMap.Builder<PathFragment, Artifact> depsMapBuilder =
ImmutableSortedMap.naturalOrder();
ImmutableSortedMap.Builder<PathFragment, Artifact> symlinksMapBuilder =
ImmutableSortedMap.naturalOrder();
createDepsMap(
ruleContext, graphProvider.getWorkingDirectory(), depsMapBuilder, symlinksMapBuilder);
NinjaGraphArtifactsHelper artifactsHelper =
new NinjaGraphArtifactsHelper(
ruleContext,
graphProvider.getOutputRoot(),
graphProvider.getWorkingDirectory(),
createSrcsMap(ruleContext),
depsMapBuilder.build(),
symlinksMapBuilder.build());
if (ruleContext.hasErrors()) {
return null;
}
try {
PhonyTargetArtifacts phonyTargetArtifacts =
new PhonyTargetArtifacts(graphProvider.getPhonyTargetsMap(), artifactsHelper);
new NinjaActionsHelper(
ruleContext,
artifactsHelper,
graphProvider.getUsualTargets(),
graphProvider.getPhonyTargetsMap(),
phonyTargetArtifacts,
pathsToBuild)
.createNinjaActions();
if (!checkOrphanArtifacts(ruleContext)) {
return null;
}
NestedSetBuilder<Artifact> filesToBuild = NestedSetBuilder.stableOrder();
TreeMap<String, NestedSet<Artifact>> groups = Maps.newTreeMap();
for (Map.Entry<String, List<String>> entry : outputGroupsMap.entrySet()) {
NestedSet<Artifact> artifacts =
getGroupArtifacts(
ruleContext,
entry.getValue(),
graphProvider.getPhonyTargetsMap(),
phonyTargetArtifacts,
artifactsHelper);
groups.put(entry.getKey(), artifacts);
filesToBuild.addTransitive(artifacts);
}
if (ruleContext.hasErrors()) {
return null;
}
return new RuleConfiguredTargetBuilder(ruleContext)
.addProvider(RunfilesProvider.class, RunfilesProvider.EMPTY)
.setFilesToBuild(filesToBuild.build())
.addOutputGroups(groups)
.build();
} catch (GenericParsingException e) {
ruleContext.ruleError(e.getMessage());
return null;
}
}
private static boolean checkOrphanArtifacts(RuleContext ruleContext) {
ImmutableSet<Artifact> orphanArtifacts =
ruleContext.getAnalysisEnvironment().getOrphanArtifacts();
if (!orphanArtifacts.isEmpty()) {
List<String> paths =
orphanArtifacts.stream().map(Artifact::getExecPathString).collect(Collectors.toList());
ruleContext.ruleError(
"The following artifacts do not have a generating action in Ninja file: "
+ String.join(", ", paths));
return false;
}
return true;
}
private static NestedSet<Artifact> getGroupArtifacts(
RuleContext ruleContext,
List<String> targets,
ImmutableSortedMap<PathFragment, PhonyTarget> phonyTargetsMap,
PhonyTargetArtifacts phonyTargetsArtifacts,
NinjaGraphArtifactsHelper artifactsHelper)
throws GenericParsingException {
NestedSetBuilder<Artifact> nestedSetBuilder = NestedSetBuilder.stableOrder();
for (String target : targets) {
PathFragment path = PathFragment.create(target);
if (phonyTargetsMap.containsKey(path)) {
NestedSet<Artifact> artifacts = phonyTargetsArtifacts.getPhonyTargetArtifacts(path);
nestedSetBuilder.addTransitive(artifacts);
} else {
Artifact usualArtifact = artifactsHelper.createOutputArtifact(path);
if (usualArtifact == null) {
ruleContext.ruleError(
String.format("Required target '%s' is not created in ninja_graph.", path));
return NestedSetBuilder.emptySet(Order.STABLE_ORDER);
}
nestedSetBuilder.add(usualArtifact);
}
}
return nestedSetBuilder.build();
}
private static ImmutableSortedMap<PathFragment, Artifact> createSrcsMap(RuleContext ruleContext) {
ImmutableList<Artifact> srcs = ruleContext.getPrerequisiteArtifacts("srcs", Mode.TARGET).list();
ImmutableSortedMap.Builder<PathFragment, Artifact> inputsMapBuilder =
ImmutableSortedMap.naturalOrder();
srcs.forEach(a -> inputsMapBuilder.put(a.getRootRelativePath(), a));
return inputsMapBuilder.build();
}
private static void createDepsMap(
RuleContext ruleContext,
PathFragment workingDirectory,
ImmutableSortedMap.Builder<PathFragment, Artifact> depsMapBuilder,
ImmutableSortedMap.Builder<PathFragment, Artifact> symlinksMapBuilder)
throws InterruptedException {
FileProvider fileProvider =
ruleContext.getPrerequisite("ninja_graph", Mode.TARGET, FileProvider.class);
Preconditions.checkNotNull(fileProvider);
new NestedSetVisitor<Artifact>(
a -> {
symlinksMapBuilder.put(a.getExecPath().relativeTo(workingDirectory), a);
},
new VisitedState<>())
.visit(fileProvider.getFilesToBuild());
Map<String, TransitiveInfoCollection> mapping = ruleContext.getPrerequisiteMap("deps_mapping");
for (Map.Entry<String, TransitiveInfoCollection> entry : mapping.entrySet()) {
NestedSet<Artifact> filesToBuild =
entry.getValue().getProvider(FileProvider.class).getFilesToBuild();
if (!filesToBuild.isSingleton()) {
ruleContext.attributeError(
"deps_mapping",
String.format(
"'%s' contains more than one output. "
+ "deps_mapping should only contain targets, producing a single output file.",
entry.getValue().getLabel().getCanonicalForm()));
return;
}
depsMapBuilder.put(PathFragment.create(entry.getKey()), filesToBuild.getSingleton());
}
}
}