How to use brace expansnsion in makefile prerequisites? - bash

In bash I can use src/{foo/{one,two},bar/{three,four}}.o to describe the files:
src/foo/one.o
src/foo/two.o
src/bar/three.o
src/bar/four.o
I would like to describe the prerequisites of a makefile target in a similar manner. Is there a way to accomplish this in GNU Make?
This is what I have:
SHELL:=/bin/bash
all: src/{foo/{one,two},bar/{three,four}}.o
#echo "$(^)"
And I get:
make: *** No rule to make target 'src/{foo/{one,two},bar/{three,four}}.o', needed by 'all'. Stop.
I came across this and this questions that suggest to add SHELL=/usr/bin/bash to the makefile. But I still cannot get it to work.
Thanks.

Ok, so GNU make has this shell command that alone is not much relevant here because the brace expansion is a bash thing, and make uses Bourne shell (/bin/sh). But, together with the recommendation of the cited questions, I can do something like this:
SHELL:=/bin/bash
all: $(shell echo src/{foo/{one,two},bar/{three,four}}.o)
#echo "$(^)"
With this, the braces are correctly expanded.

Related

How to unhide commands in Makefile?

I want to troubleshoot Makefile. A lot of commands are hidden using the # prefix. e.g.
all:
#echo "building..."
how can I tell make to show all the commands? I tried the -d option and it does not show hidden commands.
Maybe try running make with V=1 or VERBOSE=1
I'm not exactly sure this is what you are looking for, but here is a related technique:
Prefixing commands with # in a makefile recipe can be convenient, so output does not get too much cluttered, but, as you may have discovered, in some situations it can be useful to actually see what command is executed.
One solution is to avoid using the # character in a makefile but use instead a variable. Say, a one-letter one. Say L, for "log output" (but its your choice).
Then, prefix all your commands in your makefile with that variable:
target: prerequ
$(L)do_this $< $#
$(L)do_that $< $#
An simply define the variable, using some command-line switch:
ifeq "$(LOG)" ""
LOG=no
endif
ifeq "$(LOG)" "Y"
L=#
endif
Then, you can launch make from the shell like this $make target (no logging) or like this:
$ make target LOG=Y
And all the executed commands will show up!

How a new target can call a previous target with additional arguments?

I have such code snippet:
.PHONY: program1 program2
a=A
b=B
c=C
program1:
#python example.py a=$(a) b=$(b)
program2:
program1 c=$(c) d=d
Due to the DRY principle, I don't want to replicate code and composed program2 in a way calling program1.
But I understand that program1 is not in a path.
How can I correctly define program2 target?
The recipe of a rule contains shell commands. make does not execute them directly. The shell it uses to execute them does not recognize make target names as commands any more than any other shell does, so a recipe cannot use make targets as commands.
But a recipe can run make, a procedure conventionally described as "recursive make". For example, this rule would achieve what I think you are describing:
program2:
$(MAKE) program1 c='$(c)' d=d
The MAKE variable will normally expand to the name of make command you used -- often just make, but perhaps something like gmake or /special/path/make. You can also redefine it yourself inside the makefile, maybe to add flags to it, for example.

defines the variable in Makefile

The Makefile is written as the followings:
all:
iceking='$#';
echo $$iceking;
However the output is as the followings:
[root#localhost test]# make
iceking='all';
echo $iceking;
The string 'all' doesn't output. Why?
New sub-shell for each line of the recipe.
From GNU make manual:
When it is time to execute recipes to update a target, they are
executed by invoking a new sub-shell for each line of the recipe...
Set of shell commands, that called by your makefile is equivalent to:
bash -c iceking='all';
bash -c echo $iceking;
The variable iceking is not accessible from the environment of the second invocation of bash. That's why you receive empty output.
.ONESHELL (only since GNU make 3.82)
One way to fix it, is use .ONESHELL special target. From GNU make manual:
If the .ONESHELL special target appears anywhere in the makefile then
all recipe lines for each target will be provided to a single
invocation of the shell.
But there is one problem with .ONESHELL, this feature was added only since version 3.82 of GNU make.
One line recipes.
Another way to fix it, is write recipes in one line, since all commands in the line will be passed to a single invocation of the shell.
all:
iceking='$#'; echo $$iceking;

MAKEFILES variable environment

How to use MAKEFILES variable environment? I writing in a bash MAKEFILES=/home/toker/mymake, but if I'm running a /home/toker/bundocode/gettingstart/testMF/Makefile then /home/toker/mymake doesn't executed. When I'm type $MAKEFILES in the bash then bash: /home/toker/mymake: Permission denied is displayed.
The MAKEFILES variable contains one or more makefiles, not the make program. From the above messages it looks like mymake is a make program you're trying to run. The shell decides what programs to run (by looking in directories listed in the $PATH environment variable) and the shell doesn't pay any special attention to the MAKEFILES variable. Only once make is running does it look at the MAKEFILES variable to see what other makefiles it might want to parse.
What is in the /usr/toker/mymake file? What do you mean by running a .../Makefile? One typically runs make, not a makefile. Then make reads in the makefile. There are ways to change a makefile to be executable but I don't think that's what you're looking for here.
ETA:
If the file /usr/toker/mymake is actually a makefile (BTW, convention typically uses .mk extensions to denote makefiles although there's no really official extension), then you can do this:
$ cat /usr/toker/mymake
$(info loaded /usr/toker/mymake)
foo: ; #echo building $#
$ export MAKEFILES=/usr/toker/mymake
$ cat Makefile
bar: foo ; #echo building $#
$ make
loaded /usr/toker/mymake
building foo
building bar
If you can show an example of exactly what you did (show the makefile and the commands you typed and the results you got) and explain what you don't understand about it, then we can see much more accurately what's going on than by you trying to describe it in words).

Is there a configuration file for gnu make?

I want to tell make that it shall always use -j4 option even if I didn't specify it vie command line. Normally i would do this in some configuration file (i.e. ~/.makerc).
Does such file exist for gnu make?
Have a read about the $(MAKEFLAGS) variable:
export MAKEFLAGS=j4
However this will likely interfere with recursive-make-based builds (not that sensible people are using recursive make anyway!), by interfering with GNU make's ability to communicate with its sub-makes.
So the more sensible approach is probably a wrapper script or an alias or shell function.
Well, yes and no --- normally you would use an include file. Put your common configuration items together in a file, say common.mk and add
include common.mk
at the top of your makefile. If the flag doesn't have a matching way to configure it from inside the make file, you can use a function
function mk {
make -j4 $*
}
It doesn't exist, but you can do this by having a recursive call into make.
For example:
Makefile:
-include $(HOME)/.makerc
.DEFAULT_GOAL: all
# This will handle a default goal if make is just called without any target
all:
$(MAKE) $(MAKE_OPTIONS) -f Makefile.real $(MAKECMDGOALS)
# This handles all targets and passes it through
%:
$(MAKE) $(MAKE_OPTIONS) -f Makefile.real $(MAKECMDGOALS)
$(HOME)/.makerc:
MAKE_OPTIONS := -j4
I would like to expand a bit on the solution hinted in John Marshall's answer.
You can simply put a one-line wrapper script somewhere earlier in the $PATH with the following contents:
#!/bin/bash
$(type -ap make | sed -n 2p) -j4 "$#"
(The script doesn't have to be named make, and that would make it simpler, but I find it convenient if it is.)
I would argue that this is better than the other approaches for the following reasons:
Unlike MAKEFLAGS approach, it does not break recursive builds (which are actually quite common in my experience).
Unlike include .makerc approach, it can be applied locally without changing any existing makefiles or your workflow in any way.
Unlike shell alias or function approach, it is shell-agnostic (doesn't tie you to any particular shell) and works in any additional build scripts that you might have to use, too, as long as you launch them in the same environment.
I like the MAKEFLAGS approach suggested by John Marshall in lieu of make supporting something like an automatic .makerc project config file. However, I didn't want to have to remember to source a .env or similar environment variables beforehand (and unsetting them afterward).
A solution to this is to put the MAKEFLAGS assignment at the top of the Makefile itself:
#!/usr/bin/env make
MAKEFLAGS=s
.PHONY: foo
foo:
echo "hello, make"
Run it:
$ make foo
hello, make
Compared to running without the MAKEFLAGS=... line:
$ make foo
echo "hello, make"
hello, make

Resources