blob: 5ac02cd42c0ddfc854f38a58fa0af563b11ddbcb [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.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"],
compatible_with = [
"//buildenv:one",
"//buildenv:two",
],
deps = select({
":config_one": [":lib_one"],
":config_two": [":lib_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"],
compatible_with = [
"//buildenv:one",
"//buildenv:two",
],
deps = select({
":config_one": [":lib_one"],
":config_two": [":lib_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 = [],
outs = ["goodgen.out"],
cmd = "touch $@",
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 = [],
outs = ["badgen.out"],
cmd = "",
)
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();
}
}