How to programmatically define targets in GNU Make? - makefile

I am not aware of any way to define programatically targets in GNU Make. How is this possible?
Sometimes one can go away with alternate methods. The ability to define programatically targets in Makefiles is however a very important to write and organise complex production rules with make. Examples of complex production rules are found in the build system of FreeBSD or in Makefile libraries such as BSD Owl
The main differences between shell scripts and Makefiles are:
In a Makefile, the state of the program is given by the command line and the filesystem, so it is possible to resume a job after it has been interrupted. Of course, this requires to properly write the Makefiles, but even if this is rather hard, it is considerably easier than to achieve a similar effect with a shell script.
In a Makefile, it is ridiculously easy to decorate a procedure with advises or decorate it with hooks, while this is essentially impossible in shell scripts.
For instance, a very simple and useful pattern is the following:
build: pre-build
build: do-build
build: post-build
This presents the build target as a composite of three targets, one containing the actual instructions do-build and two other that are hooks, executed before and after do-build. This pattern is used by many build systems written for BSD Make, which incidentally allows programmatic definition of targets, so that one can write in a batch:
.for _target in configure build test install
.if !target(${_target})
${_target}: pre-${_target}
${_target}: do-${_target}
${_target}: post-${_target}
.endif
.endfor
The condition introduced by the .if/.endif block enables the user to use its own definition of any ${_target}.
What would be the translation of that snippet for GNU Make?

FWIW here is the make equivalent syntax for
.for _target in configure build test install
.if !target(${_target})
${_target}: pre-${_target}
${_target}: do-${_target}
${_target}: post-${_target}
.endif
.endfor
Basically, you want make to see something like this snippet:
build: pre-build
build: do-build
build: post-build
and similarly for configure, test and install. This suggests a loop with an eval somewhere:
define makerule =
$1: pre-$1
$1: do-$1
$1: post-$1
endef
targets := configure build test install
$(foreach _,${targets},$(eval $(call makerule,$_)))
(to play with this, change eval to info). Careful with those closures!
FWIW, here's the expansion of the foreach:
make expands the list to be iterated over
${targets} becomes configure, build, test and install
We have $(foreach _,configure build test install,$(eval $(call makerule,$_)))
_ is set to the first value, configure.
make expands $(eval $(call makerule,configure))
To evaluate the eval, make expands $(call makerule,configure)
It does this by setting 1 to configure, and expanding ${makerule} which produces 3 lines of text:
configure: pre-configure
configure: do-configure
configure: post-configure
$(eval) goes to work, reading this text as make syntax
Note that the expansion of the $(eval) is empty! All its work is done as a side effect.
Wash, lather, rinse, repeat.
Please note: I have to agree with all the other commenters: your pattern is bad make. If your makefile is not -j safe, then it is broken (missing dependencies).

First this structure is invalid if you ever want to support parallel builds; if you invoke make with the -j option it will run all three prerequisite rules at the same time, because while all of them must be complete before build, none of them depend on each other so there's no ordering defined (that is, you don't say that pre-build must be complete before do-build can run).
Second, GNU make has a number of facilities for programmatically defining rules. One thing GNU make does not have, currently, is the ability to search the targets which are already defined, so there's no direct analogy to .if !target(...).
However, you CAN search whether a variable has been defined or not using the .VARIABLES variable. So one workaround would be to define a variable if you want your own target and then have your rule generator check that.

Related

How to trigger the rebuild of a Makefile prerequisite file ONLY when a specific target is called?

I haven't found an answer so far, so I think this is not a repeat question.
I have some Makefile along the lines of:
include prerequisite_2
all: prerequisite_1 prerequisite_2
clean:
rm *.mod
prerequisite_1:
mkdir somedir
prerequisite_2:
re-write existing file
The issue is that I want the prerequisite_2 to rebuild whenever the default goal is called (all) or when prerequisite_2 is called from the command line, and I know I can use touch prerequisite_2, FORCE or .PHONY to achieve this. However, I DO NOT want it to run every time (the written file contains dependency information for the Fortran files involved) as it doesn't make sense to also rebuild this when calling: make clean
Is it possible to emulate the effects of FORCE or .PHONY only when the depending targets are called?
You can see what the goal targets are by looking at the MAKECMDGOALS variable.
So you can do something like:
ifeq (,$(if $(MAKECMDGOALS),$(filter-out all prerequisite-2,$(MAKECMDGOALS))))
include prerequisite-2
endif
The if condition will be true if MAKECMDGOALS is the empty string, or if it contains only all and/or prerequisite-2 but not if it contains any other target.
Usually, this is not what you want though. Usually you want to disable the include only if certain targets (clean is the classic example) are used.
This exact situation is even discussed in the GNU make manual.

Suppressing First Part of Output in Makefile

DEPRECATED_CHECK := $(shell grep "test454" tex/*.tex)
ifneq ($(DEPRECATED_CHECK), )
$(warning \test454 is deprecated. Use \test2 instead)
endif
When I run this I get:
../common/Makefile.include:133: \test454 is deprecated. Use \test2 instead
That's fine, but I'd quite like to have only:
\test454 is deprecated. Use \test2 instead
Is this possible? Some sort of awk function? I think I need something with:
#echo \text454 is deprecated ...
But I don't know how to get this working with the basic purpose of my MWE, as it keeps complaining about missing separators.
Many thanks
You could use $(info ...) instead of $(warning ...). info doesn't prepend the file and line number.
just an aside -- I usually try to do those sort of checks as part of a sanity rule, and make everything depend on that rule instead of doing it at the top level. It gives you more flexibility that way. For example, if you didn't want to run the check when building clean, it becomes simple, or if you wanted to fail the build if a check failed, it becomes simple as well.
EDIT (adding more detail on aside)
Instead of doing an ifneq at the top level of make, you could add a target as so:
sanity_check:
# ! grep -q "test454" tex/*.txt || echo "test454 is depricated"
.PHONY: sanity check
The add dependencies of your main targets to sanity check:
all maintarg1 maintarg2: sanity_check
This way the sanity check will be run before any of your main targets, and will output as desired. This is in my opinion, a cleaner way of doing the test. This way the test is only run if you are building any of your targets, and will not be run, if for example you are making clean, or if your makefile was included by a parent makefile, or in a bunch of other corner cases that might pop up in the future.
Just a quick note on the recipe syntax: the # is a make directive that tells make not to echo the command as it's run. The ! is bash syntax to inverse the return of grep (so ! grep returns false if the text is found, thereby causing the || part of the statement to be evaluated.). The .PHONY: sanity_check tells make to run the rule, even if a file called sanity_check already exists

Using Condition statements with Makefile targets

Hello i'm trying to use condition statements in my makefile to have it execute different make targets, but it skips over the condition and goes right to the else.
He's a general example of what i'm trying to do
ifdef ($(RUN_TEST))
all: install run uninstall
else
all: install uninstall
endif
You did not indicate on what system you are running the Makefile, as there are slightly differing make programs available. Make can be run on linux and windows and comes in different variants.
However, I have worked with most variants, and there is a common way you can resolve your problem. You have to realise that the makefile is not a program executed in the conventional sequential manner. It is a series of declarations or definitions of actions to be performed at some future time. You cannot read through it in a sequential manner like a program. In particular the definitions of dependencies are not executed and cannot be embedded within statements, even pre-processed statements.
The best way to achieve what you want it to put the dependencies in a variable and set that variable conditionally, like this:
if ($(RUN_TEST))
ALL=install run uninstall
else
ALL=install uninstall
endif
all : $(ALL)
This should work on most implementations.
The GNU make ifdef operation takes the name of a variable to test; you are providing it the expansion of a variable. You want to write it like this:
ifdef RUN_TEST
ALL = install run uninstall
else
ALL = install uninstall
endif
By using $(RUN_TEST) you're actually testing a variable named by the expansion of RUN_TEST. So for example if RUN_TEST is set to true, then ifdef $(RUN_TEST) actually tests to see if the variable true is defined or not.

make variables that depend on a target

I have a Variable in make that is dependant on a file that must be built before the variable can be set, is there a way to get this to work?
parsable_file: dependancies
commands to make parsable_file
targets=$(shell parse_cmd parsable_file)
$(targets): parsable_file
command to make targets
.phony: all
all:$(targets)
If I run $ make parsable_file && make all this will work (I get an error that parse_cmd cant find parsable_file but it works), but just make all will not work. Is there a Make idiom for this?
Set the variable in a file that you include in the main makefile and include a rule in the main makefile for how to build it (the one you already have should be fine).
I believe that will do what you want.
See Including Other Makefiles and How Makefiles Are Remade (which is linked from the first section) for more details on this concept.
Also, unless parseable_file has a usage independent from that parse_cmd call, it should be possible to do the creation and the parsing at the same time and just have the resulting makefile contain the correct value for $(targets) in one step.

How does "make" app know default target to build if no target is specified?

Most Linux apps are compiled with:
make
make install clean
As I understand it, the make command takes names of build targets as arguments. So for example install is usually a target that copies some files to standard locations, and clean is a target that removes temporary files.
But what target will make build if no arguments are specified (e.g. the first command in my example)?
By default, it begins by processing the first target that does not begin with a . aka the default goal; to do that, it may have to process other targets - specifically, ones the first target depends on.
The GNU Make Manual covers all this stuff, and is a surprisingly easy and informative read.
To save others a few seconds, and to save them from having to read the manual, here's the short answer. Add this to the top of your make file:
.DEFAULT_GOAL := mytarget
mytarget will now be the target that is run if "make" is executed and no target is specified.
If you have an older version of make (<= 3.80), this won't work. If this is the case, then you can do what anon mentions, simply add this to the top of your make file:
.PHONY: default
default: mytarget ;
References:
https://www.gnu.org/software/make/manual/html_node/How-Make-Works.html
GNU Make also allows you to specify the default make target using a special variable called .DEFAULT_GOAL. You can even unset this variable in the middle of the Makefile, causing the next target in the file to become the default target.
Ref: The Gnu Make manual - Special Variables
bmake's equivalent of GNU Make's .DEFAULT_GOAL is .MAIN:
$ cat Makefile
.MAIN: foo
all:
#echo all
foo:
#echo foo
$ bmake
foo
See the bmake(1) manual page.

Resources