Makefile function from make commands, NOT shell commands - makefile

Is there any way to create multiline functions out of Makefile commands?
I know we can do something like this to encapsulate a recipe (of shell commands) as a function:
define function
#echo 'First argument: $1'
#echo 'Second argument: $2'
endef
.PHONY test-function
test-function:
$(call function, a, b)
With this, running make test-function will give the output:
First argument: a
Second argument: b
I also know we can use the call directive with one-line macros consisting of make syntax/directives (example taken from here):
pathsearch = $(firstword $(wildcard $(addsuffix /$(1),$(subst :, ,$(PATH)))))
LS := $(call pathsearch,ls)
But let's say I wanted to call a macro made up of multiple make commands, including conditionals. How would I achieve that?
When I run make build-type=API build with the following Makefile:
define check-arguments
ifeq ($1, api)
#echo 'Building API'
else ifeq ($1, service)
#echo 'Building Service'
else
$$(error 'Build type must be API or Service')
endif
endef
.PHONY: build
build:
$(call check-arguments, $(build-type))
#echo 'Starting build'
...
...
I keep getting the error Makefile:13: *** missing separator. Stop..

You can use eval. The GNU Make Manual states:
...it [eval] allows you to define new makefile constructs that are not constant; which are the result of evaluating other variables and functions.
eval will parse ifeq and $(error) as part of the makefile instead of as commands for the recipe.
One thing to keep in mind is that eval parses its input by itself, without regard for the surrounding syntax of the makefile. This means that you cannot use it to define only part of a rule, like in your example:
build:
$(call check-arguments, $(build-type))
If we use $(eval $(call check-arguments, $(build-type))), then eval will parse the expansion of check-arguments by itself and complain because the recipe has no target. (See this answer.) The solution here is to include build: in check-arguments somehow.

While having $(eval) is fine, I would like to recommend a different approach, based on target resolution instead of conditionals, like so:
$ cat Makefile
supported_build_types := api service
.PHONY: build
build: build-$(build-type)
.PHONY: $(addprefix build-,$(supported_build_types))
$(addprefix build-,$(supported_build_types)): build-%:
#echo 'Building $*'
#echo 'Starting build'
.PHONY: build-
build-:
$(error Must provide build-type of: $(supported_build_types))
.PHONY: build-%
build-%:
$(error Unsupported build type: $*. Must be one of: $(supported_build_types))
This can allow easier extensibility and maintenance while keeping away nuisances of $(eval)s, $(call)s and appropriate escaping.
Running supported build types:
$ make build build-type=api
Building api
Starting build
$ make build build-type=service
Building service
Starting build
Invalid build type:
$ make build build-type=foo
Makefile:17: *** Unsupported build type: foo. Must be one of: api service. Stop.
Missing build type:
$ make build
Makefile:13: *** Must provide build-type of: api service. Stop.

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).

Missing separator error in Makefile after calling a custom function

I'm writing a macro that creates an empty target file name from a given name. And also redirects the named target to that empty target name.
After running this function I'm expecting my Makefile to look like this:
a:./build/._a
./build/._a:
#echo building $#
However I constantly get this:
$ make a
a:./build/._a
Makefile:7: *** missing separator. Stop.
Here's my Makefile:
define empty_target
$(eval $2:=./build/._$1)
$(info $1:$($2))
$(eval $1:$($2))
endef
$(call empty_target,a,_A)
$(_A):
#echo building $#
This will do it:
define empty_target
$(eval $2:=./build/._$1)
$1:$($2)
endef
$(eval $(call empty_target,a,_A))
$(info _A=$(_A))
$(_A):
#echo building $#
And yes, there's a nested eval, which you need if you want the variable assignment to take effect inside of the eval call (otherwise $2 is expanded before it is set, and expands to blank).
As to your original code, I agree, it's somewhat confusing why it raises that error. I can reproduce, and if I add the line blah:blah2 in the middle of empty_target, it seems to get rid of the message. I'm guessing it is a bug in how make colapses whitespace/newlines between the $(eval) calls.

Using ifeq to test the variables

I tried to write this dynamic target to check the variable before running the actual target:
.PHONY: check-env-%
check-env-%:
ifeq ($(${*}),)
$(error not found ${*})
endif
so that I can use it like:
build: check-env-VERSION
But looks like it cannot compare it and even when I supply the required variable, it errors: Makefile:16: *** not found VERSION. Stop.
I believe I'm using the ifeq correctly but not sure why it cannot compare it?
From the docs: "Conditionals control what make actually “sees” in the makefile, so they cannot be used to control recipes at the time of execution." So your access to $* always yields an empty string at the time of makefile analysis, leaving your $(error) as recipe instruction.
Vroomfondel is right. What you can do instead is this:
check-env-%:
test $($*) || (echo $* not found; exit 1;)
...
test shall stop when there is no variable defined.

Matching different/multiple parts of a Makefile target

Here is a Makefile that I currently use to make targets with different configurations, i.e., I am building different software packages with the same target, either all at once or individually.
.PHONY: build test %.build %.test build-all test-all
%.build %.test: PACKAGE = $*
%.build:
#echo build $(PACKAGE)
%.test:
#echo test $(PACKAGE)
build-all: a.build b.build
test-all: a.test b.test
build: $(PACKAGE).build
test: $(PACKAGE).test
I can now build all packages with make build-all or individual packages with, e.g., make build PACKAGE=a. However I would like to switch the body of the %.build and build, etc. targets as in the following:
.PHONY: build test %.build %.test build-all test-all
build:
#echo build $(PACKAGE)
test:
#echo test $(PACKAGE)
build-all: a.build b.build
test-all: a.test b.test
%.build %.test: PACKAGE = $*
$(PACKAGE).%: $*
This way, the pattern matching logic is fully separated from the "main" targets build and test that should contain the actual build commands; making the important parts of the Makefile more readable. However, the last line does not work as intended, i.e., running make a.build and thus make build-all should trigger the target build with PACKAGE=a. The variable assignment in second-last line works, the target matching in the last line does not.
Question: Is there a way to express a matching target like $(PACKAGE).%: $* or to match separate parts of a target like %.%: $2?
As MadScientist explained, the problem cannot be solved easily in GNU make. For completeness, I would like to add and explain my final and more comprehensive solution:
.PHONY: all build test clean %.build %.test build-all test-all
PACKAGES = a b c e f g
PACKAGE = a
all: clean build-all test-all
%.build %.test: PACKAGE = $*
%.build:
#echo build $(PACKAGE)
%.test:
#echo test $(PACKAGE)
clean:
#echo remove build dir
build-all: $(addsuffix .build, $(PACKAGES))
test-all: $(addsuffix .test, $(PACKAGES))
build: $(PACKAGE).build
test: $(PACKAGE).test
This solution avoids eval and foreach and is based on my initial working solution, where the dynamic %.build and %.test targets contain the actual build commands. I added the PACKAGES variable to facilitate easy addition of new packages, a default PACKAGE to prevent execution of misconfigured build commands, and the common targets all and clean as complements.
From the command line, you just call make all, clean, build PACKAGE=x, build-all, etc., i.e., only the static targets, which will then trigger the build commands in the dynamic targets. The static targets and the two variables are also visible in the Bash/Zsh auto-completion.
I think this is the most flexible and yet readable way to build multiple dynamic targets.
First you probably want:
$(PACKAGE).% : %
not using $*, which is an automatic variable and so it has no value except inside the recipe; you can't use it like that in the prerequisite lists.
Second, you can't do this in GNU make. A pattern rule with no recipe doesn't just a create prerequisite relationship, like an explicit rule would do; instead it deletes the pattern rule. Since you didn't have a pattern rule for $(PACKAGE).% yet, this is basically a no-op. Also, target-specific variables are only available inside the recipe, so trying to use $(PACKAGE) in the target definition and expecting it to take the value from some previously set target-specific variable cannot work.
You could do something like this, but it's not fully dynamic (you still need the list of packages and types):
PACKAGES = a b
TYPES = build test
$(foreach T,$(TYPES),$(eval $(addsuffix .$T,$(PACKAGES)): $T))

.ONESHELL target (a 'phony' target) has no effect on makefile

After establishing that prerequisites to .PHONY are made target-like.
And looking at the docs, where the following special targets seem to follow the same syntax rules:
'.EXPORT_ALL_VARIABLES'
Simply by being mentioned as a target...
...
...
...
'.ONESHELL'
If '.ONESHELL' is mentioned as a target...
I tried to following makefile:
all:
#foo=bar
#echo "foo=$${foo}"
.PHONY: all
.PHONY: .ONESHELL
By running it, and got:
foo=
Which definitely is not a result from "oneshell" execution.
So, are some special variables more special than others, regarding their syntax rules?
.ONESHELL should be provided as the target not as the prerequisite as you have specified in your question. If you specify .ONESHELL: all you should get the expected output of foo=bar. That is what I get when running make on the following makefile.
.ONESHELL: all
.PHONY: all
all:
#foo=bar
#echo "foo=$${foo}"

Resources