Using GNU Make's 'define' and '$(eval)' to automate rule creation

About once a week I get an email from someone random asking a question about GNU Make. I do my best to answer and figured it might be helpful for others if I shared these questions and my answers. So, here goes. This one starts with a Princess Leia style appeal:
I stumbled across your name when googling for help on a GNUmake related problem, hope you have the time to look at it as you are my last hope. I have the following problem in my Makefile:
$(LIBDIR)/libfoo.a: $(filter $(OBJDIR)/foo/%,$(OBJECTS)) 
$(LIBDIR)/libbar.a: $(filter $(OBJDIR)/bar/%,$(OBJECTS)) 
$(LIBDIR)/libbaz.a: $(filter $(OBJDIR)/baz/%,$(OBJECTS)) 
  
$(LIBDIR)/%.a: 
        ar -r $@ $^ 
        ranlib $@
so far, so good - it works as expected. The only problem is, that in the real world it's not just foo, bar and baz for me and I hate having to maintain the list manually. What I would like to do is something like this:
$(LIBDIR)/lib%.a: $(filter $(OBJDIR)/%/%,$(OBJECTS)) 
        ar -r $@ $^ 
        ranlib $@
but now the pattern for filter has two percentage-characters and this seem to confuse GNUmake. I tried to escape it, use $(call …), etc. but nothing really works. Do you have any trick/hint/idea how to solve this??

Thanks for your time, Best Regards,
And my reply:
Thanks for your mail. I can see what you are trying to do. I think the easiest way out of your predicament is as follows:
# Example of manually maintained list 
# 
# $(LIBDIR)/libfoo.a: $(filter $(OBJDIR)/foo/%,$(OBJECTS)) 
# $(LIBDIR)/libbar.a: $(filter $(OBJDIR)/bar/%,$(OBJECTS)) 
# $(LIBDIR)/libbaz.a: $(filter $(OBJDIR)/baz/%,$(OBJECTS)) 
# 
# $(LIBDIR)/%.a: 
#        ar -r $@ $^ 
#        ranlib $@ 
 
# What you'd like to be able to do 
# 
# $(LIBDIR)/lib%.a: $(filter $(OBJDIR)/%/%,$(OBJECTS)) 
#        ar -r $@ $^ 
#        ranlib $@ 
 
# The following will work 
# 
# Suppose, for example: 
 
LIBDIR  := lib 
OBJDIR  := obj 
OBJECTS := $(OBJDIR)/foo/hello.o $(OBJDIR)/foo/bar.o $(OBJDIR)/bar/hello.o \ 
$(OBJDIR)/baz/bar.o 
 
# Extract the names of the first level directories underneath 
# $(OBJDIR) and make a unique list (sort removes duplicates) and store 
# that in LIBNAMES.  This assumes that there are no spaces in any of 
# the filenames in $(OBJECTS) and that each element of $(OBJECTS) 
# starts with $(OBJDIR) 
 
LIBNAMES := $(sort $(foreach a,$(patsubst $(OBJDIR)/%,%,$(OBJECTS)),\ 
$(firstword $(subst /, ,$a)))) 
 
# The following function finds all the objects in $(OBJECTS) in a 
# particular directory whose name can be found in LIBNAMES.  So, in 
# the example here doing $(call find-objects,foo) would return 
# obj/foo/hello.o obj/foo/bar.o 
 
find-objects = $(filter $(OBJDIR)/$1/%,$(OBJECTS)) 
 
# Now need to define the rule that handles each of the libraries 
# mentioned in $(LIBNAMES).  This function can be used with $(eval) to 
# define the rule for a single directory 
 
define make-library  
$(LIBDIR)/lib$1.a: $(call find-objects,$1) 
        ar -r $$@ $$^ 
        ranlib $$@ 
endef 
 
# Now define the rules for all the directories found in $(LIBNAMES) 
 
$(foreach d,$(LIBNAMES),$(eval $(call make-library,$d)))
You will need a version of GNU Make that supports $(eval).

 

Posted by code cat