8 ms·
One of the worst things a developer accustomed to Bazel (and its relatives) can do with a modern language (say Go or Rust) is to model code and organize it thro
by matttproud 1y ago
One of the worst things a developer accustomed to Bazel (and its relatives) can do with a modern language (say Go or Rust) is to model code and organize it through the Bazel concept of a build target (https://bazel.build/concepts/build-ref https://bazel.build/concepts/build-ref) first and then second represent it with the language's local organization concepts versus the other way around. One should preferentially model the code with the language-local organizing concept in an idiomatic way (e.g., a Go package — https://go.dev/ref/spec#Package_clause https://go.dev/ref/spec#Package_clause) and THEN map that instance of organization to a build target (e.g., go_library).
When you do this in the wrong order, you end up with very poorly laid out concepts from a code organization standpoint, which is why vagaries like this needed to be written:
* https://google.github.io/styleguide/go/best-practices.html#package-size https://google.github.io/styleguide/go/best-practices.html#p...
* https://matttproud.com/blog/posts/go-package-centricity.html https://matttproud.com/blog/posts/go-package-centricity.html
In languages that operate on a flat namespace of compilable units (e.g., C++ or Java), build target sizing and grouping in Bazel (and its relatives) largely doesn't matter (from a naming the namespace and namespace findability+ergonomics perspective). But the moment Bazel starts interfacing with a language that has strict organization and namespacing concepts, this can get rather hairy. The flat namespace practice with Bazel has (IMO) led to code organization brain-rot:
> Oh, I created another small feature; here, let me place it in another (microscopic) build target (without thinking about how my users will access the symbols, locate the namespace, or have an easy way of finding it).
— — —
Note: The above is not a critique of Bazel and such. More of a meta-comment on common mispractices I have seen in the wild. The build system can be very powerful for certain types of things (e.g., FFI dependency preparation and using Aspects as a form of meta-building and -programming).
- kiitos 11mo agoWithin the Google codebase and in projects using Bazel, directory layout for Go code is different than it is in open source Go projects: you can have multiple go_library targets in a single directory. A good reason to give each package its own directory is if you expect to open source your project in the future. :o :o :o are there really people saying that "giving each package its own directory" is in any way optional?? it is literally part of the language spec, what on earth would make anyone think otherwise?? edit: ok so bazel folks are just on a completely alternative timeline it seems https://github.com/bazel-contrib/rules_go?tab=readme-ov-file#does-this-work-with-go-modules https://github.com/bazel-contrib/rules_go?tab=readme-ov-file... Bazel ignores go.mod files, and all package dependencies must be expressed through deps attributes in targets described with go_library and other rules. so bazel doesn't support go, gotcha
- matttproud 11mo agoMaybe better put: Bazel (and its predecessor) do support Go, but they don't support the traditional directory structure-to-import path semantic that we've come to expect in the outside world. And even then, the terminal directory needn't match the package name, but accomplished Go developers seldom violate that convention these days — thankfully. All of this makes it paramount for developers of Go tools to use a first-party package loading library like package packages (https://pkg.go.dev/golang.org/x/tools/go/packages https://pkg.go.dev/golang.org/x/tools/go/packages), which can ameliorate over this problem through the specification of a GOPACKAGESDRIVER environment variable to support alternative build systems and import path layouts (the worst thing someone can do is attempt to reverse engineer how package loading works itself versus delegating it to a library like this).
- actionfromafar 1y agoNot having used Bazel in anger (or barely at all) I think I might understand what you mean, but this topic cries out for a blog post.
- kiitos 1y agoindeed, no idea why so many folks seem to think `bazel` is like some co-equal alternative to language-native build tooling/processes, it's a fine tool for certain (niche) use cases but in no way is it ubiquitous or anything approaching a common standard
- jmmv 1y ago> One of the worst things a developer accustomed to Bazel (and its relatives) can do with a modern language (say Go or Rust) is to model code and organize it through the Bazel concept of a build target (https://bazel.build/concepts/build-ref https://bazel.build/concepts/build-ref) first And that's exactly what I was arguing against in the article! I've seen this happen a few times already (in Java and TypeScript specifically) where Bazel's fine-grained target definitions are pushed as "best practice" and everybody ends up hating the results, for good reasons. There are _different_ ways in which one can organize the Bazel build rules that go against those best practices (like the 1:1:1 rule for Java), and I think you can end up with something that better maps to first principles / or what native built tooling does.
- whytevuhuni 1y ago> where Bazel's fine-grained target definitions are pushed as "best practice" and everybody ends up hating the results, for good reasons. What are some of those good reasons (assuming they differ from GP's)? I don't have much experience with Bazel aside from setting up a simple local workspace and following the tutorial.
- jmmv 11mo agoYou tend to end up with way too many targets that don't actually "mean anything" to a human. In one codebase I have to deal with, the Bazel build has ~10k targets whereas the previous non-Bazel build had ~400. Too many targets have an impact in various dimensions. Some examples: * The build files are unreadable. If targets don't mean anything to a human, updates to build files become pure toil (and is when devs ask for build files to be auto-generated from source). * IDE integrations (particularly via the IntelliJ Bazel plugin) become slower because generating metadata for those targets takes time. * Binary debugging is slower because the C/C++ rules generate one intermediate .so file per target and GDB/LLDB take a long time to load those dependencies vs. a smaller set of deps. * Certain Java operations can be slower. In the case of Java, the rules generate one intermediate JAR file per target, which has a direct impact on CLASSPATH length and that may matter when you do introspection. This tends to matter for tests (not so much for prod where you use a deploy JAR which collapses all intermediate JARs into just one).