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