I don't think these two statements are fully compatible:
> Do any of the following apply to you?
> - You want to avoid hard-coding paths
> ...
> If so, you’ll benefit from a CMake-like build system.
and
> Don’t GLOB files
I find it very annoying that I, a human, am expected to keep a current list of all source code files, when listing files is something that computers are very good at. They even tell us how to structure the project with a src folder, but I still have to remember to add or remove source code files to the right CMakeLists.txt when the content of that folder changes. It's poor design.
Blanket advice always has edge cases. Re. whether GLOB'ing is good or bad, it pushes all the structure to your filesystem.
This is fine if your directories exactly map to binaries/libs/what-have-you, but what if you want unit tests for functions in each source file? Should you compile those with your binary? You could move them all into a tests folder and have a bin and a tests, but what if you want a test binary per source file (maybe your tests erratically take out their host process?)
The bottom line is that there has to be structure _somewhere_. If you're leaning heavily into CMake and your project isn't trivial, some of that structure may as well go into the build system.
DRY applies here. Placing files in a directory hierarchy and/or naming them according to a strict convention is "writing it down" once; repeating that in a text config file is writing it down a second time.
A good build system should use a concise but configurable rule to decide what to build as far as possible. Whether the details of that rule are "compile every file below this directory" or "compile every file that matches *.cpp but not *Test.cpp" (or some combination, or similar) isn't important.
Then it's the programmer's responsibility to "write the info" correctly by strictly conforming to that rule in how they name and place files, and/or tweaking the rule if necessary (e.g., "... But don't compile any file that has a period as the first character of its filename").
Rules can be clumsy, and information has different purposes. Typically, the generic and concrete concerns of keeping related files together and under source control is a much better match for the use of meaningful file names and directories than using the files for a specific purpose like compiling some build target; this makes globbing a cheap shortcut, replacing explicit specification of how to use files, except for special dynamic cases where file names are actually unknown.
As an example of file system and build system disagreeing, what if you start writing a new source file that you don't want to compile and link until it's ready and needed? If globbing file names is the source of truth, either the uncompilable draft of your new file has to be placed in some inconvenient alternate location where the build system doesn't pick it up, and later moved to a regular source folder, or it breaks the build until you waste enough time to hack together something that can be compiled (or even compiled and linked).
An IDE can use actual file lists and its internal project configurations to manage file references in build scripts (e.g. silently updating build scripts when files are renamed or moved or offering a good UI to add source files to build targets).
> what if you start writing a new source file that you don't want to compile and link until it's ready and needed?
What makes using a different top-level directory (which is something that would work with nearly all build systems) inconvenient? But if you really want these not-to-be-compiled-yet files nearby in the directory tree, customise the rule to, e.g., not compile anything having a filename beginning with IGNORE, or in a subdirectory called IGNORE, or (if you want to get fancy) listed in a .compileignore file (a la .gitignore). If the number of exceptions is small, then explicitly marking them in any of these ways is DRYer (= requires less maintenance) than explicitly recording the larger number of "business as usual" cases a second time.
> An IDE can use actual file lists and its internal project configurations
If you rely on an IDE to this extent, it's effectively part of your build system. (Which is absolutely fine, and has some benefits, though also some downsides.)
Provided that the IDE is "doing the work for you" (e.g., by adding each new .cpp file you create to the list of files to compile, until you uncheck a box somewhere that removes it), you're good. The only questions then are (1) whether this IDE configuration info is handled sanely by the VCS, and (2) whether it's readily understandable and usable to someone without the IDE (e.g., a CI build server). If the IDE stores this info in an externally defined plain-text build script like a Makefile or Maven pom.xml, then the answer to both questions is probably "yes" -- but, depending on how expressive the build script language is, it might be difficult or error-prone for the IDE to be able to update it reliably if the user makes their own changes to it as well.
All of my projects GLOB source files, headers, and test source with CONFIGURE_DEPENDS. Haven’t had a problem yet.
Yet you won't find a CMake best practices text that won't mention how bad globbing is.
There's the traditional criminal behaviour of CMake: setting a variable to a globbed set of files ONCE and obliviously caching the value ever after. A build system where it is possible cannot claim to be well designed.
For what it’s worth I think (modern) msbuild does it the best way I’ve seen so far. It operates on a “mostly” include by default. By that I mean the compilers “know” what are valid files, and everything valid in and below the project dir is included by default. You can edit the project file if you wish to include or exclude specific files or directories. Before dotnet core, you had to specify every file (ignore by default). Visual Studio would handle that for you, but adding new files often lead to version control conflicts.
Ultimately what I’m trying to say is that msbuild has a good set of default globbing rules and it’s very easy to fine tune.
I've never respected the 'don't use GLOBs' recommendation with CMake and had practically no issues. They even added CONFIGURE_DEPENDS to ease the process.
To me, the arguments against using GLOBs here seem too constructed for modern C++ developers.