Skip to content

Commit f848748

Browse files
committed
Transition on edges not self
As per bazelbuild/bazel#15157 currently we can't configure any transition-relevant attributes using select. This is because we use self-transitions on targets, and when this happens, configurable attributes aren't passed to the transition, so we make incorrect transition decisions. We only actually consult configuration data in `_go_context_data`, so we only actually need to transition on the edges which (transitively) reach a `_go_context_data`, which is `_go_context_data` itself and `deps`.
1 parent 8d6b21c commit f848748

9 files changed

+221
-84
lines changed

go/private/context.bzl

+20-7
Original file line numberDiff line numberDiff line change
@@ -365,20 +365,21 @@ def go_context(ctx, attr = None):
365365
coverdata = None
366366
nogo = None
367367
if hasattr(attr, "_go_context_data"):
368-
if CgoContextInfo in attr._go_context_data:
369-
cgo_context_info = attr._go_context_data[CgoContextInfo]
370-
go_config_info = attr._go_context_data[GoConfigInfo]
371-
stdlib = attr._go_context_data[GoStdLib]
372-
coverdata = attr._go_context_data[GoContextInfo].coverdata
373-
nogo = attr._go_context_data[GoContextInfo].nogo
368+
go_context_data = _flatten(attr._go_context_data)
369+
if CgoContextInfo in go_context_data:
370+
cgo_context_info = go_context_data[CgoContextInfo]
371+
go_config_info = go_context_data[GoConfigInfo]
372+
stdlib = go_context_data[GoStdLib]
373+
coverdata = go_context_data[GoContextInfo].coverdata
374+
nogo = go_context_data[GoContextInfo].nogo
374375
if getattr(attr, "_cgo_context_data", None) and CgoContextInfo in attr._cgo_context_data:
375376
cgo_context_info = attr._cgo_context_data[CgoContextInfo]
376377
if getattr(attr, "cgo_context_data", None) and CgoContextInfo in attr.cgo_context_data:
377378
cgo_context_info = attr.cgo_context_data[CgoContextInfo]
378379
if hasattr(attr, "_go_config"):
379380
go_config_info = attr._go_config[GoConfigInfo]
380381
if hasattr(attr, "_stdlib"):
381-
stdlib = attr._stdlib[GoStdLib]
382+
stdlib = _flatten(attr._stdlib)[GoStdLib]
382383

383384
mode = get_mode(ctx, toolchain, cgo_context_info, go_config_info)
384385
tags = mode.tags
@@ -837,3 +838,15 @@ go_config = rule(
837838

838839
def _expand_opts(go, attribute_name, opts):
839840
return [go._ctx.expand_make_variables(attribute_name, opt, {}) for opt in opts]
841+
842+
# Used to get attribute values which may have been transitioned.
843+
# Transitioned attributes end up as lists.
844+
# We never use split-transitions, so we always expect exactly one element in those lists.
845+
# But if the attribute wasn't transitioned, it won't be a list.
846+
def _flatten(maybe_list):
847+
if type(maybe_list) == "list":
848+
if len(maybe_list) == 1:
849+
return maybe_list[0]
850+
else:
851+
fail("Expected exactly one element in list but got {}".format(maybe_list))
852+
return maybe_list

go/private/mode.bzl

+2-2
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ def get_mode(ctx, go_toolchain, cgo_context_info, go_config_info):
8080
debug = go_config_info.debug if go_config_info else False
8181
linkmode = go_config_info.linkmode if go_config_info else LINKMODE_NORMAL
8282
cover_format = go_config_info and go_config_info.cover_format
83-
goos = go_toolchain.default_goos
84-
goarch = go_toolchain.default_goarch
83+
goos = go_toolchain.default_goos if getattr(ctx.attr, "goos", "auto") == "auto" else ctx.attr.goos
84+
goarch = go_toolchain.default_goarch if getattr(ctx.attr, "goarch", "auto") == "auto" else ctx.attr.goarch
8585

8686
# TODO(jayconrod): check for more invalid and contradictory settings.
8787
if pure and race:

go/private/rules/binary.bzl

+5-4
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ load(
3333
)
3434
load(
3535
"//go/private/rules:transition.bzl",
36-
"go_transition_rule",
36+
"go_transition",
3737
"non_go_transition",
3838
)
3939
load(
@@ -213,6 +213,7 @@ _go_binary_kwargs = {
213213
doc = """List of Go libraries this package imports directly.
214214
These may be `go_library` rules or compatible rules with the [GoLibrary] provider.
215215
""",
216+
cfg = go_transition,
216217
),
217218
"embed": attr.label_list(
218219
providers = [GoLibrary],
@@ -225,6 +226,7 @@ _go_binary_kwargs = {
225226
embedding binary may not also have `cgo = True`. See [Embedding] for
226227
more information.
227228
""",
229+
cfg = go_transition,
228230
),
229231
"embedsrcs": attr.label_list(
230232
allow_files = True,
@@ -390,7 +392,7 @@ _go_binary_kwargs = {
390392
</ul>
391393
""",
392394
),
393-
"_go_context_data": attr.label(default = "//:go_context_data"),
395+
"_go_context_data": attr.label(default = "//:go_context_data", cfg = go_transition),
394396
"_allowlist_function_transition": attr.label(
395397
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
396398
),
@@ -410,8 +412,7 @@ _go_binary_kwargs = {
410412
}
411413

412414
go_binary = rule(executable = True, **_go_binary_kwargs)
413-
go_transition_binary = go_transition_rule(executable = True, **_go_binary_kwargs)
414-
go_non_executable_transition_binary = go_transition_rule(executable = False, **_go_binary_kwargs)
415+
go_non_executable_binary = rule(executable = False, **_go_binary_kwargs)
415416

416417
def _go_tool_binary_impl(ctx):
417418
sdk = ctx.attr.sdk[GoSDK]

go/private/rules/test.bzl

+5-3
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ load(
4343
)
4444
load(
4545
"//go/private/rules:transition.bzl",
46-
"go_transition_rule",
46+
"go_transition",
4747
"non_go_transition",
4848
)
4949
load(
@@ -220,6 +220,7 @@ _go_test_kwargs = {
220220
doc = """List of Go libraries this test imports directly.
221221
These may be go_library rules or compatible rules with the [GoLibrary] provider.
222222
""",
223+
cfg = go_transition,
223224
),
224225
"embed": attr.label_list(
225226
providers = [GoLibrary],
@@ -231,6 +232,7 @@ _go_test_kwargs = {
231232
and the embedding library may not also have `cgo = True`. See [Embedding]
232233
for more information.
233234
""",
235+
cfg = go_transition,
234236
),
235237
"embedsrcs": attr.label_list(
236238
allow_files = True,
@@ -402,10 +404,11 @@ _go_test_kwargs = {
402404
See [Cross compilation] for more information.
403405
""",
404406
),
405-
"_go_context_data": attr.label(default = "//:go_context_data"),
407+
"_go_context_data": attr.label(default = "//:go_context_data", cfg = go_transition),
406408
"_testmain_additional_deps": attr.label_list(
407409
providers = [GoLibrary],
408410
default = ["//go/tools/bzltestutil"],
411+
cfg = go_transition,
409412
),
410413
# Workaround for bazelbuild/bazel#6293. See comment in lcov_merger.sh.
411414
"_lcov_merger": attr.label(
@@ -453,7 +456,6 @@ _go_test_kwargs = {
453456
}
454457

455458
go_test = rule(**_go_test_kwargs)
456-
go_transition_test = go_transition_rule(**_go_test_kwargs)
457459

458460
def _recompile_external_deps(go, external_source, internal_archive, library_labels):
459461
"""Recompiles some archives in order to split internal and external tests.

go/private/rules/transition.bzl

-56
Original file line numberDiff line numberDiff line change
@@ -80,62 +80,6 @@ _SETTING_KEY_TO_ORIGINAL_SETTING_KEY = {
8080
for setting in TRANSITIONED_GO_SETTING_KEYS
8181
}
8282

83-
def go_transition_wrapper(kind, transition_kind, name, **kwargs):
84-
"""Wrapper for rules that may use transitions.
85-
86-
This is used in place of instantiating go_binary or go_transition_binary
87-
directly. If one of the transition attributes is set explicitly, it
88-
instantiates the rule with a transition. Otherwise, it instantiates the
89-
regular rule. This prevents targets from being rebuilt for an alternative
90-
configuration identical to the default configuration.
91-
"""
92-
transition_keys = ("goos", "goarch", "pure", "static", "msan", "race", "gotags", "linkmode")
93-
need_transition = any([key in kwargs for key in transition_keys])
94-
if need_transition:
95-
transition_kind(name = name, **kwargs)
96-
else:
97-
kind(name = name, **kwargs)
98-
99-
def go_transition_rule(**kwargs):
100-
"""Like "rule", but adds a transition and mode attributes."""
101-
kwargs = dict(kwargs)
102-
kwargs["attrs"].update({
103-
"goos": attr.string(
104-
default = "auto",
105-
values = ["auto"] + {goos: None for goos, _ in GOOS_GOARCH}.keys(),
106-
),
107-
"goarch": attr.string(
108-
default = "auto",
109-
values = ["auto"] + {goarch: None for _, goarch in GOOS_GOARCH}.keys(),
110-
),
111-
"pure": attr.string(
112-
default = "auto",
113-
values = ["auto", "on", "off"],
114-
),
115-
"static": attr.string(
116-
default = "auto",
117-
values = ["auto", "on", "off"],
118-
),
119-
"msan": attr.string(
120-
default = "auto",
121-
values = ["auto", "on", "off"],
122-
),
123-
"race": attr.string(
124-
default = "auto",
125-
values = ["auto", "on", "off"],
126-
),
127-
"gotags": attr.string_list(default = []),
128-
"linkmode": attr.string(
129-
default = "auto",
130-
values = ["auto"] + LINKMODES,
131-
),
132-
"_whitelist_function_transition": attr.label(
133-
default = "@bazel_tools//tools/whitelists/function_transition_whitelist",
134-
),
135-
})
136-
kwargs["cfg"] = go_transition
137-
return rule(**kwargs)
138-
13983
def _go_transition_impl(settings, attr):
14084
# NOTE: Keep the list of rules_go settings set by this transition in sync
14185
# with POTENTIALLY_TRANSITIONED_SETTINGS.

go/private/rules/wrappers.bzl

+18-12
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,11 @@ load(
2626
load(
2727
"//go/private/rules:binary.bzl",
2828
"go_binary",
29-
"go_non_executable_transition_binary",
30-
"go_transition_binary",
29+
"go_non_executable_binary",
3130
)
3231
load(
3332
"//go/private/rules:test.bzl",
3433
"go_test",
35-
"go_transition_test",
36-
)
37-
load(
38-
"//go/private/rules:transition.bzl",
39-
"go_transition_wrapper",
4034
)
4135

4236
def _cgo(name, kwargs):
@@ -51,12 +45,24 @@ def go_library_macro(name, **kwargs):
5145
def go_binary_macro(name, **kwargs):
5246
"""See docs/go/core/rules.md#go_binary for full documentation."""
5347
_cgo(name, kwargs)
48+
49+
if kwargs.get("goos") != None or kwargs.get("goarch") != None:
50+
for key, value in kwargs.items():
51+
if type(value) == "select":
52+
# In the long term, we should replace goos/goarch with Bazel-native platform
53+
# support, but while we have the mechanisms, we try to avoid people trying to use
54+
# _both_ goos/goarch _and_ native platform support.
55+
#
56+
# It's unclear to users whether the select should happen before or after the
57+
# goos/goarch is reconfigured, and we can't interpose code to force either
58+
# behaviour, so we forbid this.
59+
fail("Cannot use select for go_binary with goos/goarch set, but {} was a select".format(key))
60+
5461
if kwargs.get("linkmode", default = LINKMODE_NORMAL) in LINKMODES_EXECUTABLE:
55-
go_transition_wrapper(go_binary, go_transition_binary, name = name, **kwargs)
62+
go_binary(name = name, **kwargs)
5663
else:
57-
# A non-normal link mode implies the use of transitions, so we don't have to define a
58-
# non-executable version of the untransitioned go_binary.
59-
go_transition_wrapper(None, go_non_executable_transition_binary, name = name, **kwargs)
64+
go_non_executable_binary(name = name, **kwargs)
65+
6066
if kwargs.get("linkmode") in (LINKMODE_C_ARCHIVE, LINKMODE_C_SHARED):
6167
# Create an alias to tell users of the `.cc` rule that it is deprecated.
6268
native.alias(
@@ -70,4 +76,4 @@ def go_binary_macro(name, **kwargs):
7076
def go_test_macro(name, **kwargs):
7177
"""See docs/go/core/rules.md#go_test for full documentation."""
7278
_cgo(name, kwargs)
73-
go_transition_wrapper(go_test, go_transition_test, name = name, **kwargs)
79+
go_test(name = name, **kwargs)

tests/core/go_binary/BUILD.bazel

+10
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ load(":many_deps.bzl", "many_deps")
44

55
test_suite(name = "go_binary")
66

7+
go_bazel_test(
8+
name = "configurable_attribute_bad_test",
9+
srcs = ["configurable_attribute_bad_test.go"],
10+
)
11+
12+
go_bazel_test(
13+
name = "configurable_attribute_good_test",
14+
srcs = ["configurable_attribute_good_test.go"],
15+
)
16+
717
go_binary(
818
name = "hello",
919
srcs = ["hello.go"],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright 2022 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package configurable_attribute_bad_test
16+
17+
import (
18+
"strings"
19+
"testing"
20+
21+
"github.com/bazelbuild/rules_go/go/tools/bazel_testing"
22+
)
23+
24+
func TestMain(m *testing.M) {
25+
bazel_testing.TestMain(m, bazel_testing.Args{
26+
Main: `
27+
-- BUILD.bazel --
28+
load("@io_bazel_rules_go//go:def.bzl", "go_binary")
29+
30+
go_binary(
31+
name = "main",
32+
srcs = [
33+
"main.go",
34+
],
35+
goos = "darwin",
36+
goarch = "amd64",
37+
gotags = select({
38+
"@io_bazel_rules_go//go/platform:linux": ["penguins"],
39+
"//conditions:default": ["nopenguins"],
40+
}),
41+
)
42+
43+
-- main.go --
44+
package main
45+
46+
import "fmt"
47+
48+
func main() {
49+
fmt.Println("Howdy")
50+
}
51+
`,
52+
})
53+
}
54+
55+
func TestConfigurableGotagsAttribute(t *testing.T) {
56+
_, err := bazel_testing.BazelOutput("build", "//:main")
57+
if err == nil {
58+
t.Fatal("Want error")
59+
}
60+
eErr, ok := err.(*bazel_testing.StderrExitError)
61+
if !ok {
62+
t.Fatalf("Want StderrExitError but got %v", err)
63+
}
64+
stderr := eErr.Error()
65+
want := "Cannot use select for go_binary with goos/goarch set, but gotags was a select"
66+
if !strings.Contains(stderr, want) {
67+
t.Fatalf("Want error message containing %q but got %v", want, stderr)
68+
}
69+
}

0 commit comments

Comments
 (0)