bazel: replace -layering_check overrides with scoped private_hdrs#10489
bazel: replace -layering_check overrides with scoped private_hdrs#10489maliberty wants to merge 3 commits into
Conversation
Tests that needed module-internal headers were disabling layering_check
via features = ["-layering_check"]. Mirror the cts pattern instead: a
private_hdrs cc_library with visibility = ["//src/<mod>:__subpackages__"]
exposes the internal headers to the module's own tests only.
- cts: broaden private_hdrs visibility from private to subpackages;
drop the test's -layering_check
- gpl: broaden gpl_private_hdrs visibility, add fft.h, drop flag
- drt: broaden drt_private_hdrs visibility, drop flag + ../src include
- dst: introduce private_hdrs for the four src/*.h files; tests +
the distributed binary all depend on it
- rsz: introduce private_hdrs covering the .hh files referenced by
TestNestedJournal/TestResizerMt plus their transitive .hh
chain; move those .hh files out of :rsz.srcs
- dft: drop stale -layering_check; the four tests already list every
cc_library whose headers they include
With this change, no BUILD file in src/ disables layering_check.
Fixes The-OpenROAD-Project#10478
Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
There was a problem hiding this comment.
Code Review
This pull request improves build hygiene by enabling layering_check across several modules, including cts, dft, drt, dst, gpl, and rsz. The changes involve creating or exposing private_hdrs libraries and updating test targets to depend on them rather than using relative include paths or disabling layering checks. A review comment identifies that the TestResizer target in src/rsz/test/BUILD is missing the //src/rsz:private_hdrs dependency, which will likely cause a compilation failure after the removal of the layering_check feature and internal include paths.
| copts = ["-Isrc/rsz/src"], | ||
| data = glob(["cpp/TestResizer*.v"]), | ||
| features = ["-layering_check"], # TODO: includes private headers | ||
| deps = [ |
There was a problem hiding this comment.
The TestResizer target is missing a dependency on //src/rsz:private_hdrs. Since features = ["-layering_check"] and the internal include path were removed, this test will likely fail to compile if it includes any of the headers now moved to private_hdrs. This target had the same TODO: includes private headers comment as TestNestedJournal and TestResizerMt, which both received the dependency update.
| deps = [ | |
| deps = [ | |
| "//src/rsz:private_hdrs", |
|
clang-tidy review says "All clean, LGTM! 👍" |
| "src/BalancerConnection.h", | ||
| "src/LoadBalancer.h", | ||
| "src/Worker.h", | ||
| "src/WorkerConnection.h", |
There was a problem hiding this comment.
WorkerConnection.h uses dst/JobMessage.h so this makes it a cyclic dependency: private headers needs to depend on :dst because of that but dst already depends on private_hdrs.
(this problem also exists in a bunch of other private_hdrs already in the project, but let's not make the problem worse).
If you invoke bant with -vvv you see for each header that the associated sources and headers include the corresponding library that is needed for that
$(etc/get-bant-path.sh) dwyu src/dst:private_hdrs -vvv
... here the JoMessage is found to be needing //src/dst
src/dst/BUILD:19:10-31:[ src/dst/src/WorkerConnection.h include dependency check (//src/dst:private_hdrs) ]
[...]
src/dst/src/WorkerConnection.h:10:11-42: #include "src/dst/include/dst/JobMessage.h"
src/dst/src/WorkerConnection.h:10:11-42: | //src/dst
There was a problem hiding this comment.
Perhaps the general pattern is
cpp --x--->priv_h
| |
| v
x--> public_h
There was a problem hiding this comment.
Claude said the same
public_hdrs (include/dst/*.h) ← visibility //:__subpackages__
▲ ▲
│ │
private_hdrs ◀──────── dst (srcs *.cc)
(src/*.h)
visibility //src/dst:__subpackages__
There was a problem hiding this comment.
Don't think in termss of private headers. That brings you on the wrong path of organizing the project.
If you formulate things in terms of public headers and private implementation, this is already leaning towards a hard to maintain project structure.
Because then, you only create targets to make it compilable, but there is still no structure that is easy to understand.
Instead, think of building small units, each providing their thing and compose the structure from there.
These can be properly named and visibility opened where needed.
That allows proper layering from the ground up.
So make small libraries that each have a meaning (and typically: a unittest). Give it a good name ('private_hdrs' does not say what it is), and structure in as directed acyclic graph that makes the project structure easy to follow and fast to compile.
cc_library(
name = "foo-lib",
srcs = [
"foo-impl.cc",
"some-util.cc",
"some-util.h",
],
hdrs = [
"foo-lib-something.h",
],
# visibility as needed
)
# Put assoicated tests in the same directory right next to the library so that it is
# easy to navigate to.
cc_test(
name = "foo-lib_test",
srcs = ["foo-lib_test.cc"],
deps = [
":foo-lib",
],
)
# Other, independent libraries that provide the functionality and might layer on top of libs here
cc_library(name = "bar", ...)
cc_test(name = "bar_test", ..., deps = [":bar"])
cc_library(name = "baz", ..., deps = [":foo-lib"])
cc_test(name = "baz_test", ..., deps = [":baz"])
cc_library(
name = "some-bigger-library-that-we-want-people-to-use-and-uses-these-other-libs",
srcs = [...],
hdrs = [...], # srcs and hdrs can use stuff from foo-lib
deps = [
":foo-lib",
":bar",
":baz",
...
],
visibility = ["//visibililty:public"],
)It might take a bit to detangle the current project structure, but this is the only easy-to-maintain way going forward.
For an inspiration a look at e.g. verible or xls for projects structured this way.
There was a problem hiding this comment.
The pattern of putting nearly every .cc in its own cc_library feels excessively fine grained and a maintenance burden. I am Inherently skeptical of any statement that there is only one way to accomplish a goal. I'm open to discussions of how to achieve encapsulation but I don't think the granularity must be single files.
There was a problem hiding this comment.
It is typically between 1 and 5 files indeed. It makes maintenance much easier, as it is easy to find all the related things (such as tests and the corresponding code). The automatic dependency management can be done with bant.
There was a problem hiding this comment.
Can you add the bant setup now so I can get some experience with how it works and resolves this concern?
| "src/policy/SetupLegacyBase.hh", | ||
| "src/policy/SetupLegacyMtPolicy.hh", | ||
| "src/policy/SetupLegacyPolicy.hh", | ||
| "src/policy/SetupMt1Policy.hh", |
There was a problem hiding this comment.
SetupLegacyMtPolicy.hh includes Resizer.hh so it would need //src/rsz as dependency
Similar cyclic dependency issue.
There was a problem hiding this comment.
How are you detecting these short of looking at all the includes?
There was a problem hiding this comment.
There is no short, bant actually looks at all includes.
bant dwyu looks at the global project, figures out who offers what includes. Then, with that knowledge goes through each target and looks what includes they use (by grepping for includes essentially) and sees if all of them are either mentioned in the target itself or all the dependencies mentioned. If not, it issues buildozer add commands. Dependencies that are not needed result in buildozer remove commands.
|
Looks like some of the files need to be more detangled. There are a bunch of more cyclic dependencies. |
The private impl headers (e.g. WorkerConnection.h) include dst/JobMessage.h, so :private_hdrs implicitly depended on :dst while :dst depended on :private_hdrs for the .cc compiles. Extract the public API headers into a new leaf target :dst_hdrs that both sides depend on, making the graph acyclic. The public hdrs remain listed in :dst as well so external consumers' layering_check continues to pass without changes. Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
…drs targets
Several gpl and rsz private impl headers transitively #include their own
module's public header ("gpl/Replace.h" / "rsz/Resizer.hh"), so :gpl_private_hdrs
and :rsz:private_hdrs implicitly depended on :gpl and :rsz respectively while
those main libs depend on the private_hdrs targets. Today this is masked
because the private_hdrs targets are hdrs-only and so trigger no
layering_check, but the deps as declared lie about what the headers use.
Apply the same fix used in src/dst: extract a leaf :gpl_hdrs / :rsz_hdrs
target containing only the public API hdrs, depended on by both
private_hdrs and the main lib. The public hdrs are also kept in the main
lib so external consumers continue to satisfy layering_check unchanged.
Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
|
clang-tidy review says "All clean, LGTM! 👍" |
|
@hzeller I remain unclear on how I can generate a list of circular issues to resolve. |
|
This is something where using For this, I'd go one-by-one. After you have done your splits into the sub-libraries, run To better understand why these edits are needed, use the The ( ) Apply the edits (or directly source them using the shell ... then build the project with bazel; that then will show when it sees cyclic dependencies. So these indicate that further splitting is needed until the build-graph can be represented as a DAG. After all the fixes, it will possible to run bant on the whole project: and the project compiles. Further applications of (If there are issues where bant does the wrong decision, let me know, but it should be rare. ) |
Tests that needed module-internal headers were disabling layering_check via features = ["-layering_check"]. Mirror the cts pattern instead: a private_hdrs cc_library with visibility = ["//src/:subpackages"] exposes the internal headers to the module's own tests only.
With this change, no BUILD file in src/ disables layering_check.
Fixes #10478