Coverage processing on multiple Java projects with gmake As part of my data collection, I have to run multiple kinds of coverage processing on multiple Java projects. Below is my main Makefile intented only for gmake. Portability is not a requirement for me (for this project), but DRY and following make best practices are. Please comment on my code, and if there is any way to make this better.

.PHONY: checkout all clobber clean $(checkout.all)
.SECONDARY: $(addprefix projects/,$(names))

root:=$(CURDIR)
names=$(shell cat etc/projects.txt)
checkout.all=$(addprefix checkout-,$(names))
clean.all=$(addprefix clean-,$(names))

testgen.types=original randoop tpalus

all:
  @echo use 'make &lt;type&gt;-&lt;project&gt;'
  @echo projects = $(names)
  @echo types = $(testgen.types)

checkout: $(checkout.all)
  @echo $@ done.

checkout-%: projects/%/pom.xml
  @echo $@ done.

projects build: ;  mkdir -p $@

projects/%/pom.xml: | projects build
  cd projects &amp;&amp; $(root)/bin/checkout $*
  touch $@

clean: $(clean.all)
  @echo $@ done.

clean-%: | projects/%/pom.xml
  cd projects/$* &amp;&amp; git clean -xfd &amp;&amp; git reset --hard
  @echo $@ done.

clobber-%:
  cd projects &amp;&amp; rm -rf $*

#-----------------------------------------------------------------
root:=$(CURDIR)

define testgen =
$1 : $(addprefix $(1)-,$(names))
  @echo $$(@) done.

$1-% : projects/%/.$(1).done
  @echo $$(@) done.

%/.$1.done : | %/pom.xml
  echo $$(MAKE) -C $$(*) root=$$(root) $(1)
endef

$(foreach var,$(testgen.types),$(eval $(call testgen,$(var))))


Some clarification about the intended purpose of this project. I am doing an analysis on a large number of  projects from github. My method of action is to clone each project, run multiple tests on them. Before running any project, I do a git clean to make sure that the artifacts of previous tests are not present in the project directories. The types of tests and projects names vary frequently, and hence I have kept them outside the main Makefile.
.PHONY: checkout all clobber clean $(checkout.all)


There's no clobber target, so I think that will be ignored.



The root variable is unnecessary (CURDIR should be well known by anyone using make), and is even defined twice.



names=$(shell cat etc/projects.txt)


In my experience it's more common to write out the list in the makefile. If this is so long as to clutter up the file, you could create a separate makefile for variables like this and include variables.mk or similar.



projects build: ;  mkdir -p $@


Why not split this into two lines? Also, the -p doesn't make any difference since it's only creating a single directory under the current one.



touch $@


This looks like a hack to get around checkout not updating the modified date properly, or to rebuild a project when make doesn't think it needs to be rebuilt. You might be able to just set the relevant target .PHONY or change it so that rebuilding is only done when necessary.



cd projects/$* &amp;&amp; git clean -xfd &amp;&amp; git reset --hard


This is rather brutal. I'd rather advise to use non-recursive make (based on  Recursive Make Considered Harmful) and enumerate the files to remove to make sure you never remove anything other than generated files. You could have a separate target with cd projects/$* &amp;&amp; git clean -xnd to check whether there's anything you still need to clean.



clobber-%:
  cd projects &amp;&amp; rm -rf $*


This also looks dangerous. You're effectively saying that make clobber-whatever will delete whatever no matter what it is. I can't recall where I heard this advice, but I do believe it's good practice to enumerate the files which makefiles can and should work with, rather than adding general-purpose tactical nuclear missiles.



define testgen =
...
$(foreach var,$(testgen.types),$(eval $(call testgen,$(var))))


There's a lot of indirection going on here, so it's difficult to understand what will actually be run in the end. Are you sure this couldn't be done simpler?



This is a personal preference, but I tend to avoid any echo statements because the make output should make it clear what is actually happening, and comments like this often tend to get out of sync with the rest of the code. On the other hand, you can include special targets to get the value of all/any variables.