diff --git a/go/private/context.bzl b/go/private/context.bzl index f5cd735d38..f9ee1f1fe0 100644 --- a/go/private/context.bzl +++ b/go/private/context.bzl @@ -464,7 +464,8 @@ def go_context( importpath_aliases = None, go_context_data = None, goos = "auto", - goarch = "auto"): + goarch = "auto", + impure_env = None): """Returns an API used to build Go code. See /go/toolchains.rst#go-context @@ -548,10 +549,15 @@ def go_context( # reparse points (junctions) as symbolic links. Bazel uses junctions # when constructing exec roots, and we use filepath.EvalSymlinks in # GoStdlib, so this broke us. Setting GODEBUG=winsymlink=0 restores - # the old behavior. + # the old behavior. (Ideally this would only be set on Windows) "GODEBUG": "winsymlink=0", } + if impure_env: + env.update(impure_env) + elif hasattr(attr, "impure_env"): + env.update(attr.impure_env) + # The level of support is determined by the platform constraints in # //go/constraints/amd64. # See https://go.dev/wiki/MinimumRequirements#amd64 diff --git a/go/private/rules/binary.bzl b/go/private/rules/binary.bzl index 064831bbe8..99464e41d7 100644 --- a/go/private/rules/binary.bzl +++ b/go/private/rules/binary.bzl @@ -124,6 +124,7 @@ def _go_binary_impl(ctx): go_context_data = ctx.attr._go_context_data[0], goos = ctx.attr.goos, goarch = ctx.attr.goarch, + impure_env = ctx.attr.impure_env, ) is_main = go.mode.linkmode not in (LINKMODE_SHARED, LINKMODE_PLUGIN) @@ -168,6 +169,10 @@ def _go_binary_impl(ctx): env = {} for k, v in ctx.attr.env.items(): env[k] = ctx.expand_location(v, ctx.attr.data) + + if hasattr(ctx.attr, "impure_env"): + env.update(ctx.attr.impure_env) + providers.append(RunEnvironmentInfo(environment = env)) # The executable is automatically added to the runfiles. @@ -281,6 +286,20 @@ def _go_binary_kwargs(go_cc_aspects = []): [make variable expansion](https://docs.bazel.build/versions/main/be/make-variables.html). """, ), + "impure_env": attr.string_dict( + doc = """ + A dictionary of environment variables to set during the build. + These variables will override any existing environment variables. + This is useful for setting variables like LD_LIBRARY_PATH that are needed + during the build process but should not be inherited from the host environment. + + WARNING: This attribute should be used with caution. Setting environment variables + can make builds non-hermetic and potentially non-reproducible. Only use this if you + understand the implications and have a specific need that cannot be solved through + other means. This is particularly important for shared libraries and other external + dependencies that might affect build reproducibility. + """, + ), "importpath": attr.string( doc = """The import path of this binary. Binaries can't actually be imported, but this may be used by [go_path] and other tools to report the location of source diff --git a/go/private/rules/library.bzl b/go/private/rules/library.bzl index 68a1d3b9a2..369d7b4d6e 100644 --- a/go/private/rules/library.bzl +++ b/go/private/rules/library.bzl @@ -44,6 +44,7 @@ def _go_library_impl(ctx): importpath_aliases = ctx.attr.importpath_aliases, embed = ctx.attr.embed, go_context_data = ctx.attr._go_context_data, + impure_env = ctx.attr.impure_env, ) go_info = new_go_info(go, ctx.attr) @@ -187,6 +188,31 @@ go_library = rule( Subject to ["Make variable"] substitution and [Bourne shell tokenization]. Only valid if `cgo = True`. """, ), + "env_inherit": attr.string_list( + doc = """Environment variables to inherit from the external environment. + """, + ), + "env": attr.string_dict( + doc = """Environment variables to set for the binary execution. + The values (but not keys) are subject to + [location expansion](https://docs.bazel.build/versions/main/skylark/macros.html) but not full + [make variable expansion](https://docs.bazel.build/versions/main/be/make-variables.html). + """, + ), + "impure_env": attr.string_dict( + doc = """ + A dictionary of environment variables to set during the build. + These variables will override any existing environment variables. + This is useful for setting variables like LD_LIBRARY_PATH that are needed + during the build process but should not be inherited from the host environment. + + WARNING: This attribute should be used with caution. Setting environment variables + can make builds non-hermetic and potentially non-reproducible. Only use this if you + understand the implications and have a specific need that cannot be solved through + other means. This is particularly important for shared libraries and other external + dependencies that might affect build reproducibility. + """, + ), "_go_context_data": attr.label(default = "//:go_context_data"), "_allowlist_function_transition": attr.label( default = "@bazel_tools//tools/allowlists/function_transition_allowlist", diff --git a/go/private/rules/test.bzl b/go/private/rules/test.bzl index 12dd1cc53a..1382b3b8bb 100644 --- a/go/private/rules/test.bzl +++ b/go/private/rules/test.bzl @@ -199,9 +199,13 @@ def _go_test_impl(ctx): # to run_dir configured above. "GO_TEST_RUN_FROM_BAZEL": "1", } + for k, v in ctx.attr.env.items(): env[k] = ctx.expand_location(v, ctx.attr.data) + if hasattr(ctx.attr, "impure_env"): + env.update(ctx.attr.impure_env) + run_environment_info = RunEnvironmentInfo(env, ctx.attr.env_inherit) # Bazel only looks for coverage data if the test target has an @@ -291,6 +295,14 @@ _go_test_kwargs = { doc = """Environment variables to inherit from the external environment. """, ), + "impure_env": attr.string_dict( + doc = """ + A dictionary of environment variables to set during the build and test execution. + These variables will override any existing environment variables. + WARNING: This attribute is impure and may cause non-hermetic builds. + Use with caution and only when absolutely necessary. + """, + ), "importpath": attr.string( doc = """The import path of this test. Tests can't actually be imported, but this may be used by [go_path] and other tools to report the location of source diff --git a/tests/core/impure_env/BUILD.bazel b/tests/core/impure_env/BUILD.bazel new file mode 100644 index 0000000000..b15bbbadec --- /dev/null +++ b/tests/core/impure_env/BUILD.bazel @@ -0,0 +1,51 @@ +# To run the tests: +# bazelisk test //tests/core/impure_env/... + +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") +load( + "@bazel_skylib//lib:structs.bzl", + "structs", +) +load( + "//go/private:common.bzl", + "GO_TOOLCHAIN", + "GO_TOOLCHAIN_LABEL", + "SUPPORTS_PATH_MAPPING_REQUIREMENT", + "as_list", + "asm_exts", + "cgo_exts", + "go_exts", + "syso_exts", +) + +go_library( + name = "impure_env_lib", + srcs = ["lib.go"], + importpath = "github.com/bazelbuild/rules_go/tests/core/impure_env", +) + +go_test( + name = "impure_env_test", + srcs = ["test.go"], + deps = [":impure_env_lib"], + impure_env = { + "TEST_VAR": "test_value", + }, +) + +go_test( + name = "impure_env_test_no_env", + srcs = ["test_no_env.go"], + deps = [":impure_env_lib"], +) + +go_test( + name = "impure_env_test_multiple", + srcs = ["test_multiple_env.go"], + deps = [":impure_env_lib"], + impure_env = { + "TEST_VAR1": "value1", + "TEST_VAR2": "value2", + "TEST_VAR3": "value3", + }, +) diff --git a/tests/core/impure_env/lib.go b/tests/core/impure_env/lib.go new file mode 100644 index 0000000000..719a1fb70b --- /dev/null +++ b/tests/core/impure_env/lib.go @@ -0,0 +1,17 @@ +package impure_env + +import "os" + +// GetTestVar returns the value of TEST_VAR environment variable +func GetTestVar() string { + return os.Getenv("TEST_VAR") +} + +// GetMultipleVars returns a map of multiple environment variables +func GetMultipleVars() map[string]string { + return map[string]string{ + "TEST_VAR1": os.Getenv("TEST_VAR1"), + "TEST_VAR2": os.Getenv("TEST_VAR2"), + "TEST_VAR3": os.Getenv("TEST_VAR3"), + } +} diff --git a/tests/core/impure_env/test.go b/tests/core/impure_env/test.go new file mode 100644 index 0000000000..fffca5f93e --- /dev/null +++ b/tests/core/impure_env/test.go @@ -0,0 +1,14 @@ +package impure_env_test + +import ( + "testing" + + "github.com/bazelbuild/rules_go/tests/core/impure_env" +) + +func TestEnvironmentWithImpureEnv(t *testing.T) { + got := impure_env.GetTestVar() + if got != "test_value" { + t.Errorf("GetTestVar() = %q; want %q", got, "test_value") + } +} diff --git a/tests/core/impure_env/test_multiple_env.go b/tests/core/impure_env/test_multiple_env.go new file mode 100644 index 0000000000..db49ddb14b --- /dev/null +++ b/tests/core/impure_env/test_multiple_env.go @@ -0,0 +1,22 @@ +package impure_env_test + +import ( + "testing" + + "github.com/bazelbuild/rules_go/tests/core/impure_env" +) + +func TestMultipleEnvironmentVars(t *testing.T) { + got := impure_env.GetMultipleVars() + want := map[string]string{ + "TEST_VAR1": "value1", + "TEST_VAR2": "value2", + "TEST_VAR3": "value3", + } + + for k, v := range want { + if got[k] != v { + t.Errorf("GetMultipleVars()[%q] = %q; want %q", k, got[k], v) + } + } +} diff --git a/tests/core/impure_env/test_no_env.go b/tests/core/impure_env/test_no_env.go new file mode 100644 index 0000000000..0868bf259c --- /dev/null +++ b/tests/core/impure_env/test_no_env.go @@ -0,0 +1,14 @@ +package impure_env_test + +import ( + "testing" + + "github.com/bazelbuild/rules_go/tests/core/impure_env" +) + +func TestEnvironmentWithoutImpureEnv(t *testing.T) { + got := impure_env.GetTestVar() + if got != "" { + t.Errorf("GetTestVar() = %q; want %q", got, "") + } +}