// 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.ImmutableSet; 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.MergedConfiguredTarget.DuplicateException; 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.NativeClassObjectConstructor; import com.google.devtools.build.lib.packages.SkylarkClassObject; import com.google.devtools.build.lib.rules.SkylarkRuleConfiguredTargetBuilder; import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.syntax.EvalUtils; 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.Map.Entry; 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 public final class OutputGroupProvider extends SkylarkClassObject implements TransitiveInfoProvider, SkylarkIndexable, Iterable<String> { public static final String SKYLARK_NAME = "output_groups"; public static NativeClassObjectConstructor SKYLARK_CONSTRUCTOR = new Constructor(); /** * 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 = "files_to_compile" + INTERNAL_SUFFIX; /** * 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 ImmutableSet<String> DEFAULT_GROUPS = ImmutableSet.of(DEFAULT, TEMP_FILES, HIDDEN_TOP_LEVEL); private final ImmutableMap<String, NestedSet<Artifact>> outputGroups; public OutputGroupProvider(ImmutableMap<String, NestedSet<Artifact>> outputGroups) { super(SKYLARK_CONSTRUCTOR, ImmutableMap.<String, Object>of()); this.outputGroups = outputGroups; } @Nullable public static OutputGroupProvider get(TransitiveInfoCollection collection) { return collection.getProvider(OutputGroupProvider.class); } @Nullable public static OutputGroupProvider get(ConfiguredAspect aspect) { return (OutputGroupProvider) 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 OutputGroupProvider merge(List<OutputGroupProvider> 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 (OutputGroupProvider 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 OutputGroupProvider(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> getKeys() { return outputGroups.keySet(); } /** * A constructor callable from Skylark for OutputGroupProvider. */ private static class Constructor extends NativeClassObjectConstructor { private Constructor() { super("OutputGroupInfo"); } @Override protected SkylarkClassObject createInstanceFromSkylark(Object[] args, Location loc) throws EvalException { @SuppressWarnings("unchecked") Map<String, Object> kwargs = (Map<String, Object>) args[0]; ImmutableMap.Builder<String, NestedSet<Artifact>> builder = ImmutableMap.builder(); for (Entry<String, Object> entry : kwargs.entrySet()) { builder.put(entry.getKey(), SkylarkRuleConfiguredTargetBuilder.convertToOutputGroupValue( loc, entry.getKey(), entry.getValue())); } return new OutputGroupProvider(builder.build()); } @Override public String getErrorMessageFormatForInstances() { return "Output group %s not present"; } } }