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))