Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add env and env_inherit to native_binary and native_test (using @bazel_features) #484

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions MODULE.bazel
Expand Up @@ -11,6 +11,7 @@ register_toolchains(
)

bazel_dep(name = "platforms", version = "0.0.4")
bazel_dep(name = "bazel_features", version = "1.2.0")

### INTERNAL ONLY - lines after this are not included in the release packaging.

Expand All @@ -27,3 +28,8 @@ local_path_override(
module_name = "bazel_skylib_gazelle_plugin",
path = "gazelle",
)

local_path_override(
module_name = "bazel_features",
path = "bazel_features_stub",
)
11 changes: 5 additions & 6 deletions MODULE.bazel-remove-override.patch
@@ -1,6 +1,6 @@
--- MODULE.bazel
+++ MODULE.bazel
@@ -22,8 +22,8 @@
@@ -23,12 +23,3 @@
# Needed for bazelci and for building distribution tarballs.
# If using an unreleased version of bazel_skylib via git_override, apply
# MODULE.bazel-remove-override.patch to remove the following lines:
Expand All @@ -9,8 +9,7 @@
- module_name = "bazel_skylib_gazelle_plugin",
- path = "gazelle",
-)
+# bazel_dep(name = "bazel_skylib_gazelle_plugin", dev_dependency = True)
+# local_path_override(
+# module_name = "bazel_skylib_gazelle_plugin",
+# path = "gazelle",
+# )
-local_path_override(
- module_name = "bazel_features",
- path = "bazel_features_stub",
-)
1 change: 1 addition & 0 deletions bazel_features_stub/BUILD
@@ -0,0 +1 @@
exports_files(["features.bzl"])
5 changes: 5 additions & 0 deletions bazel_features_stub/MODULE.bazel
@@ -0,0 +1,5 @@
module(
name = "bazel_features",
version = "0.0.0",
compatibility_level = 1,
)
Empty file added bazel_features_stub/WORKSPACE
Empty file.
10 changes: 10 additions & 0 deletions bazel_features_stub/features.bzl
@@ -0,0 +1,10 @@
"""
This is a stub for [bazel_features](https://github.com/bazel-contrib/bazel_features) used for
testing and updating docs.
"""

bazel_features = struct(
globals = struct(
RunEnvironmentInfo = RunEnvironmentInfo,
),
)
3 changes: 3 additions & 0 deletions docs/BUILD
Expand Up @@ -66,6 +66,9 @@ stardoc_with_diff_test(
name = "native_binary",
bzl_library_target = "//rules:native_binary",
out_label = "//docs:native_binary_doc.md",
deps = [
"@bazel_features//:features.bzl",
],
)

stardoc_with_diff_test(
Expand Down
7 changes: 7 additions & 0 deletions docs/maintainers_guide.md
Expand Up @@ -121,6 +121,13 @@ load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
bazel_skylib_workspace()
```

***Additional WORKSPACE setup for `native_binary.bzl`***

```starlark
load("@bazel_features//:deps.bzl", "bazel_features_deps")
bazel_features_deps()
```

***Additional WORKSPACE setup for the Gazelle plugin***

```starlark
Expand Down
11 changes: 9 additions & 2 deletions docs/native_binary_doc.md
Expand Up @@ -7,13 +7,17 @@ and test rule respectively. They fulfill the same goal as sh_binary and sh_test
do, but they run the wrapped binary directly, instead of through Bash, so they
don't depend on Bash and work with --shell_executable="".

If `bazel_skylib` is loaded from `WORKSPACE` rather than with bzlmod, using
this library requires additional `WORKSPACE` setup as explained in the
[release page](https://github.com/bazelbuild/bazel-skylib/releases).


<a id="native_binary"></a>

## native_binary

<pre>
native_binary(<a href="#native_binary-name">name</a>, <a href="#native_binary-data">data</a>, <a href="#native_binary-out">out</a>, <a href="#native_binary-src">src</a>)
native_binary(<a href="#native_binary-name">name</a>, <a href="#native_binary-data">data</a>, <a href="#native_binary-env">env</a>, <a href="#native_binary-out">out</a>, <a href="#native_binary-src">src</a>)
</pre>


Expand All @@ -30,6 +34,7 @@ in genrule.tools for example. You can also augment the binary with runfiles.
| :------------- | :------------- | :------------- | :------------- | :------------- |
| <a id="native_binary-name"></a>name | A unique name for this target. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
| <a id="native_binary-data"></a>data | data dependencies. See https://bazel.build/reference/be/common-definitions#typical.data | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | <code>[]</code> |
| <a id="native_binary-env"></a>env | additional environment variables to set when the target is executed by <code>bazel</code>. Setting this requires bazel version 5.3.0 or later. | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | optional | <code>{}</code> |
| <a id="native_binary-out"></a>out | An output name for the copy of the binary | String | required | |
| <a id="native_binary-src"></a>src | path of the pre-built executable | <a href="https://bazel.build/concepts/labels">Label</a> | required | |

Expand All @@ -39,7 +44,7 @@ in genrule.tools for example. You can also augment the binary with runfiles.
## native_test

<pre>
native_test(<a href="#native_test-name">name</a>, <a href="#native_test-data">data</a>, <a href="#native_test-out">out</a>, <a href="#native_test-src">src</a>)
native_test(<a href="#native_test-name">name</a>, <a href="#native_test-data">data</a>, <a href="#native_test-env">env</a>, <a href="#native_test-env_inherit">env_inherit</a>, <a href="#native_test-out">out</a>, <a href="#native_test-src">src</a>)
</pre>


Expand All @@ -56,6 +61,8 @@ the binary with runfiles.
| :------------- | :------------- | :------------- | :------------- | :------------- |
| <a id="native_test-name"></a>name | A unique name for this target. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
| <a id="native_test-data"></a>data | data dependencies. See https://bazel.build/reference/be/common-definitions#typical.data | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | <code>[]</code> |
| <a id="native_test-env"></a>env | additional environment variables to set when the target is executed by <code>bazel</code>. Setting this requires bazel version 5.3.0 or later. | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | optional | <code>{}</code> |
| <a id="native_test-env_inherit"></a>env_inherit | additional environment variables to inherit from the external environment when the test is executed by <code>bazel test</code>. Setting this requires bazel version 5.3.0 or later. | List of strings | optional | <code>[]</code> |
| <a id="native_test-out"></a>out | An output name for the copy of the binary | String | required | |
| <a id="native_test-src"></a>src | path of the pre-built executable | <a href="https://bazel.build/concepts/labels">Label</a> | required | |

Expand Down
6 changes: 4 additions & 2 deletions docs/private/stardoc_with_diff_test.bzl
Expand Up @@ -30,7 +30,8 @@ load("@io_bazel_stardoc//stardoc:stardoc.bzl", "stardoc")
def stardoc_with_diff_test(
name,
bzl_library_target,
out_label):
out_label,
deps = None):
"""Creates a stardoc target coupled with a `diff_test` for a given `bzl_library`.

This is helpful for minimizing boilerplate in repos with lots of stardoc targets.
Expand All @@ -39,6 +40,7 @@ def stardoc_with_diff_test(
name: the stardoc target name
bzl_library_target: the label of the `bzl_library` target to generate documentation for
out_label: the label of the output MD file
deps: additional files loaded by the bazel library, if any
"""
out_file = out_label.replace("//", "").replace(":", "/")

Expand All @@ -47,7 +49,7 @@ def stardoc_with_diff_test(
name = name,
out = out_file.replace(".md", "-docgen.md"),
input = bzl_library_target + ".bzl",
deps = [bzl_library_target],
deps = [bzl_library_target] + (deps or []),
)

# Ensure that the generated MD has been updated in the local source tree
Expand Down
48 changes: 42 additions & 6 deletions rules/native_binary.bzl
Expand Up @@ -18,9 +18,20 @@ These rules let you wrap a pre-built binary or script in a conventional binary
and test rule respectively. They fulfill the same goal as sh_binary and sh_test
do, but they run the wrapped binary directly, instead of through Bash, so they
don't depend on Bash and work with --shell_executable="".

If `bazel_skylib` is loaded from `WORKSPACE` rather than with bzlmod, using
this library requires additional `WORKSPACE` setup as explained in the
[release page](https://github.com/bazelbuild/bazel-skylib/releases).
"""

load("@bazel_features//:features.bzl", "bazel_features")

def _impl_rule(ctx):
if not bazel_features.globals.RunEnvironmentInfo:
for attr in ("env", "env_inherit"):
if getattr(ctx.attr, attr, None):
fail("Attribute %s specified for %s is only supported with bazel >= 5.3.0" %
(attr, ctx.label))
out = ctx.actions.declare_file(ctx.attr.out)
ctx.actions.symlink(
target_file = ctx.executable.src,
Expand All @@ -41,11 +52,21 @@ def _impl_rule(ctx):
runfiles = runfiles.merge(d[DefaultInfo].default_runfiles)
runfiles = runfiles.merge(ctx.attr.src[DefaultInfo].default_runfiles)

return DefaultInfo(
executable = out,
files = depset([out]),
runfiles = runfiles,
)
ret = [
DefaultInfo(
executable = out,
files = depset([out]),
runfiles = runfiles,
),
]
if bazel_features.globals.RunEnvironmentInfo:
ret.append(
bazel_features.globals.RunEnvironmentInfo(
environment = ctx.attr.env,
inherited_environment = getattr(ctx.attr, "env_inherit", []),
),
)
return ret

_ATTRS = {
"src": attr.label(
Expand All @@ -65,8 +86,23 @@ _ATTRS = {
),
# "out" is attr.string instead of attr.output, so that it is select()'able.
"out": attr.string(mandatory = True, doc = "An output name for the copy of the binary"),
"env": attr.string_dict(
doc = "additional environment variables to set when the target is executed by " +
"`bazel`. Setting this requires bazel version 5.3.0 or later.",
default = {},
),
}

_TEST_ATTRS = dict(
_ATTRS,
env_inherit = attr.string_list(
doc = "additional environment variables to inherit from the external " +
"environment when the test is executed by `bazel test`. " +
"Setting this requires bazel version 5.3.0 or later.",
default = [],
),
)

native_binary = rule(
implementation = _impl_rule,
attrs = _ATTRS,
Expand All @@ -81,7 +117,7 @@ in genrule.tools for example. You can also augment the binary with runfiles.

native_test = rule(
implementation = _impl_rule,
attrs = _ATTRS,
attrs = _TEST_ATTRS,
test = True,
doc = """
Wraps a pre-built binary or script with a test rule.
Expand Down
31 changes: 31 additions & 0 deletions tests/native_binary/BUILD
Expand Up @@ -30,6 +30,11 @@ cc_binary(
deps = ["@bazel_tools//tools/cpp/runfiles"],
)

cc_binary(
name = "assertenv",
srcs = ["assertenv.cc"],
)

# A rule that copies "assertarg"'s output as an opaque executable, simulating a
# binary that's not built from source and needs to be wrapped in native_binary.
copy_file(
Expand All @@ -54,6 +59,16 @@ copy_file(
is_executable = True,
)

copy_file(
name = "copy_assertenv_exe",
src = ":assertenv",
# On Windows we need the ".exe" extension.
# On other platforms the extension doesn't matter.
# Therefore we can use ".exe" on every platform.
out = "assertenv_copy.exe",
is_executable = True,
)

_ARGS = [
"'a b'",
"c\\ d",
Expand Down Expand Up @@ -115,3 +130,19 @@ native_test(
# Therefore we can use ".exe" on every platform.
out = "data_from_binary_test.exe",
)

native_test(
name = "env_test",
src = ":copy_assertenv_exe",
# On Windows we need the ".exe" extension.
# On other platforms the extension doesn't matter.
# Therefore we can use ".exe" on every platform.
out = "env_test.exe",
env = {
"TEST_ENV_VAR": "test_env_var_value",
},
env_inherit = [
"HOME", # for POSIX
"APPDATA", # for Windows
],
)
31 changes: 31 additions & 0 deletions tests/native_binary/assertenv.cc
@@ -0,0 +1,31 @@
#include <stdio.h>
#include <string.h>

#ifdef _WIN32
#define OS_VAR "APPDATA"
#define strncasecmp _strnicmp
#else
#define OS_VAR "HOME"
#endif

int main(int argc, char **argv, char **envp) {
bool test_env_found = false;
bool inherited_var_found = false;
for (char **env = envp; *env != NULL; ++env) {
printf("%s\n", *env);
if (strcmp(*env, "TEST_ENV_VAR=test_env_var_value") == 0) {
test_env_found = true;
}
if (strncasecmp(*env, OS_VAR "=", strlen(OS_VAR "=")) == 0) {
inherited_var_found = true;
}
}
if (!test_env_found) {
fprintf(stderr,
"expected TEST_ENV_VAR=test_env_var_value in environment\n");
}
if (!inherited_var_found) {
fprintf(stderr, "expected " OS_VAR " in environment\n");
}
return test_env_found && inherited_var_found ? 0 : 1;
}
9 changes: 9 additions & 0 deletions workspace.bzl
Expand Up @@ -15,7 +15,16 @@
"""Dependency registration helpers for repositories which need to load bazel-skylib."""

load("@bazel_skylib//lib:unittest.bzl", "register_unittest_toolchains")
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")

def bazel_skylib_workspace():
"""Registers toolchains and declares repository dependencies of the bazel_skylib repository."""
register_unittest_toolchains()
maybe(
http_archive,
name = "bazel_features",
sha256 = "b8789c83c893d7ef3041d3f2795774936b27ff61701a705df52fd41d6ddbf692",
strip_prefix = "bazel_features-1.2.0",
url = "https://github.com/bazel-contrib/bazel_features/releases/download/v1.2.0/bazel_features-v1.2.0.tar.gz",
)