Force Makefile to execute script before building targets - makefile

I am using Makefiles.
However, there is a command (zsh script) I want executed before any targets is executed.
How do I do this?
Thanks!

There are several techniques to have code executed before targets are built. Which one you should choose depends a little on exactly what you want to do, and why you want to do it. (What does the zsh script do? Why do you have to execute it?)
You can either do like #John suggests; placing the zsh script as the first dependency. You should then mark the zsh target as .PHONY unless it actually generates a file named zsh.
Another solution (in GNU make, at least) is to invoke the $(shell ...) function as part of a variable assignment:
ZSH_RESULT:=$(shell zsh myscript.zsh)
This will execute the script as soon as the makefile is parsed, and before any targets are executed. It will also execute the script if you invoke the makefile recursively.

Just make that a dependancy of one of the other targets
foo.obj : zsh foo.c
rule for compileing foo.c
zsh:
rule for running zsh script.
or alternatively, make your first target depend on it
goal: zsh foo.exe

Solution for both preprocessing and postprocessing in makefiles using MAKECMDGOALS and double colon rules.
MAKECMDGOALS are the targets listed on the command line.
First step is to get the first and last targets from the command line,
or if there are no targets listed, use the default target.
ifneq ($(MAKECMDGOALS),)
FIRST_GOAL := $(word 1, $(MAKECMDGOALS))
LAST_GOAL := $(word $(words $(MAKECMDGOALS)), $(MAKECMDGOALS))
else
FIRST_GOAL := all
LAST_GOAL := all
endif
Double colon rules allow multiple recipes for the same target executed in order. You'll have to change all command line targets to double colon rules.
#Dummy rule to set the default
.PHONY: all
all ::
#Preprocessing
$(FIRST_GOAL) ::
echo "Starting make..."
all :: normal_prerequistes
normal_recipe
other_stuff
#Postprocessing
$(LAST_GOAL) ::
echo "All done..."

There is a solution without modifying your existing Makefile (main difference with the accepted answer). Just create a makefile containing:
.PHONY: all
all:
pre-script
#$(MAKE) -f Makefile --no-print-directory $(MAKECMDGOALS) MAKE='$(MAKE) -f Makefile'
post-script
$(MAKECMDGOALS): all ;
The only drawback is that the pre- and post- scripts will always be run, even if there is nothing else to do. But they will not be run if you invoke make with one of the --dry-run options (other difference with the accepted answer).

Related

GNU make: mixing the $(file) function with external tools

The example below is directly from the GNU make manual:
program: $(OBJECTS)
$(file >$#.in,$^)
$(CMD) $(CMDFLAGS) #$#.in
#rm $#.in
This works perfectly. However, if I reverse the order of $(file) and the external tool, the behavior is still the same: $(file) is executed first, and only then the external tool.
Is it possible to call an external tool in a recipe, and after the successful completion of the tool, use $(file) to process the log and create further files?
The external tool is a code generator that produces an unknown set of files. The recipe should parse the log file and create make include files. Works with echo/$(shell), but that's limited by the system command line length. Using a separate rule would certainly work, but can it be done in the same recipe?
GNU make will always expand all variables and functions in the entire recipe up front, before it starts to invoke any commands in the recipe. So you cannot have a $(file ...) function (for example) operate on the results of running some command in the same recipe.
So the short answer to your question is no, it can't be done in a single recipe the way you want.
It's possible, if you run the shell command using $(shell ) rather than directly putting it in a recipe. I've been using following wrapper for it, which checks the error code and discards the result:
# Same as `$(shell ...)`, but triggers a error on failure.
ifeq ($(filter --trace,$(MAKEFLAGS)),)
override safe_shell = $(shell $1)$(if $(filter-out 0,$(.SHELLSTATUS)),$(error Unable to execute `$1`, status $(.SHELLSTATUS)))
else
override safe_shell = $(info Shell command: $1)$(shell $1)$(if $(filter-out 0,$(.SHELLSTATUS)),$(error Unable to execute `$1`, status $(.SHELLSTATUS)))
endif
# Same as `safe_shell`, but discards the output and expands to a single space.
override safe_shell_exec = $(call space,$(call safe_shell,$1))
Then $(CMD) $(CMDFLAGS) #$#.in becomes $(call safe_shell_exec,$(CMD) $(CMDFLAGS) #$#.in).

Issue with Makefile target rule and PHONY

I know this could be done in different ways, but I am actually looking for learning more about makefiles.
I want to be able to call make somefile.txt and run a given script.
In my current folder I have:
makefile
%.txt:
# perl -pe "s/#.*//g; s/^\n//g; s/\n/\t/g;" .txt
echo "Hi"
When I call make, I am getting
make: `somefile.txt' is up to date.
I know I would need to use .PHONY, but I am having trouble trying to use it with %.txt.
I already tried things such as
files = *.txt
.PHONY = $(files)
%.txt:
# perl -pe "s/#.*//g; s/^\n//g; s/\n/\t/g;" .txt
echo "Hi"
But that didn't actually work.
The (a) conventional way to do this is to make your real target have a phony one as a prerequisite. Phony targets are always considered initially out of date, so anything that depends on a phony target will also always be considered out of date. "force" is a conventional name for a target used for this purpose.
For example,
.PHONY: force
force:
%.txt: force
echo "Hi at $$(date)" > $#
As demonstrated in the example, this does not require the phony target to have a recipe.
If you don't want to list your targets to be built in your makefile but instead just give them on the command line, you can use this:
.PHONY: $(MAKECMDGOALS)
The variable MAKECMDGOALS will be set to the set of goals you gave make on the command line.
It's important to understand that a makefile is not the shell, and so just sticking *.txt anywhere in your makefile won't always expand it. Only particular areas of the makefile work with shell globs directly.
If you want to always expand it, you can use make's wildcard function like this:
files = $(wildcard *.txt)

Makefile passing additional arguments to targets command from make command

I have a Makefile that defines docker-compose project.
It essentially assembles me a command:
COMMAND := docker-compose --project-name=$(PREFIX) --file=$(FILE_PATH)
up:
$(COMMAND) up -d
I would like to add a target named dc to which I would be able to pass any arguments I want.
I know there is one solution:
target:
$(COMMAND) $(ARGS)
And then call it with make target ARGS="--help" for example.
But isn't there an easier way like in bash $# ? I would like to skip the ARGS=... part and send everything to the command after target name.
Not really. The make program interprets all arguments (that don't contain =) as target names to be built and there's no way you can override that. So even though you can obtain the list of arguments given on the command line (via the GNU make-specific $(MAKECMDGOALS) variable) you can't prevent those arguments from being considered targets.
You could do something like this, which is incredibly hacky:
KNOWN_TARGETS = target
ARGS := $(filter-out $(KNOWN_TARGETS),$(MAKECMDGOALS))
.DEFAULT: ;: do nothing
.SUFFIXES:
target:
$(COMMAND) $(ARGS)
(untested). The problem here is you have to keep KNOWN_TARGETS up to date with all the "real" targets so you can remove them from the list of targets given on the command line. Then add the .DEFAULT target which will be run for any target make doesn't know how to build, which does nothing. Reset the .SUFFIXES meta-target to remove built-in rules.
I suspect this still will have weird edge-cases where it doesn't work.
Also note you can't just add options like --help to the make command line, because make will interpret them itself. You'll have to prefix them with -- to force make to ignore them:
make target -- --help
Another option would be to add a target like this:
target%:
$(COMMAND) $*
Then you can run this:
make "target --help"
But you have to include the quotes.
In general I just recommend you reconsider what you want to do.
You could write a bash wrapper script to do what you'd like:
#/bin/bash
make target ARGS=\"$#\"
The reason you don't want to do it in make, is that make parses the command line parameters before it parse the makefile itself, so by the time you read the makefile, the targets, variables, etc have already been set. This means that make will have already interpreted the extra parameters as new targets, variables etc.
A target that re-run make containerized
.PHONY: all containerized
ifeq ($(filter containerized,$(MAKECMDGOALS)),containerized)
.NOTPARALLEL: containerized
MAKEOVERRIDES ?=
containerized: ## Build inside a container
#docker run image_with_make make $(MAKEOVERRIDES) $(filter-out containerized,$(MAKECMDGOALS))
else
# other targets here
all: xxxx
endif
Executing
make containerized all runs make all in container
The first answer is correct, no passthru of args. However, here is a plausible path for experimentation, use of branch by include selection:
# Makefile:
COMMAND := $(PYTHON) this_shit_got_real.py
LOCAL_MK ?= local.mk
# '-' important, absence of LOCAL_MK is not cause for error, just run with no overrides
- include $(LOCAL_MK)
target:
$(COMMAND) $(ARGS)
Now see how you add branching with env:
echo "ARGS=--help">>local.mk
# make target
And the other cli controlled branch
echo "ARGS=--doit">>runner.mk
# LOCAL_MK=runner.mk make target

Makefile call itself with identical command line input except prepended string

I have a complex, legacy Makefile which I need to run inside of a Docker container. Essentially, I need the Makefile to call itself again, except I need to prepend "docker run ...".
The end user may make multiple targets, and multiple command line variables and options may also be specified, so I need to duplicate the entire command-line call to make - not just recall each target.
I'm expecting something of the form:
<BLACK_MAGIC>:
docker run my_image make <COMPLETE_ORIGINAL_MAKE_COMMAND_LINE>
Suggestions?
Thanks!
Rename the makefile to something else and use the following as a stub
FLAGS := $(if $(MAKEFLAGS),-$(MAKEFLAGS))
.SUFFIXES:
.PHONY: all
%:: all ;
all:
docker run my_image $(MAKE) -f Makefile.old $(FLAGS) $(MAKEOVERRIDES) $(MAKECMDGOALS)
Make removes certain flags from MAKEFLAGS in recipes so they need to be copied in the main body
.SUFFIXES: clears the suffix list in case make tries to apply one of its implicit rules
Make all a .PHONY target just in case the folder contains a file named all
Make other targets depend on all with %:: to avoid invoking make multiple times
Since command line variables won't be placed in MAKEFLAGS until the recipes are invoked they need to be added in separately with MAKEOVERRIDES, we can't use MAKEFLAGS directly due to the first point.

How to preprocess makefiles

How to show the makefile after it's been preprocessed? For example, if we have two makefiles:
# Makefile
include Makefile2
# Makefile2
a:a.c
gcc -o a a.c
Then <preprocessor> Makefile should give:
a:a.c
gcc -o a a.c
It's similar to what a C preprocessor does (gcc -E). Is there such a makefile preprocessor?
You didn't specify for which make tool you are writing makefile. Assuming that it is GNU make, you can try running makefile with -n (--just-print) option See Command-Line Options chapter here. That will show what make is going to execute without execution (however, the commands needed for evaluation of variables will be executed). This is probably the closest to what you want to see.
This causes make to read the makefile and print every command it would
normally execute to update the target but without executing them.
Apart from that there is $(warning ) function to debug makefiles. You can place it almost to any part in makefile and the following will show you the values of all defined variables in that place:
$(warning Variables HERE: .VARIABLES)

Resources