-
-
Notifications
You must be signed in to change notification settings - Fork 668
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
Trim transitioned Go settings on non-Go dependencies #3108
Conversation
5585958
to
905bf39
Compare
23040f6
to
64429b2
Compare
880eee1
to
efd118d
Compare
@linzhp can you try this out in your big repo and see if it works as well? |
It passed in Uber, but let's merge #3005 first |
efd118d
to
2902167
Compare
2902167
to
2fec0c5
Compare
go/private/rules/transition.bzl
Outdated
if not "//go/config:" in setting: | ||
return None | ||
name = setting.split(":")[1] | ||
return filter_transition_label("@io_bazel_rules_go//go/private/rules:original_" + name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems filter_transition_label
does nothing to a string like this, right? In addition, bazelbuild/bazel#10499 is already fixed. Do we still need filter_transition_label
at all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It converts @io_bazel_rules_go//go/config:race
to //go/config:race
when io_bazel_rules_go
is the main repo and returns it unchanged otherwise. It may not be necessary here since the "original_" settings are never reference on the command-line, but using it for some settings and not others seemed even more confusing to me - it would also affect the way transition inputs/outputs are declared below.
The bug is fixed, but the full fix is only available in Bazel 5+. Once the minimum version has been updated to 5.0.0, filter_transition_label
can be dropped.
go/private/rules/transition.bzl
Outdated
# Encoding as JSON makes it possible to embed settings of arbitrary | ||
# types (currently bool, string and string_list) into a string | ||
# setting. | ||
settings[original_value_setting] = json.encode(original_settings[setting]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why can we not using the original type like:
settings[original_value_setting] = json.encode(original_settings[setting]) | |
settings[original_value_setting] = original_settings[setting] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I extended the comment to explain why that wouldn't work. In short: We need a way to distinguish between "explicitly set to default" and "not explicitly set". The alternative would be to track an additional config.bool
per memoized setting, but the embedding into JSON just seemed much simpler. It has the added benefit that all "original" settings can have the same type.
go/private/rules/transition.bzl
Outdated
if not original_value_setting: | ||
continue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if not original_value_setting: | |
continue | |
if not original_value_setting or value == original_settings[setting]: | |
continue |
This would reduce the nesting for the logic below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Transition outputs have to be set explicitly, even if to their default. If I simplify the if
below in the way I believe you are hinting at, I get transition outputs [...] were not defined by transition function
errors.
go/private/rules/transition.bzl
Outdated
name = setting.split(":")[1] | ||
return filter_transition_label("@io_bazel_rules_go//go/private/rules:original_" + name) | ||
|
||
_ORIGINAL_VALUE_SETTINGS = [_original_value_setting(setting) for setting in POTENTIALLY_TRANSITIONED_SETTINGS] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_ORIGINAL_VALUE_SETTINGS = [_original_value_setting(setting) for setting in POTENTIALLY_TRANSITIONED_SETTINGS] | |
_ORIGINAL_SETTING_KEYS = [_original_value_setting(setting) for setting in POTENTIALLY_TRANSITIONED_SETTINGS] |
Also, because _original_value_setting
may return None
. You need to filter them out
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code expects this function to be "in sync" with what is now called TRANSITIONED_GO_SETTINGS
anyway and if it is correctly synced, _ORIGINAL_SETTING_KEYS
will never contain None
. If the two get out of sync, returning None
should raise the alarm more effectively than silently dropping elements, so I would see value in not filtering them out. What do you think?
By resetting Go-specific settings changed by go_transition to their previous values when crossing a non-deps dependency edge, e.g. srcs or data, dependencies through those edges are not affected by the change in Go settings. This has two advantages: * Correctness: Previously, if a go_binary with linkmode = "c-archive" used another go_binary to generate srcs, that go_binary would also be built as a c-archive and thus fail to run during the build. This was observed in bazelbuild/bazel#14626. * Performance: With the new Bazel flag --experimental_output_directory_naming_scheme=diff_against_baseline, transitions can return to the top-level configuration since the Starlark settings that were affected by a transition at some point during the build are no longer tracked explicitly in a setting, but computed as a configuration diff. Resetting the configuration for non- deps dependencies thus prevents redundant rebuilds of non-Go deps caused by changes in Go settings, achieving a version of "configuration trimming" for Go.
The test verifies that Go settings such as gotags and linkmode do not affect dependencies through attributes other than deps.
791114a
to
461314d
Compare
461314d
to
e567994
Compare
# encoding, e.g. "\"\"" or "[]"). | ||
settings[original_key] = json.encode(original_settings[key]) | ||
else: | ||
settings[original_key] = "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you set this to None
or not setting it at all, to indicate that it's not explicitly set?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That doesn't work: Since the setting have type string
, they can only be set to a string, not None
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you tried None
? According to the official doc:
settings is a dictionary
{String:Object}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried it, it does indeed return an error with Bazel 4.2.1.
go/private/rules/transition.bzl
Outdated
original_key = _original_setting_key(key) | ||
original_value = settings[original_key] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
original_key = _original_setting_key(key) | |
original_value = settings[original_key] | |
original_key = _original_setting_key(key) | |
if not original_key or original_key not in settings: | |
continue |
So you can skip original_key
not explicitly set
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could implement that change, but could you explain why you think it's needed? If _original_setting_key(key)
is None
for key
in TRANSITIONED_GO_SETTING_KEYS
, then that's a bug in this file. I could possibly see adding a check with a fail
, but a silent continue
would - imo - just make these bugs harder to spot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, adding a fail
with more debuggable message is better. Because _original_setting_key(key)
can return None
, it's better to check for that when None
is not acceptable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a commit that pushed the conversion into the construction of a dict that contains the mapping and added a fail in the conversion function. What do you think?
# encoding, e.g. "\"\"" or "[]"). | ||
settings[original_key] = json.encode(original_settings[key]) | ||
else: | ||
settings[original_key] = "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you tried None
? According to the official doc:
settings is a dictionary
{String:Object}
go/private/rules/transition.bzl
Outdated
original_key = _original_setting_key(key) | ||
original_value = settings[original_key] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, adding a fail
with more debuggable message is better. Because _original_setting_key(key)
can return None
, it's better to check for that when None
is not acceptable.
non_go_transition = transition( | ||
implementation = _non_go_transition_impl, | ||
inputs = TRANSITIONED_GO_SETTING_KEYS + _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.values(), | ||
outputs = TRANSITIONED_GO_SETTING_KEYS + _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.values(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
outputs = TRANSITIONED_GO_SETTING_KEYS + _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.values(), | |
outputs = TRANSITIONED_GO_SETTING_KEYS, |
can we do this and not setting new_settings[original_key]
in the impl?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By not resetting all settings to their command-line defaults, non-Go dependencies of a transitioned go_binary
would still differ from top-level targets in the configuration: They would have the top-level config values in their original_*
settings, whereas top-level (untransitioned) targets do not. This would still solve the correctness issue linked in the PR description, but would not address the performance problem.
1f8a0e9
to
3859f9d
Compare
Had to fix-up the latest commit's use of |
3ae87c1
to
d1529ec
Compare
d1529ec
to
b2608bf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested again on Uber's Go monorepo: passed
…ontrib#3108)" This causes trouble with libmongocrypt. To be investigated, but patching this "correctness" fix out for now. This reverts commit fee2095.
Builds are still duplicated for C dependencies as of A
|
@ripatel-jump Are you sure that this is a regression in 0.38.0? I would think that this has been the behaviour for a long time. Do you happen to have a reproducer I can test this with? |
bazel-contrib#3108 introduced an improvement that aims to remove Go-specific transitions on non-Go dependencies. This fix was incomplete. It did not cover the case where a setting transitions from truthy to falsy. (e.g. `["bla"] => []`) This further reduces duplicated non-Go targets, specifically reducing the number of times Cgo C/C++ dependencies are rebuilt.
@fmeum Thanks for the quick response. I debugged a bit further and I believe the regression comes from this PR: #3116 This is still all very confusing to me because I cannot find a good way to debug the transitions that get applied on my targets. It is evident from the source though that after #3116 |
Attached clean reproducer in bug report #3430 |
What type of PR is this?
Bug fix
What does this PR do? Why is it needed?
By resetting Go-specific settings changed by go_transition to their previous values when crossing a non-deps dependency edge, e.g. srcs or data, dependencies through those edges are not affected by the change in Go settings. This has two advantages:
linkmode = "c-archive"
used another go_binary to generate srcs, that go_binary would also be built as a c-archive and thus fail to run during the build. This was observed in Genrules in 6.0.0-pre.20220112.2 occasionally try to run shared libraries built by go_binary bazelbuild/bazel#14626.--experimental_output_directory_naming_scheme=diff_against_baseline
, transitions can return to the top-level configuration since the Starlark settings that were affected by a transition at some point during the build are no longer tracked explicitly in a setting, but computed as a configuration diff. Resetting the configuration for non-deps dependencies thus prevents redundant rebuilds of non-Go deps caused by changes in Go settings, achieving a version of "configuration trimming" for Go.Which issues(s) does this PR fix?
Fixes #2999
Fixes #3120
Fixes bazelbuild/bazel#14626
Other notes for review
This is based on #3005 and completes the work started there. #3005 can be reviewed and merged first if preferred.