blob: 607e85357b324a33062a9e5cdc6e1231ab0e944a [file] [log] [blame]
Dmitry Lomovf4cd4752015-11-23 17:50:57 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
2//
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.analysis;
16
mstaib16514d32017-12-07 15:18:52 -080017import static com.google.common.collect.ImmutableList.toImmutableList;
lberkiaea56b32017-05-30 12:35:33 +020018import static com.google.common.truth.Truth.assertThat;
mstaib16514d32017-12-07 15:18:52 -080019import static com.google.devtools.build.lib.packages.Attribute.attr;
20import static com.google.devtools.build.lib.packages.BuildType.LABEL;
21import static com.google.devtools.build.lib.packages.BuildType.NODEP_LABEL;
Googlerc5fcc862019-09-06 16:17:47 -070022import static com.google.devtools.build.lib.packages.Type.STRING;
michajlo660d17f2020-03-27 09:01:57 -070023import static org.junit.Assert.assertThrows;
Florian Weikert10df9522015-11-26 15:00:59 +000024
Googler19226b72020-02-06 12:58:43 -080025import com.google.common.collect.ImmutableMap;
gregce06b76c92020-06-19 14:46:56 -070026import com.google.common.collect.ImmutableSet;
jhorvitz33f76482021-10-28 10:13:26 -070027import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
mstaib16514d32017-12-07 15:18:52 -080028import com.google.devtools.build.lib.analysis.config.BuildOptions;
gregce06b76c92020-06-19 14:46:56 -070029import com.google.devtools.build.lib.analysis.config.BuildOptionsView;
gregcee495e6b2019-04-30 14:07:06 -070030import com.google.devtools.build.lib.analysis.config.CoreOptions;
gregce06b76c92020-06-19 14:46:56 -070031import com.google.devtools.build.lib.analysis.config.FragmentOptions;
cparsons21e25182019-01-15 16:00:26 -080032import com.google.devtools.build.lib.analysis.config.transitions.SplitTransition;
John Cater0a9e1ed2019-03-27 11:02:01 -070033import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory;
Florian Weikertcca703a2015-12-07 09:56:38 +000034import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
mstaib16514d32017-12-07 15:18:52 -080035import com.google.devtools.build.lib.analysis.util.MockRule;
Dmitry Lomovf4cd4752015-11-23 17:50:57 +000036import com.google.devtools.build.lib.events.Event;
gregcef0a40ac2020-03-31 14:11:30 -070037import com.google.devtools.build.lib.events.EventHandler;
janakra56a6ad2018-02-02 15:52:22 -080038import com.google.devtools.build.lib.packages.Attribute.LabelLateBoundDefault;
John Cater2c0dece2019-04-02 09:18:18 -070039import com.google.devtools.build.lib.packages.AttributeTransitionData;
Dmitry Lomovf4cd4752015-11-23 17:50:57 +000040import com.google.devtools.build.lib.packages.NoSuchTargetException;
41import com.google.devtools.build.lib.packages.Package;
mstaib16514d32017-12-07 15:18:52 -080042import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
gregceecb61ee2020-05-19 10:56:29 -070043import java.util.Map;
John Cater660e8a52021-04-20 08:00:56 -070044import java.util.regex.Pattern;
Florian Weikert10df9522015-11-26 15:00:59 +000045import org.junit.Test;
46import org.junit.runner.RunWith;
47import org.junit.runners.JUnit4;
48
Googlere10873c2021-10-20 09:09:01 -070049/** Tests that check that dependency cycles are reported correctly. */
Florian Weikert10df9522015-11-26 15:00:59 +000050@RunWith(JUnit4.class)
Florian Weikertcca703a2015-12-07 09:56:38 +000051public class CircularDependencyTest extends BuildViewTestCase {
Dmitry Lomovf4cd4752015-11-23 17:50:57 +000052
Florian Weikert10df9522015-11-26 15:00:59 +000053 @Test
Dmitry Lomovf4cd4752015-11-23 17:50:57 +000054 public void testOneRuleCycle() throws Exception {
55 checkError(
56 "cycle",
57 "foo.g",
Googlere10873c2021-10-20 09:09:01 -070058 // error message
Dmitry Lomovf4cd4752015-11-23 17:50:57 +000059 selfEdgeMsg("//cycle:foo.g"),
60 // Rule
61 "genrule(name = 'foo.g',",
62 " outs = ['Foo.java'],",
63 " srcs = ['foo.g'],",
64 " cmd = 'cat $(SRCS) > $<' )");
65 }
66
Florian Weikert10df9522015-11-26 15:00:59 +000067 @Test
Dmitry Lomovf4cd4752015-11-23 17:50:57 +000068 public void testDirectPackageGroupCycle() throws Exception {
69 checkError(
70 "cycle",
71 "melon",
72 selfEdgeMsg("//cycle:moebius"),
73 "package_group(name='moebius', packages=[], includes=['//cycle:moebius'])",
74 "sh_library(name='melon', visibility=[':moebius'])");
75 }
76
Florian Weikert10df9522015-11-26 15:00:59 +000077 @Test
Dmitry Lomovf4cd4752015-11-23 17:50:57 +000078 public void testThreeLongPackageGroupCycle() throws Exception {
John Cater660e8a52021-04-20 08:00:56 -070079 @SuppressWarnings("ConstantPatternCompile")
80 Pattern expectedEvent =
81 Pattern.compile(
82 "cycle in dependency graph:\n"
83 + " //cycle:superman \\([a-f0-9]+\\)\n"
84 + ".-> //cycle:rock \\(null\\)\n"
85 + "| //cycle:paper \\(null\\)\n"
86 + "| //cycle:scissors \\(null\\)\n"
87 + "`-- //cycle:rock \\(null\\)");
Dmitry Lomovf4cd4752015-11-23 17:50:57 +000088 checkError(
89 "cycle",
90 "superman",
91 expectedEvent,
92 "# dummy line",
93 "package_group(name='paper', includes=['//cycle:scissors'])",
94 "package_group(name='rock', includes=['//cycle:paper'])",
95 "package_group(name='scissors', includes=['//cycle:rock'])",
96 "sh_library(name='superman', visibility=[':rock'])");
97
John Cater660e8a52021-04-20 08:00:56 -070098 Event foundEvent = assertContainsEvent(expectedEvent);
adonovan07b15e62020-04-09 18:32:33 -070099 assertThat(foundEvent.getLocation().toString()).isEqualTo("/workspace/cycle/BUILD:3:14");
Dmitry Lomovf4cd4752015-11-23 17:50:57 +0000100 }
101
Googlere10873c2021-10-20 09:09:01 -0700102 /** Test to detect implicit input/output file overlap in rules. */
Florian Weikert10df9522015-11-26 15:00:59 +0000103 @Test
Dmitry Lomovf4cd4752015-11-23 17:50:57 +0000104 public void testOneRuleImplicitCycleJava() throws Exception {
105 Package pkg =
106 createScratchPackageForImplicitCycle(
Googler83830c22024-10-05 08:45:04 -0700107 "cycle",
108 "load('@rules_java//java:defs.bzl', 'java_library')",
109 "java_library(name='jcyc',",
110 " srcs = ['libjcyc.jar', 'foo.java'])");
jcater42edea62019-05-01 08:46:18 -0700111 assertThrows(NoSuchTargetException.class, () -> pkg.getTarget("jcyc"));
lberkiaea56b32017-05-30 12:35:33 +0200112 assertThat(pkg.containsErrors()).isTrue();
Dmitry Lomovf4cd4752015-11-23 17:50:57 +0000113 assertContainsEvent("rule 'jcyc' has file 'libjcyc.jar' as both an" + " input and an output");
114 }
115
116 /**
Googlere10873c2021-10-20 09:09:01 -0700117 * Test not to detect implicit input/output file overlap in rules, when coming from a different
118 * package.
Dmitry Lomovf4cd4752015-11-23 17:50:57 +0000119 */
Florian Weikert10df9522015-11-26 15:00:59 +0000120 @Test
Dmitry Lomovf4cd4752015-11-23 17:50:57 +0000121 public void testInputOutputConflictDifferentPackage() throws Exception {
122 Package pkg =
123 createScratchPackageForImplicitCycle(
124 "googledata/xxx",
125 "genrule(name='geo',",
126 " srcs = ['//googledata/geo:geo_info.txt'],",
127 " outs = ['geoinfo.txt'],",
128 " cmd = '$(SRCS) > $@')");
lberkiaea56b32017-05-30 12:35:33 +0200129 assertThat(pkg.containsErrors()).isFalse();
Dmitry Lomovf4cd4752015-11-23 17:50:57 +0000130 }
131
Florian Weikert10df9522015-11-26 15:00:59 +0000132 @Test
Dmitry Lomovf4cd4752015-11-23 17:50:57 +0000133 public void testTwoRuleCycle() throws Exception {
134 scratchRule("b", "rule2", "cc_library(name='rule2',", " deps=['//a:rule1'])");
135
136 checkError(
137 "a",
138 "rule1",
John Cater660e8a52021-04-20 08:00:56 -0700139 Pattern.compile(
140 "in cc_library rule //a:rule1: cycle in dependency graph:\n"
141 + ".-> //a:rule1 \\([a-f0-9]+\\)\n"
142 + "| //b:rule2 \\([a-f0-9]+\\)\n"
143 + "`-- //a:rule1 \\([a-f0-9]+\\)"),
Dmitry Lomovf4cd4752015-11-23 17:50:57 +0000144 "cc_library(name='rule1',",
145 " deps=['//b:rule2'])");
146 }
147
Florian Weikert10df9522015-11-26 15:00:59 +0000148 @Test
Dmitry Lomovf4cd4752015-11-23 17:50:57 +0000149 public void testTwoRuleCycle2() throws Exception {
150 reporter.removeHandler(failFastHandler); // expect errors
151 scratch.file(
Googlercdb03cc2024-03-27 12:02:12 -0700152 "x/BUILD",
153 """
Googler83830c22024-10-05 08:45:04 -0700154 load("@rules_java//java:defs.bzl", "java_library")
Googlercdb03cc2024-03-27 12:02:12 -0700155 java_library(
156 name = "x",
157 deps = ["y"],
158 )
159
160 java_library(
161 name = "y",
162 deps = ["x"],
163 )
164 """);
Dmitry Lomovf4cd4752015-11-23 17:50:57 +0000165 getConfiguredTarget("//x");
166 assertContainsEvent("in java_library rule //x:x: cycle in dependency graph");
167 }
168
Florian Weikert10df9522015-11-26 15:00:59 +0000169 @Test
Dmitry Lomovf4cd4752015-11-23 17:50:57 +0000170 public void testIndirectOneRuleCycle() throws Exception {
171 scratchRule(
172 "cycle",
173 "foo.h",
174 "genrule(name = 'foo.h',",
175 " outs = ['bar.h'],",
176 " srcs = ['foo.h'],",
177 " cmd = 'cp $< $@')");
178 checkError(
179 "main",
180 "mygenrule",
Googlere10873c2021-10-20 09:09:01 -0700181 // error message
Dmitry Lomovf4cd4752015-11-23 17:50:57 +0000182 selfEdgeMsg("//cycle:foo.h"),
183 // Rule
184 "genrule(name='mygenrule',",
185 " outs = ['baz.h'],",
186 " srcs = ['//cycle:foo.h'],",
187 " cmd = 'cp $< $@')");
188 }
189
John Cater660e8a52021-04-20 08:00:56 -0700190 private Pattern selfEdgeMsg(String label) {
191 return Pattern.compile(label + " \\([a-f0-9]+|null\\) \\[self-edge\\]");
Dmitry Lomovf4cd4752015-11-23 17:50:57 +0000192 }
193
194 // Regression test for: "IllegalStateException in
195 // AbstractConfiguredTarget.initialize()".
196 // Failure to mark all cycle-forming nodes when there are *two* cycles led to
197 // an attempt to initialise a node we'd already visited.
Florian Weikert10df9522015-11-26 15:00:59 +0000198 @Test
Dmitry Lomovf4cd4752015-11-23 17:50:57 +0000199 public void testTwoCycles() throws Exception {
200 reporter.removeHandler(failFastHandler); // expect errors
201 scratch.file(
202 "x/BUILD",
Googlercdb03cc2024-03-27 12:02:12 -0700203 """
204 genrule(
205 name = "b",
206 srcs = ["c"],
207 outs = ["b.out"],
208 cmd = ":",
209 tools = ["c"],
210 )
211
212 genrule(
213 name = "c",
214 srcs = ["b.out"],
215 outs = [],
216 cmd = ":",
217 )
218 """);
Dmitry Lomovf4cd4752015-11-23 17:50:57 +0000219 getConfiguredTarget("//x:b"); // doesn't crash!
Lukacs Berkibba75d82016-06-14 09:08:29 +0000220 assertContainsEvent("cycle in dependency graph");
221 }
222
223 @Test
224 public void testAspectCycle() throws Exception {
225 reporter.removeHandler(failFastHandler);
Googlere10873c2021-10-20 09:09:01 -0700226 scratch.file(
227 "x/BUILD",
Googlercdb03cc2024-03-27 12:02:12 -0700228 """
229 load("//x:x.bzl", "aspected", "plain")
230
231 # Using data= makes the dependency graph clearer because then the aspect does not propagate
232 # from aspectdep through a to b (and c)
233 plain(
234 name = "a",
235 noaspect_deps = [":b"],
236 )
237
238 aspected(
239 name = "b",
240 aspect_deps = ["c"],
241 )
242
243 plain(name = "c")
244
245 plain(
246 name = "aspectdep",
247 aspect_deps = ["a"],
248 )
249 """);
Lukacs Berkibba75d82016-06-14 09:08:29 +0000250
cparsons0f22a962019-04-18 10:25:37 -0700251 scratch.file(
252 "x/x.bzl",
Googlercdb03cc2024-03-27 12:02:12 -0700253 """
254 def _impl(ctx):
255 return []
256
257 rule_aspect = aspect(
258 implementation = _impl,
259 attr_aspects = ["aspect_deps"],
260 attrs = {"_implicit": attr.label(default = Label("//x:aspectdep"))},
261 )
262
263 plain = rule(
264 implementation = _impl,
265 attrs = {"aspect_deps": attr.label_list(), "noaspect_deps": attr.label_list()},
266 )
267
268 aspected = rule(
269 implementation = _impl,
270 attrs = {"aspect_deps": attr.label_list(aspects = [rule_aspect])},
271 )
272 """);
Lukacs Berkibba75d82016-06-14 09:08:29 +0000273
274 getConfiguredTarget("//x:a");
275 assertContainsEvent("cycle in dependency graph");
276 assertContainsEvent("//x:c with aspect //x:x.bzl%rule_aspect");
Dmitry Lomovf4cd4752015-11-23 17:50:57 +0000277 }
mstaib16514d32017-12-07 15:18:52 -0800278
279 /** A late bound dependency which depends on the 'dep' label if the 'define' is in --defines. */
280 // TODO(b/65746853): provide a way to do this without passing the entire configuration
jhorvitz33f76482021-10-28 10:13:26 -0700281 private static final LabelLateBoundDefault<BuildConfigurationValue> LATE_BOUND_DEP =
janakra56a6ad2018-02-02 15:52:22 -0800282 LabelLateBoundDefault.fromTargetConfiguration(
jhorvitz33f76482021-10-28 10:13:26 -0700283 BuildConfigurationValue.class,
mstaib16514d32017-12-07 15:18:52 -0800284 null,
285 (rule, attributes, config) ->
286 config.getCommandLineBuildVariables().containsKey(attributes.get("define", STRING))
287 ? attributes.get("dep", NODEP_LABEL)
288 : null);
289
290 /** A rule which always depends on the given label. */
291 private static final MockRule NORMAL_DEPENDER =
292 () -> MockRule.define("normal_dep", attr("dep", LABEL).allowedFileTypes());
293
294 /** A rule which depends on a given label only if the given define is set. */
295 private static final MockRule LATE_BOUND_DEPENDER =
296 () ->
297 MockRule.define(
298 "late_bound_dep",
299 attr("define", STRING).mandatory(),
300 attr("dep", NODEP_LABEL).mandatory(),
301 attr(":late_bound_dep", LABEL).value(LATE_BOUND_DEP));
302
303 /** A rule which removes a define from the configuration of its dependency. */
304 private static final MockRule DEFINE_CLEARER =
305 () ->
306 MockRule.define(
307 "define_clearer",
308 attr("define", STRING).mandatory(),
309 attr("dep", LABEL)
310 .mandatory()
311 .allowedFileTypes()
312 .cfg(
Googler81ca4aa2024-05-10 09:07:38 -0700313 new TransitionFactory<>() {
cparsons21e25182019-01-15 16:00:26 -0800314 @Override
John Cater2c0dece2019-04-02 09:18:18 -0700315 public SplitTransition create(AttributeTransitionData data) {
gregceecb61ee2020-05-19 10:56:29 -0700316 return new SplitTransition() {
gregce06b76c92020-06-19 14:46:56 -0700317
318 @Override
319 public ImmutableSet<Class<? extends FragmentOptions>>
320 requiresOptionFragments() {
321 return ImmutableSet.of(CoreOptions.class);
322 }
323
gregceecb61ee2020-05-19 10:56:29 -0700324 @Override
325 public Map<String, BuildOptions> split(
gregce06b76c92020-06-19 14:46:56 -0700326 BuildOptionsView options, EventHandler eventHandler) {
gregceecb61ee2020-05-19 10:56:29 -0700327 String define = data.attributes().get("define", STRING);
gregce06b76c92020-06-19 14:46:56 -0700328 BuildOptionsView newOptions = options.clone();
gregceecb61ee2020-05-19 10:56:29 -0700329 CoreOptions optionsFragment = newOptions.get(CoreOptions.class);
330 optionsFragment.commandLineBuildVariables =
331 optionsFragment.commandLineBuildVariables.stream()
332 .filter((pair) -> !pair.getKey().equals(define))
333 .collect(toImmutableList());
gregce06b76c92020-06-19 14:46:56 -0700334 return ImmutableMap.of("define_cleaner", newOptions.underlying());
gregceecb61ee2020-05-19 10:56:29 -0700335 }
cparsons21e25182019-01-15 16:00:26 -0800336 };
337 }
John Cater0a9e1ed2019-03-27 11:02:01 -0700338
339 @Override
Googler81ca4aa2024-05-10 09:07:38 -0700340 public TransitionType transitionType() {
341 return TransitionType.ATTRIBUTE;
342 }
343
344 @Override
John Cater0a9e1ed2019-03-27 11:02:01 -0700345 public boolean isSplit() {
346 return true;
347 }
cparsons21e25182019-01-15 16:00:26 -0800348 }));
mstaib16514d32017-12-07 15:18:52 -0800349
350 @Override
brandjon232a6b82020-06-08 12:32:49 -0700351 protected ConfiguredRuleClassProvider createRuleClassProvider() {
mstaib16514d32017-12-07 15:18:52 -0800352 ConfiguredRuleClassProvider.Builder builder =
353 new ConfiguredRuleClassProvider.Builder()
354 .addRuleDefinition(NORMAL_DEPENDER)
355 .addRuleDefinition(LATE_BOUND_DEPENDER)
356 .addRuleDefinition(DEFINE_CLEARER);
357 TestRuleClassProvider.addStandardRules(builder);
358 return builder.build();
359 }
360
361 @Test
362 public void testLateBoundTargetCycleNotConfiguredTargetCycle() throws Exception {
363 // Target graph: //a -> //b -?> //c -> //a (loop)
364 // Configured target graph: //a -> //b -> //c -> //a (2) -> //b (2)
365 scratch.file("a/BUILD", "normal_dep(name = 'a', dep = '//b')");
366 scratch.file("b/BUILD", "late_bound_dep(name = 'b', dep = '//c', define = 'CYCLE_ON')");
367 scratch.file("c/BUILD", "define_clearer(name = 'c', dep = '//a', define = 'CYCLE_ON')");
368
369 useConfiguration("--define=CYCLE_ON=yes");
370 getConfiguredTarget("//a");
371 assertNoEvents();
372 }
373
374 @Test
375 public void testSelectTargetCycleNotConfiguredTargetCycle() throws Exception {
376 // Target graph: //a -> //b -?> //c -> //a (loop)
377 // Configured target graph: //a -> //b -> //c -> //a (2) -> //b (2) -> //b:stop (2)
378 scratch.file("a/BUILD", "normal_dep(name = 'a', dep = '//b')");
Googlere10873c2021-10-20 09:09:01 -0700379 scratch.file(
380 "b/BUILD",
Googlercdb03cc2024-03-27 12:02:12 -0700381 """
382 config_setting(
383 name = "cycle",
384 define_values = {"CYCLE_ON": "yes"},
385 )
386
387 normal_dep(name = "stop")
388
389 normal_dep(
390 name = "b",
391 dep = select({
392 ":cycle": "//c",
393 "//conditions:default": ":stop",
394 }),
395 )
396 """);
mstaib16514d32017-12-07 15:18:52 -0800397 scratch.file("c/BUILD", "define_clearer(name = 'c', dep = '//a', define = 'CYCLE_ON')");
398
399 useConfiguration("--define=CYCLE_ON=yes");
400 getConfiguredTarget("//a");
401 assertNoEvents();
402 }
Googlere10873c2021-10-20 09:09:01 -0700403
404 @Test
405 public void testInvalidVisibility() throws Exception {
406 scratch.file(
407 "a/BUILD",
Googlercdb03cc2024-03-27 12:02:12 -0700408 """
409 cc_library(
410 name = "rule1",
411 visibility = ["//b:rule2"],
412 deps = ["//b:rule2"],
413 )
414 """);
Googlere10873c2021-10-20 09:09:01 -0700415 scratch.file("b/BUILD", "cc_library(name='rule2')");
416
417 AssertionError expected =
418 assertThrows(AssertionError.class, () -> getConfiguredTarget("//a:rule1"));
419
420 assertThat(expected)
421 .hasMessageThat()
422 .contains("Label '//b:rule2' does not refer to a package group.");
423 }
424
425 @Test
426 public void testInvalidVisibilityWithSelect() throws Exception {
427 scratch.file(
428 "a/BUILD",
Googlercdb03cc2024-03-27 12:02:12 -0700429 """
430 cc_library(
431 name = "rule1",
432 visibility = ["//b:rule2"],
433 deps = ["//b:rule2"],
434 )
435 """);
Googlere10873c2021-10-20 09:09:01 -0700436 scratch.file(
437 "b/BUILD",
Googlercdb03cc2024-03-27 12:02:12 -0700438 """
439 config_setting(
440 name = "fastbuild",
441 values = {"compilation_mode": "fastbuild"},
442 )
443
444 cc_library(
445 name = "rule2",
446 hdrs = select({
447 ":fastbuild": glob(
448 [
449 "*.h",
450 ],
451 allow_empty = True,
452 ),
453 }),
454 )
455 """);
Googlere10873c2021-10-20 09:09:01 -0700456
457 AssertionError expected =
458 assertThrows(AssertionError.class, () -> getConfiguredTarget("//a:rule1"));
459
460 assertThat(expected)
461 .hasMessageThat()
462 .contains("Label '//b:rule2' does not refer to a package group.");
463 }
Dmitry Lomovf4cd4752015-11-23 17:50:57 +0000464}