Skip to content

Commit 309f4e1

Browse files
twiggcopybara-github
authored andcommitted
Add --experimental_retain_test_configuration_across_testonly
Action conflicts continue to be an issue when non-test (e.g. genquery, filegroup, etc.) depends on cc_test. This flag averts the issue by skipping trimming when testonly is true (and testonly must already be true for a non-test target to depend on a test). This does mean you only get the performance gain for non-testonly targets; however, this is still sufficiently substantial. The expected progression of this flag is False -(soon-after-release)-> True -(eventually-after-depot-migration)-> False -> gone/deprecated. Alternative name was experimental_trim_test_configuration_ignores_testonly with reversed behavior (flag True is pre-CL behavior) which would have incurred a flag progression of False (default, causing immediate behavior change on release) -(eventually-after-depot-migration)-> True -> gone/deprecated. PiperOrigin-RevId: 372479312
1 parent afba8ac commit 309f4e1

File tree

3 files changed

+97
-8
lines changed

3 files changed

+97
-8
lines changed

src/main/java/com/google/devtools/build/lib/analysis/test/TestConfiguration.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.google.devtools.build.lib.analysis.test;
1616

1717
import com.google.common.collect.ImmutableMap;
18+
import com.google.common.collect.ImmutableSet;
1819
import com.google.common.collect.Iterables;
1920
import com.google.common.collect.Lists;
2021
import com.google.devtools.build.lib.analysis.OptionsDiffPredicate;
@@ -46,8 +47,8 @@
4647
public class TestConfiguration extends Fragment {
4748
public static final OptionsDiffPredicate SHOULD_INVALIDATE_FOR_OPTION_DIFF =
4849
(options, changedOption, oldValue, newValue) -> {
49-
if (TestOptions.TRIM_TEST_CONFIGURATION.equals(changedOption)) {
50-
// changes in --trim_test_configuration itself always prompt invalidation
50+
if (TestOptions.ALWAYS_INVALIDATE_WHEN_CHANGED.contains(changedOption)) {
51+
// changes in --trim_test_configuration itself or related flags always prompt invalidation
5152
return true;
5253
}
5354
if (!changedOption.getField().getDeclaringClass().equals(TestOptions.class)) {
@@ -60,8 +61,11 @@ public class TestConfiguration extends Fragment {
6061

6162
/** Command-line options. */
6263
public static class TestOptions extends FragmentOptions {
63-
private static final OptionDefinition TRIM_TEST_CONFIGURATION =
64-
OptionsParser.getOptionDefinitionByName(TestOptions.class, "trim_test_configuration");
64+
private static final ImmutableSet<OptionDefinition> ALWAYS_INVALIDATE_WHEN_CHANGED =
65+
ImmutableSet.of(
66+
OptionsParser.getOptionDefinitionByName(TestOptions.class, "trim_test_configuration"),
67+
OptionsParser.getOptionDefinitionByName(
68+
TestOptions.class, "experimental_retain_test_configuration_across_testonly"));
6569

6670
@Option(
6771
name = "test_timeout",
@@ -140,6 +144,21 @@ public static class TestOptions extends FragmentOptions {
140144
+ " re-analyzed.")
141145
public boolean trimTestConfiguration;
142146

147+
@Option(
148+
name = "experimental_retain_test_configuration_across_testonly",
149+
defaultValue = "false",
150+
documentationCategory = OptionDocumentationCategory.BUILD_TIME_OPTIMIZATION,
151+
effectTags = {
152+
OptionEffectTag.LOADING_AND_ANALYSIS,
153+
OptionEffectTag.LOSES_INCREMENTAL_STATE,
154+
},
155+
help =
156+
"When enabled, --trim_test_configuration will not trim the test configuration for rules"
157+
+ " marked testonly=1. This is meant to reduce action conflict issues when non-test"
158+
+ " rules depend on cc_test rules. No effect if --trim_test_configuration is"
159+
+ " false.")
160+
public boolean experimentalRetainTestConfigurationAcrossTestonly;
161+
143162
@Option(
144163
name = "test_arg",
145164
allowMultiple = true,
@@ -291,6 +310,8 @@ public FragmentOptions getHost() {
291310
hostOptions.coverageReportGenerator = this.coverageReportGenerator;
292311
// trimTestConfiguration is a global analysis option and should be platform-agnostic
293312
hostOptions.trimTestConfiguration = this.trimTestConfiguration;
313+
hostOptions.experimentalRetainTestConfigurationAcrossTestonly =
314+
this.experimentalRetainTestConfigurationAcrossTestonly;
294315
return hostOptions;
295316
}
296317
}

src/main/java/com/google/devtools/build/lib/analysis/test/TestTrimmingTransitionFactory.java

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// limitations under the License.
1414
package com.google.devtools.build.lib.analysis.test;
1515

16+
import com.google.common.annotations.VisibleForTesting;
1617
import com.google.common.collect.ImmutableSet;
1718
import com.google.devtools.build.lib.analysis.config.BuildOptions;
1819
import com.google.devtools.build.lib.analysis.config.BuildOptionsCache;
@@ -24,9 +25,11 @@
2425
import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory;
2526
import com.google.devtools.build.lib.analysis.test.TestConfiguration.TestOptions;
2627
import com.google.devtools.build.lib.events.EventHandler;
28+
import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper;
2729
import com.google.devtools.build.lib.packages.Rule;
2830
import com.google.devtools.build.lib.packages.RuleClass;
2931
import com.google.devtools.build.lib.packages.TargetUtils;
32+
import com.google.devtools.build.lib.packages.Type;
3033
import com.google.devtools.common.options.Options;
3134

3235
/**
@@ -39,15 +42,29 @@ public final class TestTrimmingTransitionFactory implements TransitionFactory<Ru
3942

4043
/**
4144
* Trimming transition which removes the test config fragment if --trim_test_configuration is on.
45+
*
46+
* <p>At the moment, need to know the value of the testonly attribute from the underlying rule. So
47+
* the factory, which has access to attributes but not the configuration, attaches the appropriate
48+
* TestTrimmingTransition, which will have access to the configuration.
4249
*/
43-
public enum TestTrimmingTransition implements PatchTransition {
44-
INSTANCE;
50+
public static class TestTrimmingTransition implements PatchTransition {
51+
// These are essentially a cache of the two versions of the transition depending on if
52+
// the associated rule is testonly = true or not.
53+
private static final TestTrimmingTransition TESTONLY_TRUE = new TestTrimmingTransition(true);
54+
private static final TestTrimmingTransition TESTONLY_FALSE = new TestTrimmingTransition(false);
55+
@VisibleForTesting public static final TestTrimmingTransition INSTANCE = TESTONLY_FALSE;
56+
57+
private final boolean testonly;
58+
59+
private TestTrimmingTransition(boolean testonly) {
60+
this.testonly = testonly;
61+
}
4562

4663
// This cache is to prevent major slowdowns when using --trim_test_configuration. This
4764
// transition is always invoked on every target in the top-level invocation. Thus, a wide
4865
// invocation, like //..., will cause the transition to be invoked on a large number of targets
4966
// leading to significant performance degradation. (Notably, the transition itself is somewhat
50-
// fast; however, the post-processing of the BuildOptions results into a BuildConfiguration
67+
// fast; however, the post-processing of the BuildOptions into the actual BuildConfiguration
5168
// takes a significant amount of time).
5269
private static final BuildOptionsCache<Integer> cache = new BuildOptionsCache<>();
5370

@@ -65,6 +82,7 @@ public BuildOptions patch(BuildOptionsView originalOptions, EventHandler eventHa
6582
CoreOptions originalCoreOptions = originalOptions.get(CoreOptions.class);
6683
TestOptions originalTestOptions = originalOptions.get(TestOptions.class);
6784
if (!originalTestOptions.trimTestConfiguration
85+
|| (originalTestOptions.experimentalRetainTestConfigurationAcrossTestonly && testonly)
6886
|| !originalCoreOptions.useDistinctHostConfiguration) {
6987
// nothing to do, trimming is disabled
7088
// Due to repercussions of b/117932061, do not trim when `--nodistinct_host_configuration`
@@ -103,6 +121,13 @@ public PatchTransition create(Rule rule) {
103121
}
104122

105123
// Non-test rule. Trim it!
106-
return TestTrimmingTransition.INSTANCE;
124+
// Use an attribute mapper to ensure testonly is resolved to an actual boolean value.
125+
// It is expected all rules should have a boolean testonly value so the `has` check is only
126+
// there as an over-abundance of caution.
127+
NonconfigurableAttributeMapper attrs = NonconfigurableAttributeMapper.of(rule);
128+
if (attrs.has("testonly", Type.BOOLEAN) && attrs.get("testonly", Type.BOOLEAN)) {
129+
return TestTrimmingTransition.TESTONLY_TRUE;
130+
}
131+
return TestTrimmingTransition.TESTONLY_FALSE;
107132
}
108133
}

src/test/java/com/google/devtools/build/lib/analysis/test/TrimTestConfigurationTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,49 @@ public void flagOnTestSuiteWithTestDependencies_CanBeAnalyzed() throws Exception
824824
assertThat(getAnalysisResult().getTargetsToBuild()).hasSize(2);
825825
}
826826

827+
@Test
828+
public void flagOnNonTestTargetWithTestDependencies_isTrimmed() throws Exception {
829+
scratch.file(
830+
"test/BUILD",
831+
"load(':test.bzl', 'starlark_test')",
832+
"load(':lib.bzl', 'starlark_lib')",
833+
"starlark_lib(",
834+
" name = 'starlark_dep',",
835+
" deps = [':starlark_test'],",
836+
" testonly = 1,",
837+
")",
838+
"starlark_test(",
839+
" name = 'starlark_test',",
840+
")");
841+
useConfiguration(
842+
"--trim_test_configuration", "--noexperimental_retain_test_configuration_across_testonly");
843+
update("//test:starlark_dep");
844+
ConfiguredTarget top = getConfiguredTarget("//test:starlark_dep");
845+
assertThat(getConfiguration(top).hasFragment(TestConfiguration.class)).isFalse();
846+
}
847+
848+
@Test
849+
public void flagOnNonTestTargetWithTestDependencies_isNotTrimmedWithExperimentalFlag()
850+
throws Exception {
851+
scratch.file(
852+
"test/BUILD",
853+
"load(':test.bzl', 'starlark_test')",
854+
"load(':lib.bzl', 'starlark_lib')",
855+
"starlark_lib(",
856+
" name = 'starlark_dep',",
857+
" deps = [':starlark_test'],",
858+
" testonly = 1,",
859+
")",
860+
"starlark_test(",
861+
" name = 'starlark_test',",
862+
")");
863+
useConfiguration(
864+
"--trim_test_configuration", "--experimental_retain_test_configuration_across_testonly");
865+
update("//test:starlark_dep");
866+
ConfiguredTarget top = getConfiguredTarget("//test:starlark_dep");
867+
assertThat(getConfiguration(top).hasFragment(TestConfiguration.class)).isTrue();
868+
}
869+
827870
// Test Starlark API of AnalysisFailure{,Info}.
828871
@Test
829872
public void testAnalysisFailureInfo() throws Exception {

0 commit comments

Comments
 (0)