I love make files. There is something special when you just run make
and all gets built automatically. Even better, you can use multiple targets to chain a few operations together, e.g., make clean test debug
All this is available to you under Linux. Under Windows, all this magic is gone.
For Windows, most of the time I see either a separate script handling build tasks, or nothing at all. A separate script is not a bad solution but it does introduce a potential difference between builds. Assuming you have Git installed, the easiest way out is to simply forward Makefile entries to the bash script. Something like this:
.PHONY: clean test debug release
clean:
@./Make.sh clean
test:
@./Make.sh test
debug:
@./Make.sh debug
release:
@./Make.sh release
And honestly, this is probably good enough. If you are on linux, you use make debug
and on Windows you use Make.sh debug
. For years now I used this approach whenever I needed things to work on both Linux and Windows. But there were issues - mainly with target “chaining”.
For example, if you want to run clean as a prerequisite to release, you can do that in Makefile
.
…
clean:
@./Make.sh clean
release: clean
@./Make.sh release
This will, under Linux, do what you expect it. But, under Windows, this is not enough. So, alternatively, you might leave Makefile
as-is and do the chaining in Make.sh
. And that works on Windows but, under Linux, it will double call to clean
, i.e.,
make clean release
will translate into
./Make.sh clean # first call doing only clean
./Move.sh release # second call internally does clean again
It’s not the worst issue out there and god knows I lived with it for a long time. What I need was to just forward whatever arguments I receive in make
command to my Make.sh
script. Reading GNU make documentation did point toward MAKECMDGOALS
special variable that was exactly what I needed. It even pointed to last resort %::
syntax. So, the following Makefile
looked to be all I needed.
%::
@./Make.sh $(MAKECMDGOALS)
Only if life was that easy. This last-resort rule will unfortunately call script once for each target given to make. I.e., the final call in our example would be:
./Make.sh clean release
./Move.sh clean release
And there is no fool-proof way I found to prevent the second run. You cannot set a variable, you cannot really detect which argument you’re forwarding, you cannot exit. You could write in file that you are already running but that gets messy when task is cancelled.
I spent quite a lot of time messing with this but I never found a generic way. But, I finally managed to find something incredibly close.
all clean run test debug release &:
@./Make.sh $(MAKECMDGOALS)
As long as you list all targets, listing only one or all of them will lead to the same command. And, because they are all grouped together, it will run it only one. It’s not ideal because I do need to keep target list in two places, but that list is not likely to change.
If you want to check my whole build script, you can check my GitHub.