1# Copyright 2020 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
15load(
16 "@bazel_skylib//lib:paths.bzl",
17 "paths",
18)
19load(
20 "//go/private:mode.bzl",
21 "LINKMODES",
22 "LINKMODE_NORMAL",
23)
24load(
25 "//go/private:platforms.bzl",
26 "CGO_GOOS_GOARCH",
27 "GOOS_GOARCH",
28)
29load(
30 "//go/private:providers.bzl",
31 "GoArchive",
32 "GoLibrary",
33 "GoSource",
34)
35
36# A list of rules_go settings that are possibly set by go_transition.
37# Keep their package name in sync with the implementation of
38# _original_setting_key.
39TRANSITIONED_GO_SETTING_KEYS = [
40 "//go/config:static",
41 "//go/config:msan",
42 "//go/config:race",
43 "//go/config:pure",
44 "//go/config:linkmode",
45 "//go/config:tags",
46 "//go/config:pgoprofile",
47]
48
49def _deduped_and_sorted(strs):
50 return sorted({s: None for s in strs}.keys())
51
52def _original_setting_key(key):
53 if not "//go/config:" in key:
54 fail("_original_setting_key currently assumes that all Go settings live under //go/config, got: " + key)
55 name = key.split(":")[1]
56 return "//go/private/rules:original_" + name
57
58_SETTING_KEY_TO_ORIGINAL_SETTING_KEY = {
59 setting: _original_setting_key(setting)
60 for setting in TRANSITIONED_GO_SETTING_KEYS
61}
62
63def _go_transition_impl(settings, attr):
64 # NOTE: Keep the list of rules_go settings set by this transition in sync
65 # with POTENTIALLY_TRANSITIONED_SETTINGS.
66 #
67 # NOTE(bazelbuild/bazel#11409): Calling fail here for invalid combinations
68 # of flags reports an error but does not stop the build.
69 # In any case, get_mode should mainly be responsible for reporting
70 # invalid modes, since it also takes --features flags into account.
71
72 original_settings = settings
73 settings = dict(settings)
74
75 _set_ternary(settings, attr, "static")
76 race = _set_ternary(settings, attr, "race")
77 msan = _set_ternary(settings, attr, "msan")
78 pure = _set_ternary(settings, attr, "pure")
79 if race == "on":
80 if pure == "on":
81 fail('race = "on" cannot be set when pure = "on" is set. race requires cgo.')
82 pure = "off"
83 settings["//go/config:pure"] = False
84 if msan == "on":
85 if pure == "on":
86 fail('msan = "on" cannot be set when msan = "on" is set. msan requires cgo.')
87 pure = "off"
88 settings["//go/config:pure"] = False
89 if pure == "on":
90 race = "off"
91 settings["//go/config:race"] = False
92 msan = "off"
93 settings["//go/config:msan"] = False
94 cgo = pure == "off"
95
96 goos = getattr(attr, "goos", "auto")
97 goarch = getattr(attr, "goarch", "auto")
98 _check_ternary("pure", pure)
99 if goos != "auto" or goarch != "auto":
100 if goos == "auto":
101 fail("goos must be set if goarch is set")
102 if goarch == "auto":
103 fail("goarch must be set if goos is set")
104 if (goos, goarch) not in GOOS_GOARCH:
105 fail("invalid goos, goarch pair: {}, {}".format(goos, goarch))
106 if cgo and (goos, goarch) not in CGO_GOOS_GOARCH:
107 fail('pure is "off" but cgo is not supported on {} {}'.format(goos, goarch))
108 platform = "@io_bazel_rules_go//go/toolchain:{}_{}{}".format(goos, goarch, "_cgo" if cgo else "")
109 settings["//command_line_option:platforms"] = platform
110
111 tags = getattr(attr, "gotags", [])
112 if tags:
113 settings["//go/config:tags"] = _deduped_and_sorted(tags)
114
115 linkmode = getattr(attr, "linkmode", "auto")
116 if linkmode != "auto":
117 if linkmode not in LINKMODES:
118 fail("linkmode: invalid mode {}; want one of {}".format(linkmode, ", ".join(LINKMODES)))
119 settings["//go/config:linkmode"] = linkmode
120
121 pgoprofile = getattr(attr, "pgoprofile", "auto")
122 if pgoprofile != "auto":
123 settings["//go/config:pgoprofile"] = pgoprofile
124
125 for key, original_key in _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.items():
126 old_value = original_settings[key]
127 value = settings[key]
128
129 # If the outgoing configuration would differ from the incoming one in a
130 # value, record the old value in the special original_* key so that the
131 # real setting can be reset to this value before the new configuration
132 # would cross a non-deps dependency edge.
133 if value != old_value:
134 # Encoding as JSON makes it possible to embed settings of arbitrary
135 # types (currently bool, string and string_list) into a single type
136 # of setting (string) with the information preserved whether the
137 # original setting wasn't set explicitly (empty string) or was set
138 # explicitly to its default (always a non-empty string with JSON
139 # encoding, e.g. "\"\"" or "[]").
140 if type(old_value) == "Label":
141 # Label is not JSON serializable, so we need to convert it to a string.
142 old_value = str(old_value)
143 settings[original_key] = json.encode(old_value)
144 else:
145 settings[original_key] = ""
146
147 return settings
148
149def _request_nogo_transition(settings, _attr):
150 """Indicates that we want the project configured nogo instead of a noop.
151
152 This does not guarantee that the project configured nogo will be used (if
153 bootstrap is true we are currently building nogo so that is a cyclic
154 dependency).
155
156 The config setting nogo_active requires bootstrap to be false and
157 request_nogo to be true to provide the project configured nogo.
158 """
159 settings = dict(settings)
160 settings["//go/private:request_nogo"] = True
161 return settings
162
163request_nogo_transition = transition(
164 implementation = _request_nogo_transition,
165 inputs = [],
166 outputs = ["//go/private:request_nogo"],
167)
168
169go_transition = transition(
170 implementation = _go_transition_impl,
171 inputs = [
172 "//command_line_option:platforms",
173 ] + TRANSITIONED_GO_SETTING_KEYS,
174 outputs = [
175 "//command_line_option:platforms",
176 ] + TRANSITIONED_GO_SETTING_KEYS + _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.values(),
177)
178
179_common_reset_transition_dict = dict({
180 "//go/private:request_nogo": False,
181 "//go/config:static": False,
182 "//go/config:msan": False,
183 "//go/config:race": False,
184 "//go/config:pure": False,
185 "//go/config:debug": False,
186 "//go/config:linkmode": LINKMODE_NORMAL,
187 "//go/config:tags": [],
188 "//go/config:pgoprofile": Label("//go/config:empty"),
189}, **{setting: "" for setting in _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.values()})
190
191_reset_transition_dict = dict(_common_reset_transition_dict, **{
192 "//go/private:bootstrap_nogo": True,
193})
194
195_reset_transition_keys = sorted(_reset_transition_dict.keys())
196
197_stdlib_keep_keys = sorted([
198 "//go/config:msan",
199 "//go/config:race",
200 "//go/config:pure",
201 "//go/config:linkmode",
202 "//go/config:tags",
203 "//go/config:pgoprofile",
204])
205
206def _go_tool_transition_impl(settings, _attr):
207 """Sets most Go settings to default values (use for external Go tools).
208
209 go_tool_transition sets all of the //go/config settings to their default
210 values and disables nogo. This is used for Go tool binaries like nogo
211 itself. Tool binaries shouldn't depend on the link mode or tags of the
212 target configuration and neither the tools nor the code they potentially
213 generate should be subject to nogo's static analysis. This transition
214 doesn't change the platform (goos, goarch), but tool binaries should also
215 have `cfg = "exec"` so tool binaries should be built for the execution
216 platform.
217 """
218 return dict(settings, **_reset_transition_dict)
219
220go_tool_transition = transition(
221 implementation = _go_tool_transition_impl,
222 inputs = _reset_transition_keys,
223 outputs = _reset_transition_keys,
224)
225
226def _non_go_tool_transition_impl(settings, _attr):
227 """Sets all Go settings to default values (use for external non-Go tools).
228
229 non_go_tool_transition sets all of the //go/config settings as well as the
230 nogo settings to their default values. This is used for all tools that are
231 not themselves targets created from rules_go rules and thus do not read
232 these settings. Resetting all of them to defaults prevents unnecessary
233 configuration changes for these targets that could cause rebuilds.
234
235 Examples: This transition is applied to attributes referencing proto_library
236 targets or protoc directly.
237 """
238 settings = dict(settings, **_reset_transition_dict)
239 settings["//go/private:bootstrap_nogo"] = False
240 return settings
241
242non_go_tool_transition = transition(
243 implementation = _non_go_tool_transition_impl,
244 inputs = _reset_transition_keys,
245 outputs = _reset_transition_keys,
246)
247
248def _go_stdlib_transition_impl(settings, _attr):
249 """Sets all Go settings to their default values, except for those affecting the Go SDK.
250
251 This transition is similar to _non_go_tool_transition except that it keeps the
252 parts of the configuration that determine how to build the standard library.
253 It's used to consolidate the configurations used to build the standard library to limit
254 the number built.
255 """
256 settings = dict(settings)
257 for label, value in _reset_transition_dict.items():
258 if label not in _stdlib_keep_keys:
259 settings[label] = value
260 settings["//go/config:tags"] = [t for t in settings["//go/config:tags"] if t in _TAG_AFFECTS_STDLIB]
261 settings["//go/private:bootstrap_nogo"] = False
262 return settings
263
264go_stdlib_transition = transition(
265 implementation = _go_stdlib_transition_impl,
266 inputs = _reset_transition_keys,
267 outputs = _reset_transition_keys,
268)
269
270def _go_reset_target_impl(ctx):
271 t = ctx.attr.dep[0] # [0] seems to be necessary with the transition
272 providers = [t[p] for p in [GoLibrary, GoSource, GoArchive] if p in t]
273
274 # We can't pass DefaultInfo through as-is, since Bazel forbids executable
275 # if it's a file declared in a different target. To emulate that, symlink
276 # to the original executable, if there is one.
277 default_info = t[DefaultInfo]
278
279 new_executable = None
280 original_executable = default_info.files_to_run.executable
281 default_runfiles = default_info.default_runfiles
282 if original_executable:
283 # In order for the symlink to have the same basename as the original
284 # executable (important in the case of proto plugins), put it in a
285 # subdirectory named after the label to prevent collisions.
286 new_executable = ctx.actions.declare_file(paths.join(ctx.label.name, original_executable.basename))
287 ctx.actions.symlink(
288 output = new_executable,
289 target_file = original_executable,
290 is_executable = True,
291 )
292 default_runfiles = default_runfiles.merge(ctx.runfiles([new_executable]))
293
294 providers.append(
295 DefaultInfo(
296 files = default_info.files,
297 data_runfiles = default_info.data_runfiles,
298 default_runfiles = default_runfiles,
299 executable = new_executable,
300 ),
301 )
302 return providers
303
304go_reset_target = rule(
305 implementation = _go_reset_target_impl,
306 attrs = {
307 "dep": attr.label(
308 mandatory = True,
309 cfg = go_tool_transition,
310 ),
311 "_allowlist_function_transition": attr.label(
312 default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
313 ),
314 },
315 doc = """Forwards providers from a target and applies go_tool_transition.
316
317go_reset_target depends on a single target, built using go_tool_transition. It
318forwards Go providers and DefaultInfo.
319
320This is used to work around a problem with building tools: Go tools should be
321built with 'cfg = "exec"' so they work on the execution platform, but we also
322need to apply go_tool_transition so that e.g. a tool isn't built as a shared
323library with race instrumentation. This acts as an intermediate rule that allows
324to apply both both transitions.
325""",
326)
327
328non_go_reset_target = rule(
329 implementation = _go_reset_target_impl,
330 attrs = {
331 "dep": attr.label(
332 mandatory = True,
333 cfg = non_go_tool_transition,
334 ),
335 "_allowlist_function_transition": attr.label(
336 default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
337 ),
338 },
339 doc = """Forwards providers from a target and applies non_go_tool_transition.
340
341non_go_reset_target depends on a single target, built using
342non_go_tool_transition. It forwards Go providers and DefaultInfo.
343
344This is used to work around a problem with building tools: Non-Go tools should
345be built with 'cfg = "exec"' so they work on the execution platform, but they
346also shouldn't be affected by Go-specific config changes applied by
347go_transition.
348""",
349)
350
351def _non_go_transition_impl(settings, _attr):
352 """Sets all Go settings to the values they had before the last go_transition.
353
354 non_go_transition sets all of the //go/config settings to the value they had
355 before the last go_transition. This should be used on all attributes of
356 go_library/go_binary/go_test that are built in the target configuration and
357 do not constitute advertise any Go providers.
358
359 Examples: This transition is applied to the 'data' attribute of go_binary so
360 that other Go binaries used at runtime aren't affected by a non-standard
361 link mode set on the go_binary target, but still use the same top-level
362 settings such as e.g. race instrumentation.
363 """
364 new_settings = {}
365 for key, original_key in _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.items():
366 original_value = settings[original_key]
367 if original_value:
368 # Reset to the original value of the setting before go_transition.
369 new_settings[key] = json.decode(original_value)
370 else:
371 new_settings[key] = settings[key]
372
373 # Reset the value of the helper setting to its default for two reasons:
374 # 1. Performance: This ensures that the Go settings of non-Go
375 # dependencies have the same values as before the go_transition,
376 # which can prevent unnecessary rebuilds caused by configuration
377 # changes.
378 # 2. Correctness in edge cases: If there is a path in the build graph
379 # from a go_binary's non-Go dependency to a go_library that does not
380 # pass through another go_binary (e.g., through a custom rule
381 # replacement for go_binary), this transition could be applied again
382 # and cause incorrect Go setting values.
383 new_settings[original_key] = ""
384
385 return new_settings
386
387non_go_transition = transition(
388 implementation = _non_go_transition_impl,
389 inputs = TRANSITIONED_GO_SETTING_KEYS + _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.values(),
390 outputs = TRANSITIONED_GO_SETTING_KEYS + _SETTING_KEY_TO_ORIGINAL_SETTING_KEY.values(),
391)
392
393def _check_ternary(name, value):
394 if value not in ("on", "off", "auto"):
395 fail('{}: must be "on", "off", or "auto"'.format(name))
396
397def _set_ternary(settings, attr, name):
398 value = getattr(attr, name, "auto")
399 _check_ternary(name, value)
400 if value != "auto":
401 label = "//go/config:{}".format(name)
402 settings[label] = value == "on"
403 return value
404
405_SDK_VERSION_BUILD_SETTING = "//go/toolchain:sdk_version"
406TRANSITIONED_GO_CROSS_SETTING_KEYS = [
407 _SDK_VERSION_BUILD_SETTING,
408 "//command_line_option:platforms",
409]
410
411def _go_cross_transition_impl(settings, attr):
412 settings = dict(settings)
413 if attr.sdk_version != None:
414 settings[_SDK_VERSION_BUILD_SETTING] = attr.sdk_version
415
416 if attr.platform != None:
417 settings["//command_line_option:platforms"] = str(attr.platform)
418
419 return settings
420
421go_cross_transition = transition(
422 implementation = _go_cross_transition_impl,
423 inputs = TRANSITIONED_GO_CROSS_SETTING_KEYS,
424 outputs = TRANSITIONED_GO_CROSS_SETTING_KEYS,
425)
426
427# A list of Go build tags that potentially affect the build of the standard
428# library.
429#
430# This should be updated to contain the union of all tags relevant for all
431# versions of Go that are still relevant.
432#
433# Currently supported versions: 1.18, 1.19, 1.20
434#
435# To regenerate, run and paste the output of
436# bazel run //go/tools/internal/stdlib_tags:stdlib_tags -- path/to/go_sdk_1/src ...
437_TAG_AFFECTS_STDLIB = {
438 "alpha": None,
439 "appengine": None,
440 "asan": None,
441 "boringcrypto": None,
442 "cmd_go_bootstrap": None,
443 "compiler_bootstrap": None,
444 "debuglog": None,
445 "faketime": None,
446 "gc": None,
447 "gccgo": None,
448 "gen": None,
449 "generate": None,
450 "gofuzz": None,
451 "ignore": None,
452 "libfuzzer": None,
453 "m68k": None,
454 "math_big_pure_go": None,
455 "msan": None,
456 "netcgo": None,
457 "netgo": None,
458 "nethttpomithttp2": None,
459 "nios2": None,
460 "noopt": None,
461 "osusergo": None,
462 "purego": None,
463 "race": None,
464 "sh": None,
465 "shbe": None,
466 "tablegen": None,
467 "testgo": None,
468 "timetzdata": None,
469}
View as plain text