The problem with build systems is they don't cover the entire scope required to actually build software reliably. Ideally, you want to take just your code as input and produce a target, but the reality is that you're taking your code, plus an environment with potentially infinite number of configuration options, and you're asking the build system to produce a target. This is effectively saying "Here's some code and some shit, please do your wizardry and make me something that looks like this." Is it any wonder that every proposed solution to this difficult (impossible?) problem gets it wrong?
The correct solution is to declare the target you want, then build up the dependencies you need as input to meet that target and declare them explicitly, then perform the build in that isolated environment to ensure that no undeclared configuration options can alter the result of the build. This is what Nix (http://nixos.org/nix/) does, and by derivation, Guix (https://www.gnu.org/software/guix/), and Debian's ReproducibleBuilds is also attempting. The build process becomes effectively a pure function, free of unwanted side-effects.
Nix doesn't actually perform builds in itself, except for trivial ./configure && make. It piggy-backs on existing build systems via bash scripts if needed, but it completely manages the environment in which such script will run, so it has local-side effects which are controlled, similar to how you might use the ST monad in place of IO in Haskell perhaps.
I find Guix a bit more interesting because it can take on the whole problem - including the build system. While it can piggy-back on existing build systems in the same way Nix can, you can also write your own in Guile (or some other language and invoke from Guile). It doesn't really make sense to depend on the old cruft under this new mentality, because things like pkg-config are obsoleted by known, explicitly declared dependencies. The build process can be simplified (and probably sped-up).