Build watcher
Table of Contents
I thought I’d write a simple build watcher in a couple of hours. Of course, it didn’t take that long.
It took longer.
The code is here: https://github.com/barrylapthorn/build-watch.
It’s the very defintion of yak shaving.
The basic requirements were (and still are):
- watch a set of source directories
- watch for changes (add/delete) for a given set of file extensions
- read a template build file and write it with the new set of source files
It ‘works on my machine’, i.e. linux, with a modern version of gcc and cmake.
Non-requirements #
This doesn’t support Windows or Apple (yet). And it is expected that the source code is in a git repository. It may work with Mercurial, but is untested.
Requirements ad nausem #
Of course, the basic set of requirements were woefully naive.
At its most basic, you could watch a set of directories from the root level, and you’re done.
Use blocking inotify_init
and you’re halfway there.
Of course, a git repository, with source code has far more directories than you need to actually watch. And just because you’re not watching it, doesn’t mean someone might not create a new directory, or delete an existing one.
Now the feature creep comes in:
- You may want to gracefully terminate on a signal, because
you opened all those handles using
inotify
. What’s the point of C++ (stop right there), if not to RRID system resources? - You also want to be non-blocking and use
epoll
. - A nice CLI, and some logging.
- What configuration format do I use?
- What template language do I choose?
- It’s 2024, let’s dump the stack if we have a problem.
- Use cmake. Yeah.
The original requirement, redux #
But back to the original problem. I wanted to add and remove files from my CMakeLists.txt
as they were created or
deleted on disk. Modification of those files is a no-op as far as the build files are concerned.
We therefore need a template file and we need to read that, and then write it with the updated list.
It’s actually pretty trivial using mustache:
add_executable(build-watch
{{#files}}
{{relpath}}
{{/files}}
)
And that’s it.
Of course, the next step from supporting CMakeLists.txt
is to support… something else. Since I have a slight
familiarity with Bazel, BUILD
files should be supported.
Of course, Bazel wants the files quoted, and comma delimited, so we have to do a little bit of work with “inverted sections” in mustache:
cc_binary(
name = "build-watch",
srcs = [
{{#files}}
"{{relpath}}"{{^last}}, {{/last}}
{{/files}}
],
)
The C++ code must populate those mustache data fields:
for (const auto& file : matchingFiles) {
const bool isLast = (file == matchingFiles.back());
data d;
d.set("relpath", file.string());
d.set("last", data(isLast ? data::type::bool_true : data::type::bool_false));
files << d;
}
Configuration #
Supporting one or more build files in the same folder just requires a bit of configuration.
{
"files": [
{
"src": "CMakeLists.txt.mustache",
"dest": "CMakeLists.txt",
"extensions": [".hpp", ".cpp", ".h"]
},
{
"src": "BUILD.mustache",
"dest": "BUILD",
"extensions": [".hpp", ".cpp", ".h"]
}
],
"ignoreFiles": [
".gitignore"
]
}
Of course we can also add BUILD.py.mustache
:
py_binary(
name = "app",
srcs = [
{{#files}}
"{{relpath}}"{{^last}}, {{/last}}
{{/files}}
],
deps = [
"//projects/python_folder/sample_library:calculator",
requirement("Flask"), #or '@python_deps_pypi__flask//:pkg'
],
main = "app.py"
)
With a config:
{
"files": [
{
"src": "CMakeLists.txt.mustache",
"dest": "CMakeLists.txt",
"extensions": [".hpp", ".cpp", ".h"]
},
{
"src": "BUILD.mustache",
"dest": "BUILD",
"extensions": [".hpp", ".cpp", ".h"]
},
{
"src": "BUILD.py.mustache",
"dest": "BUILD",
"extensions": [".py"]
}
],
"ignoreFiles": [
".gitignore"
]
}
Enough, let’s run it. #
I like just
.
Assuming you’re running a modern-ish linux distro, you probably only need to install just
.
Then just dogfood
will build the release version and run it with the build-watch
repo.
That’s it.
There’s more in the README
.
It’s version 1, as it works, runs, and does what I want to do. None of the 0.1-alpha-prelease
nonsense.
If you find a bug, try fixing it and sending me a PR.
What doesn’t it do? #
There are a few things it doesn’t do:
- doesn’t reload if you modify the
.gitignore
file - doesn’t handle nested
.gitignore
files - doesn’t reload if you modify
.config/BuildWatch/config.json
- may not run correctly if not pointed at the root of your repository
- the tests (or lack thereof) work fine, but need more coverage
- needs refactoring
- I’ve probably missed out some other things
Help! #
Lastly, the help screen:
Links #
- The images are via https://ray.so/