blob: 83ee6a733975c10a1716c09798f6a69a88e61b41 [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.analysis;
import static com.google.common.truth.Truth.assertThat;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.Fragment;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.analysis.config.RequiresOptions;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.testutil.Scratch;
import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionDocumentationCategory;
import com.google.devtools.common.options.OptionEffectTag;
import com.google.devtools.common.options.OptionMetadataTag;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Test of common logic between Starlark-defined transitions. Rule-transition- or
* attr-transition-specific logic should be tested in {@link StarlarkRuleTransitionProviderTest} and
* {@link StarlarkAttrTransitionProviderTest}.
*/
@RunWith(JUnit4.class)
public class StarlarkTransitionTest extends BuildViewTestCase {
/** Extra options for this test. */
public static class DummyTestOptions extends FragmentOptions {
public DummyTestOptions() {}
@Option(
name = "immutable_option",
documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
effectTags = {OptionEffectTag.NO_OP},
defaultValue = "super secret",
metadataTags = {OptionMetadataTag.IMMUTABLE})
public String immutableOption;
}
/** Test fragment. */
@RequiresOptions(options = {DummyTestOptions.class})
public static final class DummyTestOptionsFragment extends Fragment {
private final BuildOptions buildOptions;
public DummyTestOptionsFragment(BuildOptions buildOptions) {
this.buildOptions = buildOptions;
}
// Getter required to satisfy AutoCodec.
public BuildOptions getBuildOptions() {
return buildOptions;
}
}
@Override
protected ConfiguredRuleClassProvider createRuleClassProvider() {
ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder();
TestRuleClassProvider.addStandardRules(builder);
builder.addConfigurationFragment(DummyTestOptionsFragment.class);
return builder.build();
}
static void writeAllowlistFile(Scratch scratch) throws Exception {
scratch.overwriteFile(
"tools/allowlists/function_transition_allowlist/BUILD",
"""
package_group(
name = "function_transition_allowlist",
packages = [
"//test/...",
],
)
""");
}
@Test
public void testDupeSettingsInInputsThrowsError() throws Exception {
scratch.file(
"test/defs.bzl",
"""
def _setting_impl(ctx):
return []
string_flag = rule(
implementation = _setting_impl,
build_setting = config.string(flag = True),
)
def _transition_impl(settings, attr):
return {"//test:formation": "mesa"}
formation_transition = transition(
implementation = _transition_impl,
inputs = ["@//test:formation", "//test:formation"], # duplicates here
outputs = ["//test:formation"],
)
def _impl(ctx):
return []
state = rule(
implementation = _impl,
cfg = formation_transition,
)
""");
scratch.file(
"test/BUILD",
"""
load("//test:defs.bzl", "state", "string_flag")
state(name = "arizona")
string_flag(
name = "formation",
build_setting_default = "canyon",
)
""");
reporter.removeHandler(failFastHandler);
getConfiguredTarget("//test:arizona");
assertContainsEvent(
"Transition declares duplicate build setting '@@//test:formation' in INPUTS");
}
@Test
public void testDupeSettingsInOutputsThrowsError() throws Exception {
scratch.file(
"test/defs.bzl",
"""
def _setting_impl(ctx):
return []
string_flag = rule(
implementation = _setting_impl,
build_setting = config.string(flag = True),
)
def _transition_impl(settings, attr):
return {"//test:formation": "mesa"}
formation_transition = transition(
implementation = _transition_impl,
inputs = ["//test:formation"],
outputs = ["@//test:formation", "//test:formation"], # duplicates here
)
def _impl(ctx):
return []
state = rule(
implementation = _impl,
cfg = formation_transition,
)
""");
scratch.file(
"test/BUILD",
"""
load("//test:defs.bzl", "state", "string_flag")
state(name = "arizona")
string_flag(
name = "formation",
build_setting_default = "canyon",
)
""");
reporter.removeHandler(failFastHandler);
getConfiguredTarget("//test:arizona");
assertContainsEvent(
"Transition declares duplicate build setting '@@//test:formation' in OUTPUTS");
}
@Test
public void testDifferentFormsOfFlagInInputsAndOutputs() throws Exception {
writeAllowlistFile(scratch);
scratch.file(
"test/defs.bzl",
"""
def _setting_impl(ctx):
return []
string_flag = rule(
implementation = _setting_impl,
build_setting = config.string(flag = True),
)
def _transition_impl(settings, attr):
formation = settings["@//test:formation"]
if formation.endswith("-transitioned"):
new_value = formation
else:
new_value = formation + "-transitioned"
return {
"//test:formation": new_value,
}
formation_transition = transition(
implementation = _transition_impl,
inputs = ["@//test:formation"],
outputs = ["//test:formation"],
)
def _impl(ctx):
return []
state = rule(
implementation = _impl,
cfg = formation_transition,
)
""");
scratch.file(
"test/BUILD",
"""
load("//test:defs.bzl", "state", "string_flag")
state(name = "arizona")
string_flag(
name = "formation",
build_setting_default = "canyon",
)
""");
Map<Label, Object> starlarkOptions =
getConfiguration(getConfiguredTarget("//test:arizona")).getOptions().getStarlarkOptions();
assertThat(starlarkOptions).hasSize(1);
assertThat(starlarkOptions.get(Label.parseCanonicalUnchecked("//test:formation")))
.isEqualTo("canyon-transitioned");
}
private void writeDefBzlWithStringFlagAndEaterRule() throws Exception {
scratch.file(
"test/defs.bzl",
"""
def _setting_impl(ctx):
return []
string_flag = rule(
implementation = _setting_impl,
build_setting = config.string(flag = True),
)
def _transition_impl(settings, attr):
if settings["@//options:fruit"].endswith("-eaten"):
return {"//options:fruit": settings["@//options:fruit"]}
return {"//options:fruit": settings["@//options:fruit"] + "-eaten"}
eating_transition = transition(
implementation = _transition_impl,
inputs = ["@//options:fruit"],
outputs = ["//options:fruit"],
)
def _impl(ctx):
return []
eater = rule(
implementation = _impl,
cfg = eating_transition,
)
""");
}
@Test
public void testDifferentDefaultsRerunsTransitionTest() throws Exception {
writeAllowlistFile(scratch);
writeDefBzlWithStringFlagAndEaterRule();
scratch.file(
"options/BUILD",
"""
load("//test:defs.bzl", "string_flag")
string_flag(
name = "fruit",
build_setting_default = "apple",
)
""");
scratch.file(
"test/BUILD",
"""
load("//test:defs.bzl", "eater")
eater(name = "foo")
""");
assertThat(
getConfiguration(getConfiguredTarget("//test:foo"))
.getOptions()
.getStarlarkOptions()
.get(Label.parseCanonicalUnchecked("//options:fruit")))
.isEqualTo("apple-eaten");
scratch.overwriteFile(
"options/BUILD",
"""
load("//test:defs.bzl", "string_flag")
string_flag(
name = "fruit",
build_setting_default = "orange",
)
""");
invalidatePackages();
assertThat(
getConfiguration(getConfiguredTarget("//test:foo"))
.getOptions()
.getStarlarkOptions()
.get(Label.parseCanonicalUnchecked("//options:fruit")))
.isEqualTo("orange-eaten");
}
@Test
public void testAliasChangeRerunsTransitionTest() throws Exception {
writeAllowlistFile(scratch);
writeDefBzlWithStringFlagAndEaterRule();
scratch.file(
"options/BUILD",
"""
load("//test:defs.bzl", "string_flag")
string_flag(
name = "usually_apple",
build_setting_default = "apple",
)
string_flag(
name = "usually_orange",
build_setting_default = "orange",
)
alias(
name = "fruit",
actual = ":usually_apple",
)
""");
scratch.file(
"test/BUILD",
"""
load("//test:defs.bzl", "eater")
eater(name = "foo")
""");
assertThat(
getConfiguration(getConfiguredTarget("//test:foo")).getOptions().getStarlarkOptions())
.containsExactly(Label.parseCanonicalUnchecked("//options:usually_apple"), "apple-eaten");
scratch.overwriteFile(
"options/BUILD",
"""
load("//test:defs.bzl", "string_flag")
string_flag(
name = "usually_apple",
build_setting_default = "apple",
)
string_flag(
name = "usually_orange",
build_setting_default = "orange",
)
alias(
name = "fruit",
actual = ":usually_orange",
)
""");
invalidatePackages();
assertThat(
getConfiguration(getConfiguredTarget("//test:foo")).getOptions().getStarlarkOptions())
.containsExactly(Label.parseCanonicalUnchecked("//options:usually_orange"), "orange-eaten");
}
@Test
public void testChangingImmutableOptionFails() throws Exception {
scratch.file(
"test/defs.bzl",
"""
def _transition_impl(settings, attr):
return {"//command_line_option:immutable_option": "something_else"}
_transition = transition(
implementation = _transition_impl,
inputs = [],
outputs = ["//command_line_option:immutable_option"],
)
def _impl(ctx):
return []
state = rule(
implementation = _impl,
cfg = _transition,
)
""");
scratch.file(
"test/BUILD",
"""
load("//test:defs.bzl", "state")
state(name = "arizona")
""");
reporter.removeHandler(failFastHandler);
getConfiguredTarget("//test:arizona");
assertContainsEvent(
"transition outputs [//command_line_option:immutable_option] cannot be changed: they are"
+ " immutable");
}
}