blob: 25e053a0bafc06d92e7bac3be9f1f7d8ee7300ae [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.devtools.build.lib.packages;
16
janakrdc8b2e9a2017-08-18 22:52:37 +020017import static com.google.devtools.build.lib.packages.BuildType.TRISTATE;
Googlerc5fcc862019-09-06 16:17:47 -070018import static com.google.devtools.build.lib.packages.Type.BOOLEAN;
janakrdc8b2e9a2017-08-18 22:52:37 +020019
tomlua155b532017-11-08 20:12:47 +010020import com.google.common.base.Preconditions;
Lukacs Berki9f74dfb2016-11-11 10:00:53 +000021import com.google.common.base.Predicate;
Klaus Aehlig6afb4fd2019-03-04 12:17:19 -080022import com.google.common.base.Strings;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010023import com.google.common.collect.ImmutableMap;
ulfjack5b790982018-04-25 07:09:13 -070024import com.google.common.collect.Maps;
Googleredb893d2020-06-18 11:16:15 -070025import com.google.devtools.build.lib.actions.ExecutionRequirements;
Lukacs Berki6e91eb92015-09-21 09:12:37 +000026import com.google.devtools.build.lib.cmdline.Label;
Googlera9c93632019-11-13 10:48:07 -080027import com.google.devtools.build.lib.syntax.Dict;
ishikhman53ee8c32019-07-17 01:18:14 -070028import com.google.devtools.build.lib.syntax.EvalException;
adonovanf5262c52020-04-02 09:25:14 -070029import com.google.devtools.build.lib.syntax.Location;
Lukacs Berki9f74dfb2016-11-11 10:00:53 +000030import com.google.devtools.build.lib.util.Pair;
janakrdc8b2e9a2017-08-18 22:52:37 +020031import java.util.ArrayList;
Lukacs Berki9f74dfb2016-11-11 10:00:53 +000032import java.util.Collection;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010033import java.util.HashMap;
Lukacs Berki9f74dfb2016-11-11 10:00:53 +000034import java.util.List;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010035import java.util.Map;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010036import javax.annotation.Nullable;
37
38/**
39 * Utility functions over Targets that don't really belong in the base {@link
40 * Target} interface.
41 */
42public final class TargetUtils {
43
44 // *_test / test_suite attribute that used to specify constraint keywords.
45 private static final String CONSTRAINTS_ATTR = "tags";
46
ulfjack5b790982018-04-25 07:09:13 -070047 // We don't want to pollute the execution info with random things, and we also need to reserve
48 // some internal tags that we don't allow to be set on targets. We also don't want to
49 // exhaustively enumerate all the legal values here. Right now, only a ~small set of tags is
50 // recognized by Bazel.
ishikhman53ee8c32019-07-17 01:18:14 -070051 private static boolean legalExecInfoKeys(String tag) {
52 return tag.startsWith("block-")
53 || tag.startsWith("requires-")
54 || tag.startsWith("no-")
55 || tag.startsWith("supports-")
56 || tag.startsWith("disable-")
Googleredb893d2020-06-18 11:16:15 -070057 || tag.startsWith("cpu:")
58 || tag.equals(ExecutionRequirements.LOCAL)
59 || tag.equals(ExecutionRequirements.WORKER_KEY_MNEMONIC);
ishikhman53ee8c32019-07-17 01:18:14 -070060 }
ulfjack5b790982018-04-25 07:09:13 -070061
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010062 private TargetUtils() {} // Uninstantiable.
63
64 public static boolean isTestRuleName(String name) {
65 return name.endsWith("_test");
66 }
67
68 public static boolean isTestSuiteRuleName(String name) {
69 return name.equals("test_suite");
70 }
71
72 /**
73 * Returns true iff {@code target} is a {@code *_test} rule; excludes {@code
74 * test_suite}.
75 */
76 public static boolean isTestRule(Target target) {
77 return (target instanceof Rule) && isTestRuleName(((Rule) target).getRuleClass());
78 }
79
80 /**
81 * Returns true iff {@code target} is a {@code test_suite} rule.
82 */
83 public static boolean isTestSuiteRule(Target target) {
lberki231d77d2019-01-03 11:18:11 -080084 return target instanceof Rule && isTestSuiteRuleName(((Rule) target).getRuleClass());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010085 }
86
lberki00731532019-11-18 04:42:35 -080087 /** Returns true iff {@code target} is an {@code alias} rule. */
88 public static boolean isAlias(Target target) {
89 if (!(target instanceof Rule)) {
90 return false;
91 }
92
93 Rule rule = (Rule) target;
gregce74d84d42020-04-17 10:02:03 -070094 return !rule.getRuleClassObject().isStarlark() && rule.getRuleClass().equals("alias");
lberki00731532019-11-18 04:42:35 -080095 }
96
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010097 /**
98 * Returns true iff {@code target} is a {@code *_test} or {@code test_suite}.
99 */
100 public static boolean isTestOrTestSuiteRule(Target target) {
101 return isTestRule (target) || isTestSuiteRule(target);
102 }
103
104 /**
105 * Returns true if {@code target} has "manual" in the tags attribute and thus should be ignored by
106 * command-line wildcards or by test_suite $implicit_tests attribute.
107 */
108 public static boolean hasManualTag(Target target) {
109 return (target instanceof Rule) && hasConstraint((Rule) target, "manual");
110 }
111
112 /**
113 * Returns true if test marked as "exclusive" by the appropriate keyword
114 * in the tags attribute.
115 *
116 * Method assumes that passed target is a test rule, so usually it should be
117 * used only after isTestRule() or isTestOrTestSuiteRule(). Behavior is
118 * undefined otherwise.
119 */
120 public static boolean isExclusiveTestRule(Rule rule) {
121 return hasConstraint(rule, "exclusive");
122 }
123
124 /**
125 * Returns true if test marked as "local" by the appropriate keyword
126 * in the tags attribute.
127 *
128 * Method assumes that passed target is a test rule, so usually it should be
129 * used only after isTestRule() or isTestOrTestSuiteRule(). Behavior is
130 * undefined otherwise.
131 */
132 public static boolean isLocalTestRule(Rule rule) {
133 return hasConstraint(rule, "local")
134 || NonconfigurableAttributeMapper.of(rule).get("local", Type.BOOLEAN);
135 }
136
137 /**
138 * Returns true if the rule is a test or test suite and is local or exclusive.
139 * Wraps the above calls into one generic check safely applicable to any rule.
140 */
141 public static boolean isTestRuleAndRunsLocally(Rule rule) {
lberki231d77d2019-01-03 11:18:11 -0800142 return isTestOrTestSuiteRule(rule) && (isLocalTestRule(rule) || isExclusiveTestRule(rule));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100143 }
144
145 /**
146 * Returns true if test marked as "external" by the appropriate keyword
147 * in the tags attribute.
148 *
149 * Method assumes that passed target is a test rule, so usually it should be
150 * used only after isTestRule() or isTestOrTestSuiteRule(). Behavior is
151 * undefined otherwise.
152 */
153 public static boolean isExternalTestRule(Rule rule) {
154 return hasConstraint(rule, "external");
155 }
156
janakrdc8b2e9a2017-08-18 22:52:37 +0200157 public static List<String> getStringListAttr(Target target, String attrName) {
158 Preconditions.checkArgument(target instanceof Rule);
159 return NonconfigurableAttributeMapper.of((Rule) target).get(attrName, Type.STRING_LIST);
160 }
161
162 public static String getStringAttr(Target target, String attrName) {
163 Preconditions.checkArgument(target instanceof Rule);
164 return NonconfigurableAttributeMapper.of((Rule) target).get(attrName, Type.STRING);
165 }
166
167 public static Iterable<String> getAttrAsString(Target target, String attrName) {
168 Preconditions.checkArgument(target instanceof Rule);
169 List<String> values = new ArrayList<>(); // May hold null values.
170 Attribute attribute = ((Rule) target).getAttributeDefinition(attrName);
171 if (attribute != null) {
172 Type<?> attributeType = attribute.getType();
173 for (Object attrValue :
174 AggregatingAttributeMapper.of((Rule) target)
175 .visitAttribute(attribute.getName(), attributeType)) {
176
177 // Ugly hack to maintain backward 'attr' query compatibility for BOOLEAN and TRISTATE
178 // attributes. These are internally stored as actual Boolean or TriState objects but were
179 // historically queried as integers. To maintain compatibility, we inspect their actual
180 // value and return the integer equivalent represented as a String. This code is the
181 // opposite of the code in BooleanType and TriStateType respectively.
182 if (attributeType == BOOLEAN) {
183 values.add(Type.BOOLEAN.cast(attrValue) ? "1" : "0");
184 } else if (attributeType == TRISTATE) {
185 switch (BuildType.TRISTATE.cast(attrValue)) {
186 case AUTO:
187 values.add("-1");
188 break;
189 case NO:
190 values.add("0");
191 break;
192 case YES:
193 values.add("1");
194 break;
195 default:
196 throw new AssertionError("This can't happen!");
197 }
198 } else {
199 values.add(attrValue == null ? null : attrValue.toString());
200 }
201 }
202 }
203 return values;
204 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100205
206 /**
207 * If the given target is a rule, returns its <code>deprecation<code/> value, or null if unset.
208 */
209 @Nullable
210 public static String getDeprecation(Target target) {
211 if (!(target instanceof Rule)) {
212 return null;
213 }
214 Rule rule = (Rule) target;
215 return (rule.isAttrDefined("deprecation", Type.STRING))
216 ? NonconfigurableAttributeMapper.of(rule).get("deprecation", Type.STRING)
217 : null;
218 }
219
220 /**
221 * Checks whether specified constraint keyword is present in the
222 * tags attribute of the test or test suite rule.
223 *
224 * Method assumes that provided rule is a test or a test suite. Behavior is
225 * undefined otherwise.
226 */
227 private static boolean hasConstraint(Rule rule, String keyword) {
228 return NonconfigurableAttributeMapper.of(rule).get(CONSTRAINTS_ATTR, Type.STRING_LIST)
229 .contains(keyword);
230 }
231
232 /**
ishikhman53ee8c32019-07-17 01:18:14 -0700233 * Returns the execution info from the tags declared on the target. These include only some tags
234 * {@link #legalExecInfoKeys} as keys with empty values.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100235 */
236 public static Map<String, String> getExecutionInfo(Rule rule) {
237 // tags may contain duplicate values.
238 Map<String, String> map = new HashMap<>();
239 for (String tag :
240 NonconfigurableAttributeMapper.of(rule).get(CONSTRAINTS_ATTR, Type.STRING_LIST)) {
ishikhman53ee8c32019-07-17 01:18:14 -0700241 if (legalExecInfoKeys(tag)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100242 map.put(tag, "");
243 }
244 }
245 return ImmutableMap.copyOf(map);
246 }
247
248 /**
ishikhman94c24b32019-08-30 04:46:25 -0700249 * Returns the execution info from the tags declared on the target. These include only some tags
250 * {@link #legalExecInfoKeys} as keys with empty values.
251 *
252 * @param rule a rule instance to get tags from
253 * @param allowTagsPropagation if set to true, tags will be propagated from a target to the
254 * actions' execution requirements, for more details {@see
gregce61ec8d12020-05-18 11:02:05 -0700255 * StarlarkSemanticsOptions#experimentalAllowTagsPropagation}
ishikhman94c24b32019-08-30 04:46:25 -0700256 */
257 public static ImmutableMap<String, String> getExecutionInfo(
258 Rule rule, boolean allowTagsPropagation) {
259 if (allowTagsPropagation) {
260 return ImmutableMap.copyOf(getExecutionInfo(rule));
261 } else {
262 return ImmutableMap.of();
263 }
264 }
265
266 /**
ishikhman53ee8c32019-07-17 01:18:14 -0700267 * Returns the execution info, obtained from the rule's tags and the execution requirements
268 * provided. Only supported tags are included into the execution info, see {@link
269 * #legalExecInfoKeys}.
270 *
271 * @param executionRequirementsUnchecked execution_requirements of a rule, expected to be of a
Googlera9c93632019-11-13 10:48:07 -0800272 * {@code Dict<String, String>} type, null or {@link
ishikhman53ee8c32019-07-17 01:18:14 -0700273 * com.google.devtools.build.lib.syntax.Runtime#NONE}
274 * @param rule a rule instance to get tags from
ishikhman7e837212019-08-21 03:22:35 -0700275 * @param allowTagsPropagation if set to true, tags will be propagated from a target to the
276 * actions' execution requirements, for more details {@see
gregce773b95f2020-05-19 09:51:09 -0700277 * StarlarkSematicOptions#experimentalAllowTagsPropagation}
ishikhman53ee8c32019-07-17 01:18:14 -0700278 */
279 public static ImmutableMap<String, String> getFilteredExecutionInfo(
adonovan85803902020-04-16 14:46:57 -0700280 @Nullable Object executionRequirementsUnchecked, Rule rule, boolean allowTagsPropagation)
ishikhman53ee8c32019-07-17 01:18:14 -0700281 throws EvalException {
282 Map<String, String> checkedExecutionRequirements =
283 TargetUtils.filter(
adonovan85803902020-04-16 14:46:57 -0700284 executionRequirementsUnchecked == null
285 ? ImmutableMap.of()
286 : Dict.noneableCast(
287 executionRequirementsUnchecked,
288 String.class,
289 String.class,
290 "execution_requirements"));
ishikhman53ee8c32019-07-17 01:18:14 -0700291
292 Map<String, String> executionInfoBuilder = new HashMap<>();
293 // adding filtered execution requirements to the execution info map
294 executionInfoBuilder.putAll(checkedExecutionRequirements);
295
ishikhman7e837212019-08-21 03:22:35 -0700296 if (allowTagsPropagation) {
ishikhman53ee8c32019-07-17 01:18:14 -0700297 Map<String, String> checkedTags = getExecutionInfo(rule);
298 // merging filtered tags to the execution info map avoiding duplicates
299 checkedTags.forEach(executionInfoBuilder::putIfAbsent);
300 }
301
302 return ImmutableMap.copyOf(executionInfoBuilder);
303 }
304
305 /**
ulfjack5b790982018-04-25 07:09:13 -0700306 * Returns the execution info. These include execution requirement tags ('block-*', 'requires-*',
307 * 'no-*', 'supports-*', 'disable-*', 'local', and 'cpu:*') as keys with empty values.
308 */
309 public static Map<String, String> filter(Map<String, String> executionInfo) {
ishikhman53ee8c32019-07-17 01:18:14 -0700310 return Maps.filterKeys(executionInfo, TargetUtils::legalExecInfoKeys);
ulfjack5b790982018-04-25 07:09:13 -0700311 }
312
313 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100314 * Returns the language part of the rule name (e.g. "foo" for foo_test or foo_binary).
315 *
316 * <p>In practice this is the part before the "_", if any, otherwise the entire rule class name.
317 *
318 * <p>Precondition: isTestRule(target) || isRunnableNonTestRule(target).
319 */
320 public static String getRuleLanguage(Target target) {
321 return getRuleLanguage(((Rule) target).getRuleClass());
322 }
323
324 /**
325 * Returns the language part of the rule name (e.g. "foo" for foo_test or foo_binary).
326 *
327 * <p>In practice this is the part before the "_", if any, otherwise the entire rule class name.
328 */
329 public static String getRuleLanguage(String ruleClass) {
Ulf Adams07dba942015-03-05 14:47:37 +0000330 int index = ruleClass.lastIndexOf('_');
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100331 // Chop off "_binary" or "_test".
332 return index != -1 ? ruleClass.substring(0, index) : ruleClass;
333 }
334
lberki231d77d2019-01-03 11:18:11 -0800335 private static boolean isExplicitDependency(Rule rule, Label label) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100336 if (rule.getVisibility().getDependencyLabels().contains(label)) {
337 return true;
338 }
339
janakrd0a3c5e2018-08-09 15:59:24 -0700340 for (AttributeMap.DepEdge depEdge : AggregatingAttributeMapper.of(rule).visitLabels()) {
341 if (rule.isAttributeValueExplicitlySpecified(depEdge.getAttribute())
342 && label.equals(depEdge.getLabel())) {
343 return true;
344 }
345 }
346 return false;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100347 }
348
Lukacs Berki9f74dfb2016-11-11 10:00:53 +0000349 /**
350 * Returns a predicate to be used for test tag filtering, i.e., that only accepts tests that match
351 * all of the required tags and none of the excluded tags.
352 */
353 public static Predicate<Target> tagFilter(List<String> tagFilterList) {
354 Pair<Collection<String>, Collection<String>> tagLists =
355 TestTargetUtils.sortTagsBySense(tagFilterList);
356 final Collection<String> requiredTags = tagLists.first;
357 final Collection<String> excludedTags = tagLists.second;
laurentlb3d2a68c2017-06-30 00:32:04 +0200358 return input -> {
359 if (requiredTags.isEmpty() && excludedTags.isEmpty()) {
360 return true;
Lukacs Berki9f74dfb2016-11-11 10:00:53 +0000361 }
laurentlb3d2a68c2017-06-30 00:32:04 +0200362
363 if (!(input instanceof Rule)) {
lberki1fa2dfb2017-12-05 07:11:03 -0800364 return requiredTags.isEmpty();
laurentlb3d2a68c2017-06-30 00:32:04 +0200365 }
ulfjackaa2ff992018-06-07 07:05:37 -0700366 // Note that test_tags are those originating from the XX_test rule, whereas the requiredTags
367 // and excludedTags originate from the command line or test_suite rule.
368 // TODO(ulfjack): getRuleTags is inconsistent with TestFunction and other places that use
369 // tags + size, but consistent with TestSuite.
laurentlb3d2a68c2017-06-30 00:32:04 +0200370 return TestTargetUtils.testMatchesFilters(
371 ((Rule) input).getRuleTags(), requiredTags, excludedTags, false);
Lukacs Berki9f74dfb2016-11-11 10:00:53 +0000372 };
373 }
374
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100375 /**
376 * Return {@link Location} for {@link Target} target, if it should not be null.
377 */
378 public static Location getLocationMaybe(Target target) {
379 return (target instanceof Rule) || (target instanceof InputFile) ? target.getLocation() : null;
380 }
381
382 /**
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000383 * Return nicely formatted error message that {@link Label} label that was pointed to by {@link
384 * Target} target did not exist, due to {@link NoSuchThingException} e.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100385 */
Janak Ramakrishnan3c0adb22016-08-15 21:54:55 +0000386 public static String formatMissingEdge(
Klaus Aehlig6afb4fd2019-03-04 12:17:19 -0800387 @Nullable Target target, Label label, NoSuchThingException e, @Nullable Attribute attr) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100388 // instanceof returns false if target is null (which is exploited here)
389 if (target instanceof Rule) {
390 Rule rule = (Rule) target;
Nathan Harmatac1563c22016-01-29 23:03:03 +0000391 if (isExplicitDependency(rule, label)) {
392 return String.format("%s and referenced by '%s'", e.getMessage(), target.getLabel());
393 } else {
Klaus Aehlig6afb4fd2019-03-04 12:17:19 -0800394 String additionalInfo = "";
395 if (attr != null && !Strings.isNullOrEmpty(attr.getDoc())) {
396 additionalInfo =
397 String.format(
398 "\nDocumentation for implicit attribute %s of rules of type %s:\n%s",
399 attr.getPublicName(), rule.getRuleClass(), attr.getDoc());
400 }
Nathan Harmatac1563c22016-01-29 23:03:03 +0000401 // N.B. If you see this error message in one of our integration tests during development of
402 // a change that adds a new implicit dependency when running Blaze, maybe you forgot to add
403 // a new mock target to the integration test's setup.
Klaus Aehlig6afb4fd2019-03-04 12:17:19 -0800404 return String.format(
405 "every rule of type %s implicitly depends upon the target '%s', but "
406 + "this target could not be found because of: %s%s",
407 rule.getRuleClass(), label, e.getMessage(), additionalInfo);
Nathan Harmatac1563c22016-01-29 23:03:03 +0000408 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100409 } else if (target instanceof InputFile) {
410 return e.getMessage() + " (this is usually caused by a missing package group in the"
411 + " package-level visibility declaration)";
412 } else {
413 if (target != null) {
Nathan Harmatac1563c22016-01-29 23:03:03 +0000414 return String.format("in target '%s', no such label '%s': %s", target.getLabel(), label,
415 e.getMessage());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100416 }
417 return e.getMessage();
418 }
419 }
Klaus Aehlig6afb4fd2019-03-04 12:17:19 -0800420
421 public static String formatMissingEdge(
422 @Nullable Target target, Label label, NoSuchThingException e) {
423 return formatMissingEdge(target, label, e, null);
424 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100425}