Forwarding Makefile Targets to a Script

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.