Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preserve analysis cache through --test_env changes #24316

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/main/java/com/google/devtools/build/lib/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/analysis:template_expansion_exception",
"//src/main/java/com/google/devtools/build/lib/analysis:test/coverage_artifacts_known_event",
"//src/main/java/com/google/devtools/build/lib/analysis:test/coverage_report_action_factory",
"//src/main/java/com/google/devtools/build/lib/analysis:test/test_configuration",
"//src/main/java/com/google/devtools/build/lib/analysis:top_level_artifact_context",
"//src/main/java/com/google/devtools/build/lib/analysis:transitive_info_collection",
"//src/main/java/com/google/devtools/build/lib/analysis:view_creation_failed_exception",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,7 @@ java_library(
":config/run_under",
":config/starlark_defined_config_transition",
":platform_options",
":test/test_configuration",
"//src/main/java/com/google/devtools/build/lib/actions:action_environment",
"//src/main/java/com/google/devtools/build/lib/actions:artifacts",
"//src/main/java/com/google/devtools/build/lib/actions:build_configuration_event",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.google.devtools.build.lib.actions.CommandLineLimits;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
import com.google.devtools.build.lib.analysis.PlatformOptions;
import com.google.devtools.build.lib.analysis.test.TestConfiguration.TestOptions;
import com.google.devtools.build.lib.buildeventstream.BuildEvent;
import com.google.devtools.build.lib.buildeventstream.BuildEventIdUtil;
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos;
Expand Down Expand Up @@ -165,9 +166,13 @@ public void reportInvalidOptions(EventHandler reporter) {
* be inherited from the client environment.
*/
private ActionEnvironment setupTestEnvironment() {
// We make a copy first to remove duplicate entries; last one wins.
if (!buildOptions.contains(TestOptions.class)) {
// TestOptions have been trimmed.
return ActionEnvironment.EMPTY;
}
// Order doesn't matter here as ActionEnvironment sorts by key.
Map<String, String> testEnv = new HashMap<>();
for (Map.Entry<String, String> entry : options.testEnvironment) {
for (Map.Entry<String, String> entry : buildOptions.get(TestOptions.class).testEnvironment) {
testEnv.put(entry.getKey(), entry.getValue());
}
return ActionEnvironment.split(testEnv);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,9 @@
import com.google.devtools.common.options.OptionMetadataTag;
import com.google.devtools.common.options.OptionsParsingException;
import com.google.devtools.common.options.TriState;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
Expand Down Expand Up @@ -420,24 +418,6 @@ public ExecConfigurationDistinguisherSchemeConverter() {
help = "Specifies a suffix to be added to the configuration directory.")
public String platformSuffix;

// TODO(bazel-team): The test environment is actually computed in BlazeRuntime and this option
// is not read anywhere else. Thus, it should be in a different options class, preferably one
// specific to the "test" command or maybe in its own configuration fragment.
@Option(
name = "test_env",
converter = Converters.OptionalAssignmentConverter.class,
allowMultiple = true,
defaultValue = "null",
documentationCategory = OptionDocumentationCategory.TESTING,
effectTags = {OptionEffectTag.TEST_RUNNER},
help =
"Specifies additional environment variables to be injected into the test runner "
+ "environment. Variables can be either specified by name, in which case its value "
+ "will be read from the Bazel client environment, or by the name=value pair. "
+ "This option can be used multiple times to specify several variables. "
+ "Used only by the 'bazel test' command.")
public List<Map.Entry<String, String>> testEnvironment;

// TODO(bazel-team): The set of available variables from the client environment for actions
// is computed independently in CommandEnvironment to inject a more restricted client
// environment to skyframe.
Expand Down Expand Up @@ -985,20 +965,6 @@ public ImmutableMap<String, String> getNormalizedCommandLineBuildVariables() {
.collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
}

// Normalizes list of map entries by keeping only the last entry for each key.
private static List<Map.Entry<String, String>> normalizeEntries(
List<Map.Entry<String, String>> entries) {
LinkedHashMap<String, String> normalizedEntries = new LinkedHashMap<>();
for (Map.Entry<String, String> entry : entries) {
normalizedEntries.put(entry.getKey(), entry.getValue());
}
// If we made no changes, return the same instance we got to reduce churn.
if (normalizedEntries.size() == entries.size()) {
return entries;
}
return normalizedEntries.entrySet().stream().map(SimpleEntry::new).collect(toImmutableList());
}

// Sort the map entries by key.
private static List<Map.Entry<String, String>> sortEntries(
List<Map.Entry<String, String>> entries) {
Expand Down Expand Up @@ -1056,7 +1022,6 @@ public CoreOptions getNormalized() {

result.actionEnvironment = normalizeEntries(actionEnvironment);
result.hostActionEnvironment = normalizeEntries(hostActionEnvironment);
result.testEnvironment = normalizeEntries(testEnvironment);
result.commandLineFlagAliases = sortEntries(normalizeEntries(commandLineFlagAliases));

return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.Options;
import com.google.devtools.common.options.OptionsBase;
import java.util.AbstractMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;

/** Command-line build options for a Blaze module. */
Expand Down Expand Up @@ -89,6 +92,25 @@ protected static ImmutableList<String> dedupAndSort(@Nullable List<String> value
return result.equals(values) ? ImmutableList.copyOf(values) : result;
}

/**
* Helper method for subclasses to normalize list of map entries by keeping only the last entry
* for each key. The order of the entries is preserved.
*/
protected static List<Map.Entry<String, String>> normalizeEntries(
List<Map.Entry<String, String>> entries) {
LinkedHashMap<String, String> normalizedEntries = new LinkedHashMap<>();
for (Map.Entry<String, String> entry : entries) {
normalizedEntries.put(entry.getKey(), entry.getValue());
}
// If we made no changes, return the same instance we got to reduce churn.
if (normalizedEntries.size() == entries.size()) {
return entries;
}
return normalizedEntries.entrySet().stream()
.map(AbstractMap.SimpleEntry::new)
.collect(toImmutableList());
}

/** Tracks limitations on referring to an option in a {@code config_setting}. */
// TODO(bazel-team): There will likely also be a need to customize whether or not an option is
// visible to users for setting on the command line (or perhaps even in a test of a Starlark
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.google.devtools.build.lib.packages.TestTimeout;
import com.google.devtools.build.lib.util.Pair;
import com.google.devtools.build.lib.util.RegexFilter;
import com.google.devtools.common.options.Converters;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionDefinition;
import com.google.devtools.common.options.OptionDocumentationCategory;
Expand Down Expand Up @@ -73,6 +74,21 @@ public static class TestOptions extends FragmentOptions {
OptionsParser.getOptionDefinitionByName(
TestOptions.class, "experimental_retain_test_configuration_across_testonly"));

@Option(
name = "test_env",
converter = Converters.OptionalAssignmentConverter.class,
allowMultiple = true,
defaultValue = "null",
documentationCategory = OptionDocumentationCategory.TESTING,
effectTags = {OptionEffectTag.TEST_RUNNER},
help =
"Specifies additional environment variables to be injected into the test runner "
+ "environment. Variables can be either specified by name, in which case its value "
+ "will be read from the Bazel client environment, or by the name=value pair. "
+ "This option can be used multiple times to specify several variables. "
+ "Used only by the 'bazel test' command.")
public List<Map.Entry<String, String>> testEnvironment;

@Option(
name = "test_timeout",
defaultValue = "-1",
Expand Down Expand Up @@ -335,6 +351,13 @@ public static class TestOptions extends FragmentOptions {
effectTags = {OptionEffectTag.EXECUTION},
help = "If true, Bazel will allow local tests to run.")
public boolean allowLocalTests;

@Override
public TestOptions getNormalized() {
TestOptions result = (TestOptions) clone();
result.testEnvironment = normalizeEntries(testEnvironment);
return result;
}
}

private final TestOptions options;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.google.devtools.build.lib.analysis.BuildInfoEvent;
import com.google.devtools.build.lib.analysis.config.AdditionalConfigurationChangeEvent;
import com.google.devtools.build.lib.analysis.config.CoreOptions;
import com.google.devtools.build.lib.analysis.test.TestConfiguration.TestOptions;
import com.google.devtools.build.lib.bazel.repository.downloader.DelegatingDownloader;
import com.google.devtools.build.lib.bazel.repository.downloader.HttpDownloader;
import com.google.devtools.build.lib.buildtool.BuildRequestOptions;
Expand Down Expand Up @@ -329,7 +330,7 @@ public void exit(AbruptExitException exception) {
}
}
for (Map.Entry<String, String> entry :
options.getOptions(CoreOptions.class).testEnvironment) {
options.getOptions(TestOptions.class).testEnvironment) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@katre I'm not 100% sure whether this will work as is - would build and info need to declare that they use TestOptions?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just add a check that TestOptions is present: if it's absent the entire block can be skipped.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm worried that it just won't ever be there for info. The tests didn't trigger any crashes though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you wrap the entire for loop in if (options.contains(TestOptions.class)) then it'll be safe for any command.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be safe in the sense of "doesn't crash", but will this still be a backwards compatible change in the sense that bazel info client-env shows test env entries?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was able to run a quick demo:

$ FOO=bar ~/repos/bazel/bazel-bin/src/bazel --host_jvm_debug info --test_env=FOO client-env
Running host JVM under debugger (listening on TCP port 5005).
build --test_env=FOO=bar

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. In that case I would leave it as is so that we get a crash instead of silent loss of functionality if this ever breaks. What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SGTM.

if (entry.getValue() == null) {
visibleTestEnv.add(entry.getKey());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,28 @@ public void testStarlarkWithTestEnvOptions() throws Exception {
MyInfo = provider()

def _test_rule_impl(ctx):
return MyInfo(test_env = ctx.configuration.test_env)

test_rule = rule(
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(out, "exit 0", is_executable = True)
return [
DefaultInfo(executable = out),
MyInfo(test_env = ctx.configuration.test_env),
]

my_test = rule(
implementation = _test_rule_impl,
attrs = {},
test = True,
)
""");

scratch.file(
"examples/config_starlark/BUILD",
"""
load("//examples/rule:config_test.bzl", "test_rule")
load("//examples/rule:config_test.bzl", "my_test")

package(default_visibility = ["//visibility:public"])

test_rule(
my_test(
name = "my_target",
)
""");
Expand Down
1 change: 1 addition & 0 deletions src/test/java/com/google/devtools/build/lib/remote/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ java_library(
"//src/main/java/com/google/devtools/build/lib/analysis:constraints/constraint_constants",
"//src/main/java/com/google/devtools/build/lib/analysis:server_directories",
"//src/main/java/com/google/devtools/build/lib/analysis:symlink_entry",
"//src/main/java/com/google/devtools/build/lib/analysis:test/test_configuration",
"//src/main/java/com/google/devtools/build/lib/analysis/platform",
"//src/main/java/com/google/devtools/build/lib/authandtls",
"//src/main/java/com/google/devtools/build/lib/authandtls/credentialhelper",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import com.google.devtools.build.lib.analysis.BlazeDirectories;
import com.google.devtools.build.lib.analysis.ServerDirectories;
import com.google.devtools.build.lib.analysis.config.CoreOptions;
import com.google.devtools.build.lib.analysis.test.TestConfiguration.TestOptions;
import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions;
import com.google.devtools.build.lib.authandtls.credentialhelper.CredentialHelperEnvironment;
import com.google.devtools.build.lib.authandtls.credentialhelper.CredentialModule;
Expand Down Expand Up @@ -139,6 +140,7 @@ private static CommandEnvironment createTestCommandEnvironment(
PackageOptions packageOptions = Options.getDefaults(PackageOptions.class);
ClientOptions clientOptions = Options.getDefaults(ClientOptions.class);
ExecutionOptions executionOptions = Options.getDefaults(ExecutionOptions.class);
TestOptions testOptions = Options.getDefaults(TestOptions.class);

AuthAndTLSOptions authAndTLSOptions = Options.getDefaults(AuthAndTLSOptions.class);

Expand All @@ -150,6 +152,7 @@ private static CommandEnvironment createTestCommandEnvironment(
when(options.getOptions(RemoteOptions.class)).thenReturn(remoteOptions);
when(options.getOptions(AuthAndTLSOptions.class)).thenReturn(authAndTLSOptions);
when(options.getOptions(ExecutionOptions.class)).thenReturn(executionOptions);
when(options.getOptions(TestOptions.class)).thenReturn(testOptions);

String productName = "bazel";
Scratch scratch = new Scratch(new InMemoryFileSystem(DigestHashFunction.SHA256));
Expand Down
27 changes: 27 additions & 0 deletions src/test/shell/bazel/bazel_test_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1116,4 +1116,31 @@ EOF
expect_log "-- Test exited prematurely (TEST_PREMATURE_EXIT_FILE exists) --"
}

function test_test_env_change_preserves_cache() {
local -r pkg="pkg${LINENO}"
mkdir -p "${pkg}"
cat > "$pkg/BUILD" <<'EOF'
load(":defs.bzl", "my_rule")
my_rule(
name = "my_rule",
)
EOF
cat > "$pkg/defs.bzl" <<'EOF'
def _my_rule_impl(ctx):
print("my_rule is being analyzed")
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(out, "hi")
return [DefaultInfo(files = depset([out]))]
my_rule = rule(_my_rule_impl)
EOF

bazel build "${pkg}:my_rule" >$TEST_log 2>&1 \
|| fail "expected build to pass"
expect_log "my_rule is being analyzed"

bazel build --test_env=FOO=bar "${pkg}:my_rule" >$TEST_log 2>&1 \
|| fail "expected build to pass"
expect_not_log "my_rule is being analyzed"
}

run_suite "bazel test tests"
Loading