Build systems: Buck
This blog is part of the 11 posts long series "C++ Build tools":
- Build systems: Intro (April 10, 2017)
- Build systems: Make (April 17, 2017)
- Build systems: MSBuild (April 24, 2017)
- Build systems: SCons (May 01, 2017)
- Build systems: Waf (May 08, 2017)
- Build systems: FASTbuild (May 15, 2017)
- Build systems: Bazel (May 22, 2017)
- Build systems: Buck (May 29, 2017)
- Build systems: Premake (June 05, 2017)
- Build systems: CMake (June 12, 2017)
- Build systems: Conclusion (June 19, 2017)
Last week we looked at Bazel, and today we’re going to look at one of the build systems in the Bazel family: Buck. Buck is Facebook’s variant of Bazel, so most of what was written last week still applies. The interesting bit we’re going highlight today is how Buck handles include directories. Before we start though, let’s understand first why this is something worth writing about!
The include mess
What’s wrong with #include
files? Well, what isn’t – especially when it comes to building bigger projects. The main issue is collision of include file names. Assume you have two libraries A
and B
both with an utility.h
, and which you want to consume from a single project. As we’ve seen before in Bazel and Waf, some build systems allow to transitively forward include directories, which is great as nothing is ambiguous. But in the setup just described, you’ll have a collision between the two utility.h
files, and if you don’t have full control over the layout of your project, you can’t actually resolve it! The only way to disambiguate is to find a parent directory from which the utility.h
files have a unique path.
Interestingly, this is something which C++ modules are supposed to fix eventually, but in the meantime, the solution space is rather sparse. In my own framework, I decided to use what I call canonical include files. Qt, another huge C++ library, uses a similar approach. In both cases, an indirection layer is used to solve the problem, but the indirection layer is created manually.
Buck & includes
Buck provides tooling to solve the problem once and for all. Just like in Bazel, we define the public #include
files, but what Buck does now it automatically copies them into a new directory, and then forces this as the include directory – with the project name attached to it. What we included previously using #include "StaticLibraryHeader.h"
now becomes #include "statlib/StaticLibraryHeader.h"
. When building a project, you’ll notice that in the buck-out/gen/statlib
folder, there’s a new folder named statlib#default,private-headers
and this contains all headers we marked as export from the static library.
The only difference compared to Bazel – where we specified an includes
directive which contained the current directory – is that we need to specify them manually:
cxx_library(
name = "statlib",
srcs = ["StaticLibrarySource.cpp", ":gentable"],
headers = ["StaticLibraryHeader.h"],
visibility = ["//dynlib:dynlib"],
exported_headers = ["StaticLibraryHeader.h"],
link_style = "static_pic"
)
As long as your library names don’t collide, this seems like a very nice solution to this age-old C++ problem. Going forward, it should allow a clean transition to modules, which will cause other build tools quite some headache as they introduce yet another build target and dependency per C++ library.
Otherwise, Buck behaves very similar to Bazel, with some changes to the command names, but nothing earth-shattering. See for yourself in the sample repository! Thanks for reading and see you again next week!