I’m writing a build system at the moment. As it turns out, even the most mundane builds end up having to specify a lot of options. Imagine I want to make life easy for the end-user (and of course I do) and instead of specifying all the files to be built, we just specify directories instead. In pseudo-code:
myApp = build(["path/to/srcs", "other/path/srcs"])
But, for reasons unbeknownt to me as well as a few good ones, it’s often the case that there are files in those directories that aren’t supposed to be built. Or extra files lying around that aren’t in those directories but that do need to be built. And then there’s compiler flags and include directories, so a C++ build might look like this:
myApp = buildC++("name_of_binary", ["path/to/srcs", "other/path/srcs"], ["extrafile.cpp", "otherextra.cpp"], "-g -O0", ["include_from_here", "other_include_dir", "yet_another"], ["badfile.cpp", "otherbadfile.cpp"])
If you’re anything like me you’ll find this confusing. All the parameters (and there are several) are either strings or lists of strings. The compiler flags were passed in as a string so they stand out a bit, but the API could have chosen yet another list and it’s hard to say what is what. This didn’t seem like an API I wanted to use so I definitely wouldn’t expect anyone else to want to use it either.
Then the mental warping caused by me learning Haskell kicked in. What if… I add types to everything? They won’t even really do anything, they’re just there to tag what’s what and cause a compilation error if the wrong one is used. So I added a bunch of wrapper types instead, and in actual D code it started to look like this:
alias cObjs = cObjects!(SrcDirs(["etc/c/zlib"], Flags("-m64 -fPIC -O3"), IncludePaths(["dir1, "dir2"]), SrcFiles("extra_src.c"), ExcludeFiles(["badfile.c", "otherbadfile.c"]);
Better? I think so! You can’t confuse what’s what, and even better, neither can the compiler. After I exposed this to other people, it was pointed out to me that there are many ways to select files and that the API as it was would have to have even more parameters to satisfy all needs. It was big enough as it is so I changed it to something that looks like this now:
alias objs = targetsFromSources!(Sources!(Dirs(["src"]), Files(["first.d", "second.d"]), Filter!(a => a != "badfile.d")), Flags("-g -debug -cov -unitest"), ImportPaths(["dir1", "dir2"]), StringImportPaths(["dir1", "dir2"]));
Better? I think so. What do you think?