Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best practice to write reusable code

Tags:

makefile

What is a best practice to writing reusable code in Makefiles?

Suppose I have a Makefile:

.PHONY: all task01-all task01-clean task01-run task02-all task02-clean task02-run

all: task01-all task02-all

###############################################################################
task01-all: task01-clean task01 task01-run

task01-clean:
     rm task01 task01.{exi,o} -f

task01:
    compiler task01.ext -O2 --make

task01-run:
    ./task01

###############################################################################
task02-all: task02-clean task02 task02-run

task02-clean:
    rm task02 task02.{exi,o} -f

task02:
    compiler task02.ext -O2 --make

task02-run:
    ./task02

Now I want to add new family of tasks (task03), and I need to copypaste whole section, make s/02/03/ for it and add them to .PHONY section - it's noisy, disgusting and just not right.

How can I avoid that? Could I redefine all tasks with templates somehow to have nice mechanism for adding new task group in one line?

like image 900
ДМИТРИЙ МАЛИКОВ Avatar asked Dec 27 '12 09:12

ДМИТРИЙ МАЛИКОВ


1 Answers

Since the question is about writing re-usable code in Makefiles, I'll give an example of how to use pattern rules in GNU Make (it looks like that's what you're using since you mention the .PHONY target). However, if you're not using any of Make's dependency checking, it may be simpler to do this with a shell script--something like:

#!/bin/sh
TASKS="task01 task02 task03"

for i in $TASKS; do
    rm $i $i.ext $i.o -f;
    compiler $i.ext -O2 --make;
    ./$i;
done

But, to expand the principle to Make, we have another issue to tackle. Lines of the form:

task01-all: task01-clean task01 task01-run

don't tell Make in what order to build the pre-requisites--just that they all need to be done before task01-all gets built. Instead, each step to run should depend on the step before it. Something like this:

TASKS=task01-run task02-run task03-run

.PHONY: all $(TASKS) $(TASKS:run=clean)

all: $(TASKS)

$(TASKS:run=clean): %-clean:
    rm $* $*.ext $*.o -f

%: %.ext | %-clean
    compiler $< -O2 --make

$(TASKS): %-run: %
    ./$<

The rules with % are called "pattern rules", and they're a great tool to avoid re-writing the same rule multiple times for different targets. One caveat is that Make doesn't normally check pattern rules for a .PHONY target; we tell Make to do this explicitly by prefixing those rules with the list of targets and a second colon (e.g., $(TASKS):).

Since task01-run needs task01 in order to work, we make %-run targets depend on %. Also, your Makefile shows that you want to run clean every time, so we make % depend on %-clean. Since %-clean doesn't actually produce any output, we make this an "order only" dependency--Make won't look for a time-stamp or anything, it will just run the %-clean rule first any time it needs to run the % rule. "Order only" dependencies are placed after a |:

%: %.ext | %-clean

It's worth mentioning that one of Make's greatest strengths is that it can save time by not repeating work that doesn't need to be repeated--i.e., it only runs a rule if the dependencies are newer than the target. So, you could leave off the dependency on %-clean, which would cause make to only run compiler $< -O2 --make if %.ext is newer than %:

%: %.ext
    compiler $< -O2 --make

You could then add a rule just to run all of the %-clean targets:

.PHONY: all $(TASKS) $(TASKS:run=clean) clean

clean: $(TASKS:run=clean)

Last thing: I use some special variables in the recipes. $@ stands for the target being built. $< stands for the first dependency. $* stands for the "stem" of a pattern rule (i.e., the part matched by the %).

like image 78
laindir Avatar answered Sep 28 '22 06:09

laindir