Search code examples
build-processgnu-make

How do I specify target dependant prerequisits with GNUMake?


Say I have a list of source files like this:

SOURCE=aaa.src bbb.src ccc.src

I want to create a rule to build each corresponding target in its own folder, the name of which depends on the soruce file.

Sourcefile ----> Corresponding output file / target


aaa.src -------------------> aaa/aaa.out

bbb.src -------------------> bbb/bbb.out

ccc.src -------------------> ccc/ccc.out

How do I write a rule for this using GNUMake? My best effort was the following Makefile:

.PHONY: all clean

CC=somecompiler

SOURCE := aaa.src bbb.src ccc.src
RTARGS := $(SOURCE:%.src=%.out)
TDIRS := $(addsuffix /,$(basename $(SOURCE)))
TARGS := $(join $(TDIRS), $(RTARGS))

all:$(TARGS)

%.out: $(SOURCE)     # Better source specification?
    @[ ! -d "$(dir $*)" ] && mkdir "$(dir $*)"
    $(CC) "$(patsubst %/,%,$(dir $*)).src" "$@"

clean:
    rm -f $(TARGS)
    rmdir --ignore-fail-on-non-empty $(TDIRS)

The problem here is, that any one target (i.e. .out file) depends on every source (i.e. .src file), instead of just the one that has the same basename. I could change the commented line to %.out: %.src but than the source file would have to be in the same directory as the output file. I could also compile it something like this:

%.out: %.src
    $(CC) "$>" -o "$*/$@"

But then make would always compile every target regardless of whether it already exists or not.

How do I get make to use the appropriate source file only. What is the proper way of specifying dependences for each target separately in a generic rule?


Solution

  • In GNU Make you can specify prerequisites separately from recipes and still have a generic / pattern rule for all .out files:

    .PHONY: all clean
    
    all :
    
    SOURCES := aaa.src bbb.src ccc.src
    OUTPUTS := $(foreach src,${SOURCES},$(basename ${src})/${src:%.src=%.out})
    
    # Build dependencies in the form of x/x.out : x.src | x
    define ESTABLISH_DEPENDENCY =
    $(basename ${1})/${1:%.src=%.out} : ${1} | $(basename ${1}) # Also depend on the output directory.
    $(basename ${1}) : # Rule to create the output directory.
        mkdir $$@
    endef
    
    $(foreach src,${SOURCES},$(eval $(call ESTABLISH_DEPENDENCY,${src})))
    
    all : ${OUTPUTS}
    
    # Generice rule for all .out files.
    %.out :
        @echo "build $@ from $^"
        touch $@
    
    %.src : # For debugging only.
        touch $@
    
    .PHONY: all
    

    Output:

    $ make
    touch aaa.src
    mkdir aaa
    build aaa/aaa.out from aaa.src
    touch aaa/aaa.out
    touch bbb.src
    mkdir bbb
    build bbb/bbb.out from bbb.src
    touch bbb/bbb.out
    touch ccc.src
    mkdir ccc
    build ccc/ccc.out from ccc.src
    touch ccc/ccc.out
    

    Note that it is more efficient and elegant to have order-only dependencies on directories and let make create them, than to have each recipe checking directory existence first.