makefile calling function inside a function - makefile

This might be a very basic but I need to call a function within a function but I cannot figure it out:
chmod-prod:
chown git:git-R ./
composer-dev:
[who do I how chmod-prod: within here]
Any help would be appreciated

Note that chmod-prod and composer-dev are known as a targets in make terminology.
Using targets
If the result of chmod-prod needs to happen before the rest of the "recipe" in composer-dev then the most natural way in make would be to add it as a dependency.
composer-dev: chmod-prod
[the rest of composer-dev recipe]
Given that both these targets do not lead to the creation of a file with their name it would be good practice to declare them as .PHONY:
.PHONY: chmod-prod
.PHONY: composer-dev
This ensures that the targets will not be affected if someone adds a file with the same name.
Using a variable
Another route would be store the command in a variable:
chmod_prod_cmd:=chown git:git-R ./
Which could then be used in the recipes of both your targets:
composer-dev:
$(chmod_prod_cmd)
composer-dev:
$(chmod_prod_cmd)
[presumably some more recipe]
Canning a multi-line recipe
If the bit of recipe you want to reuse involves multiple lines then you will need to use the define directive instead of a normal variable assignment:
define chmod_prod_cmd
echo Doing something
chown git:git-R ./
endef

Assuming those need to be targets (and they sequence that way) then PeterSW has the right answer.
If they need finer-grained sequencing within the body of the composer-dev recipe then you could always just run make again (e.g. $(MAKE) chmod-prod) though I wouldn't suggest this structure of makefiles as it is expensive (to say the least).
That being said if the recipe body of chmod-prod is actually just a simple single (or series) of shell commands (that don't need to be run externally manually i.e. as make chmod-prod) then using a make variable/define instead of a target.
Something like this
chmod-prod := chown git:git-R ./
or this
define chmod-prod
chown git:git-R ./
endef
and used like this
composer-dev:
some_command
$(chmod-prod)
other_command

Related

Loop through files in make file

Right now I have the following in my make file to create a symbolic link to a file in my current directory:
MY_FILE := "$(SOME_PATH)/file.txt"
ln -s $(MY_FILE)
What I would like is to do some sort of glob operation to link a bunch of files (or none).
MY_FILES := "$(SOME_PATH)/*.txt"
for file in files:
ln -s $(MY_FILE)
Could anyone point me in the right direction to do this?
Thanks!
The following should do what you want:
MY_FILES := $(wildcard $(SOME_PATH)/*.txt)
LINKS := $(notdir $(MY_FILES))
.PHONY: links clean-links
links: $(LINKS)
$(LINKS): %: $(SOME_PATH)/%
ln -s $<
clean-links:
rm -f $(LINKS)
Explanation:
Make functions. wildcard and notdir are two make functions. Knowing the make functions (at least the most frequently used) really helps writing nice, elegant and efficient make files. Of course, if you have spaces or special characters in your file names you will encounter some problems because most make functions consider spaces as separators. But if you have such files and directory names you should probably use something else than make.
Static pattern rules:
$(LINKS): %: $(SOME_PATH)/%
is a static pattern rule. For each word foo.txt in $(LINKS) it instantiates one single make rule:
foo.txt: $(SOME_PATH)/foo.txt
ln -s $(SOME_PATH)/foo.txt
Knowing how to use pattern rules (static or not) is essential if you want to write compact and generic make files.
Automatic variables. $< is a make automatic variable. In recipes (the commands part of rules) it expands as the first listed pre-requisite. There are many other automatic variables and they are quite handy to write generic rules.
Phony targets. links and clean-links are phony targets because they do not correspond to real files that we want make to create or update. They are kind of short-hands for actions. make links creates all missing links and make clean-links removes them all. As make has no way to guess that these targets are not regular file names we tell it with the .PHONY special target.
Make creates "targets" from "prerequisites". In your case MY_FILES holds prerequisites to create the (link) targets. The rule for a single file might look like this:
file.txt: $(SOME_PATH)/file.txt
ln -s $<
You want multiple files, and in this case we can use this pattern rule:
%.txt: $(SOME_PATH)/%.txt
ln -s $<
You can now create links with the make commands:
make file.txt
make otherfile.txt
Finally we will look into the wildcard function to get all files, and the function notdir function to get the link names from the file names. Following rule will print all text files, and depends on all files in SOME_PATH linked to current working directory:
MY_FILES := $(notdir $(wildcard $(SOME_PATH)/*.txt))
cat: $(MY_FILES)
cat $^
%.txt: $(SOME_PATH)/%.txt
ln -s $<

Global prerequisite in GNU make - is it possible

I have a Makefile with tons of targets and would like for a certain script to get executed first, irrespective of what target is being called. I like to call it a global prerequisite.
I do not want to create a target for the script and set it as a prerequisite for all existing targets (which, as I said aren't few). Besides, someone else could add a target in future and not add my script as a prerequisite for their target, so the global prerequisite would take care of that.
Does GNU-make provide for a means to achieve this?
Another approach:
-include dummy
.PHONY: dummy
dummy:
run-the-script
Make will always attempt to rebuild any file which the makefile attempts to include (if it is out of date or does not exist). In this case there is no such file, and the rule to build it runs the script and does nothing else.
There is a solution without modifying your existing Makefile (main difference with the answers pointed to by tripleee). 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 answers pointed to by tripleee).

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.

Sub-makefiles and passing variables upward

I have a project that involves sub-directories with sub-makefiles. I'm aware that I can pass variables from a parent makefile to a sub-makefile through the environment using the export command. Is there a way to pass variables from a sub-makefile to its calling makefile? I.e. can export work in the reverse? I've attempted this with no success. I'm guessing once the sub-make finishes its shell is destroyed along with its environment variables. Is there another standard way of passing variables upward?
The short answer to your question is: no, you can't [directly] do what you want for a recursive build (see below for a non-recursive build).
Make executes a sub-make process as a recipe line like any other command. Its stdout/stderr get printed to the terminal like any other process. In general, a sub-process cannot affect the parent's environment (obviously we're not talking about environment here, but the same principle applies) -- unless you intentionally build something like that into the parent process, but then you'd be using IPC mechanisms to pull it off.
There are a number of ways I could imagine for pulling this off, all of which sound like an awful thing to do. For example you could write to a file and source it with an include directive (note: untested) inside an eval:
some_target:
${MAKE} ${MFLAGS} -f /path/to/makefile
some_other_target : some_target
$(eval include /path/to/new/file)
... though it has to be in a separate target as written above because all $(macro statements) are evaluated before the recipe begins execution, even if the macro is on a later line of the recipe.
gmake v4.x has a new feature that allows you to write out to a file directly from a makefile directive. An example from the documentation:
If the command required each argument to be on a separate line of the
input file, you might write your recipe like this:
program: $(OBJECTS)
$(file >$#.in) $(foreach O,$^,$(file >>$#.in,$O))
$(CMD) $(CMDFLAGS) #$#.in
#rm $#.in
(gnu.org)
... but you'd still need an $(eval include ...) macro in a separate recipe to consume the file contents.
I'm very leery of using $(eval include ...) in a recipe; in a parallel build, the included file can affect make variables and the timing for when the inclusion occurs could be non-deterministic w/respect to other targets being built in parallel.
You'd be much better off finding a more natural solution to your problem. I would start by taking a step back and asking yourself "what problem am I trying to solve, and how have other people solved that problem?" If you aren't finding people trying to solve that problem, there's a good chance it's because they didn't start down a path you're on.
edit You can do what you want for a non-recursive build. For example:
# makefile1
include makefile2
my_tool: ${OBJS}
# makefile2
OBJS := some.o list.o of.o objects.o
... though I caution you to be very careful with this. The build I maintain is extremely large (around 250 makefiles). Each level includes with a statement like the following:
include ${SOME_DIRECTORY}/*/makefile
The danger here is you don't want people in one tree depending on variables from another tree. There are a few spots where for the short term I've had to do something like what you want: sub-makefiles append to a variable, then that variable gets used in the parent makefile. In the long term that's going away because it's brittle/unsafe, but for the time being I've had to use it.
I suggest you read the paper Recursive Make Considered Harmful (if that link doesn't work, just google the name of the paper).
Your directory structure probably looks like this:
my_proj
|-- Makefile
|-- dir1
| `-- Makefile
`-- dir2
`-- Makefile
And what you are doing in your parent Makefile is probably this:
make -C ./dir1
make -C ./dir2
This actually spawns/forks a new child process for every make call.
You are asking for updating the environment of the parent process from its children, but that's not possible by design (1, 2).
You still could work around this by:
using a file as shared memory between two processes (see Brian's answer)
using the child's exit error code as a trigger for different actions [ugly trick]
I think the simplest solution is using standard out from a sub Makefile.
Parent Makefile
VAR := $(shell $(MAKE) -s -C child-directory)
all:
echo $(VAR)
Child Makefile
all:
#echo "MessageToTheParent"

Override target in makefile to add more commands?

At work we use a common makefile that other makefiles include (via the include statement) and it has a generic "clean" target that kills some common files. I want to add on to that target in my new makefile so I can delete some specific files, but if I add a clean target in my makefile, it just overrides the old one.
I know I can just make a new target with a new name and have it call clean, and then do other stuff, but for sake of consistency I'd like to be able to just call make clean and have it do everything.
Is that possible?
I've seen this done at several shops. The most common approach is to use double-colon rules, assuming you're using something like GNU make. In your common makefile you would have something like this:
clean::
# standard cleanup, like remove all .o's:
rm -f *.o
Note that there are two colons following clean, not just one!
In your other makefile you just declare clean again, as a double-colon rule:
clean::
# custom cleanup, like remove my special generated files:
rm -f *.h.gen
When you invoke make clean, GNU make will automagically run both of these "branches" of the clean rule:
% make clean
rm -f *.o
rm -f *.h.gen
It's simple to set up and it composes quite neatly I think. Note that specifically because it is a double-colon rule, you don't get the "overriding commands" errors you normally get when you define two rules for the same target. That's sort of the point of double-colon rules.
You can write your own clean and make it a preq of the common clean.
clean: myclean
myclean:
rm whatever
Yours will run first. If for some reason you want the common clean to run first then the solution will be more complicated.
EDIT:
Here is the best solution I can see which runs the common rule before the local one:
include Makefile.common
clean:
$(MAKE) -f Makefile.common $#
rm whatever additional things
The include directive is necessary because the local makefile relies on the common one for things other than clean. The local clean rule overrides the common clean rule, but invokes the common clean rule before doing the additional work. (This overriding will cause some warnings, which is a nuisance; I don't know a good way to silence them.)
Use implicit rules:
existing-target: my-extention
my-extention:
echo running command 1
echo running command 2
Very simple make tutorial to ramp up.
When using :: you can run into issues since make complains when you mix single colon : and double colon :: rules:
a:
echo a
a::
echo aa
will result in:
. . .
*** target file `a' has both : and :: entries. Stop.
It seems like the common makefile's rule should be called something like common-clean. Then each main makefile would declare their clean rule as
clean: common-clean
and you're set.
If that isn't an option, you could take a look at double colon rules, but those introduce a whole other set of issues to consider.
Adding another possible solution I've seen for posterity... I know the OP was wary about changing the common makefile, but something like this works and involves minimal changes.
local makefile 1:
CLEAN=MyExe1 MyExe2
....
include /my/common/makefile
local makefile 2:
CLEAN=MyExe3 MyExe4
....
include /my/common/makefile
common makefile:
clean:
rm -f *.dep *.o *.a $(CLEAN)
Basically the idea is to define some variable (in this case CLEAN) in each local makefile with all the specific items you want to delete. Then the common makefile runs rm -f on all the common file types to delete, plus whatever was specifically flagged for deletion in each local makefile via the CLEAN variable. If there's nothing specific to delete, simply omit the variable declaration or leave it empty (CLEAN=)
So now if we run make clean for local makefile 1, it executes
rm -f *.dep *.o *.a MyExe1 MyExe2
And if we run make clean for local makefile 2, it executes
rm -f *.dep *.o *.a MyExe3 MyExe4
I've found a better solution:
.PHONY: my-extra-clean
clean: my-extra-clean
my-extra-clean:
rm <whatever-you-want>
include Makefile.common
The key line is clean: my-extra-clean. Ie, you can add dependencies in separate stanzas in different makefiles to add behaviour. my-extra-clean is run as a dependency of the root clean target.
For ours, we define a variable, EXTRAFILESTOCLEAN, then when the clean rule runs, it has a step to remove anything specified in the EXTRAFILESTOCLEAN variable
clean:
rm -f *.o
ifdef $(EXTRAFILESTOCLEAN)
rm -f $(EXTRAFILESTOCLEAN)
endif
That can cause unexpected problems if you set that variable to weird values, but you could guard against those by adding prefixes or other tests.
It's in the docs: https://www.gnu.org/software/make/manual/html_node/Overriding-Makefiles.html
So instead of include Makefile you use a wildcard target and forward it to the base Makefile:
# -include base.Makefile <--- not this
%:
#$(MAKE) -f base.Makefile $#

Resources