Where it gets harder is when this language feature is integrated with an existing build system. C# is normally build with it's own build system, and to build it with Bazel you have to essentially rewrite some of it's logic in Bazel, which can be hard, but doable.
So you need several layers of tooling just to get something built.
It's bad
But fear not, (and I'm not sure if BAZEL does this yet, but does it for java). If you've declared only "a.cpp", but forgot "a.h" - then bazel would detect that (/showIncludes in MSVC, or file system sandbox in linux, etc.) - and it'll tell you - you need to add "a.h" to your BUILD file.
Fear not again, it's possible to create a BUILDIFIER command that given what BAZEL told you it can do it automatically for you. Hence the BUILD files needs to be kept simple, for everyone to understand (even non-engineers), while the ugly parts go into the .bzl files and workspaces.
So in tha java world (but any language could do it), anything that was discovered dynamically but not declared in, bazel can offer how to fix it (copy+paste the command offered, or tool makes it for you) - at worst report it and you have to fix it manually
The important thing is - bazel can use only the information provided in the BUILD files to detect - oh hey, things have changed in the CI - so I need to rebuild. Without relying on "cl/gcc/clang" to run this through preproc step to discover actual headers. or keep horrible make-like deps.
But really, I think that people don't go extremely granular on this. For Rust projects that are spread over 10-100 crates, I think that most people would just model the crate dependencies rather than at a file level. Mostly for pragmatic reasons, but also because that will likely model your test suite closely and your build requirements.
Bazel is a build tool, so one "module" (in Bazel you talk about target) per build artifact basically aligns with what people want. But you can in theory go really granular, making it so that tests only run if files it depend on change (less granular and that becomes "crates they depend on change")
> For Rust projects that are spread over 10-100 crates
It's already 10-100 crates you need to manually declare and keep track of, or use a yet another tool to generate this for you.
And for big projects you'd also want to go more granular and split by parts of your app I guess.
The "obvious" thing would be to either generate cargo files from Bazel stuff, generate Bazel stuff from cargo files, or have a third source that generates both. And like... yeah, if you are suffering from compile time issues, Bazel can make that stuff order of magnitudes faster (especially if your dep tree is pretty horizontal), so there's a reason to do it!