From 26b1ab1f854735c124714b77b6bd9e3a0f3b0562 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Aug 2026 19:28:21 +0000 Subject: [PATCH 1/2] Initial plan From a3a630bfcb07d287609220b1e1c5fe6cc194035a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Aug 2026 19:37:57 +0000 Subject: [PATCH 2/2] bazel/deps: Handle transitions in reachability aspect (re-land #5149) Supersedes envoyproxy/toolshed#5149. Resolves conflicts by: - Split transition carries both config-matrix flags and the two exclusion build settings (_excluded_edges, _excluded_patterns) so exec-cfg safety is preserved across all analyzed configurations. - consumers[].attrs is accumulated and emitted as a sorted union across configs, consistent with roots union semantics. New features (from #5149): - dependency_reachability_rule(flags, defines) constructor for split transitions - dependency_reachability_macro(impl) macro wrapper accepting configs dict - Multi-config JSON output with per-repo configs list and union merge semantics - _decode_configs, _encode_configs, config_validation_error, merge_defines helpers (config_validation_error and merge_defines exported for unit testing) Kept from main: - excluded_edges / excluded_patterns attrs on all rule forms - _allowlist_function_transition - consumers[].attrs in JSON output with union semantics across configs - BuildSettingInfo transport for exec-cfg safe exclusions Tests: - reachability_test_rules.bzl: test rule constructed with reachability_mode flag - reachability_config_validation_test.bzl: unit tests for config_validation_error - reachability_merge_defines_test.bzl: unit tests for merge_defines - reachability_test.sh: multi-config assertions including consumers[].attrs union - BUILD: new string_flag, config_setting, variant filegroups, multiconfig target Co-authored-by: phlax <454682+phlax@users.noreply.github.com> --- bazel/dependency/reachability.bzl | 354 ++++++++++++++---- bazel/dependency/test/BUILD | 68 +++- .../reachability_config_validation_test.bzl | 31 ++ .../test/reachability_merge_defines_test.bzl | 27 ++ bazel/dependency/test/reachability_test.sh | 50 ++- .../test/reachability_test_rules.bzl | 11 + 6 files changed, 460 insertions(+), 81 deletions(-) create mode 100644 bazel/dependency/test/reachability_config_validation_test.bzl create mode 100644 bazel/dependency/test/reachability_merge_defines_test.bzl create mode 100644 bazel/dependency/test/reachability_test_rules.bzl diff --git a/bazel/dependency/reachability.bzl b/bazel/dependency/reachability.bzl index 7c36fd5ba1..fc6f865b39 100644 --- a/bazel/dependency/reachability.bzl +++ b/bazel/dependency/reachability.bzl @@ -27,6 +27,39 @@ sh_test( ) ``` +For multi-configuration analysis, construct a reachability rule whose transition +outputs are fixed up front, then pass a config matrix: + +```starlark +load( + "@envoy_toolshed//dependency:reachability.bzl", + "dependency_reachability_macro", + "dependency_reachability_rule", +) + +_envoy_dependency_reachability = dependency_reachability_rule( + flags = ["//bazel:wasm_runtime"], + defines = True, +) + +envoy_dependency_reachability = dependency_reachability_macro(_envoy_dependency_reachability) + +envoy_dependency_reachability( + name = "dep-reachability", + roots = ["//source/exe:envoy_main_common_with_core_extensions_lib"], + configs = { + "default": {}, + "wasmtime": {"//bazel:wasm_runtime": "wasmtime"}, + "wamr": {"//bazel:wasm_runtime": "wamr"}, + "legacy-define": {"wasm": "v8"}, + }, +) +``` + +`flags` must be fixed at construction because transition `outputs` are static: +they cannot vary per target instantiation. Prefer Starlark build settings where +possible. `defines = True` is supported for legacy `--define`-based matrices. + Building the target writes `.json`: ```json @@ -35,6 +68,7 @@ Building the target writes `.json`: "": { "name": "", "production": true, + "configs": ["default"], "reached_by": [ {"root": "//source/exe:envoy_main_common_with_core_extensions_lib", "production": true} ], @@ -73,6 +107,16 @@ Building the target writes `.json`: | "\\(.target)\\t\\(.attrs | join(","))"' dep-reachability.json ``` +- `configs` lists the analyzed config names in which the repository is reached. + Reachability data is only as complete as the declared matrix: if a dependency + is reachable in no declared config, it is absent from the emitted JSON. + +When a dependency is reached in multiple analyzed configs, data is merged by +union semantics: `targets`/`configs` are unioned, `consumers[*].roots` and +`consumers[*].attrs` are unioned, and +`production`/`reached_by[*].production`/`consumers[*].testonly` are merged +with logical OR. + The aspect emits raw truth: no repository is filtered. Policy (ignore lists, test-only exemptions, bucketing by surface/extension/contrib) belongs in the consumer of the JSON. @@ -92,7 +136,9 @@ Exclusion settings (`excluded_edges`, `excluded_patterns`) are transported via Starlark build settings rather than `--features`, so they survive Bazel's exec configuration transition. They therefore apply uniformly across target and exec configurations: edges reached through `cfg = "exec"` attributes are excluded -just as reliably as edges in the target configuration. +just as reliably as edges in the target configuration. The exclusion settings +are carried through every branch of the split transition so they apply +uniformly across all analyzed configurations. Note on exclusion semantics: an excluded repository is neither recorded nor descended into. Pruning descent means that repositories reachable *only through* @@ -172,20 +218,57 @@ def _repo_is_excluded(repo_name, patterns): return True return False -def _reachability_transition_impl(settings, attr): - return { - _EXCLUDED_EDGES_SETTING: attr.excluded_edges, - _EXCLUDED_PATTERNS_SETTING: attr.excluded_patterns, - } +def _decode_configs(configs_attr): + configs = {} + for config in sorted(configs_attr.keys()): + values = {} + for assignment in configs_attr[config]: + if "=" not in assignment: + fail("Invalid config assignment '{}' in config '{}' (expected '=')".format(assignment, config)) + key, value = assignment.split("=", 1) + values[key] = value + configs[config] = values + if not configs: + return {"default": {}} + return configs -_reachability_transition = transition( - implementation = _reachability_transition_impl, - inputs = [], - outputs = [ - _EXCLUDED_EDGES_SETTING, - _EXCLUDED_PATTERNS_SETTING, - ], -) +def config_validation_error(configs, flags, defines): + allowed = {flag: True for flag in flags} + declared = sorted(flags) + for config in sorted(configs.keys()): + for label in sorted(configs[config].keys()): + if label.startswith("//"): + if label not in allowed: + return "Config '{}' varies '{}' but it is not declared in flags. Declared flags: {}".format( + config, + label, + declared, + ) + elif not defines: + return "Config '{}' varies define '{}' but this rule was constructed with defines = False".format( + config, + label, + ) + return None + +def _validate_config_labels(configs, flags, defines): + error = config_validation_error(configs, flags, defines) + if error != None: + fail(error) + +def _merge_defines(existing, values): + merged = {} + for define in existing: + if "=" not in define: + continue + key, value = define.split("=", 1) + merged[key] = value + for key in sorted(values.keys()): + if not key.startswith("//"): + merged[key] = values[key] + return ["{}={}".format(key, merged[key]) for key in sorted(merged.keys())] + +merge_defines = _merge_defines def _reachability_aspect_impl(target, ctx): consumer = _label_string(target.label) @@ -253,81 +336,142 @@ reachability_aspect = aspect( ), ) -def _dependency_reachability_impl(ctx): - deps = {} - for target in ctx.attr.roots: - root = _label_string(target.label) - info = target[DependencyReachabilityInfo] - production = {edge: True for edge in info.production_edges.to_list()} - for edge in info.edges.to_list(): - entry = deps.setdefault(edge.repo, dict( - name = edge.name, - reached_by = {}, - targets = {}, - consumers = {}, - )) - entry["targets"][edge.target] = True - reached = entry["reached_by"].setdefault(root, dict( - production = False, - )) - if edge in production: - reached["production"] = True - consumer = entry["consumers"].setdefault(edge.consumer, dict( - repo = edge.consumer_repo, - testonly = edge.testonly, - attrs = {}, - roots = {}, - )) - consumer["attrs"][edge.attr] = True - consumer["roots"][root] = True - dependencies = {} - for repo in sorted(deps.keys()): - entry = deps[repo] - reached_by = [ - dict( - root = root, - production = entry["reached_by"][root]["production"], - ) - for root in sorted(entry["reached_by"].keys()) - ] - dependencies[repo] = dict( - name = entry["name"], - production = any([ - reached["production"] - for reached in reached_by - ]), - reached_by = reached_by, - targets = sorted(entry["targets"].keys()), - consumers = [ +def _record_edge(deps, config, root, edge, production): + entry = deps.setdefault(edge.repo, dict( + name = edge.name, + reached_by = {}, + targets = {}, + consumers = {}, + configs = {}, + )) + entry["configs"][config] = True + entry["targets"][edge.target] = True + reached = entry["reached_by"].setdefault(root, dict( + production = False, + )) + if production: + reached["production"] = True + consumer = entry["consumers"].setdefault(edge.consumer, dict( + repo = edge.consumer_repo, + testonly = False, + attrs = {}, + roots = {}, + )) + consumer["testonly"] = consumer["testonly"] or edge.testonly + consumer["attrs"][edge.attr] = True + consumer["roots"][root] = True + +def _dependency_reachability_impl(): + def _impl(ctx): + deps = {} + # Split transitions fan out each root once per config, so flattening + # depsets here scales with the declared matrix size. + for config in sorted(ctx.split_attr.roots.keys()): + for target in ctx.split_attr.roots[config]: + root = _label_string(target.label) + info = target[DependencyReachabilityInfo] + production = {edge: True for edge in info.production_edges.to_list()} + for edge in info.edges.to_list(): + _record_edge( + deps, + config, + root, + edge, + production = edge in production, + ) + dependencies = {} + for repo in sorted(deps.keys()): + entry = deps[repo] + reached_by = [ dict( - target = consumer, - repo = entry["consumers"][consumer]["repo"], - testonly = entry["consumers"][consumer]["testonly"], - attrs = sorted(entry["consumers"][consumer]["attrs"].keys()), - roots = sorted(entry["consumers"][consumer]["roots"].keys()), + root = root, + production = entry["reached_by"][root]["production"], ) - for consumer in sorted(entry["consumers"].keys()) - ], + for root in sorted(entry["reached_by"].keys()) + ] + dependencies[repo] = dict( + name = entry["name"], + production = any([ + reached["production"] + for reached in reached_by + ]), + configs = sorted(entry["configs"].keys()), + reached_by = reached_by, + targets = sorted(entry["targets"].keys()), + consumers = [ + dict( + target = consumer, + repo = entry["consumers"][consumer]["repo"], + testonly = entry["consumers"][consumer]["testonly"], + attrs = sorted(entry["consumers"][consumer]["attrs"].keys()), + roots = sorted(entry["consumers"][consumer]["roots"].keys()), + ) + for consumer in sorted(entry["consumers"].keys()) + ], + ) + output = ctx.actions.declare_file("%s.json" % ctx.label.name) + ctx.actions.write( + output = output, + content = json.encode_indent( + dict(dependencies = dependencies), + indent = " ", + ) + "\n", ) - output = ctx.actions.declare_file("%s.json" % ctx.label.name) - ctx.actions.write( - output = output, - content = json.encode_indent( - dict(dependencies = dependencies), - indent = " ", - ) + "\n", + return [DefaultInfo(files = depset([output]))] + + return _impl + +def _dependency_reachability_transition(flags, defines): + def _impl(settings, attr): + configs = _decode_configs(attr.configs) + _validate_config_labels(configs, flags, defines) + transitioned = {} + for config in sorted(configs.keys()): + values = configs[config] + output = {} + for flag in flags: + output[flag] = values.get(flag, settings[flag]) + if defines: + output["//command_line_option:define"] = _merge_defines( + settings["//command_line_option:define"], + values, + ) + # Carry exclusion settings through every branch of the split so they + # apply uniformly across all analyzed configurations. + output[_EXCLUDED_EDGES_SETTING] = attr.excluded_edges + output[_EXCLUDED_PATTERNS_SETTING] = attr.excluded_patterns + transitioned[config] = output + return transitioned + + flag_inputs = list(flags) + if defines: + flag_inputs.append("//command_line_option:define") + transition_outputs = flag_inputs + [_EXCLUDED_EDGES_SETTING, _EXCLUDED_PATTERNS_SETTING] + return transition( + implementation = _impl, + inputs = flag_inputs, + outputs = transition_outputs, ) - return [DefaultInfo(files = depset([output]))] -def dependency_reachability_rule(): +def _dependency_reachability_rule(flags = [], defines = False): + """Construct the internal dependency_reachability rule. + + flags: list of build setting labels (string_flag/bool_flag/label_flag) that + instantiated targets may vary. Fixed at construction because transition + outputs must be static. + defines: whether --define may also be varied (adds + //command_line_option:define to outputs). Prefer Starlark settings; + this exists for legacy define-based consumers. + """ + reachability_transition = _dependency_reachability_transition(flags, defines) return rule( - implementation = _dependency_reachability_impl, + implementation = _dependency_reachability_impl(), attrs = { "roots": attr.label_list( aspects = [reachability_aspect], allow_files = True, mandatory = True, - cfg = _reachability_transition, + cfg = reachability_transition, doc = ( "Concrete root targets to analyze. Each entry must be a " + "resolved label — Bazel target patterns such as " + @@ -341,6 +485,15 @@ def dependency_reachability_rule(): "the root is." ), ), + "configs": attr.string_list_dict( + default = {"default": []}, + doc = ( + "Configuration matrix keyed by config name. Each value is " + + "a list of '=' assignments. " + + "Use the dependency_reachability macro form to pass a " + + "dict of assignment maps." + ), + ), "excluded_edges": attr.string_list( default = [], doc = ( @@ -372,4 +525,47 @@ def dependency_reachability_rule(): ), ) -dependency_reachability = dependency_reachability_rule() +def dependency_reachability_macro(impl): + def _macro(name, roots, configs = None, **kwargs): + impl( + name = name, + roots = roots, + configs = _encode_configs(configs), + **kwargs + ) + + return _macro + +def _encode_configs(configs): + if configs == None: + return {"default": []} + if type(configs) != "dict": + fail("configs must be a dict of config-name -> dict(flag_or_define -> value)") + if not configs: + return {"default": []} + encoded = {} + for config in sorted(configs.keys()): + assignments = configs[config] + if type(assignments) != "dict": + fail("configs['{}'] must be a dict(flag_or_define -> value)".format(config)) + encoded[config] = [ + "{}={}".format(key, assignments[key]) + for key in sorted(assignments.keys()) + ] + return encoded + +def dependency_reachability_rule(flags = [], defines = False): + """Construct a dependency_reachability rule varying the given build settings. + + flags: list of build setting labels (string_flag/bool_flag/label_flag) that + instantiated targets may vary. Fixed at construction because transition + outputs must be static. + defines: whether --define may also be varied (adds + //command_line_option:define to outputs). Prefer Starlark settings; + this exists for legacy define-based consumers. + """ + return _dependency_reachability_rule(flags, defines) + +_dependency_reachability = dependency_reachability_rule() + +dependency_reachability = dependency_reachability_macro(_dependency_reachability) diff --git a/bazel/dependency/test/BUILD b/bazel/dependency/test/BUILD index 1d4f07f67d..746c1f34a3 100644 --- a/bazel/dependency/test/BUILD +++ b/bazel/dependency/test/BUILD @@ -1,8 +1,12 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +load("@bazel_skylib//rules:common_settings.bzl", "string_flag") load("@rules_shell//shell:sh_test.bzl", "sh_test") load("//dependency:reachability.bzl", "dependency_reachability") load(":custom_rule_test.bzl", "custom_library_rule") load(":reachability_apparent_name_test.bzl", "apparent_name_test") +load(":reachability_config_validation_test.bzl", "reachability_config_validation_test") +load(":reachability_merge_defines_test.bzl", "reachability_merge_defines_test") +load(":reachability_test_rules.bzl", "dependency_reachability_with_mode") sh_test( name = "updater_test", @@ -48,12 +52,61 @@ dependency_reachability( ], ) +string_flag( + name = "reachability_mode", + build_setting_default = "default", + values = ["default", "extra"], +) + +config_setting( + name = "reachability_mode_extra", + flag_values = {":reachability_mode": "extra"}, +) + +filegroup( + name = "variant_external_consumer", + srcs = [ + "@bazel_skylib//lib:paths", + ] + select({ + ":reachability_mode_extra": ["@jq_toolchains//:resolved_toolchain"], + "//conditions:default": [], + }), +) + +filegroup( + name = "variant_core_root", + srcs = [":variant_external_consumer"], +) + +filegroup( + name = "variant_test_root", + testonly = True, + srcs = [ + ":variant_external_consumer", + "@bazel_skylib//lib:sets", + ], +) + +dependency_reachability_with_mode( + name = "reachability_multiconfig", + testonly = True, + roots = [ + ":variant_core_root", + ":variant_test_root", + ], + configs = { + "default": {}, + "extra": {"//dependency/test:reachability_mode": "extra"}, + }, +) + sh_test( name = "reachability_test", size = "small", srcs = ["reachability_test.sh"], data = [ ":reachability", + ":reachability_multiconfig", "@jq_toolchains//:resolved_toolchain", ], env = { @@ -206,7 +259,12 @@ sh_test( bzl_library( name = "reachability_apparent_name_test_lib", - srcs = ["reachability_apparent_name_test.bzl"], + srcs = [ + "reachability_apparent_name_test.bzl", + "reachability_config_validation_test.bzl", + "reachability_merge_defines_test.bzl", + "reachability_test_rules.bzl", + ], deps = [ "@bazel_skylib//lib:unittest", ], @@ -215,3 +273,11 @@ bzl_library( apparent_name_test( name = "apparent_name_test", ) + +reachability_config_validation_test( + name = "reachability_config_validation_test", +) + +reachability_merge_defines_test( + name = "reachability_merge_defines_test", +) diff --git a/bazel/dependency/test/reachability_config_validation_test.bzl b/bazel/dependency/test/reachability_config_validation_test.bzl new file mode 100644 index 0000000000..68119fed02 --- /dev/null +++ b/bazel/dependency/test/reachability_config_validation_test.bzl @@ -0,0 +1,31 @@ +load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") +load("//dependency:reachability.bzl", "config_validation_error") + +def _reachability_config_validation_test_impl(ctx): + env = unittest.begin(ctx) + + message = config_validation_error( + {"invalid": {"//dependency/test:reachability_mode": "extra"}}, + [], + False, + ) + asserts.equals( + env, + "Config 'invalid' varies '//dependency/test:reachability_mode' but it is not declared in flags. Declared flags: []", + message, + ) + + message = config_validation_error( + {"invalid_define": {"wasm": "wasmtime"}}, + ["//dependency/test:reachability_mode"], + False, + ) + asserts.equals( + env, + "Config 'invalid_define' varies define 'wasm' but this rule was constructed with defines = False", + message, + ) + + return unittest.end(env) + +reachability_config_validation_test = unittest.make(_reachability_config_validation_test_impl) diff --git a/bazel/dependency/test/reachability_merge_defines_test.bzl b/bazel/dependency/test/reachability_merge_defines_test.bzl new file mode 100644 index 0000000000..7946f45f44 --- /dev/null +++ b/bazel/dependency/test/reachability_merge_defines_test.bzl @@ -0,0 +1,27 @@ +load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") +load("//dependency:reachability.bzl", "merge_defines") + +def _reachability_merge_defines_test_impl(ctx): + env = unittest.begin(ctx) + + merged = merge_defines( + [ + "existing=old", + "skipmalformed", + "override=old", + ], + { + "//dependency/test:reachability_mode": "extra", + "override": "new", + "added": "value", + }, + ) + asserts.equals( + env, + ["added=value", "existing=old", "override=new"], + merged, + ) + + return unittest.end(env) + +reachability_merge_defines_test = unittest.make(_reachability_merge_defines_test_impl) diff --git a/bazel/dependency/test/reachability_test.sh b/bazel/dependency/test/reachability_test.sh index 9e35107677..121e2127a9 100755 --- a/bazel/dependency/test/reachability_test.sh +++ b/bazel/dependency/test/reachability_test.sh @@ -15,6 +15,7 @@ if [ -n "${TEST_SRCDIR:-}" ]; then RUNFILES_DIR="${TEST_SRCDIR}/_main" fi REACHABILITY_JSON="${RUNFILES_DIR}/dependency/test/reachability.json" + REACHABILITY_MULTICONFIG_JSON="${RUNFILES_DIR}/dependency/test/reachability_multiconfig.json" else echo "This test must be run under Bazel" >&2 exit 1 @@ -28,8 +29,9 @@ check() { local description="$1" local query="$2" local expected="$3" + local file="${4:-${REACHABILITY_JSON}}" local actual - actual="$("${JQ}" -r "${query}" "${REACHABILITY_JSON}")" + actual="$("${JQ}" -r "${query}" "${file}")" if [ "${actual}" != "${expected}" ]; then echo "FAIL: ${description}" >&2 echo " query: ${query}" >&2 @@ -45,6 +47,7 @@ check() { # ("bazel_skylib") and bzlmod ("bazel_skylib+"/"bazel_skylib~") builds, so # select entries via the emitted apparent name. SKYLIB='.dependencies | to_entries[] | select(.value.name == "bazel_skylib") | .value' +JQ_TOOLCHAINS='.dependencies | to_entries[] | select(.value.name == "jq_toolchains") | .value' check "bazel_skylib is the only reported dependency" \ '[.dependencies[] | .name] | unique | join(",")' \ @@ -82,6 +85,51 @@ check "testonly consumer is only attributed to the test root" \ "${SKYLIB} | .consumers[] | select(.target == \"//dependency/test:test_root\") | .roots | join(\",\")" \ "//dependency/test:test_root" +check "multi-config bazel_skylib records all configs" \ + "${SKYLIB} | .configs | sort | join(\",\")" \ + "default,extra" \ + "${REACHABILITY_MULTICONFIG_JSON}" + +check "multi-config bazel_skylib remains production because one root is non-testonly" \ + "${SKYLIB} | .production" \ + "true" \ + "${REACHABILITY_MULTICONFIG_JSON}" + +check "multi-config bazel_skylib reached_by preserves both roots with unioned production" \ + "${SKYLIB} | [.reached_by[] | \"\(.root) \(.production)\"] | sort | join(\",\")" \ + "//dependency/test:variant_core_root true,//dependency/test:variant_test_root false" \ + "${REACHABILITY_MULTICONFIG_JSON}" + +check "multi-config bazel_skylib testonly root remains scoped to that root" \ + "${SKYLIB} | .consumers[] | select(.target == \"//dependency/test:variant_test_root\") | .roots | sort | join(\",\")" \ + "//dependency/test:variant_test_root" \ + "${REACHABILITY_MULTICONFIG_JSON}" + +check "config-gated jq_toolchains dependency appears in merged output" \ + "${JQ_TOOLCHAINS} | .name" \ + "jq_toolchains" \ + "${REACHABILITY_MULTICONFIG_JSON}" + +check "config-gated jq_toolchains dependency is attributed to extra only" \ + "${JQ_TOOLCHAINS} | .configs | join(\",\")" \ + "extra" \ + "${REACHABILITY_MULTICONFIG_JSON}" + +check "config-gated jq_toolchains dependency tracks only the root and consumer that reach it" \ + "${JQ_TOOLCHAINS} | [.consumers[] | \"\(.target) \(.testonly) \(.roots | sort | join(\"|\"))\"] | sort | join(\",\")" \ + "//dependency/test:variant_external_consumer false //dependency/test:variant_core_root|//dependency/test:variant_test_root" \ + "${REACHABILITY_MULTICONFIG_JSON}" + +check "config-gated jq_toolchains consumer attrs are recorded and unioned across configs" \ + "${JQ_TOOLCHAINS} | .consumers[] | select(.target == \"//dependency/test:variant_external_consumer\") | .attrs | join(\",\")" \ + "srcs" \ + "${REACHABILITY_MULTICONFIG_JSON}" + +check "multi-config bazel_skylib consumer attrs are recorded" \ + "${SKYLIB} | .consumers[] | select(.target == \"//dependency/test:variant_external_consumer\") | .attrs | join(\",\")" \ + "srcs" \ + "${REACHABILITY_MULTICONFIG_JSON}" + if [ "${FAILED}" -ne 0 ]; then exit 1 fi diff --git a/bazel/dependency/test/reachability_test_rules.bzl b/bazel/dependency/test/reachability_test_rules.bzl new file mode 100644 index 0000000000..6fb060110c --- /dev/null +++ b/bazel/dependency/test/reachability_test_rules.bzl @@ -0,0 +1,11 @@ +load( + "//dependency:reachability.bzl", + "dependency_reachability_macro", + "dependency_reachability_rule", +) + +_dependency_reachability_with_mode = dependency_reachability_rule( + flags = ["//dependency/test:reachability_mode"], +) + +dependency_reachability_with_mode = dependency_reachability_macro(_dependency_reachability_with_mode)