Most of the build issues that a user will face is there system not matching the developers.
We can have pretty complicated logic (arbitrary logic in most build systems) for probing the build system, and much of this logic will inevitably get lost when compiling a linear script.
Once you put all of the complexity in the compiled script, I don't think you've gained anything over "make clean ; make" (or equivelent)
From what I've seen cargo gets most of the parts right. You can have a programmatic pre-build script bit can't take over the whole building process.
Here’s a few examples:
Dynamically using git describe to determine version and build number (which is actually just the # of commits that ever occured, to allow reproducable builds)
versionCode = cmd("git", "rev-list", "--count", "HEAD")?.toIntOrNull() ?: 1
versionName = cmd("git", "describe", "--always", "--tags", "HEAD") ?: "1.0.0"
Putting git infos into certain classes buildConfigField("String", "GIT_HEAD", "\"${cmd("git", "rev-parse", "HEAD") ?: ""}\"")
buildConfigField("String", "FANCY_VERSION_NAME", "\"${fancyVersionName() ?: ""}\"")
buildConfigField("long", "GIT_COMMIT_DATE", "${cmd("git", "show", "-s", "--format=%ct") ?: 0}L")
Using string interpolation to change the output file to include the version setProperty("archivesBaseName", "Quasseldroid-$versionName")
But the worst part is that often on StackOverflow you only find ugly hacks adding custom tasks, removing tasks, modifying them, etc with ugly hacks.And even worse, sometimes there’s just no good alternative at all.
Which is why gradle took so long to clean up some parts of their API, or introduce parallel builds.
Probably one of the biggest features in a configure/build/packaging software package is ubiquity, IMO. Ideally C/C++ would have had a prescribed-but-not-required thing like cargo/setuptools/go get/etc. IMO CMake is probably the least bad offering these days.