| // Copyright 2018 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.query2.cquery; |
| |
| import static com.google.common.collect.ImmutableList.toImmutableList; |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.devtools.build.lib.packages.Attribute.attr; |
| import static com.google.devtools.build.lib.packages.BuildType.LABEL; |
| import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST; |
| import static com.google.devtools.build.lib.packages.Type.STRING; |
| import static com.google.devtools.build.lib.packages.Types.STRING_LIST; |
| import static org.junit.Assert.assertThrows; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue; |
| import com.google.devtools.build.lib.analysis.config.BuildOptions; |
| import com.google.devtools.build.lib.analysis.config.BuildOptionsView; |
| import com.google.devtools.build.lib.analysis.config.ExecutionTransitionFactory; |
| import com.google.devtools.build.lib.analysis.config.FragmentOptions; |
| import com.google.devtools.build.lib.analysis.config.TransitionFactories; |
| import com.google.devtools.build.lib.analysis.config.transitions.PatchTransition; |
| import com.google.devtools.build.lib.analysis.test.TestConfiguration.TestOptions; |
| import com.google.devtools.build.lib.analysis.util.DummyTestFragment.DummyTestOptions; |
| import com.google.devtools.build.lib.analysis.util.MockRule; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.cmdline.TargetParsingException; |
| import com.google.devtools.build.lib.events.EventHandler; |
| import com.google.devtools.build.lib.packages.BuildType; |
| import com.google.devtools.build.lib.packages.LabelPrinter; |
| import com.google.devtools.build.lib.query2.common.CqueryNode; |
| import com.google.devtools.build.lib.query2.engine.QueryEnvironment.Setting; |
| import com.google.devtools.build.lib.query2.engine.QueryException; |
| import com.google.devtools.build.lib.query2.testutil.PostAnalysisQueryHelper; |
| import com.google.devtools.build.lib.server.FailureDetails; |
| import com.google.devtools.build.lib.server.FailureDetails.ConfigurableQuery; |
| import com.google.devtools.build.lib.server.FailureDetails.Query; |
| import com.google.devtools.build.lib.server.FailureDetails.Query.Code; |
| import com.google.devtools.build.lib.util.FileTypeSet; |
| import com.google.devtools.build.lib.vfs.Path; |
| import java.util.Iterator; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** |
| * Tests for {@link ConfiguredTargetQueryEnvironment}. |
| * |
| * <p>This tests core cquery behavior (behavior that doesn't depend on <code>--output</code>). |
| * Output format-specific behavior is covered in dedicated test classes. |
| */ |
| @RunWith(JUnit4.class) |
| public class ConfiguredTargetQuerySemanticsTest extends ConfiguredTargetQueryTest { |
| @Test |
| public void testConfigurationRespected() throws Exception { |
| writeBuildFilesWithConfigurableAttributesUnconditionally(); |
| assertThat(eval("deps(//configurable:main) ^ //configurable:adep")).isEmpty(); |
| assertThat(eval("deps(//configurable:main) ^ //configurable:defaultdep")).hasSize(1); |
| } |
| |
| private void setUpLabelsFunctionTests() throws Exception { |
| MockRule ruleWithTransitions = |
| () -> |
| MockRule.define( |
| "rule_with_transitions", |
| attr("patch_dep", LABEL) |
| .allowedFileTypes(FileTypeSet.ANY_FILE) |
| .cfg(TransitionFactories.of(new FooPatchTransition("SET BY PATCH"))), |
| attr("string_dep", STRING), |
| attr("split_dep", LABEL) |
| .allowedFileTypes(FileTypeSet.ANY_FILE) |
| .cfg( |
| TransitionFactories.of( |
| new FooSplitTransition("SET BY SPLIT 1", "SET BY SPLIT 2"))), |
| attr("patch_dep_list", LABEL_LIST) |
| .allowedFileTypes(FileTypeSet.ANY_FILE) |
| .cfg(TransitionFactories.of(new FooPatchTransition("SET BY PATCH 2")))); |
| MockRule noAttributeRule = () -> MockRule.define("no_attribute_rule"); |
| |
| helper.useRuleClassProvider( |
| setRuleClassProviders(ruleWithTransitions, noAttributeRule).build()); |
| |
| writeFile( |
| "test/BUILD", |
| """ |
| rule_with_transitions( |
| name = "my_rule", |
| patch_dep = ":dep-1", |
| patch_dep_list = [ |
| ":dep-3", |
| ":dep-4", |
| ], |
| split_dep = ":dep-2", |
| string_dep = "some string", |
| ) |
| |
| no_attribute_rule(name = "dep-1") |
| |
| no_attribute_rule(name = "dep-2") |
| |
| no_attribute_rule(name = "dep-3") |
| |
| no_attribute_rule(name = "dep-4") |
| """); |
| |
| helper.setUniverseScope("//test:*"); |
| } |
| |
| @Test |
| public void testLabelFunction_getsCorrectConfigurations() throws Exception { |
| setUpLabelsFunctionTests(); |
| |
| // (Test that you can use the labels function without an error (b/112593112)). |
| // Note - 'labels' as a command for cquery is a slight misnomer since it always prints |
| // labels AND configurations. But still a helpful function so oh well. |
| assertThat(Iterables.getOnlyElement(eval("labels('patch_dep', //test:my_rule)"))).isNotNull(); |
| } |
| |
| @Test |
| public void testLabelFunction_getCorrectlyConfiguredDeps() throws Exception { |
| setUpLabelsFunctionTests(); |
| |
| // Test that this retrieves the correctly configured version(s) of the dep(s). |
| CqueryNode patchDep = Iterables.getOnlyElement(eval("labels('patch_dep', //test:my_rule)")); |
| CqueryNode myRule = Iterables.getOnlyElement(eval("//test:my_rule")); |
| String targetConfiguration = myRule.getConfigurationChecksum(); |
| assertThat(patchDep.getConfigurationChecksum()).doesNotMatch(targetConfiguration); |
| } |
| |
| @Test |
| public void testLabelsFunction_splitTransitionAttribute() throws Exception { |
| setUpLabelsFunctionTests(); |
| |
| CqueryNode myRule = Iterables.getOnlyElement(eval("//test:my_rule")); |
| String targetConfiguration = myRule.getConfigurationChecksum(); |
| |
| Set<CqueryNode> splitDeps = eval("labels('split_dep', //test:my_rule)"); |
| assertThat(splitDeps).hasSize(2); |
| for (CqueryNode kct : splitDeps) { |
| assertThat(kct.getConfigurationChecksum()).doesNotMatch(targetConfiguration); |
| } |
| } |
| |
| @Test |
| public void testLabelsFunction_labelListAttribute() throws Exception { |
| setUpLabelsFunctionTests(); |
| |
| CqueryNode myRule = Iterables.getOnlyElement(eval("//test:my_rule")); |
| String targetConfiguration = myRule.getConfigurationChecksum(); |
| |
| // Test that this works for label_lists as well. |
| Set<CqueryNode> deps = eval("labels('patch_dep_list', //test:my_rule)"); |
| assertThat(deps).hasSize(2); |
| for (CqueryNode kct : deps) { |
| assertThat(kct.getConfigurationChecksum()).doesNotMatch(targetConfiguration); |
| } |
| } |
| |
| @Test |
| public void testLabelsFunction_errorsOnBadAttribute() throws Exception { |
| setUpLabelsFunctionTests(); |
| |
| // Test that the proper error is thrown when requesting an attribute that doesn't exist. |
| EvalThrowsResult evalThrowsResult = evalThrows("labels('fake_attr', //test:my_rule)", true); |
| assertConfigurableQueryCode( |
| evalThrowsResult.getFailureDetail(), ConfigurableQuery.Code.ATTRIBUTE_MISSING); |
| assertThat(evalThrowsResult.getMessage()) |
| .isEqualTo( |
| "in 'fake_attr' of rule //test:my_rule: configured target of type" |
| + " rule_with_transitions does not have attribute 'fake_attr'"); |
| } |
| |
| @Test |
| public void testLabelsFunction_nonLabelAttribute() throws Exception { |
| setUpLabelsFunctionTests(); |
| assertThat(eval("labels('string_dep', //test:my_rule)")).isEmpty(); |
| } |
| |
| /** |
| * Regression test for b/162431514. the {@code labels} query operator uses {@link |
| * ConfiguredTargetAccessor#getPrerequisites} which is the actual logic being tested here. |
| */ |
| @Test |
| public void testGetPrerequisitesFromAliasReturnsActualPrerequisites() throws Exception { |
| MockRule ruleWithDep = |
| () -> |
| MockRule.define( |
| "rule_with_dep", attr("dep", LABEL).allowedFileTypes(FileTypeSet.ANY_FILE)); |
| |
| helper.useRuleClassProvider(setRuleClassProviders(ruleWithDep).build()); |
| writeFile( |
| "test/BUILD", |
| """ |
| alias( |
| name = "alias", |
| actual = ":actual", |
| ) |
| |
| rule_with_dep( |
| name = "actual", |
| dep = ":dep", |
| ) |
| |
| rule_with_dep(name = "dep") |
| """); |
| |
| CqueryNode dep = Iterables.getOnlyElement(eval("labels('dep', '//test:alias')")); |
| assertThat(dep.getLabel()).isEqualTo(Label.parseCanonicalUnchecked("//test:dep")); |
| } |
| |
| @Test |
| public void testAlias_filtering() throws Exception { |
| MockRule ruleWithExecDep = |
| () -> |
| MockRule.define( |
| "rule_with_exec_dep", |
| attr("exec_dep", LABEL) |
| .allowedFileTypes(FileTypeSet.ANY_FILE) |
| .cfg(ExecutionTransitionFactory.createFactory()), |
| attr("$impl_dep", LABEL) |
| .allowedFileTypes(FileTypeSet.ANY_FILE) |
| .value(Label.parseCanonicalUnchecked("//test:other"))); |
| MockRule simpleRule = () -> MockRule.define("simple_rule"); |
| |
| helper.useRuleClassProvider(setRuleClassProviders(ruleWithExecDep, simpleRule).build()); |
| writeFile( |
| "test/BUILD", |
| """ |
| alias( |
| name = "other_my_rule", |
| actual = ":my_rule", |
| ) |
| |
| rule_with_exec_dep( |
| name = "my_rule", |
| exec_dep = ":exec_dep", |
| ) |
| |
| alias( |
| name = "other_exec_dep", |
| actual = ":exec_dep", |
| ) |
| |
| simple_rule(name = "exec_dep") |
| |
| alias( |
| name = "other_impl_dep", |
| actual = "impl_dep", |
| ) |
| |
| simple_rule(name = "impl_dep") |
| """); |
| |
| CqueryNode other = Iterables.getOnlyElement(eval("//test:other_my_rule")); |
| CqueryNode myRule = Iterables.getOnlyElement(eval("//test:my_rule")); |
| // Note: {@link ConfiguredTarget#getLabel} returns the label of the "actual" value not the |
| // label of the alias, so we need to check the underlying label. |
| assertThat(other.getLabel()).isEqualTo(myRule.getLabel()); |
| |
| // Regression test for b/73496081 in which alias-ed configured targets were skipping filtering. |
| helper.setQuerySettings(Setting.ONLY_TARGET_DEPS, Setting.NO_IMPLICIT_DEPS); |
| assertThat( |
| evalToListOfStrings( |
| "deps(//test:other_my_rule)-//test:other_my_rule" |
| + getDependencyCorrectionWithGen())) |
| .isEqualTo(evalToListOfStrings("//test:my_rule")); |
| } |
| |
| @Test |
| public void testTopLevelTransition() throws Exception { |
| MockRule ruleClassTransition = |
| () -> |
| MockRule.define( |
| "rule_class_transition", |
| (builder, env) -> |
| builder.cfg(unused -> new FooPatchTransition("SET BY PATCH")).build()); |
| |
| helper.useRuleClassProvider(setRuleClassProviders(ruleClassTransition).build()); |
| helper.setUniverseScope("//test:rule_class"); |
| |
| writeFile("test/BUILD", "rule_class_transition(name='rule_class')"); |
| |
| Set<CqueryNode> ruleClass = eval("//test:rule_class"); |
| DummyTestOptions testOptions = |
| getConfiguration(Iterables.getOnlyElement(ruleClass)) |
| .getOptions() |
| .get(DummyTestOptions.class); |
| assertThat(testOptions.foo).isEqualTo("SET BY PATCH"); |
| } |
| |
| private void createConfigRulesAndBuild() throws Exception { |
| MockRule ruleWithTransitions = |
| () -> |
| MockRule.define( |
| "my_rule", |
| attr("target", LABEL).allowedFileTypes(FileTypeSet.ANY_FILE), |
| attr("exec", LABEL) |
| .allowedFileTypes(FileTypeSet.ANY_FILE) |
| .cfg(ExecutionTransitionFactory.createFactory()), |
| attr("deps", BuildType.LABEL_LIST).allowedFileTypes(FileTypeSet.ANY_FILE)); |
| MockRule simpleRule = |
| () -> |
| MockRule.define( |
| "simple_rule", attr("dep", LABEL).allowedFileTypes(FileTypeSet.ANY_FILE)); |
| helper.useRuleClassProvider(setRuleClassProviders(ruleWithTransitions, simpleRule).build()); |
| |
| writeFile( |
| "test/BUILD", |
| """ |
| my_rule( |
| name = "my_rule", |
| exec = ":exec_dep", |
| target = ":target_dep", |
| deps = [":dep"], |
| ) |
| |
| simple_rule( |
| name = "target_dep", |
| dep = ":dep", |
| ) |
| |
| simple_rule( |
| name = "exec_dep", |
| dep = ":dep", |
| ) |
| |
| simple_rule(name = "dep") |
| """); |
| } |
| |
| private void createConfigTransitioningRuleClass() throws Exception { |
| overwriteFile( |
| "tools/allowlists/function_transition_allowlist/BUILD", |
| """ |
| package_group( |
| name = "function_transition_allowlist", |
| packages = [ |
| "//test/...", |
| ], |
| ) |
| """); |
| writeFile( |
| "test/rules.bzl", |
| """ |
| def _rule_impl(ctx): |
| return [] |
| |
| string_flag = rule( |
| implementation = _rule_impl, |
| build_setting = config.string(), |
| ) |
| |
| def _transition_impl(settings, attr): |
| return {"//test:my_flag": "custom string"} |
| |
| my_transition = transition( |
| implementation = _transition_impl, |
| inputs = [], |
| outputs = ["//test:my_flag"], |
| ) |
| rule_with_deps_transition = rule( |
| implementation = _rule_impl, |
| attrs = { |
| "deps": attr.label_list(cfg = my_transition), |
| }, |
| ) |
| simple_rule = rule( |
| implementation = _rule_impl, |
| attrs = {}, |
| ) |
| """); |
| } |
| |
| @Test |
| public void testConfig_target() throws Exception { |
| createConfigRulesAndBuild(); |
| |
| assertThat(eval("config(//test:target_dep, target)")).isEqualTo(eval("//test:target_dep")); |
| |
| getHelper().setWholeTestUniverseScope("test:my_rule"); |
| |
| assertThat(eval("config(//test:target_dep, target)")).isEqualTo(eval("//test:target_dep")); |
| EvalThrowsResult execResult = evalThrows("config(//test:exec_dep, target)", true); |
| assertThat(execResult.getMessage()) |
| .isEqualTo("No target (in) //test:exec_dep could be found in the 'target' configuration"); |
| assertConfigurableQueryCode( |
| execResult.getFailureDetail(), ConfigurableQuery.Code.TARGET_MISSING); |
| |
| BuildConfigurationValue configuration = |
| getConfiguration(Iterables.getOnlyElement(eval("config(//test:dep, target)"))); |
| |
| assertThat(configuration).isNotNull(); |
| assertThat(configuration.isExecConfiguration()).isFalse(); |
| assertThat(configuration.isToolConfiguration()).isFalse(); |
| } |
| |
| @Test |
| public void testConfig_nullConfig() throws Exception { |
| writeFile( |
| "test/BUILD", |
| """ |
| java_library( |
| name = "my_java", |
| srcs = ["foo.java"], |
| ) |
| """); |
| |
| assertThat(getConfiguration(Iterables.getOnlyElement(eval("config(//test:foo.java,null)")))) |
| .isNull(); |
| } |
| |
| @Test |
| public void testConfig_configHash() throws Exception { |
| createConfigTransitioningRuleClass(); |
| writeFile( |
| "test/BUILD", |
| """ |
| load("//test:rules.bzl", "rule_with_deps_transition", "simple_rule", "string_flag") |
| |
| string_flag( |
| name = "my_flag", |
| build_setting_default = "", |
| ) |
| |
| rule_with_deps_transition( |
| name = "buildme", |
| deps = [":mydep"], |
| ) |
| |
| simple_rule(name = "mydep") |
| """); |
| |
| // If we don't set --universe_scope=//test:buildme, cquery builds both //test:buildme and |
| // //test:mydep as top-level targets. That means //test:mydep will have two configured targets: |
| // one under the transitioned configuration and one under the top-level configuration. By |
| // setting --universe_scope we ensure only the transitioned version exists. |
| helper.setUniverseScope("//test:buildme"); |
| helper.setQuerySettings(Setting.ONLY_TARGET_DEPS, Setting.NO_IMPLICIT_DEPS); |
| Set<CqueryNode> result = eval("deps(//test:buildme, 1)" + getDependencyCorrection()); |
| assertThat(result).hasSize(2); |
| |
| ImmutableList<CqueryNode> stableOrderList = ImmutableList.copyOf(result); |
| int myDepIndex = stableOrderList.get(0).getLabel().toString().equals("//test:mydep") ? 0 : 1; |
| BuildConfigurationValue myDepConfig = getConfiguration(stableOrderList.get(myDepIndex)); |
| BuildConfigurationValue stringFlagConfig = |
| getConfiguration(stableOrderList.get(1 - myDepIndex)); |
| |
| // Note: eval() resets the universe scope after each call. We have to xplicitly set it again. |
| helper.setUniverseScope("//test:buildme"); |
| assertThat(eval("config(//test:mydep, " + myDepConfig.checksum() + ")")).hasSize(1); |
| |
| helper.setUniverseScope("//test:buildme"); |
| QueryException e = |
| assertThrows( |
| QueryException.class, |
| () -> eval("config(//test:mydep, " + stringFlagConfig.checksum() + ")")); |
| assertThat(e) |
| .hasMessageThat() |
| .contains("No target (in) //test:mydep could be found in the configuration with checksum"); |
| } |
| |
| @Test |
| public void testConfig_configHashPrefix() throws Exception { |
| createConfigRulesAndBuild(); |
| writeFile("mytest/BUILD", "simple_rule(name = 'mytarget')"); |
| |
| Set<CqueryNode> result = eval("//mytest:mytarget"); |
| String configHash = getConfiguration(Iterables.getOnlyElement(result)).checksum(); |
| String hashPrefix = configHash.substring(0, configHash.length() / 2); |
| |
| Set<CqueryNode> resultFromPrefix = eval("config(//mytest:mytarget," + hashPrefix + ")"); |
| assertThat(resultFromPrefix).containsExactlyElementsIn(result); |
| } |
| |
| @Test |
| public void testConfig_configHashUnknownPrefix() throws Exception { |
| createConfigRulesAndBuild(); |
| writeFile("mytest/BUILD", "simple_rule(name = 'mytarget')"); |
| |
| Set<CqueryNode> result = eval("//mytest:mytarget"); |
| String configHash = getConfiguration(Iterables.getOnlyElement(result)).checksum(); |
| String rightPrefix = configHash.substring(0, configHash.length() / 2); |
| char lastChar = rightPrefix.charAt(rightPrefix.length() - 1); |
| String wrongPrefix = rightPrefix.substring(0, rightPrefix.length() - 1) + (lastChar + 1); |
| |
| QueryException e = |
| assertThrows( |
| QueryException.class, () -> eval("config(//mytest:mytarget," + wrongPrefix + ")")); |
| assertConfigurableQueryCode( |
| e.getFailureDetail(), ConfigurableQuery.Code.INCORRECT_CONFIG_ARGUMENT_ERROR); |
| assertThat(e) |
| .hasMessageThat() |
| .contains("config()'s second argument must identify a unique configuration"); |
| } |
| |
| @Test |
| public void testConfig_exprArgumentFailure() throws Exception { |
| writeFile( |
| "test/BUILD", |
| """ |
| java_library( |
| name = "my_java", |
| srcs = ["foo.java"], |
| ) |
| """); |
| |
| EvalThrowsResult evalThrowsResult = |
| evalThrows( |
| "config(filter(\"??not-a-valid-regex\", //test:foo.java), null)", |
| /* unconditionallyThrows= */ true); |
| assertThat(evalThrowsResult.getMessage()) |
| .startsWith("illegal 'filter' pattern regexp '??not-a-valid-regex'"); |
| assertThat(evalThrowsResult.getFailureDetail().hasQuery()).isTrue(); |
| assertThat(evalThrowsResult.getFailureDetail().getQuery().getCode()) |
| .isEqualTo(Code.SYNTAX_ERROR); |
| } |
| |
| @Test |
| public void testExecTransitionNotFilteredByNoToolDeps() throws Exception { |
| createConfigRulesAndBuild(); |
| helper.setQuerySettings(Setting.ONLY_TARGET_DEPS, Setting.NO_IMPLICIT_DEPS); |
| assertThat(evalToListOfStrings("deps(//test:my_rule)" + getDependencyCorrection())) |
| .containsExactly("//test:my_rule", "//test:target_dep", "//test:dep"); |
| } |
| |
| @Test |
| public void testRecursiveTargetPatternNeverThrowsError() throws Exception { |
| Path parent = |
| getHelper() |
| .getScratch() |
| .file("parent/BUILD", "sh_library(name = 'parent')") |
| .getParentDirectory(); |
| Path child = parent.getRelative("child"); |
| child.createDirectory(); |
| Path badBuild = child.getRelative("BUILD"); |
| badBuild.createSymbolicLink(badBuild); |
| |
| helper.setKeepGoing(true); |
| assertThat(eval("//parent:all")).isEqualTo(eval("//parent:parent")); |
| |
| helper.setKeepGoing(false); |
| getHelper().turnOffFailFast(); |
| TargetParsingException e = |
| assertThrows(TargetParsingException.class, () -> eval("//parent/...")); |
| assertThat(e) |
| .hasMessageThat() |
| .isEqualTo( |
| "error loading package under directory 'parent': no such package 'parent/child':" |
| + " Symlink cycle detected while trying to find BUILD file" |
| + " /workspace/parent/child/BUILD"); |
| assertThat(e.getDetailedExitCode().getFailureDetail().getPackageLoading().getCode()) |
| .isEqualTo(FailureDetails.PackageLoading.Code.SYMLINK_CYCLE_OR_INFINITE_EXPANSION); |
| } |
| |
| // Regression test for b/175739699 |
| @Test |
| public void testRecursiveTargetPatternOutsideOfScopeFailsGracefully() throws Exception { |
| writeFile("testA/BUILD", "sh_library(name = 'testA')"); |
| writeFile("testB/BUILD", "sh_library(name = 'testB')"); |
| writeFile("testB/testC/BUILD", "sh_library(name = 'testC')"); |
| helper.setUniverseScope("//testA"); |
| QueryException e = assertThrows(QueryException.class, () -> eval("//testB/...")); |
| assertThat(e.getFailureDetail().getQuery().getCode()) |
| .isEqualTo(Query.Code.TARGET_NOT_IN_UNIVERSE_SCOPE); |
| assertThat(e).hasMessageThat().contains("package is not in scope"); |
| } |
| |
| @Override |
| @Test |
| public void testMultipleTopLevelConfigurations_nullConfigs() throws Exception { |
| writeFile( |
| "test/BUILD", |
| """ |
| java_library( |
| name = "my_java", |
| srcs = ["foo.java"], |
| ) |
| """); |
| |
| Set<CqueryNode> result = eval("//test:my_java+//test:foo.java"); |
| |
| assertThat(result).hasSize(2); |
| |
| Iterator<CqueryNode> resultIterator = result.iterator(); |
| CqueryNode first = resultIterator.next(); |
| if (first.getLabel().toString().equals("//test:foo.java")) { |
| assertThat(getConfiguration(first)).isNull(); |
| assertThat(getConfiguration(resultIterator.next())).isNotNull(); |
| } else { |
| assertThat(getConfiguration(first)).isNotNull(); |
| assertThat(getConfiguration(resultIterator.next())).isNull(); |
| } |
| } |
| |
| @Test |
| public void testSomePath_depInCustomConfiguration() throws Exception { |
| createConfigTransitioningRuleClass(); |
| writeFile( |
| "test/BUILD", |
| """ |
| load("//test:rules.bzl", "rule_with_deps_transition", "simple_rule", "string_flag") |
| |
| string_flag( |
| name = "my_flag", |
| build_setting_default = "", |
| ) |
| |
| rule_with_deps_transition( |
| name = "buildme", |
| deps = [":mydep"], |
| ) |
| |
| simple_rule(name = "mydep") |
| """); |
| |
| // If we don't set --universe_scope=//test:buildme, then cquery builds both //test:buildme and |
| // //test:mydep as top-level targets. That means //test:mydep will have two configured targets: |
| // one under the transitioned configuration and one under the top-level configuration. In these |
| // cases cquery prefers the top-level configured one, which won't produce a match since that's |
| // not the one down this dependency path. |
| helper.setUniverseScope("//test:buildme"); |
| Set<CqueryNode> result = eval("somepath(//test:buildme, //test:mydep)"); |
| assertThat(result.stream().map(kct -> kct.getLabel().toString()).collect(Collectors.toList())) |
| .contains("//test:mydep"); |
| } |
| |
| /** Return an empty BuildOptions for testing fragment dropping. * */ |
| public static class RemoveTestOptionsTransition implements PatchTransition { |
| @Override |
| public ImmutableSet<Class<? extends FragmentOptions>> requiresOptionFragments() { |
| return ImmutableSet.of(TestOptions.class); |
| } |
| |
| @Override |
| public BuildOptions patch(BuildOptionsView options, EventHandler eventHandler) { |
| BuildOptions.Builder builder = BuildOptions.builder(); |
| for (FragmentOptions option : options.underlying().getNativeOptions()) { |
| if (!(option instanceof TestOptions)) { |
| builder.addFragmentOptions(option); |
| } |
| } |
| // This does not copy over Starlark options!! |
| return builder.build(); |
| } |
| } |
| |
| @Test |
| public void testQueryHandlesDroppingFragments() throws Exception { |
| MockRule ruleDropOptions = |
| () -> |
| MockRule.define( |
| "rule_drop_options", |
| attr("dep", LABEL) |
| .allowedFileTypes(FileTypeSet.ANY_FILE) |
| .cfg(TransitionFactories.of(new RemoveTestOptionsTransition()))); |
| MockRule simpleRule = |
| () -> |
| MockRule.define( |
| "simple_rule", attr("deps", LABEL_LIST).allowedFileTypes(FileTypeSet.ANY_FILE)); |
| |
| helper.useRuleClassProvider(setRuleClassProviders(ruleDropOptions, simpleRule).build()); |
| writeFile( |
| "test/BUILD", |
| """ |
| rule_drop_options( |
| name = "top", |
| dep = ":foo", |
| ) |
| |
| simple_rule( |
| name = "foo", |
| deps = [":bar"], |
| ) |
| |
| simple_rule(name = "bar") |
| """); |
| |
| Set<CqueryNode> result = eval("somepath(//test:top, filter(//test:bar, deps(//test:top)))"); |
| assertThat(result).isNotEmpty(); |
| } |
| |
| @Test |
| public void testLabelExpressionsMatchesAllConfiguredTargetsWithLabel() throws Exception { |
| createConfigTransitioningRuleClass(); |
| writeFile( |
| "test/BUILD", |
| """ |
| load("//test:rules.bzl", "rule_with_deps_transition", "simple_rule", "string_flag") |
| |
| string_flag( |
| name = "my_flag", |
| build_setting_default = "", |
| ) |
| |
| rule_with_deps_transition( |
| name = "transitioner", |
| deps = [":simple"], |
| ) |
| |
| simple_rule(name = "simple") |
| """); |
| |
| helper.setUniverseScope("//test:transitioner,//test:simple"); |
| Set<CqueryNode> result = eval("//test:simple"); |
| assertThat(result.size()).isEqualTo(2); |
| } |
| |
| @Test |
| public void testConfigFunctionRefinesMultipleMatches() throws Exception { |
| // Peer to testLabelExpressionsMatchesAllConfiguredTargetsWithLabel. The point of that test is |
| // to show "cquery //foo:bar" might return multiple configured targets. The point of this test |
| // is to show that config() can refine the same query to a specific one. |
| createConfigTransitioningRuleClass(); |
| writeFile( |
| "test/BUILD", |
| """ |
| load("//test:rules.bzl", "rule_with_deps_transition", "simple_rule", "string_flag") |
| |
| string_flag( |
| name = "my_flag", |
| build_setting_default = "", |
| ) |
| |
| rule_with_deps_transition( |
| name = "transitioner", |
| deps = [":simple"], |
| ) |
| |
| simple_rule(name = "simple") |
| """); |
| |
| helper.setUniverseScope("//test:transitioner,//test:simple"); |
| Set<CqueryNode> result = eval("config(//test:simple, target)"); |
| assertThat(result.size()).isEqualTo(1); |
| } |
| |
| @Test |
| public void testAspectDepsAppearInCqueryDeps() throws Exception { |
| writeFile( |
| "donut/test.bzl", |
| """ |
| TestAspectInfo = provider("TestAspectInfo", fields = ["info"]) |
| |
| def _test_aspect_impl(target, ctx): |
| return [ |
| TestAspectInfo( |
| info = depset([target.label]), |
| ), |
| ] |
| |
| _test_aspect = aspect( |
| implementation = _test_aspect_impl, |
| attr_aspects = ["deps"], |
| attrs = { |
| "_test_attr": attr.label( |
| allow_files = True, |
| default = Label("//donut:test_filegroup"), |
| ), |
| }, |
| provides = [TestAspectInfo], |
| ) |
| |
| def _test_impl(ctx): |
| pass |
| |
| test_rule = rule( |
| _test_impl, |
| attrs = { |
| "deps": attr.label_list( |
| aspects = [_test_aspect], |
| ), |
| }, |
| ) |
| """); |
| writeFile( |
| "donut/BUILD", |
| """ |
| load(":test.bzl", "test_rule") |
| |
| filegroup( |
| name = "test_filegroup", |
| srcs = ["test.bzl"], |
| ) |
| |
| test_rule( |
| name = "test_rule_dep", |
| ) |
| |
| test_rule( |
| name = "test_rule", |
| deps = [":test_rule_dep"], |
| ) |
| """); |
| |
| helper.setQuerySettings(Setting.INCLUDE_ASPECTS, Setting.EXPLICIT_ASPECTS); |
| var result = |
| eval("filter(//donut, deps(//donut:test_rule))").stream() |
| .map(cf -> cf.getDescription(LabelPrinter.legacy())) |
| .collect(ImmutableList.toImmutableList()); |
| assertThat(result) |
| .containsExactly( |
| "//donut:test_rule", |
| "//donut:test_rule_dep", |
| "//donut:test.bzl%_test_aspect of //donut:test_rule_dep", |
| "//donut:test.bzl", |
| "//donut:test_filegroup"); |
| } |
| |
| @Test |
| public void testAspectOnAspectDepsAppearInCqueryDeps() throws Exception { |
| writeFile( |
| "donut/test.bzl", |
| """ |
| TestAspectInfo = provider("TestAspectInfo", fields = ["info"]) |
| TestAspectOnAspectInfo = provider("TestAspectOnAspectInfo", fields = ["info"]) |
| |
| def _test_aspect_impl(target, ctx): |
| return [ |
| TestAspectInfo( |
| info = depset([target.label]), |
| ), |
| ] |
| |
| _test_aspect = aspect( |
| implementation = _test_aspect_impl, |
| attr_aspects = ["deps"], |
| attrs = { |
| "_test_attr": attr.label( |
| allow_files = True, |
| default = Label("//donut:test_aspect_filegroup"), |
| ), |
| }, |
| provides = [TestAspectInfo], |
| ) |
| |
| def _test_aspect_on_aspect_impl(target, ctx): |
| return [ |
| TestAspectOnAspectInfo( |
| info = depset( |
| direct = [target.label], |
| transitive = [target[TestAspectInfo].info], |
| ), |
| ), |
| ] |
| |
| _test_aspect_on_aspect = aspect( |
| implementation = _test_aspect_on_aspect_impl, |
| attr_aspects = ["deps"], |
| attrs = { |
| "_test_attr": attr.label( |
| allow_files = True, |
| default = Label("//donut:test_aspect_on_aspect_filegroup"), |
| ), |
| }, |
| required_aspect_providers = [TestAspectInfo], |
| provides = [TestAspectOnAspectInfo], |
| ) |
| |
| def _test_impl(ctx): |
| pass |
| |
| test_rule = rule( |
| _test_impl, |
| attrs = { |
| "deps": attr.label_list( |
| aspects = [_test_aspect], |
| ), |
| }, |
| ) |
| |
| def _test_aspect_on_aspect_rule_impl(ctx): |
| pass |
| |
| test_aspect_on_aspect_rule = rule( |
| _test_aspect_on_aspect_rule_impl, |
| attrs = { |
| "deps": attr.label_list( |
| aspects = [_test_aspect, _test_aspect_on_aspect], |
| ), |
| }, |
| ) |
| """); |
| writeFile("donut/test_aspect.file"); |
| writeFile("donut/test_aspect_on_aspect.file"); |
| writeFile( |
| "donut/BUILD", |
| """ |
| load(":test.bzl", "test_aspect_on_aspect_rule", "test_rule") |
| |
| filegroup( |
| name = "test_aspect_filegroup", |
| srcs = ["test_aspect.file"], |
| ) |
| |
| filegroup( |
| name = "test_aspect_on_aspect_filegroup", |
| srcs = ["test_aspect_on_aspect.file"], |
| ) |
| |
| test_rule( |
| name = "test_rule_dep", |
| ) |
| |
| test_rule( |
| name = "test_rule", |
| deps = [":test_rule_dep"], |
| ) |
| |
| test_aspect_on_aspect_rule( |
| name = "test_aspect_on_aspect_rule", |
| deps = ["test_rule"], |
| ) |
| """); |
| |
| helper.setUniverseScope("//donut/..."); |
| helper.setQuerySettings(Setting.INCLUDE_ASPECTS, Setting.EXPLICIT_ASPECTS); |
| var result = |
| eval("filter(//donut, deps(//donut:test_aspect_on_aspect_rule))").stream() |
| .map(cf -> cf.getDescription(LabelPrinter.legacy())) |
| .collect(toImmutableList()); |
| assertThat(result) |
| .containsExactly( |
| "//donut:test.bzl%_test_aspect_on_aspect on top of" |
| + " [//donut:test.bzl%_test_aspect of //donut:test_rule_dep]", |
| "//donut:test.bzl%_test_aspect_on_aspect on top of" |
| + " [//donut:test.bzl%_test_aspect of //donut:test_rule]", |
| "//donut:test_rule_dep", |
| "//donut:test_rule", |
| "//donut:test.bzl%_test_aspect of //donut:test_rule_dep", |
| "//donut:test.bzl%_test_aspect of //donut:test_rule", |
| "//donut:test_aspect_on_aspect_rule", |
| "//donut:test_aspect.file", |
| "//donut:test_aspect_on_aspect_filegroup", |
| "//donut:test_aspect_on_aspect.file", |
| "//donut:test_aspect_filegroup"); |
| } |
| |
| @Test |
| public void testAspectDepsAppearInCqueryRdeps() throws Exception { |
| writeFile( |
| "donut/test.bzl", |
| """ |
| TestAspectInfo = provider("TestAspectInfo", fields = ["info"]) |
| |
| def _test_aspect_impl(target, ctx): |
| return [ |
| TestAspectInfo( |
| info = depset([target.label]), |
| ), |
| ] |
| |
| _test_aspect = aspect( |
| implementation = _test_aspect_impl, |
| attr_aspects = ["deps"], |
| attrs = { |
| "_test_attr": attr.label( |
| allow_files = True, |
| default = Label("//donut:test_filegroup"), |
| ), |
| }, |
| provides = [TestAspectInfo], |
| ) |
| |
| def _test_impl(ctx): |
| pass |
| |
| test_rule = rule( |
| _test_impl, |
| attrs = { |
| "deps": attr.label_list( |
| aspects = [_test_aspect], |
| ), |
| }, |
| ) |
| """); |
| writeFile( |
| "donut/BUILD", |
| """ |
| load(":test.bzl", "test_rule") |
| |
| filegroup( |
| name = "test_filegroup", |
| srcs = ["test.bzl"], |
| ) |
| |
| test_rule( |
| name = "test_rule_dep", |
| ) |
| |
| test_rule( |
| name = "test_rule", |
| deps = [":test_rule_dep"], |
| ) |
| """); |
| |
| helper.setQuerySettings(Setting.INCLUDE_ASPECTS, Setting.EXPLICIT_ASPECTS); |
| var result = |
| eval("rdeps(//donut/..., //donut:test_filegroup)").stream() |
| .map(cf -> cf.getDescription(LabelPrinter.legacy())) |
| .collect(toImmutableList()); |
| assertThat(result) |
| .containsExactly( |
| "//donut:test_filegroup", |
| "//donut:test_rule", |
| "//donut:test.bzl%_test_aspect of //donut:test_rule_dep"); |
| } |
| |
| @Test |
| public void testAttrRespectsConfiguration() throws Exception { |
| MockRule ruleWithList = |
| () -> MockRule.define("rule_with_list", attr("string_values", STRING_LIST)); |
| |
| helper.useRuleClassProvider(setRuleClassProviders(ruleWithList).build()); |
| |
| writeFile( |
| "test/BUILD", |
| """ |
| load(":flag.bzl", "bool_flag") |
| bool_flag( |
| name = "enable", |
| build_setting_default = False, |
| ) |
| """); |
| writeFile( |
| "configurable/BUILD", |
| """ |
| config_setting( |
| name = "enabled", |
| define_values = {"test_enable": "true"}) |
| rule_with_list( |
| name = 'target', |
| string_values = select({ |
| ':enabled': ['foo', 'bar'], |
| '//conditions:default': ['quux'], |
| }), |
| ) |
| """); |
| |
| // Using default configuration, 'quux' is the only value in the attribute. |
| assertThat(evalToString("attr(string_values, 'foo', '//configurable:target')")).isEmpty(); |
| assertThat(evalToString("attr(string_values, 'bar', '//configurable:target')")).isEmpty(); |
| assertThat(evalToString("attr(string_values, 'quux', '//configurable:target')")) |
| .isEqualTo("//configurable:target"); |
| |
| // When the flag is enabled, 'foo' and 'bar' are present, but not 'quux' |
| ((PostAnalysisQueryHelper<CqueryNode>) helper).useConfiguration("--define=test_enable=true"); |
| assertThat(evalToString("attr(string_values, 'foo', '//configurable:target')")) |
| .isEqualTo("//configurable:target"); |
| assertThat(evalToString("attr(string_values, 'bar', '//configurable:target')")) |
| .isEqualTo("//configurable:target"); |
| assertThat(evalToString("attr(string_values, 'quux', '//configurable:target')")).isEmpty(); |
| } |
| } |