blob: fe55def3c322414508e61220a46d760443101e58 [file] [log] [blame]
// 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 static com.google.devtools.build.lib.skyframe.BzlLoadValue.keyForBuild;
import com.google.common.collect.ImmutableList;
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.BuildConfigurationValue;
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.AspectKeyCreator.AspectKey;
import com.google.devtools.build.lib.testutil.TestConstants;
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\ntarget '//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\ntarget '//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",
String.format(
"""
alias(
name = "simple_alias",
actual = "//test/starlark:test",
)
alias(
name = "selecting_alias",
actual = select(
{"%s": ":simple_alias"}
),
)
""",
TestConstants.CONSTRAINTS_PACKAGE_ROOT + "cpu:x86_64"));
// Set --platforms so we can test alias :selecting_alias that selects on the CPU.
useConfiguration("--platforms=" + TestConstants.PLATFORM_LABEL);
// 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");
BuildConfigurationValue actualConfig =
(BuildConfigurationValue) 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(
keyForBuild(Label.parseCanonical("//myinfo:myinfo.bzl")), "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),
},
)
""");
scratch.file(
"test/starlark/BUILD",
"""
load("//test/starlark:my_rule.bzl", "my_rule")
my_rule(
name = "test",
dep = ":main1",
deps = [
":main1",
":main2",
],
)
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"],
cmd = "cmd",
tools = [":b"],
)
genrule(
name = "e",
srcs = [":b"],
outs = ["eo"],
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")
.get(LicensesProvider.PROVIDER)
.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).get(LicensesProvider.PROVIDER);
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",
"load('//test_defs:foo_test.bzl', 'foo_test')",
"foo_test(name='a', srcs=['a.sh'])");
scratch.file(
"b/BUILD",
"""
alias(
name = "b",
testonly = 1,
actual = "//a:a",
)
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 {
scratch.file(
"a/BUILD",
"alias(name='cc', actual='" + TestConstants.PLATFORM_LABEL + "')",
"cc_library(name='a', srcs=['a.cc'])");
useConfiguration("--platforms=" + "//a:cc");
getConfiguredTarget("//a:a");
}
@Test
public void testNoActual() throws Exception {
checkError("a", "a", "missing value for mandatory attribute 'actual'", "alias(name='a')");
}
}