Skip to content

Commit d2468a5

Browse files
committed
Implement aspect for checking_deps
1 parent 326dd1e commit d2468a5

File tree

11 files changed

+262
-8
lines changed

11 files changed

+262
-8
lines changed

MODULE.bazel.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cc/check_allowed_cc_deps.bzl

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
"""Aspect to validate allowed C/C++ dependencies at build time.
2+
3+
This aspect enforces the dependency hierarchy based on tags:
4+
- internal (level 0): Can depend on anything
5+
- prod (level 1): Can depend on prod or safe only
6+
- safe (level 2): Can depend on safe only
7+
8+
Additionally:
9+
- portable targets can only depend on other portable targets
10+
- Targets without level tags (including external dependencies) are ignored
11+
"""
12+
13+
# Provider to carry dependency level information through the dependency graph
14+
DependencyLevelInfo = provider(
15+
"Information about a target's dependency level",
16+
fields = {
17+
"level": "Dependency level (0=internal, 1=prod, 2=safe, None=no level)",
18+
"portable": "Whether the target is portable",
19+
"label": "The target's label"
20+
}
21+
)
22+
23+
def _get_coding_level_from_tags(tags):
24+
"""Extract coding standard level from tags.
25+
26+
Returns:
27+
0 for internal, 1 for prod, 2 for safe, None if no level found
28+
"""
29+
if "internal" in tags:
30+
return 0
31+
if "prod" in tags:
32+
return 1
33+
if "safe" in tags:
34+
return 2
35+
return None
36+
37+
def _level_to_str(level):
38+
"""Convert numeric level to string."""
39+
if level == 0:
40+
return "internal"
41+
if level == 1:
42+
return "prod"
43+
if level == 2:
44+
return "safe"
45+
return "unknown"
46+
47+
def _is_portable(tags):
48+
"""Check if target is marked as portable."""
49+
return "portable" in tags
50+
51+
def _check_allowed_cc_deps_impl(target, ctx):
52+
"""Aspect implementation to check allowed C/C++ dependencies."""
53+
54+
# Skip non-C/C++ targets
55+
if not CcInfo in target:
56+
return []
57+
58+
# Skip if this is not a rule (e.g., a source file)
59+
if not hasattr(ctx.rule, "attr"):
60+
return []
61+
62+
# Get the current target's tags
63+
current_tags = getattr(ctx.rule.attr, "tags", [])
64+
current_level = _get_coding_level_from_tags(current_tags)
65+
current_portable = _is_portable(current_tags)
66+
label_str = str(target.label)
67+
68+
# If target has no dependency level, skip validation
69+
# This includes external dependencies, test libraries, and other untagged targets
70+
if current_level == None:
71+
# Check if it's a test target - tests are implicitly internal (level 0)
72+
if "test" in current_tags or "test_library" in current_tags:
73+
current_level = 0
74+
else:
75+
# Return provider indicating no level (external deps, untagged targets, etc.)
76+
return [DependencyLevelInfo(
77+
level = None,
78+
portable = current_portable,
79+
label = label_str
80+
)]
81+
82+
# Check dependencies
83+
deps = getattr(ctx.rule.attr, "deps", [])
84+
85+
for dep in deps:
86+
# Get dependency level info from dependency via provider
87+
if not DependencyLevelInfo in dep:
88+
# Dependency doesn't have dependency level info - skip it
89+
continue
90+
91+
dep_info = dep[DependencyLevelInfo]
92+
dep_level = dep_info.level
93+
dep_portable = dep_info.portable
94+
95+
# If dependency has no level, skip validation
96+
# This handles external dependencies and untagged targets
97+
if dep_level == None:
98+
continue
99+
100+
# Check dependency level hierarchy
101+
if dep_level < current_level:
102+
error_msg = ("ERROR: Target {} (level={}) cannot depend on {} (level={}). " +
103+
"Higher level targets can only depend on same or higher level targets.").format(
104+
target.label,
105+
_level_to_str(current_level),
106+
dep_info.label,
107+
_level_to_str(dep_level)
108+
)
109+
fail(error_msg)
110+
111+
# Check portability requirements
112+
if current_portable and not dep_portable:
113+
error_msg = ("ERROR: Target {} is marked as portable but depends on {} which is not portable. " +
114+
"Portable targets can only depend on other portable targets.").format(
115+
target.label,
116+
dep_info.label
117+
)
118+
fail(error_msg)
119+
120+
# Return provider for this target
121+
return [DependencyLevelInfo(
122+
level = current_level,
123+
portable = current_portable,
124+
label = label_str
125+
)]
126+
127+
check_allowed_cc_deps = aspect(
128+
implementation = _check_allowed_cc_deps_impl,
129+
attr_aspects = ["deps"],
130+
attrs = {}
131+
)
132+
133+
def _validate_allowed_deps_rule_impl(ctx):
134+
"""Rule that applies the validation aspect to specified targets."""
135+
# This rule doesn't produce any output, it just triggers the aspect
136+
output = ctx.actions.declare_file(ctx.label.name + ".validation")
137+
ctx.actions.write(
138+
output = output,
139+
content = "Allowed C/C++ dependency validation passed\n"
140+
)
141+
return [DefaultInfo(files = depset([output]))]
142+
143+
validate_allowed_cc_deps = rule(
144+
implementation = _validate_allowed_deps_rule_impl,
145+
attrs = {
146+
"targets": attr.label_list(
147+
aspects = [check_allowed_cc_deps],
148+
doc = "Targets to validate"
149+
)
150+
},
151+
doc = "Validates allowed C/C++ dependencies for specified targets"
152+
)
153+
154+
def validate_all_allowed_cc_deps(name, targets = ["//..."]):
155+
"""Macro to create a validation target for allowed C/C++ dependencies.
156+
157+
Args:
158+
name: Name of the validation target
159+
targets: List of target patterns to validate (default: all targets)
160+
"""
161+
validate_allowed_cc_deps(
162+
name = name,
163+
targets = targets,
164+
tags = ["manual"] # Don't build by default, only when explicitly requested
165+
)

examples/small_world/BUILD.bazel

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
load("@buildifier_prebuilt//:rules.bzl", "buildifier")
2+
load("@rules_swiftnav//cc:check_allowed_cc_deps.bzl", "validate_allowed_cc_deps")
23

34
buildifier(
45
name = "buildifier",
56
)
7+
8+
# Validation target to check allowed C/C++ dependencies
9+
# Run with: bazel build //:validate_deps
10+
validate_allowed_cc_deps(
11+
name = "validate_deps",
12+
targets = [
13+
"//src/base_math:base_math",
14+
"//src/fibonacci:fibonacci",
15+
],
16+
)

examples/small_world/MODULE.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ register_toolchains(
2525
"@rules_swiftnav//cc/toolchains/llvm20/x86_64-linux:cc-toolchain-x86_64-linux",
2626
)
2727

28+
bazel_dep(name = "eigen", version = "5.0.0")
29+
2830
bazel_dep(name = "googletest", version = "1.16.0", dev_dependency = True)
2931
bazel_dep(name = "lcov", version = "2.3.1", dev_dependency = True)
3032

examples/small_world/MODULE.bazel.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/small_world/src/base_math/BUILD.bazel

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
load("@rules_swiftnav//cc:defs.bzl", "UNIT", "swift_cc_library", "swift_cc_test")
1+
load("@rules_swiftnav//cc:defs2.bzl", "UNIT", "swift_add_prod_cc_library", "swift_add_cc_test")
22

3-
swift_cc_library(
3+
swift_add_prod_cc_library(
44
name = "base_math",
55
srcs = ["base_math.cc"],
66
hdrs = ["base_math.hpp"],
77
visibility = ["//visibility:public"],
88
)
99

10-
swift_cc_test(
10+
swift_add_cc_test(
1111
name = "base_math_test",
1212
srcs = ["base_math_test.cc"],
1313
type = UNIT,

examples/small_world/src/fibonacci/BUILD.bazel

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
load("@rules_swiftnav//cc:defs.bzl", "UNIT", "swift_cc_library", "swift_cc_test")
2-
3-
swift_cc_library(
1+
load("@rules_swiftnav//cc:defs2.bzl", "UNIT", "swift_add_safe_cc_library", "swift_add_cc_test")
2+
3+
swift_add_safe_cc_library(
44
name = "fibonacci",
55
srcs = ["fibonacci.cc"],
66
hdrs = ["fibonacci.hpp"],
77
visibility = ["//visibility:public"],
88
deps = ["//src/base_math"],
99
)
1010

11-
swift_cc_test(
11+
swift_add_cc_test(
1212
name = "fibonacci_test",
1313
srcs = ["fibonacci_test.cc"],
1414
type = UNIT,
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
load("@rules_swiftnav//cc:defs2.bzl", "UNIT", "swift_add_prod_cc_library", "swift_add_cc_test")
2+
3+
swift_add_prod_cc_library(
4+
name = "use_external_dep",
5+
srcs = ["use_external_dep.cc"],
6+
hdrs = ["use_external_dep.hpp"],
7+
visibility = ["//visibility:public"],
8+
deps = [
9+
"@eigen",
10+
],
11+
)
12+
13+
swift_add_cc_test(
14+
name = "use_external_dep_test",
15+
srcs = ["use_external_dep_test.cc"],
16+
type = UNIT,
17+
deps = [
18+
":use_external_dep",
19+
"@googletest//:gtest_main",
20+
],
21+
)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include "use_external_dep.hpp"
2+
3+
double compute_dot_product(const Eigen::Vector3d& a, const Eigen::Vector3d& b) {
4+
return a.dot(b);
5+
}
6+
7+
double compute_magnitude(const Eigen::Vector3d& v) {
8+
return v.norm();
9+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#ifndef USE_EXTERNAL_DEP_HPP
2+
#define USE_EXTERNAL_DEP_HPP
3+
4+
#include <Eigen/Dense>
5+
6+
// Compute the dot product of two 3D vectors using Eigen
7+
double compute_dot_product(const Eigen::Vector3d& a, const Eigen::Vector3d& b);
8+
9+
// Compute the magnitude of a 3D vector using Eigen
10+
double compute_magnitude(const Eigen::Vector3d& v);
11+
12+
#endif

0 commit comments

Comments
 (0)