perf: Only resolve cc toolchain for rules that will need it (#4593)

This PR is a re-write of
https://github.com/bazel-contrib/rules_go/pull/4591 which was incorrect
- the cc toolchain must be resolved by the rule owning the action that
uses it, it cannot be resolved by a dependent rule (CgoContextData) as
that will not work correctly when there are multiple execution
platforms. See
https://bazelbuild.slack.com/archives/CDBP88Z0D/p1776380536174449 for
discussion
diff --git a/extras/gomock.bzl b/extras/gomock.bzl
index d2faa7e..da40963 100644
--- a/extras/gomock.bzl
+++ b/extras/gomock.bzl
@@ -32,7 +32,11 @@
 _MOCKGEN_MODEL_LIB = Label("//extras/gomock:mockgen_model")
 
 def _gomock_source_impl(ctx):
-    go = go_context(ctx, include_deprecated_properties = False)
+    go = go_context(
+        ctx,
+        include_deprecated_properties = False,
+        maybe_needs_cc_toolchain = False,
+    )
 
     # In Source mode, it's not necessary to pass through a library, as the only thing we use it for is setting up
     # the relative file locations. Forcing users to pass a library makes it difficult in the case where a mock should
@@ -313,7 +317,11 @@
 )
 
 def _gomock_prog_exec_impl(ctx):
-    go = go_context(ctx, include_deprecated_properties = False)
+    go = go_context(
+        ctx,
+        include_deprecated_properties = False,
+        maybe_needs_cc_toolchain = False,
+    )
 
     args = ["-exec_only", ctx.file.prog_bin.path]
     args, needed_files = _handle_shared_args(ctx, args)
diff --git a/go/private/context.bzl b/go/private/context.bzl
index 0bd1734..f054253 100644
--- a/go/private/context.bzl
+++ b/go/private/context.bzl
@@ -480,6 +480,31 @@
             return True
     return False
 
+def _go_infos_use_cgo(go_infos):
+    for go_info in go_infos:
+        if GoInfo in go_info and go_info[GoInfo].cgo:
+            return True
+    return False
+
+def _sources_use_cgo(attr, go_infos):
+    """Returns whether attr's sources may need cgo processing."""
+    if getattr(attr, "cgo", False):
+        return True
+    return _go_infos_use_cgo(getattr(attr, "embed", [])) or _go_infos_use_cgo(go_infos)
+
+def maybe_needs_cc_toolchain(attr, go_infos = []):
+    """Returns whether this rule's own sources may use the C/C++ toolchain."""
+    return _sources_use_cgo(attr, go_infos)
+
+def _precomputed_cgo_context_info(attr, go_context_data):
+    if go_context_data and CgoContextInfo in go_context_data:
+        return go_context_data[CgoContextInfo]
+    if getattr(attr, "_cgo_context_data", None) and CgoContextInfo in attr._cgo_context_data:
+        return attr._cgo_context_data[CgoContextInfo]
+    if getattr(attr, "cgo_context_data", None) and CgoContextInfo in attr.cgo_context_data:
+        return attr.cgo_context_data[CgoContextInfo]
+    return None
+
 def validate_nogo(go):
     """Whether nogo should be run as a validation action rather than just to generate fact files for the current
     target."""
@@ -522,6 +547,7 @@
         embed = None,
         importpath_aliases = None,
         go_context_data = None,
+        maybe_needs_cc_toolchain = True,
         goos = "auto",
         goarch = "auto"):
     """Returns an API used to build Go code.
@@ -551,16 +577,26 @@
         stdlib = go_context_data[GoStdLib]
         go_context_info = go_context_data[GoContextInfo]
 
-    if getattr(attr, "_cc_toolchain", None) and CPP_TOOLCHAIN_TYPE in ctx.toolchains:
-        cgo_context_info = cgo_context_data_impl(ctx)
-    elif go_context_data and CgoContextInfo in go_context_data:
-        cgo_context_info = go_context_data[CgoContextInfo]
-    elif getattr(attr, "_cgo_context_data", None) and CgoContextInfo in attr._cgo_context_data:
-        cgo_context_info = attr._cgo_context_data[CgoContextInfo]
-    elif getattr(attr, "cgo_context_data", None) and CgoContextInfo in attr.cgo_context_data:
-        cgo_context_info = attr.cgo_context_data[CgoContextInfo]
+    cgo_disabled = (go_config_info and go_config_info.pure) or (
+        getattr(attr, "_pure_constraint", None) and
+        ctx.target_platform_has_constraint(attr._pure_constraint[platform_common.ConstraintValueInfo])
+    )
 
-    if goos == "auto" and goarch == "auto" and cgo_context_info and (go_config_info == None or not go_config_info.pure):
+    if maybe_needs_cc_toolchain and not cgo_disabled:
+        has_cc_toolchain = getattr(attr, "_cc_toolchain", None) and CPP_TOOLCHAIN_TYPE in ctx.toolchains
+        if has_cc_toolchain:
+            cgo_context_info = cgo_context_data_impl(ctx)
+        else:
+            cgo_context_info = _precomputed_cgo_context_info(attr, go_context_data)
+
+    if maybe_needs_cc_toolchain:
+        cgo_available = cgo_context_info != None
+    else:
+        # Preserve cgo build-mode/tag behavior for rules that won't use the
+        # C/C++ toolchain in their own actions.
+        cgo_available = not cgo_disabled and _precomputed_cgo_context_info(attr, go_context_data) != None
+
+    if goos == "auto" and goarch == "auto" and cgo_available and (go_config_info == None or not go_config_info.pure):
         # Fast-path to reuse the GoConfigInfo as-is
         mode = go_config_info or default_go_config_info
     else:
@@ -569,7 +605,7 @@
         mode_kwargs = structs.to_dict(go_config_info)
         mode_kwargs["goos"] = toolchain.default_goos if goos == "auto" else goos
         mode_kwargs["goarch"] = toolchain.default_goarch if goarch == "auto" else goarch
-        if not cgo_context_info:
+        if not cgo_available:
             if getattr(ctx.attr, "pure", None) == "off":
                 fail("{} has pure explicitly set to off, but no C++ toolchain could be found for its platform".format(ctx.label))
             mode_kwargs["pure"] = True
diff --git a/go/private/rules/info.bzl b/go/private/rules/info.bzl
index 261c571..e06e540 100644
--- a/go/private/rules/info.bzl
+++ b/go/private/rules/info.bzl
@@ -22,7 +22,7 @@
 )
 
 def _go_info_impl(ctx):
-    go = go_context(ctx)
+    go = go_context(ctx, maybe_needs_cc_toolchain = False)
     report = go.declare_file(go, ext = ".txt")
     args = go.actions.args()
     args.add("-sdk", go.sdk.root_file.dirname)
diff --git a/go/private/rules/library.bzl b/go/private/rules/library.bzl
index ae1ce57..1deb90b 100644
--- a/go/private/rules/library.bzl
+++ b/go/private/rules/library.bzl
@@ -26,6 +26,7 @@
     "CGO_FRAGMENTS",
     "CGO_TOOLCHAINS",
     "go_context",
+    "maybe_needs_cc_toolchain",
     "new_go_info",
 )
 load(
@@ -47,6 +48,7 @@
         importpath_aliases = ctx.attr.importpath_aliases,
         embed = ctx.attr.embed,
         go_context_data = ctx.attr._go_context_data,
+        maybe_needs_cc_toolchain = maybe_needs_cc_toolchain(ctx.attr),
     )
 
     go_info = new_go_info(go, ctx.attr)
@@ -214,7 +216,11 @@
 
 def _go_tool_library_impl(ctx):
     """Implements the go_tool_library() rule."""
-    go = go_context(ctx, include_deprecated_properties = False)
+    go = go_context(
+        ctx,
+        include_deprecated_properties = False,
+        maybe_needs_cc_toolchain = maybe_needs_cc_toolchain(ctx.attr),
+    )
 
     go_info = new_go_info(go, ctx.attr)
     archive = go.archive(go, go_info)
diff --git a/go/private/rules/nogo.bzl b/go/private/rules/nogo.bzl
index e7ea643..7df83e9 100644
--- a/go/private/rules/nogo.bzl
+++ b/go/private/rules/nogo.bzl
@@ -41,7 +41,11 @@
         return None
 
     # Generate the source for the nogo binary.
-    go = go_context(ctx, include_deprecated_properties = False)
+    analyzer_archives = [dep[GoArchive] for dep in ctx.attr.deps]
+    go = go_context(
+        ctx,
+        include_deprecated_properties = False,
+    )
     nogo_main = go.declare_file(go, path = "nogo_main.go")
     nogo_args = ctx.actions.args()
     nogo_args.add("gennogomain")
@@ -49,7 +53,6 @@
     if ctx.attr.debug:
         nogo_args.add("-debug")
     nogo_inputs = []
-    analyzer_archives = [dep[GoArchive] for dep in ctx.attr.deps]
     analyzer_importpaths = [archive.data.importpath for archive in analyzer_archives]
     nogo_args.add_all(analyzer_importpaths, before_each = "-analyzer_importpath")
     if ctx.file.config:
diff --git a/go/private/rules/source.bzl b/go/private/rules/source.bzl
index 4c55dca..58c8fb0 100644
--- a/go/private/rules/source.bzl
+++ b/go/private/rules/source.bzl
@@ -28,7 +28,11 @@
 
 def _go_source_impl(ctx):
     """Implements the go_source() rule."""
-    go = go_context(ctx, include_deprecated_properties = False)
+    go = go_context(
+        ctx,
+        include_deprecated_properties = False,
+        maybe_needs_cc_toolchain = False,
+    )
     go_info = new_go_info(go, ctx.attr)
     return [
         go_info,
diff --git a/proto/compiler.bzl b/proto/compiler.bzl
index f77ab5a..e1b16d1 100644
--- a/proto/compiler.bzl
+++ b/proto/compiler.bzl
@@ -216,7 +216,11 @@
     return src.path[len(prefix):]
 
 def _go_proto_compiler_impl(ctx):
-    go = go_context(ctx, include_deprecated_properties = False)
+    go = go_context(
+        ctx,
+        include_deprecated_properties = False,
+        maybe_needs_cc_toolchain = False,
+    )
     go_info = new_go_info(go, ctx.attr)
     proto_toolchain = _find_toolchain(
         ctx,
diff --git a/proto/def.bzl b/proto/def.bzl
index 9a5ec11..1ed0e38 100644
--- a/proto/def.bzl
+++ b/proto/def.bzl
@@ -35,6 +35,7 @@
     "CGO_FRAGMENTS",
     "CGO_TOOLCHAINS",
     "new_go_info",
+    rule_maybe_needs_cc_toolchain = "maybe_needs_cc_toolchain",
 )
 load(
     "//go/private/rules:transition.bzl",
@@ -89,6 +90,7 @@
         include_deprecated_properties = False,
         importpath = attr.importpath,
         go_context_data = attr._go_context_data,
+        maybe_needs_cc_toolchain = False,
     )
     imports = get_imports(attr, go.importpath)
     return [GoProtoImports(imports = imports)]
@@ -111,6 +113,10 @@
         if GoInfo in compiler:
             merge(source, compiler[GoInfo])
 
+def _go_proto_library_maybe_needs_cc_toolchain(ctx):
+    compilers = [ctx.attr.compiler] if ctx.attr.compiler else ctx.attr.compilers
+    return rule_maybe_needs_cc_toolchain(ctx.attr, go_infos = compilers)
+
 def _go_proto_library_impl(ctx):
     go = go_context(
         ctx,
@@ -120,6 +126,7 @@
         importpath_aliases = ctx.attr.importpath_aliases,
         embed = ctx.attr.embed,
         go_context_data = ctx.attr._go_context_data,
+        maybe_needs_cc_toolchain = _go_proto_library_maybe_needs_cc_toolchain(ctx),
     )
     if ctx.attr.compiler:
         #TODO: print("DEPRECATED: compiler attribute on {}, use compilers instead".format(ctx.label))