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

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

Related

Makefile function from make commands, NOT shell commands

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.

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

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)

Passing additional variables from command line to make

Can I pass variables to a GNU Makefile as command line arguments? In other words, I want to pass some arguments which will eventually become variables in the Makefile.
You have several options to set up variables from outside your makefile:
From environment - each environment variable is transformed into a makefile variable with the same name and value.
You may also want to set -e option (aka --environments-override) on, and your environment variables will override assignments made into makefile (unless these assignments themselves use the override directive . However, it's not recommended, and it's much better and flexible to use ?= assignment (the conditional variable assignment operator, it only has an effect if the variable is not yet defined):
FOO?=default_value_if_not_set_in_environment
Note that certain variables are not inherited from environment:
MAKE is gotten from name of the script
SHELL is either set within a makefile, or defaults to /bin/sh (rationale: commands are specified within the makefile, and they're shell-specific).
From command line - make can take variable assignments as part of his command line, mingled with targets:
make target FOO=bar
But then all assignments to FOO variable within the makefile will be ignored unless you use the override directive in assignment. (The effect is the same as with -e option for environment variables).
Exporting from the parent Make - if you call Make from a Makefile, you usually shouldn't explicitly write variable assignments like this:
# Don't do this!
target:
$(MAKE) -C target CC=$(CC) CFLAGS=$(CFLAGS)
Instead, better solution might be to export these variables. Exporting a variable makes it into the environment of every shell invocation, and Make calls from these commands pick these environment variable as specified above.
# Do like this
CFLAGS=-g
export CFLAGS
target:
$(MAKE) -C target
You can also export all variables by using export without arguments.
The simplest way is:
make foo=bar target
Then in your makefile you can refer to $(foo). Note that this won't propagate to sub-makes automatically.
If you are using sub-makes, see this article: Communicating Variables to a Sub-make
Say you have a makefile like this:
action:
echo argument is $(argument)
You would then call it make action argument=something
From the manual:
Variables in make can come from the environment in which make is run. Every environment variable that make sees when it starts up is transformed into a make variable with the same name and value. However, an explicit assignment in the makefile, or with a command argument, overrides the environment.
So you can do (from bash):
FOOBAR=1 make
resulting in a variable FOOBAR in your Makefile.
It seems command args overwrite environment variable.
Makefile:
send:
echo $(MESSAGE1) $(MESSAGE2)
Example run:
$ MESSAGE1=YES MESSAGE2=NG make send MESSAGE2=OK
echo YES OK
YES OK
There's another option not cited here which is included in the GNU Make book by Stallman and McGrath (see http://www.chemie.fu-berlin.de/chemnet/use/info/make/make_7.html). It provides the example:
archive.a: ...
ifneq (,$(findstring t,$(MAKEFLAGS)))
+touch archive.a
+ranlib -t archive.a
else
ranlib archive.a
endif
It involves verifying if a given parameter appears in MAKEFLAGS. For example .. suppose that you're studying about threads in c++11 and you've divided your study across multiple files (class01, ... , classNM) and you want to: compile then all and run individually or compile one at a time and run it if a flag is specified (-r, for instance). So, you could come up with the following Makefile:
CXX=clang++-3.5
CXXFLAGS = -Wall -Werror -std=c++11
LDLIBS = -lpthread
SOURCES = class01 class02 class03
%: %.cxx
$(CXX) $(CXXFLAGS) -o $#.out $^ $(LDLIBS)
ifneq (,$(findstring r, $(MAKEFLAGS)))
./$#.out
endif
all: $(SOURCES)
.PHONY: clean
clean:
find . -name "*.out" -delete
Having that, you'd:
build and run a file w/ make -r class02;
build all w/ make or make all;
build and run all w/ make -r (suppose that all of them contain some certain kind of assert stuff and you just want to test them all)
If you make a file called Makefile and add a variable like this $(unittest)
then you will be able to use this variable inside the Makefile even with wildcards
example :
make unittest=*
I use BOOST_TEST and by giving a wildcard to parameter --run_test=$(unittest)
then I will be able to use regular expression to filter out the test I want my Makefile
to run
export ROOT_DIR=<path/value>
Then use the variable, $(ROOT_DIR) in the Makefile.

Force Makefile to execute script before building targets

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

Resources