qtmakefilemoc

Makefile patsubst apply pattern to filename not to full path


I am creating a Makefile to build an application with Qt using moc.

MOC_HEADERS=include/test.hpp include/test/test.hpp

MOC_SRCS=$(patsubst include/%.hpp,build/moc/moc_%.cpp,${MOC_HEADERS})

The first problem is that include/test/test.hpp will be converted to build/moc/moc_test/test.cpp and not to build/moc/test/moc_test.cpp

The second problem is that I don't know how to write this pattern target correctly:

build/moc/moc_%.cpp: include/%.hpp
    ${MOC} $< -o $@

Can someone help me?


Solution

  • The following is for GNU make only.

    To compute MOC_SRCS you could play with various built-in functions (patsubst, dir, notdir). You could also create your own user functions. Example where we define a function to convert a header filename to a source filename (HPP2CPP) and the opposite (CPP2HPP):

    HPP2CPP = $(patsubst include%,build/moc%,$(dir $1))$(patsubst %.hpp,moc_%.cpp,$(notdir $1))
    CPP2HPP = $(patsubst build/moc%,include%,$(dir $1))$(patsubst moc_%.cpp,%.hpp,$(notdir $1))
    
    MOC_HEADERS := include/test.hpp include/test/test.hpp
    MOC_SRCS    := $(foreach h,$(MOC_HEADERS),$(call HPP2CPP,$h))
    

    For your rule, things are more complex because pattern rules do not allow multiple wildcards. You need something more powerful. There are basically 2 options:

    Secondary expansion:
    HPP2CPP = $(patsubst include%,build/moc%,$(dir $1))$(patsubst %.hpp,moc_%.cpp,$(notdir $1))
    CPP2HPP = $(patsubst build/moc%,include%,$(dir $1))$(patsubst moc_%.cpp,%.hpp,$(notdir $1))
    
    MOC_HEADERS := include/test.hpp include/test/test.hpp
    MOC_SRCS    := $(foreach h,$(MOC_HEADERS),$(call HPP2CPP,$h))
    
    .PHONY: all
    .DEFAULT_GOAL := all
    
    all: $(MOC_SRCS)
    
    .SECONDEXPANSION:
    
    $(MOC_SRCS): $$(call CPP2HPP,$$@)
        $(MOC) $< -o $@
    

    All rules after the special .SECONDEXPANSION target have their list of prerequisites expanded twice: once during the parsing of the Makefile, and a second time when make checks the freshness of prerequisites versus targets. During this second expansion automatic variables are defined, while they are not during the first pass. This allows, for example, to programmatically compute prerequisites from target names ($@). Note the $$ to escape the first expansion.

    foreach-eval-call:
    HPP2CPP = $(patsubst include%,build/moc%,$(dir $1))$(patsubst %.hpp,moc_%.cpp,$(notdir $1))
    CPP2HPP = $(patsubst build/moc%,include%,$(dir $1))$(patsubst moc_%.cpp,%.hpp,$(notdir $1))
    
    MOC_HEADERS := include/test.hpp include/test/test.hpp
    MOC_SRCS    := $(foreach h,$(MOC_HEADERS),$(call HPP2CPP,$h))
    
    .PHONY: all
    .DEFAULT_GOAL := all
    
    all: $(MOC_SRCS)
    
    define MOC_RULE
    $1: $$(call CPP2HPP,$1)
        $$(MOC) $$< -o $$@
    endef
    $(foreach c,$(MOC_SRCS),$(eval $(call MOC_RULE,$c)))
    

    The eval function dynamically instantiates other make statements. Here again $$ is needed to escape the first expansion. Note that with this second approach we could use only HPP2CPP and also compute MOC_SRCS iteratively with the MOC_RULE:

    HPP2CPP = $(patsubst include%,build/moc%,$(dir $1))$(patsubst %.hpp,moc_%.cpp,$(notdir $1))
    
    MOC_HEADERS := include/test.hpp include/test/test.hpp
    
    define MOC_RULE
    $1-cpp := $$(call HPP2CPP,$1)
    MOC_SRCS += $$($1-cpp)
    
    $$($1-cpp): $1
        $$(MOC) $$< -o $$@
    endef
    $(foreach h,$(MOC_HEADERS),$(eval $(call MOC_RULE,$h)))
    
    .PHONY: all
    .DEFAULT_GOAL := all
    
    all: $(MOC_SRCS)