| // 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()); |
| } |
| } |
| } |