Can you prioritize makefile targets? - makefile

I have a very large and convoluted make system that takes hours to complete. I'm researching potential optimizations, and I had a question on whether it is possible to prioritize certain targets.
Here's a sample of a simple Makefile that illustrates the concept:
Test%: Prog%
#run some post build verification on Prog{n}
# -- takes 30min or less on failure...
Prog1: A B
# do some linking
Prog2: B C
# do some linking
A B C:
# take 10 minutes each
And then I run
make -j2 Prog1 Test2
Then the fastest way to build this would be: B & C, followed by Prog2 & A, followed by Test2 and Prog2. Of course, typically, make would build A and B first, which would delay my final output by 10 minutes.
If I have foreknowledge that Test2 is going to be a bottleneck, is it possible to prioritize that target and all its dependencies over other targets in gnu make?
To add to the question, Let's say I'm building Test1, Test2 ... Test20. Then in that case, I'd like one of the targets -- say Test1 to be completed as soon as possible, as if it that fails, I would like to know as soon as possible so I can terminate the build and let someone else use the build machine.
I considered doing separate instances of make as so:
make Test2 && make Prog1
But, while the test is building, then Prog1 will not be building, resulting in wasted cycles on the build server. If I tried to build them in parrallel with priorities:
`make Test2; nice -n -10 make Prog1`
This could result in race conditions when building B.
I've not yet found any good solutions, but I thought I'd ask on in-case I missed something. (Also, I'm not sure if this should be on SO or SuperUser -- I chose here as it's technically about 'programming', but please feel free to correct me and I can move it).

GNU make offers no syntax to add a weight to a target. I.e. when make is able to start the next job and the independent(!) targets A and B are unblocked (all their dependencies have been fulfilled) and require remaking, then it depends on their order in the internal make database which one gets selected first for execution.
But you could use additional dependencies to achieve your goal. To take the example from your question:
TESTS := Test1 Test2
$(TESTS): Test%: Prog%
Prog1: A B
Prog2: B C
ifdef _PRIORITIZE_TEST2
# additional dependencies to make sure Prog2 -> Test2 is prioritized
A: B C
endif
A B C Prog1 Prog2 Test1 Test2 all:
#echo $#
.PHONY: A B C Prog1 Prog2 $(TESTS) all
Test runs:
$ make --trace -j10 Prog1 Test2
Makefile:13: target 'A' does not exist
echo A
Makefile:13: target 'B' does not exist
echo B
Makefile:13: target 'C' does not exist
echo C
A
B
C
Makefile:13: update target 'Prog1' due to: A B
echo Prog1
Makefile:13: update target 'Prog2' due to: B C
echo Prog2
Prog1
Prog2
Makefile:13: update target 'Test2' due to: Prog2
echo Test2
Test2
$ make --trace -j10 _PRIORITIZE_TEST2=1 Prog1 Test2
Makefile:13: target 'B' does not exist
echo B
Makefile:13: target 'C' does not exist
echo C
B
C
Makefile:13: update target 'A' due to: B C
echo A
Makefile:13: update target 'Prog2' due to: B C
echo Prog2
A
Makefile:13: update target 'Prog1' due to: A B
echo Prog1
Prog2
Makefile:13: update target 'Test2' due to: Prog2
echo Test2
Prog1
Test2
This will probably involve a lot of hand-crafting to get the result you want. You might even need to write different sets of additional dependencies to cover different make invocation scenarios.
You could look into dumping the GNU make database (make --no-builtin-rules --print-data-base), parsing it to extract all targets & their dependencies and visualizing the resulting graph. Here is an example how to generate a directed graph in the DOT language of Graphviz:
#!/usr/bin/perl
use warnings;
use strict;
BEGIN {
print "digraph build\n";
print "{\n";
}
my $default_goal = '???';
my #goals;
while (<STDIN>) {
if (my($target, $dependencies) = /^([\w_-]+):\s+(.*)/) {
#print "Node ${target}\n";
print " $_ -> ${target}\n"
for (split(' ', $dependencies));
} elsif (my($goals) = /^MAKECMDGOALS :=\s+(.+)/) {
#goals = split(' ', $goals);
} elsif (my($goal) = /^\.DEFAULT_GOAL :=\s+(.+)/) {
$default_goal = $goal;
}
}
END {
#goals = ( $default_goal )
unless (#goals);
#print "Root $_\n"
# for (#goals);
print "}\n";
}
exit 0;
For the above example that would result in:
$ make -n --no-builtin-rules --print-data-base Prog1 Test2 2>&1 | perl dummy.pl
digraph build
{
A -> Prog1
B -> Prog1
Prog1 -> Test1
B -> Prog2
C -> Prog2
Prog2 -> Test2
}

Related

Makefile ifeq always true

I have the following Makefile target:
target1:
$(eval count_abc := $(shell grep -c "ABC" myFileA))
$(eval count_def := $(shell grep -c "DEF" myFileB))
echo $(count_abc)
echo $(count_def)
ifeq ($(count_abc),$(count_def))
echo "TRUE"
else
echo "FALSE"
endif
But the output is always TRUE, e.g.:
echo 22
22
echo 21
21
echo TRUE
TRUE
What am I doing wrong here? What I want is INSIDE the target do 2 greps and compare their outputs and do something or something else based on the result. Please note that the greps must be done within the target since myFileA and myFileB get created on the target before and don't exist at the beginning when running make.
Thanks,
Amir
The rule file for "make" is declarative in nature - the makefile defines rules and targets, and then the make program evaluate the rules, and decide which action to take based on the target. As a result, execution is not always in the order the lines are entered into the file.
More specifically, the "ifeq" is evaluated at the rule definition stage, but the actions for building the target (eval count_abc ...) are executed when the target is built. As a result, when the ifeq is processed, both count_abc and count_def are still uninitialized, expanded to empty strings.
For the specific case you described - building a target that will compare the grep -c output from the two files, you can try something like below, effectively using shell variables (evaluated when target is evaluated), and not make variables (which are mostly declarative, evaluated when makefile is read)
target1:
count_abc=$(grep -c "ABC" myFileA) ; \
count_def=$(grep -c "DEF" myFileB) ; \
echo $(count_abc) ; \
echo $(count_def) ; \
if [ "$count_abc" -eq "$count_def" ] ; then echo TRUE ; else echo FALSE ; fi
Disclaimer: I did not run the revised makefile, not having access to desktop at this time.

Makefile: Running a specific target in parallel

I am learning Makefile and trying to implement parallelism. I am aware of the "-j" option. However, for example having the following makefile (on Windows)-
all: a b c d
a:
# some build rule
b:
# some build rule with parallelism
c:
# some build rule
d:
#some build rule
I am trying to run make all with only target "b" running in parallel. Passing the -j option with the build rule for "b" doesn't work. Any pointers?
You could get b's recipe to run in the background as so:
all: a b c d
#echo running $#
.PHONY: a b c d all
a c d: | b
#echo -n _$#0 && \
sleep 1 && echo -n _$#1 && \
sleep 1 && echo _$#2
b:
#(echo -n _$#0 && \
sleep 2 && echo -n _$#1 && \
sleep 2 && echo -n _$#2\
) &
Which outputs:
_b0_a0_a1_b1_a2
_c0_c1_b2_c2
_d0_d1_d2
running all
The order-only dependency on b makes b run first, otherwise it wouldn't start until after a completes with -j1... It does of course mean that you have to build b if you build either a c or d.
Alternatively, (and I'm not recommending this) you could use some manual locking mechanism such as flock to prevent a, c, and d from running in parallel (note that the flock only protects a single shell, so you would have to collapse your recipes into a single line protected by flock for this to work).

Writing out dependencies into a file using Make

I'm writing a Makefile for Verilog compilation (isn't important if you aren't familiar with it). The compiler command can either take compile units or a flat file that has the compile units 'in order'. For the latter case, I'd like to write a rule that will spit out a file that has the dependencies in the right order.
Let's assume a simple makefile below:
a.output:
<a.files>
b.output: a.output
<b.files>
c.output: d.output
<c.files>
d.output: d.input
<d.files>
What I'd like is a file that contains:
a.files
b.files
d.files
c.files
An idea I had was to have a variable in the recipe that can be appended to, like
a.output:
MY_FILES += <a.files>
but I don't think that will work in a recipe context. Does anyone have a decent solution to this problem?
Also, I'd like the Makefile to be parallel but obviously it won't work for this target. How can I specify disabling parallel execution for this specific target or set of targets?
EDIT Oct 8 2019
To make it simpler for a person not familiar with Make syntax to write dependencies, I've basically let them write out their intent using variables:
MODULES += module_a
module_a.prerequisites = module_b module c
module_a.other_var = some_string_a
MODULES += module_b
module_b.prerequisites =
module_b.other_var = some_string_b
I then use a define directive to generate the rules necessary for compilation (I was inspired by this example). This means I do have flexibility on what I can create. So, in the previous example, <a.output> is actually the target for module_a. This can be a PHONY target. <a.files> actually represents the variables (prerequisites and other_var in the example).
I'm sorry for the miscommunication but what I'm trying to do is write out all the target and dependent module_x.other_var in the right order for a given module. I hope that makes it clear.
I'm currently concatenating a file in the right order which is one of the solutions mentioned below. I was wondering if there's some other Make magic that I could apply.
In its simplest form, you can just echo the names to the file
OUTPUT_FILE = output.file
a.output:
echo "<a.files>" > $(OUTPUT_FILE)
b.output: a.output
echo "<b.files>" >> $(OUTPUT_FILE)
c.output: d.output
echo "<c.files>" >> $(OUTPUT_FILE)
d.output: d.input
echo "<d.files>" >> $(OUTPUT_FILE)
If these files are prerequisites then you can print each dep or target using the auto vars:
Print the target/goal
d.output: d.input
echo "$#" >> $(OUTPUT_FILE)
print the first prerequisite
d.output: d.input
echo "$<" >> $(OUTPUT_FILE)
print all prerequisites
d.output: d.input
echo "$^" >> $(OUTPUT_FILE)
update for parallel build
Not pretty, can certainly improve this, but this will be safe for parallel builds. However as I say I think the echo method alone seems to work ok.
Also note this is not tested, so treat as pseudo code
OUTPUT_FILE = output.file
OUTPUT_FILE_A = output.file.a
OUTPUT_FILE_B = output.file.b
OUTPUT_FILE_C = output.file.c
OUTPUT_FILE_D = output.file.d
OUTPUT_FILES = $(OUTPUT_FILE_A)
OUTPUT_FILES += $(OUTPUT_FILE_B)
OUTPUT_FILES += $(OUTPUT_FILE_C)
OUTPUT_FILES += $(OUTPUT_FILE_D)
final: a.output b.output c.output d.output
echo "" > $(OUTPUT_FILE)
for file in "$(OUTPUT_FILES)" ; do \
cat $$file >> $(OUTPUT_FILE) ; \
done
a.output:
echo "<a.files>" > $(OUTPUT_FILE_A)
b.output: a.output
echo "<b.files>" > $(OUTPUT_FILE_B)
c.output: d.output
echo "<c.files>" > $(OUTPUT_FILE_C)
d.output: d.input
echo "<d.files>" > $(OUTPUT_FILE_D)

How to declare a deferred variable that is computed only once for all?

I have a shell program that takes ages to complete. As written, executing make build takes 4 x 2 seconds to complete because $(value) is computed for each file.
A solution is to declare value a deferred variable by using := instead of =.
Unfortunately this is not a solution either because it slows down the execution of make clean and any other targets by 2 seconds because value is computed for nothing.
value = $(shell sleep 2 && echo 42)
in = a b c d
out = $(addsuffix .out,$(in))
build: $(out)
%.out: %
echo $(value) > $< || [ rm $# -a true ]
init:
touch $(in)
clean:
rm -vf $(out)
How can I set a variable what is assigned only if used, but only computed once ?
Said differently, I would like build to take 2 seconds to complete and clean to be immediate.
I am not interested to a solution that involves conditionals in order to bypass the assignment of value if the target is not build.
An alternative solution would be this. Unfortunately in this case I need to check whether or not the shelve file needs to be regenerated.
value = $(cat shelve)
shelve:
sleep 2 && echo 42 > $# || [ rm $# -a true ]
in = a b c d
out = $(addsuffix .out,$(in))
build: $(out)
%.out: %
echo $(value) > $< || [ rm $# -a true ]
init:
touch $(in)
clean:
rm -vf $(out)
Here's a trick you can play:
value = $(eval value := $(shell cat shelve))$(value)
How this works: value is first assigned using recursive assignment so the value on the RHS is not expanded.
The first time value is expanded the make parser will first run the $(eval ...) which starts up a "new parser" for makefiles. In that parser, the content value := $(cat shelve) is evaluated. Here, value is a simple variable assignment so the RHS is expanded immediately and the $(shell ...) is run and assigned to value.
Remember make doesn't really have a concept of variable scope, so this value is just the same global value variable that we are setting in the outer parser.
Then the eval completes and expands to the empty string, and make continues parsing things. Here it finds the value $(value) and expands that... value now has the result from the eval, not the eval text itself, so that's what will be expanded.
Maybe this will help:
value = $(eval value := $(shell cat shelve))$(value)
Here value contains the string $(eval value := $(shell cat shelve))$(value)
Now you expand it:
%.out: %
echo $(value) > $< ...
Make starts to expand this recipe. It gets to $(value) and sees it needs to expand the variable value: since it's recursive it expands the value:
$(eval value := $(shell cat shelve))$(value)
First it expands the eval, which parses this:
value := $(shell cat shelve)
That sets the value variable as a simply-expanded variable, so the RHS is expanded immediately. Say the results of cat shelve are "foo", so value is now set to foo (and it's marked simply expanded).
That's the end of the eval, so then make starts the next part which is $(value), so it looks up the variable value and discovers it's a simply-expanded variable with the value foo.
One solution would be to turn that value into a regular file target that gets updated only when its prerequisites change. If you insist on rebuilding that target for every build, mark it as phony.
When clean target does not depend on that file, then it won't be rebuilt when you invoke make clean.
In
%.out: %
echo $(value) > $< || [ rm $# -a true ]
echo $(value) > $< updates the prerequisite, whereas make expects it to update the target only. Updating a prerequisite must be done by a separate rule with that prerequisite being the target.
You can make the assignment depend on the target name in $(MAKECMDGOALS):
ifneq ($(MAKECMDGOALS),clean)
value := $(shell sleep 2 && echo 42)
endif
See also the docs for details.

Parametrize Makefile Targets

I have a parameter space, say A={1,2}, B={u,v}, and I need to create a file for each pair (a,b) in A x B.
What I need generated is a set of Makefile targets such as:
file_1_u.csv:
./run_program --A 1 --B u > file_1_u.csv
file_1_v.csv:
./my_program --A 1 --B v > file_1_u.csv
file_2_u.csv:
./run_program --A 2 --B u > file_1_u.csv
file_2_v.csv:
./my_program --A 2 --B v > file_1_u.csv
Is it possible to generate the targets and use the pertaining parameters in each generated target?
PS.:
I know that targets can be created by using variables, but then $# only gives only the full target name (e.g. file_1_u.csv). Instead, I need a and b individually to pass them as arguments to my_program.
Generally speaking, handling more than 1 element in the pattern in a makefile require eval, $$, includes and other non-straightforward processing.
However, in your specific example there is an obvious substitution that could be used like this:
A:=1 2
B:=u v
l:=$(foreach a, $(A), $(foreach b, $(B), $(a)_$(b)))
all: $(l:%=file_%.csv)
file_%.csv:
./run_program --A $(subst _, --B ,$*) > $#
It looks like you need something like this:
define foo
test_$(1)_$(2) :
./my_program --A $(1) --B $(2) > file_$(1)_$(2).csv
endef
a=1 2
b=u v
$(foreach a_,$(a),$(foreach b_,$(b),$(eval $(call foo,$(a_),$(b_)))))

Resources