(FYI, I expect I'm the source of the suggestion that ghc -M is broken)
First, just to clarify, I don't think ghc -M is obviously broken. Rather, I think it's broken in subtle, unobvious ways, such that trying to develop a make-based project with ghc -M will fail at various times in a non-obvious fashion, at least without substantial additional rules. For an example of some of the extra steps necessary to make something like this work, see e.g.
https://github.com/nh2/multishake (which is admittedly for a more complicated setup, and also has some issues). The especially frustrating part is, just when you think you have everything working, someone wants to add some other tool to a workflow (hsc2hs, .cmm files, etc), and your build system doesn't support it.
ghc --make doesn't allow building several binaries in one run, however if you use cabal all the separate runs will use a shared build directory, so subsequent builds will be able to take advantage of the intermediate output of the first build. Of course you could do the same without cabal, but it's a convenient way to create a common build directory and manage multiple targets. This is the approach I would take to building multiple executables from the same source files.
ghc doesn't do any locking of build files AFAIK. Running parallel ghc commands for two main modules that have the same import, using the same working directory, is not safe. In pathological cases the two different main modules may even generate different code *for the imported module*. This sort of situation can arise with the IncoherentInstances extension, for example.
The obvious approach is of course to make a library out of your common files. This has the downsides of requiring a bit more work on the developer's part, but if the common files are relatively stable it'll probably lead to the fastest builds of your executables. Also in this case you could run multiple `ghc --make`s in parallel, using different build directories, since they won't be rebuilding any common code.
John L.