| // Copyright 2016 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.rules; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Iterables; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.util.ActionsTestUtil; |
| import com.google.devtools.build.lib.analysis.AnalysisResult; |
| import com.google.devtools.build.lib.analysis.ConfiguredAspect; |
| import com.google.devtools.build.lib.analysis.ConfiguredTarget; |
| import com.google.devtools.build.lib.analysis.FileProvider; |
| import com.google.devtools.build.lib.analysis.LicensesProvider; |
| import com.google.devtools.build.lib.analysis.LicensesProvider.TargetLicense; |
| import com.google.devtools.build.lib.analysis.RunfilesProvider; |
| import com.google.devtools.build.lib.analysis.config.BuildConfiguration; |
| import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSet; |
| import com.google.devtools.build.lib.packages.License.LicenseType; |
| import com.google.devtools.build.lib.packages.Provider; |
| import com.google.devtools.build.lib.packages.StarlarkProvider; |
| import com.google.devtools.build.lib.packages.StructImpl; |
| import com.google.devtools.build.lib.rules.cpp.CcInfo; |
| import com.google.devtools.build.lib.skyframe.AspectValueKey.AspectKey; |
| import java.util.Set; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Unit tests for the <code>alias</code> rule. */ |
| @RunWith(JUnit4.class) |
| public class AliasTest extends BuildViewTestCase { |
| @Test |
| public void smoke() throws Exception { |
| scratch.file("a/BUILD", |
| "cc_library(name='a', srcs=['a.cc'])", |
| "alias(name='b', actual='a')"); |
| |
| ConfiguredTarget b = getConfiguredTarget("//a:b"); |
| assertThat(b.get(CcInfo.PROVIDER).getCcCompilationContext()).isNotNull(); |
| } |
| |
| @Test |
| public void aliasToInputFile() throws Exception { |
| scratch.file("a/BUILD", |
| "exports_files(['a'])", |
| "alias(name='b', actual='a')"); |
| |
| ConfiguredTarget b = getConfiguredTarget("//a:b"); |
| assertThat(ActionsTestUtil.baseArtifactNames(getFilesToBuild(b))).containsExactly("a"); |
| } |
| |
| @Test |
| public void visibilityIsOverriddenAndIsOkay() throws Exception { |
| scratch.file("a/BUILD", |
| "filegroup(name='a', visibility=['//b:__pkg__'])"); |
| scratch.file("b/BUILD", |
| "alias(name='b', actual='//a:a', visibility=['//visibility:public'])"); |
| scratch.file("c/BUILD", |
| "filegroup(name='c', srcs=['//b:b'])"); |
| |
| getConfiguredTarget("//c:c"); |
| } |
| |
| @Test |
| public void visibilityIsOverriddenAndIsError() throws Exception { |
| scratch.file("a/BUILD", |
| "filegroup(name='a', visibility=['//visibility:public'])"); |
| scratch.file("b/BUILD", |
| "alias(name='b', actual='//a:a', visibility=['//visibility:private'])"); |
| scratch.file("c/BUILD", |
| "filegroup(name='c', srcs=['//b:b'])"); |
| |
| reporter.removeHandler(failFastHandler); |
| getConfiguredTarget("//c:c"); |
| assertContainsEvent( |
| "alias '//b:b' referring to target '//a:a' is not visible from target '//c:c'"); |
| } |
| |
| @Test |
| public void visibilityIsOverriddenAndIsErrorAfterMultipleAliases() throws Exception { |
| scratch.file("a/BUILD", |
| "filegroup(name='a', visibility=['//visibility:public'])"); |
| scratch.file("b/BUILD", |
| "alias(name='b', actual='//a:a', visibility=['//visibility:public'])"); |
| scratch.file("c/BUILD", |
| "alias(name='c', actual='//b:b', visibility=['//visibility:private'])"); |
| scratch.file("d/BUILD", |
| "filegroup(name='d', srcs=['//c:c'])"); |
| |
| reporter.removeHandler(failFastHandler); |
| getConfiguredTarget("//d:d"); |
| assertContainsEvent("alias '//c:c' referring to target '//a:a' through '//b:b' " |
| + "is not visible from target '//d:d'"); |
| } |
| |
| @Test |
| public void testAliasWithPrivateVisibilityAccessibleFromSamePackage() throws Exception { |
| scratch.file("a/BUILD", "exports_files(['af'])"); |
| scratch.file("b/BUILD", |
| "package(default_visibility=['//visibility:private'])", |
| "alias(name='al', actual='//a:af')", |
| "filegroup(name='ta', srcs=[':al'])"); |
| |
| getConfiguredTarget("//b:ta"); |
| } |
| |
| @Test |
| public void testAliasCycle() throws Exception { |
| scratch.file("a/BUILD", |
| "alias(name='a', actual=':b')", |
| "alias(name='b', actual=':c')", |
| "alias(name='c', actual=':a')", |
| "filegroup(name='d', srcs=[':c'])"); |
| |
| reporter.removeHandler(failFastHandler); |
| getConfiguredTarget("//a:d"); |
| assertContainsEvent("cycle in dependency graph"); |
| } |
| |
| @Test |
| public void testAliasedInvalidDependency() throws Exception { |
| scratch.file("a/BUILD", |
| "cc_library(name='a', deps=[':b'])", |
| "alias(name='b', actual=':c')", |
| "filegroup(name='c')"); |
| |
| reporter.removeHandler(failFastHandler); |
| getConfiguredTarget("//a:a"); |
| assertContainsEvent("alias '//a:b' referring to filegroup rule '//a:c' is misplaced here"); |
| } |
| |
| @Test |
| public void testAspectPropagation() throws Exception { |
| writeConfigTransitionTestFiles(); |
| scratch.file( |
| "test/aspect.bzl", |
| "load('//myinfo:myinfo.bzl', 'MyInfo')", |
| "def _impl(target, ctx):", |
| " if not target[MyInfo]:", |
| " fail('missing MyInfo')", |
| " if target[MyInfo].config != ctx.configuration:", |
| " fail('mismatched configs')", |
| " return MyInfo(", |
| " origin = 'aspect',", |
| " config = target[MyInfo].config)", |
| "MyAspect = aspect(implementation=_impl)"); |
| scratch.file( |
| "test/BUILD", |
| "alias(name = 'simple_alias', actual = '//test/starlark:test')", |
| "alias(name = 'selecting_alias',", |
| " actual = select({':arm': ':simple_alias'}))", |
| "config_setting(name = 'arm', values = {'cpu': 'armeabi-v7a'})"); |
| |
| // Set --cpu so we can test alias :selecting_alias that selects on this flag |
| useConfiguration("--cpu=armeabi-v7a"); |
| |
| // 1. Query "actual" target to establish reference values to compare to below. Make some basic |
| // assertions that tie aspect's config to underlying target. |
| AnalysisResult analysisResult = |
| update( |
| ImmutableList.of("//test/starlark:test"), |
| ImmutableList.of("//test:aspect.bzl%MyAspect"), |
| true, |
| 1, |
| true, |
| eventBus); |
| assertThat(analysisResult.getTargetsToBuild()).hasSize(1); |
| assertThat(analysisResult.getAspectsMap()).hasSize(1); |
| |
| ConfiguredTarget actualTarget = Iterables.getOnlyElement(analysisResult.getTargetsToBuild()); |
| ConfiguredAspect aspect = Iterables.getOnlyElement(analysisResult.getAspectsMap().values()); |
| AspectKey actualKey = Iterables.getOnlyElement(analysisResult.getAspectsMap().keySet()); |
| assertThat(actualKey.getBaseConfiguredTargetKey().getConfigurationKey()) |
| .isEqualTo(actualTarget.getConfigurationKey()); |
| assertThat(getMyInfoFromTarget(aspect).getValue("origin")).isEqualTo("aspect"); |
| BuildConfiguration actualConfig = |
| (BuildConfiguration) getMyInfoFromTarget(aspect).getValue("config"); |
| assertThat(actualKey.getBaseConfiguredTargetKey().getConfigurationKey().getOptions().checksum()) |
| .isEqualTo(actualConfig.checksum()); |
| |
| // 2. Query :simple_alias and assert that its aspect value is the same as above. |
| analysisResult = |
| update( |
| ImmutableList.of("//test:simple_alias"), |
| ImmutableList.of("//test:aspect.bzl%MyAspect"), |
| true, |
| 1, |
| true, |
| eventBus); |
| assertThat(analysisResult.getTargetsToBuild()).hasSize(1); |
| assertThat(analysisResult.getAspectsMap()).hasSize(1); |
| |
| ConfiguredTarget alias = Iterables.getOnlyElement(analysisResult.getTargetsToBuild()); |
| assertThat(alias.getActual()).isEqualTo(actualTarget); |
| // Alias and actual must have different configs for this test to be meaningful |
| assertThat(alias.getConfigurationKey()).isNotEqualTo(alias.getActual().getConfigurationKey()); |
| AspectKey aspectKey = Iterables.getOnlyElement(analysisResult.getAspectsMap().keySet()); |
| assertThat(aspectKey.getBaseConfiguredTargetKey().getConfigurationKey()) |
| .isEqualTo(alias.getConfigurationKey()); |
| |
| aspect = Iterables.getOnlyElement(analysisResult.getAspectsMap().values()); |
| assertThat(getMyInfoFromTarget(aspect).getValue("origin")).isEqualTo("aspect"); |
| // We should be seeing actual's config here |
| assertThat(getMyInfoFromTarget(aspect).getValue("config")).isEqualTo(actualConfig); |
| |
| // 3. Do the same with :selecting_alias, which is an indirect alias through :simple_alias. |
| // This alias also uses a (non-trivial) select to resolve its actual. |
| analysisResult = |
| update( |
| ImmutableList.of("//test:selecting_alias"), |
| ImmutableList.of("//test:aspect.bzl%MyAspect"), |
| true, |
| 1, |
| true, |
| eventBus); |
| assertThat(analysisResult.getTargetsToBuild()).hasSize(1); |
| assertThat(analysisResult.getAspectsMap()).hasSize(1); |
| |
| ConfiguredTarget indirectAlias = Iterables.getOnlyElement(analysisResult.getTargetsToBuild()); |
| assertThat(indirectAlias.getActual()).isEqualTo(actualTarget); |
| assertThat(indirectAlias.getConfigurationKey()).isEqualTo(alias.getConfigurationKey()); |
| |
| aspect = Iterables.getOnlyElement(analysisResult.getAspectsMap().values()); |
| assertThat(getMyInfoFromTarget(aspect).getValue("origin")).isEqualTo("aspect"); |
| assertThat(getMyInfoFromTarget(aspect).getValue("config")).isEqualTo(actualConfig); |
| } |
| |
| private void writeAllowlistFile() throws Exception { |
| scratch.overwriteFile( |
| "tools/allowlists/function_transition_allowlist/BUILD", |
| "package_group(", |
| " name = 'function_transition_allowlist',", |
| " packages = [", |
| " '//test/...',", |
| " ],", |
| ")"); |
| } |
| |
| private static StructImpl getMyInfoFromTarget(ConfiguredAspect configuredAspect) |
| throws Exception { |
| Provider.Key key = |
| new StarlarkProvider.Key( |
| Label.parseAbsolute("//myinfo:myinfo.bzl", ImmutableMap.of()), "MyInfo"); |
| return (StructImpl) configuredAspect.get(key); |
| } |
| |
| public void setupMyInfo() throws Exception { |
| scratch.file("myinfo/myinfo.bzl", "MyInfo = provider()"); |
| scratch.file("myinfo/BUILD"); |
| } |
| |
| private void writeConfigTransitionTestFiles() throws Exception { |
| writeAllowlistFile(); |
| setupMyInfo(); |
| getAnalysisMock().ccSupport().setupCcToolchainConfigForCpu(mockToolsConfig, "armeabi-v7a"); |
| scratch.file( |
| "test/starlark/my_rule.bzl", |
| "load('//myinfo:myinfo.bzl', 'MyInfo')", |
| "def transition_func(settings, attr):", |
| " return [", |
| " {'//command_line_option:cpu': 'k8'},", |
| " {'//command_line_option:cpu': 'armeabi-v7a'}", |
| " ]", |
| "my_transition = transition(implementation = transition_func, inputs = [],", |
| " outputs = ['//command_line_option:cpu'])", |
| "def impl(ctx): ", |
| " print(ctx.label, ctx.configuration)", |
| " return MyInfo(", |
| " config = ctx.configuration,", |
| " attr_deps = ctx.split_attr.deps,", |
| " attr_dep = ctx.split_attr.dep)", |
| "my_rule = rule(", |
| " implementation = impl,", |
| " attrs = {", |
| " 'deps': attr.label_list(cfg = my_transition),", |
| " 'dep': attr.label(cfg = my_transition),", |
| " '_allowlist_function_transition': attr.label(", |
| " default = '//tools/allowlists/function_transition_allowlist',", |
| " ),", |
| " })"); |
| |
| scratch.file( |
| "test/starlark/BUILD", |
| "load('//test/starlark:my_rule.bzl', 'my_rule')", |
| "my_rule(name = 'test', deps = [':main1', ':main2'], dep = ':main1')", |
| "cc_binary(name = 'main1', srcs = ['main1.c'])", |
| "cc_binary(name = 'main2', srcs = ['main2.c'])"); |
| } |
| |
| @Test |
| public void licensesAreCollected() throws Exception { |
| scratch.file("a/BUILD", |
| "filegroup(name='a', licenses=['restricted'], output_licenses=['unencumbered'])", |
| "alias(name='b', actual=':a')", |
| "filegroup(name='c', srcs=[':b'])", |
| "genrule(name='d', outs=['do'], tools=[':b'], cmd='cmd')", |
| "genrule(name='e', outs=['eo'], srcs=[':b'], cmd='cmd')"); |
| useConfiguration("--check_licenses"); |
| assertThat(getLicenses("//a:d", "//a:a")).containsExactly(LicenseType.UNENCUMBERED); |
| assertThat(getLicenses("//a:e", "//a:a")).containsExactly(LicenseType.RESTRICTED); |
| assertThat(getLicenses("//a:b", "//a:a")).containsExactly(LicenseType.RESTRICTED); |
| assertThat( |
| getConfiguredTarget("//a:b") |
| .getProvider(LicensesProvider.class) |
| .getTransitiveLicenses() |
| .toList()) |
| .hasSize(1); |
| } |
| |
| @Test |
| public void assertNoLicensesAttribute() throws Exception { |
| scratch.file("a/BUILD", |
| "filegroup(name='a')", |
| "alias(name='b', actual=':a', licenses=['unencumbered'])"); |
| |
| reporter.removeHandler(failFastHandler); |
| getConfiguredTarget("//a:b"); |
| assertContainsEvent("no such attribute 'licenses' in 'alias' rule"); |
| } |
| |
| private Set<LicenseType> getLicenses(String topLevelTarget, String licenseTarget) |
| throws Exception { |
| LicensesProvider licenses = |
| getConfiguredTarget(topLevelTarget).getProvider(LicensesProvider.class); |
| for (TargetLicense license : licenses.getTransitiveLicenses().toList()) { |
| if (license.getLabel().toString().equals(licenseTarget)) { |
| return license.getLicense().getLicenseTypes(); |
| } |
| } |
| |
| throw new IllegalStateException("License for '" + licenseTarget |
| + "' not found in the transitive closure of '" + topLevelTarget + "'"); |
| } |
| |
| @Test |
| public void passesTargetTypeCheck() throws Exception { |
| scratch.file("a/BUILD", |
| "cc_library(name='a', srcs=['a.cc'], deps=[':b'])", |
| "alias(name='b', actual=':c')", |
| "cc_library(name='c', srcs=['c.cc'])"); |
| |
| getConfiguredTarget("//a:a"); |
| } |
| |
| @Test |
| public void packageGroupInAlias() throws Exception { |
| scratch.file("a/BUILD", |
| "package_group(name='a', packages=['//a'])", |
| "alias(name='b', actual=':a')", |
| "filegroup(name='c', srcs=[':b'])"); |
| |
| reporter.removeHandler(failFastHandler); |
| getConfiguredTarget("//a:c"); |
| assertContainsEvent( |
| "in actual attribute of alias rule //a:b: package group '//a:a' is misplaced here"); |
| } |
| |
| @Test |
| public void aliasedFile() throws Exception { |
| scratch.file("a/BUILD", |
| "exports_files(['a'])", |
| "alias(name='b', actual='a')", |
| "filegroup(name='c', srcs=[':b'])"); |
| |
| ConfiguredTarget c = getConfiguredTarget("//a:c"); |
| assertThat(ActionsTestUtil.baseArtifactNames( |
| c.getProvider(FileProvider.class).getFilesToBuild())) |
| .containsExactly("a"); |
| } |
| |
| @Test |
| public void aliasedConfigSetting() throws Exception { |
| scratch.file("a/BUILD", |
| "filegroup(name='a', srcs=select({':b': ['f1'], '//conditions:default': ['f2']}))", |
| "alias(name='b', actual=':c')", |
| "config_setting(name='c', values={'define': 'foo=bar'})"); |
| |
| useConfiguration("--define=foo=bar"); |
| getConfiguredTarget("//a"); |
| } |
| |
| @Test |
| public void aliasedTestSuiteDep() throws Exception { |
| scratch.file("a/BUILD", |
| "sh_test(name='a', srcs=['a.sh'])"); |
| scratch.file("b/BUILD", |
| "alias(name='b', actual='//a:a', testonly=1)", |
| "test_suite(name='c', tests=[':b'])"); |
| |
| ConfiguredTarget c = getConfiguredTarget("//b:c"); |
| NestedSet<Artifact> runfiles = |
| c.getProvider(RunfilesProvider.class).getDataRunfiles().getAllArtifacts(); |
| assertThat(ActionsTestUtil.baseArtifactNames(runfiles)).contains("a.sh"); |
| } |
| |
| @Test |
| public void testRedirectChasing() throws Exception { |
| String toolsRepository = ruleClassProvider.getToolsRepository(); |
| scratch.file("a/BUILD", |
| "alias(name='cc', actual='" + toolsRepository + "//tools/cpp:toolchain')", |
| "cc_library(name='a', srcs=['a.cc'])"); |
| |
| useConfiguration("--crosstool_top=//a:cc"); |
| getConfiguredTarget("//a:a"); |
| } |
| |
| @Test |
| public void testNoActual() throws Exception { |
| checkError("a", "a", "missing value for mandatory attribute 'actual'", "alias(name='a')"); |
| } |
| } |