| // 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; |
| |
| import static com.google.devtools.build.lib.syntax.EvalUtils.SKYLARK_COMPARATOR; |
| |
| import com.google.common.collect.ImmutableCollection; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSortedSet; |
| import com.google.common.collect.Sets; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.analysis.configuredtargets.MergedConfiguredTarget.DuplicateException; |
| import com.google.devtools.build.lib.analysis.skylark.SkylarkRuleConfiguredTargetUtil; |
| 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.events.Location; |
| import com.google.devtools.build.lib.packages.BuiltinProvider; |
| import com.google.devtools.build.lib.packages.NativeInfo; |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; |
| import com.google.devtools.build.lib.skylarkbuildapi.OutputGroupInfoApi; |
| import com.google.devtools.build.lib.syntax.EvalException; |
| import com.google.devtools.build.lib.syntax.EvalUtils; |
| import com.google.devtools.build.lib.syntax.SkylarkDict; |
| import com.google.devtools.build.lib.syntax.SkylarkIndexable; |
| import com.google.devtools.build.lib.syntax.SkylarkNestedSet; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import javax.annotation.Nullable; |
| |
| /** |
| * {@code ConfiguredTarget}s implementing this interface can provide artifacts that <b>can</b> be |
| * built when the target is mentioned on the command line (as opposed to being always built, like |
| * {@link com.google.devtools.build.lib.analysis.FileProvider}) |
| * |
| * <p>The artifacts are grouped into "output groups". Which output groups are built is controlled by |
| * the {@code --output_groups} undocumented command line option, which in turn is added to the |
| * command line at the discretion of the build command being run. |
| * |
| * <p>Output groups starting with an underscore are "not important". This means that artifacts built |
| * because such an output group is mentioned in a {@code --output_groups} command line option are |
| * not mentioned on the output. |
| */ |
| @Immutable |
| @AutoCodec |
| public final class OutputGroupInfo extends NativeInfo |
| implements SkylarkIndexable, Iterable<String>, OutputGroupInfoApi { |
| public static final String SKYLARK_NAME = "output_groups"; |
| |
| public static final OutputGroupInfoProvider SKYLARK_CONSTRUCTOR = new OutputGroupInfoProvider(); |
| |
| /** |
| * Prefix for output groups that are not reported to the user on the terminal output of Blaze when |
| * they are built. |
| */ |
| public static final String HIDDEN_OUTPUT_GROUP_PREFIX = "_"; |
| |
| /** |
| * Suffix for output groups that are internal to bazel and may not be referenced from a filegroup. |
| */ |
| public static final String INTERNAL_SUFFIX = "_INTERNAL_"; |
| |
| /** |
| * Building these artifacts only results in the compilation (and not e.g. linking) of the |
| * associated target. Mostly useful for C++, less so for e.g. Java. |
| */ |
| public static final String FILES_TO_COMPILE = "compilation_outputs"; |
| |
| /** |
| * These artifacts are the direct requirements for compilation, but building these does not |
| * actually compile the target. Mostly useful when IDEs want Blaze to emit generated code so that |
| * they can do the compilation in their own way. |
| */ |
| public static final String COMPILATION_PREREQUISITES = |
| "compilation_prerequisites" + INTERNAL_SUFFIX; |
| |
| /** |
| * These files are built when a target is mentioned on the command line, but are not reported to |
| * the user. This is mostly runfiles, which is necessary because we don't want a target to |
| * successfully build if a file in its runfiles is broken. |
| */ |
| public static final String HIDDEN_TOP_LEVEL = |
| HIDDEN_OUTPUT_GROUP_PREFIX + "hidden_top_level" + INTERNAL_SUFFIX; |
| |
| /** |
| * Temporary files created during building a rule, for example, .i, .d and .s files for C++ |
| * compilation. |
| * |
| * <p>This output group is somewhat special: it is always built, but it only contains files when |
| * the {@code --save_temps} command line option present. I'm not sure if this is to save RAM by |
| * not creating the associated actions and artifacts if we don't need them or just historical |
| * baggage. |
| */ |
| public static final String TEMP_FILES = "temp_files" + INTERNAL_SUFFIX; |
| |
| /** |
| * The default group of files built by a target when it is mentioned on the command line. |
| */ |
| public static final String DEFAULT = "default"; |
| |
| /** |
| * The default set of OutputGroups we typically want to build. |
| */ |
| public static final ImmutableSortedSet<String> DEFAULT_GROUPS = |
| ImmutableSortedSet.of(DEFAULT, TEMP_FILES, HIDDEN_TOP_LEVEL); |
| |
| private final ImmutableMap<String, NestedSet<Artifact>> outputGroups; |
| |
| public OutputGroupInfo(ImmutableMap<String, NestedSet<Artifact>> outputGroups) { |
| super(SKYLARK_CONSTRUCTOR); |
| this.outputGroups = outputGroups; |
| } |
| |
| @Nullable |
| public static OutputGroupInfo get(TransitiveInfoCollection collection) { |
| return collection.get(OutputGroupInfo.SKYLARK_CONSTRUCTOR); |
| } |
| |
| @Nullable |
| public static OutputGroupInfo get(ConfiguredAspect aspect) { |
| return (OutputGroupInfo) aspect.get(SKYLARK_CONSTRUCTOR.getKey()); |
| } |
| |
| |
| /** Return the artifacts in a particular output group. |
| * |
| * @return the artifacts in the output group with the given name. The return value is never null. |
| * If the specified output group is not present, the empty set is returned. |
| */ |
| public NestedSet<Artifact> getOutputGroup(String outputGroupName) { |
| return outputGroups.containsKey(outputGroupName) |
| ? outputGroups.get(outputGroupName) |
| : NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER); |
| } |
| |
| /** |
| * Merges output groups from two output providers. The set of output groups must be disjoint. |
| * |
| * @param providers providers to merge {@code this} with. |
| */ |
| @Nullable |
| public static OutputGroupInfo merge(List<OutputGroupInfo> providers) |
| throws DuplicateException { |
| if (providers.size() == 0) { |
| return null; |
| } |
| if (providers.size() == 1) { |
| return providers.get(0); |
| } |
| |
| ImmutableMap.Builder<String, NestedSet<Artifact>> resultBuilder = new ImmutableMap.Builder<>(); |
| Set<String> seenGroups = new HashSet<>(); |
| for (OutputGroupInfo provider : providers) { |
| for (String outputGroup : provider.outputGroups.keySet()) { |
| if (!seenGroups.add(outputGroup)) { |
| throw new DuplicateException( |
| "Output group " + outputGroup + " provided twice"); |
| } |
| |
| resultBuilder.put(outputGroup, provider.getOutputGroup(outputGroup)); |
| } |
| } |
| return new OutputGroupInfo(resultBuilder.build()); |
| } |
| |
| public static ImmutableSortedSet<String> determineOutputGroups(List<String> outputGroups) { |
| return determineOutputGroups(DEFAULT_GROUPS, outputGroups); |
| } |
| |
| public static ImmutableSortedSet<String> determineOutputGroups( |
| Set<String> defaultOutputGroups, List<String> outputGroups) { |
| |
| Set<String> current = Sets.newHashSet(); |
| |
| // If all of the requested output groups start with "+" or "-", then these are added or |
| // subtracted to the set of default output groups. |
| // If any of them don't start with "+" or "-", then the list of requested output groups |
| // overrides the default set of output groups. |
| boolean addDefaultOutputGroups = true; |
| for (String outputGroup : outputGroups) { |
| if (!(outputGroup.startsWith("+") || outputGroup.startsWith("-"))) { |
| addDefaultOutputGroups = false; |
| break; |
| } |
| } |
| if (addDefaultOutputGroups) { |
| current.addAll(defaultOutputGroups); |
| } |
| |
| for (String outputGroup : outputGroups) { |
| if (outputGroup.startsWith("+")) { |
| current.add(outputGroup.substring(1)); |
| } else if (outputGroup.startsWith("-")) { |
| current.remove(outputGroup.substring(1)); |
| } else { |
| current.add(outputGroup); |
| } |
| } |
| |
| return ImmutableSortedSet.copyOf(current); |
| } |
| |
| @Override |
| public Object getIndex(Object key, Location loc) throws EvalException { |
| if (!(key instanceof String)) { |
| throw new EvalException(loc, String.format( |
| "Output grout names must be strings, got %s instead", |
| EvalUtils.getDataTypeName(key))); |
| } |
| |
| NestedSet<Artifact> result = outputGroups.get(key); |
| if (result != null) { |
| return SkylarkNestedSet.of(Artifact.class, result); |
| } else { |
| throw new EvalException(loc, String.format( |
| "Output group %s not present", key |
| )); |
| } |
| } |
| |
| @Override |
| public boolean containsKey(Object key, Location loc) throws EvalException { |
| return outputGroups.containsKey(key); |
| } |
| |
| @Override |
| public Iterator<String> iterator() { |
| return SKYLARK_COMPARATOR.sortedCopy(outputGroups.keySet()).iterator(); |
| } |
| |
| @Override |
| public Object getValue(String name) { |
| NestedSet<Artifact> result = outputGroups.get(name); |
| if (result == null) { |
| return null; |
| } |
| return SkylarkNestedSet.of(Artifact.class, result); |
| } |
| |
| @Override |
| public ImmutableCollection<String> getFieldNames() { |
| return outputGroups.keySet(); |
| } |
| |
| /** |
| * Provider implementation for {@link OutputGroupInfoApi.OutputGroupInfoApiProvider}. |
| */ |
| public static class OutputGroupInfoProvider extends BuiltinProvider<OutputGroupInfo> |
| implements OutputGroupInfoApi.OutputGroupInfoApiProvider { |
| private OutputGroupInfoProvider() { |
| super("OutputGroupInfo", OutputGroupInfo.class); |
| } |
| |
| @Override |
| public OutputGroupInfoApi constructor(SkylarkDict<?, ?> kwargs, Location loc) |
| throws EvalException { |
| Map<String, Object> kwargsMap = kwargs.getContents(String.class, Object.class, "kwargs"); |
| |
| ImmutableMap.Builder<String, NestedSet<Artifact>> builder = ImmutableMap.builder(); |
| for (Map.Entry<String, Object> entry : kwargsMap.entrySet()) { |
| builder.put( |
| entry.getKey(), |
| SkylarkRuleConfiguredTargetUtil.convertToOutputGroupValue( |
| loc, entry.getKey(), entry.getValue())); |
| } |
| return new OutputGroupInfo(builder.build()); |
| } |
| } |
| } |