blob: 59fc523fed50b0fdc80dad5db7823b63b2ae5ec6 [file] [log] [blame]
// Copyright 2020 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.buildtool;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.ViewCreationFailedException;
import com.google.devtools.build.lib.buildtool.util.BuildIntegrationTestCase;
import com.google.testing.junit.testparameterinjector.TestParameter;
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests use of the --target_environment flag. */
@RunWith(TestParameterInjector.class)
public class EnvironmentRestrictedBuildTest extends BuildIntegrationTestCase {
@TestParameter boolean mergedSkyframeAnalysisExecution;
@Before
public final void addNoBuildOption() throws Exception {
if (mergedSkyframeAnalysisExecution) {
// TODO(b/223761810): Add --nobuild after Skymeld supports it.
addOptions("--experimental_merged_skyframe_analysis_execution");
} else {
addOptions("--nobuild"); // Target enforcement happens before the execution phase.
}
}
private void writeEnvironmentRules(String... defaults) throws Exception {
StringBuilder defaultsBuilder = new StringBuilder();
for (String defaultEnv : defaults) {
defaultsBuilder.append("'" + defaultEnv + "', ");
}
write("buildenv/BUILD",
"environment_group(",
" name = 'group',",
" environments = [':one', ':two'],",
" defaults = [" + defaultsBuilder + "])",
"environment(name = 'one')",
"environment(name = 'two')");
}
@Test
public void testTargetEnvironmentError() throws Exception {
writeEnvironmentRules();
write("foo/BUILD",
"sh_library(name = 'bar', srcs = ['bar.sh'])");
addOptions("--target_environment=//buildenv:one");
assertThat(assertThrows(ViewCreationFailedException.class, () -> buildTarget("//foo:bar")))
.hasMessageThat()
.contains(
""
+ "//foo:bar declares compatibility with:\n"
+ " []\n"
+ "but does not support:\n"
+ " //buildenv:one");
}
@Test
public void testTargetEnvironmentSuccess() throws Exception {
writeEnvironmentRules();
write("foo/BUILD",
"sh_library(name = 'bar', srcs = ['bar.sh'], compatible_with = ['//buildenv:one'])");
write("foo/bar.sh");
addOptions("--target_environment=//buildenv:one");
buildTarget("//foo:bar");
assertThat(getResult().getSuccess()).isTrue();
}
@Test
public void testMultipleTargetEnvironments() throws Exception {
writeEnvironmentRules();
write("foo/BUILD",
"sh_library(name = 'bar', srcs = ['bar.sh'], compatible_with = ['//buildenv:one'])");
addOptions("--target_environment=//buildenv:one", "--target_environment=//buildenv:two");
assertThat(assertThrows(ViewCreationFailedException.class, () -> buildTarget("//foo:bar")))
.hasMessageThat()
.contains(
""
+ "//foo:bar declares compatibility with:\n"
+ " [//buildenv:one]\n"
+ "but does not support:\n"
+ " //buildenv:two");
}
@Test
public void testTargetEnvironmentIsDefault() throws Exception {
writeEnvironmentRules(":one");
write("foo/BUILD",
"sh_library(name = 'bar', srcs = ['bar.sh'])");
write("foo/bar.sh");
addOptions("--target_environment=//buildenv:one");
buildTarget("//foo:bar");
assertThat(getResult().getSuccess()).isTrue();
}
@Test
public void testEmptyTargetEnvironment() throws Exception {
writeEnvironmentRules();
write("foo/BUILD",
"sh_library(name = 'bar', srcs = ['bar.sh'])");
write("foo/bar.sh");
buildTarget("//foo:bar");
assertThat(getResult().getSuccess()).isTrue();
}
@Test
public void testOnlySomeTargetsQualify() throws Exception {
writeEnvironmentRules();
write("foo/BUILD",
"sh_library(name = 'good_bar', srcs = ['bar.sh'], compatible_with = ['//buildenv:one'])",
"sh_library(name = 'bad_bar', srcs = ['bar.sh'], compatible_with = ['//buildenv:two'])");
write("foo/bar.sh");
addOptions("--target_environment=//buildenv:one");
assertThat(assertThrows(ViewCreationFailedException.class, () -> buildTarget("//foo:all")))
.hasMessageThat()
.contains(
""
+ "//foo:bad_bar declares compatibility with:\n"
+ " [//buildenv:two]\n"
+ "but does not support:\n"
+ " //buildenv:one");
}
@Test
public void testNoConstraintEnforcement() throws Exception {
writeEnvironmentRules();
write("foo/BUILD",
"sh_library(name = 'bar', srcs = ['bar.sh'])");
write("foo/bar.sh");
addOptions("--target_environment=//buildenv:one", "--noenforce_constraints");
buildTarget("//foo:bar");
assertThat(getResult().getSuccess()).isTrue();
}
@Test
public void testFlagUsesNonexistentTarget() throws Exception {
writeEnvironmentRules();
write("foo/BUILD",
"sh_library(name = 'bar', srcs = ['bar.sh'])");
addOptions("--target_environment=//buildenv:nada");
assertThat(assertThrows(ViewCreationFailedException.class, () -> buildTarget("//foo:bar")))
.hasMessageThat()
.contains("invalid target environment: no such target '//buildenv:nada'");
}
@Test
public void testFlagUsesWrongTargetType() throws Exception {
write("foo/BUILD",
"sh_library(name = 'bar', srcs = ['bar.sh'])");
addOptions("--target_environment=//foo:bar");
assertThat(assertThrows(ViewCreationFailedException.class, () -> buildTarget("//foo:bar")))
.hasMessageThat()
.contains("//foo:bar is not a valid environment definition");
}
@Test
public void testRefinedEnvironmentCheckValidTarget() throws Exception {
writeEnvironmentRules();
write("foo/BUILD",
"config_setting(name = 'config_one', values = {'define': 'mode=one'})",
"config_setting(name = 'config_two', values = {'define': 'mode=two'})",
"sh_library(name = 'lib_one', srcs = [], compatible_with = ['//buildenv:one'])",
"sh_library(name = 'lib_two', srcs = [], compatible_with = ['//buildenv:two'])",
"sh_library(name = 'toplevel',",
" srcs = ['toplevel.sh'],",
" deps = select({",
" ':config_one': [':lib_one'],",
" ':config_two': [':lib_two'],",
" }),",
" compatible_with = ['//buildenv:one', '//buildenv:two'])");
// "--define mode=one" refines :toplevel to (matching) ["//buildenv:one"]:
addOptions("--target_environment=//buildenv:one", "--define", "mode=one");
write("foo/toplevel.sh");
buildTarget("//foo:toplevel");
assertThat(getResult().getSuccess()).isTrue();
}
@Test
public void testRefinedEnvironmentCheckBadTarget() throws Exception {
writeEnvironmentRules();
write("foo/BUILD",
"config_setting(name = 'config_one', values = {'define': 'mode=one'})",
"config_setting(name = 'config_two', values = {'define': 'mode=two'})",
"sh_library(name = 'lib_one', srcs = [], compatible_with = ['//buildenv:one'])",
"sh_library(name = 'lib_two', srcs = [], compatible_with = ['//buildenv:two'])",
"sh_library(name = 'toplevel',",
" srcs = ['toplevel.sh'],",
" deps = select({",
" ':config_one': [':lib_one'],",
" ':config_two': [':lib_two'],",
" }),",
" compatible_with = ['//buildenv:one', '//buildenv:two'])");
// "--define mode=two" refines :toplevel to (non-matching) ["//buildenv:two"]:
addOptions("--target_environment=//buildenv:one", "--define", "mode=two");
assertThat(assertThrows(ViewCreationFailedException.class, () -> buildTarget("//foo:toplevel")))
.hasMessageThat()
.contains(
""
+ "//foo:toplevel declares compatibility with:\n"
+ " [//buildenv:one, //buildenv:two]\n"
+ "but does not support:\n"
+ " environment: //buildenv:one\n"
+ " removed by: //foo:toplevel");
}
@Test
public void topLevelOutputFile() throws Exception {
writeEnvironmentRules();
write(
"foo/rule.bzl",
"def _impl(ctx):",
" file = ctx.actions.declare_file('libbar.a')",
" ctx.actions.write(file, 'hello')",
" return [DefaultInfo(files = depset([file]))]",
"crule = rule(",
" _impl,",
" outputs = {",
" 'archive': 'lib%{name}.a'",
" },",
");");
write(
"foo/BUILD",
"load(':rule.bzl', 'crule')",
"crule(name = 'bar', compatible_with = ['//buildenv:one'])");
addOptions("--target_environment=//buildenv:one");
buildTarget("//foo:libbar.a");
assertThat(getResult().getSuccess()).isTrue();
}
@Test
public void topLevelAliasToCompatibleOutputFile() throws Exception {
writeEnvironmentRules();
write(
"foo/BUILD",
"genrule(",
" name = 'goodgen',",
" srcs = [],",
" cmd = 'touch $@',",
" outs = ['goodgen.out'],",
" compatible_with = ['//buildenv:one'])",
"alias(",
" name = 'goodalias',",
" actual = 'goodgen.out')");
addOptions("--target_environment=//buildenv:one");
buildTarget("//foo:goodalias");
assertThat(getResult().getSuccess()).isTrue();
}
@Test
public void topLevelAliasToBadOutputFile() throws Exception {
writeEnvironmentRules();
write(
"foo/BUILD",
"genrule(",
" name = 'badgen',",
" srcs = [],",
" cmd = '',",
" outs = ['badgen.out'])",
"alias(",
" name = 'badalias',",
" actual = 'badgen.out')");
addOptions("--target_environment=//buildenv:one");
assertThat(assertThrows(ViewCreationFailedException.class, () -> buildTarget("//foo:badalias")))
.hasMessageThat()
.contains(
""
+ "//foo:badgen.out declares compatibility with:\n"
+ " []\n"
+ "but does not support:\n"
+ " //buildenv:one");
}
@Test
public void doesNotCheckDefaultEnvironments() throws Exception {
write(
"buildenv/a/BUILD",
"environment_group(",
" name = 'a',",
" defaults = [':a1'],",
" environments = [':a1', ':a2'])",
"environment(name = 'a1')",
"environment(name = 'a2')");
write(
"buildenv/b/BUILD",
"environment_group(",
" name = 'b',",
" defaults = [':b1'],",
" environments = [':b1', ':b2'])",
"environment(name = 'b1')",
"environment(name = 'b2')");
write(
"foo/bar.sh",
"echo Bar!");
write(
"foo/BUILD",
"sh_library(name = 'bar', srcs = ['bar.sh'], restricted_to = ['//buildenv/b:b2'])");
addOptions("--target_environment=//buildenv/a:a1");
buildTarget("//foo:bar");
assertThat(getResult().getSuccess()).isTrue();
}
@Test
public void autoTargetEnvironment_success() throws Exception {
write(
"buildenv/auto_cpu/BUILD",
"environment_group(",
" name = 'cpus',",
" defaults = [':k8'],",
" environments = [':k8', ':ppc'])",
"environment(name = 'k8')",
"environment(name = 'ppc')");
write(
"foo/bar.sh",
"echo Bar!");
write(
"foo/BUILD",
"sh_library(name = 'bar', srcs = ['bar.sh'], restricted_to = ['//buildenv/auto_cpu:k8'])");
addOptions("--build", "--auto_cpu_environment_group=//buildenv/auto_cpu:cpus", "--cpu=k8");
buildTarget("//foo:bar");
ConfiguredTarget successful = Iterables.getOnlyElement(getResult().getSuccessfulTargets());
assertThat(successful.getLabel().toString()).isEqualTo("//foo:bar");
}
@Test
public void autoTargetEnvironment_cpuNotSet() throws Exception {
write(
"buildenv/auto_cpu/BUILD",
"environment_group(",
" name = 'cpus',",
" defaults = [':k8'],",
" environments = [':k8', ':ppc'])",
"environment(name = 'k8')",
"environment(name = 'ppc')");
write(
"foo/bar.sh",
"echo Bar!");
write(
"foo/BUILD",
"sh_library(name = 'bar', srcs = ['bar.sh'], restricted_to = ['//buildenv/auto_cpu:k8'])");
addOptions("--build", "--auto_cpu_environment_group=//buildenv/auto_cpu:cpus");
buildTarget("//foo:bar");
ConfiguredTarget successful = Iterables.getOnlyElement(getResult().getSuccessfulTargets());
assertThat(successful.getLabel().toString()).isEqualTo("//foo:bar");
}
@Test
public void autoTargetEnvironment_disabled() throws Exception {
write(
"buildenv/auto_cpu/BUILD",
"environment_group(",
" name = 'cpus',",
" defaults = [':k8'],",
" environments = [':k8', ':ppc'])",
"environment(name = 'k8')",
"environment(name = 'ppc')");
write(
"foo/bar.sh",
"echo Bar!");
write(
"foo/BUILD",
"sh_library(name = 'bar', srcs = ['bar.sh'], restricted_to = ['//buildenv/auto_cpu:k8'])");
addOptions("--build", "--auto_cpu_environment_group=", "--cpu=k8");
buildTarget("//foo:bar");
ConfiguredTarget successful = Iterables.getOnlyElement(getResult().getSuccessfulTargets());
assertThat(successful.getLabel().toString()).isEqualTo("//foo:bar");
}
@Test
public void autoTargetEnvironment_error() throws Exception {
write(
"buildenv/auto_cpu/BUILD",
"environment_group(",
" name = 'cpus',",
" defaults = [':k8'],",
" environments = [':k8', ':ppc'])",
"environment(name = 'k8')",
"environment(name = 'ppc')");
write(
"foo/BUILD",
"sh_library(name = 'bar', srcs = ['bar.sh'], restricted_to = ['//buildenv/auto_cpu:k8'])");
addOptions("--build", "--auto_cpu_environment_group=//buildenv/auto_cpu:cpus", "--cpu=ppc");
buildTarget("//foo:bar");
assertThat(getResult().getSuccessfulTargets()).isEmpty();
}
@Test
public void autoTargetEnvironment_does_not_check_default_environments() throws Exception {
write(
"buildenv/auto_cpu/BUILD",
"environment_group(",
" name = 'cpus',",
" defaults = [':k8'],",
" environments = [':k8', ':ppc'])",
"environment(name = 'k8')",
"environment(name = 'ppc')");
write(
"buildenv/b/BUILD",
"environment_group(",
" name = 'b',",
" defaults = [':b1'],",
" environments = [':b1', ':b2'])",
"environment(name = 'b1')",
"environment(name = 'b2')");
write(
"foo/bar.sh",
"echo Bar!");
write(
"foo/BUILD",
"sh_library(name = 'bar', srcs = ['bar.sh'], restricted_to = ['//buildenv/b:b2'])");
addOptions("--build", "--auto_cpu_environment_group=//buildenv/auto_cpu:cpus", "--cpu=k8");
// Even though //foo:bar doesn't support the expected //buildenv/b:b1 (which is the default
// for //buildenv/b), --auto_cpu_environment_group is only concerned about //buildenv/auto_cpu.
buildTarget("//foo:bar");
ConfiguredTarget successful = Iterables.getOnlyElement(getResult().getSuccessfulTargets());
assertThat(successful.getLabel().toString()).isEqualTo("//foo:bar");
}
@Test
public void autoTargetEnvironment_good_explicit_checking_bad() throws Exception {
write(
"buildenv/auto_cpu/BUILD",
"environment_group(",
" name = 'cpus',",
" defaults = [':k8'],",
" environments = [':k8', ':ppc'])",
"environment(name = 'k8')",
"environment(name = 'ppc')");
write(
"buildenv/b/BUILD",
"environment_group(",
" name = 'b',",
" defaults = [':b1'],",
" environments = [':b1', ':b2'])",
"environment(name = 'b1')",
"environment(name = 'b2')");
write(
"foo/bar.sh",
"echo Bar!");
write(
"foo/BUILD",
"sh_library(name = 'bar', srcs = ['bar.sh'], restricted_to = ['//buildenv/b:b2'])");
addOptions("--build", "--auto_cpu_environment_group=//buildenv/auto_cpu:cpus", "--cpu=k8",
"--target_environment=//buildenv/b:b1");
assertThat(assertThrows(ViewCreationFailedException.class, () -> buildTarget("//foo:bar")))
.hasMessageThat()
.contains(
""
+ "//foo:bar declares compatibility with:\n"
+ " [//buildenv/b:b2]\n"
+ "but does not support:\n"
+ " //buildenv/b:b1");
}
@Test
public void autoTargetEnvironment_bad_explicit_checking_good() throws Exception {
write(
"buildenv/auto_cpu/BUILD",
"environment_group(",
" name = 'cpus',",
" defaults = [':k8'],",
" environments = [':k8', ':ppc'])",
"environment(name = 'k8')",
"environment(name = 'ppc')");
write(
"buildenv/b/BUILD",
"environment_group(",
" name = 'b',",
" defaults = [':b1'],",
" environments = [':b1', ':b2'])",
"environment(name = 'b1')",
"environment(name = 'b2')");
write(
"foo/bar.sh",
"echo Bar!");
write(
"foo/BUILD",
"sh_library(name = 'bar', srcs = ['bar.sh'], restricted_to = ['//buildenv/b:b2'])");
addOptions("--build", "--auto_cpu_environment_group=//buildenv/auto_cpu:cpus", "--cpu=ppc",
"--target_environment=//buildenv/b:b2");
buildTarget("//foo:bar");
assertThat(getResult().getSuccessfulTargets()).isEmpty();
assertThat(Iterables.getOnlyElement(getResult().getSkippedTargets()).getLabel().toString())
.isEqualTo("//foo:bar");
}
}