Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Complex pattern substitution in Makefile prerequisites

I’ve got the following directory structure:

./
 |- obj/
 |--- debug/
 |--- release/
 |- bin/
 |--- debug/
 |--- release/
 |- src/
 |--- main.c
 |--- libb/
 |----- foo.c
 |- include/
 |--- libb/
 |----- foo.h

Furthermore, I manually specify dependencies of individual objects in my Makefile in the following format (example):

 main_DEPS = libb/foo

Now I want to be able to type make bin/debug/main, and this generates the necessary dependencies and target file:

  • obj/debug/libb/foo.o
  • obj/debug/main.o
  • bin/debug/main

How do I do that? My current approach is extremely convoluted and incomplete:

  • The following creates the object files. However, it fails to create any necessary subfolders (libb) if they don’t yet exist. It also doesn’t keep track of any changed header files. The Makefile tutorial trick to generate prerequisites automatically could be adapted maybe?

    CFLAGS+=-I include
    
    obj/*/%.o: src/%.cpp
        $(CC) $(CFLAGS) $< -c -o $@
    
  • The following tries to resolve the dependencies of a binary and built it from all required object files. But I have no idea how to retrieve those object files from the *_DEPS variable.

    .SECONDEXPANSION:
    
    bin/debug/%: obj/debug/%.o $(patsubst %,obj/debug/%.o,$($(patsubst bin/debug/%,%,$@)_DEPS))
        $(LD) $(LDFLAGS) $< $(patsubst %,obj/debug/%.o,$($(patsubst bin/debug/%,%,$@)_DEPS)) -o $@
    
    bin/release/%: obj/release/%.o $(patsubst %,obj/release/%.o,$($(patsubst bin/release/%,%,$@)_DEPS))
        $(CXX) $(LDFLAGS) $< $(patsubst %,obj/release/%.o,$($(patsubst bin/release/%,%,$@)_DEPS)) -o $@
    

    Unfortunately, this doesn’t build dependencies automatically: it just complains “obj/debug/libb/foo.o: No such file or directory” – but it works weirdly enough if I built this file manually using mkdir obj/debug/libb; make obj/debug/libb/foo.o.

    It’s also hopelessly complicated and uses gratuitous code duplication (which I cannot get rid of without making it even more complex).

What’s the least complicated solution for this? In principle I’m not opposed to using something other than make if it’s widely available (autoconf …) but I prefer keeping the effort (and new learning required) to a minimum and from what I’ve seen so far all other build systems seem to be vastly more complex than make.

like image 355
Konrad Rudolph Avatar asked Dec 22 '25 10:12

Konrad Rudolph


1 Answers

Update in response to chat message

@sehe It makes it slightly easier, yes … but the duplication is still there, and the problem of not building the dependencies as well

I have found a verbose, but effective (non-complex) way to to make it work with submake calls (in a single Makefile).

We have the 'outer make' that contains just a single rule and the dependency definitions

A_DEPS=dep1 dep2
C_DEPS=dep2

##########################
# "outer" Make
bin/%/A bin/%/B bin/%/C:
    $(MAKE) BUILD="$*" TARGET=$(@F) DEPS="$($(@F)_DEPS)" GENRULE

In effect, we translate the 'deduced' information into explicit variables on sub-call to make. The rest of the Makefile contains the definition of our 'generic target rule', GENRULE:

##########################
# "inner" Make

%/*.o:
    mkdir -p "$(@D)" && touch "$@"

GENRULE: obj/$(BUILD)/$(TARGET).o $(DEPS:%=obj/$(BUILD)/%.o) 
    echo gcc -o "bin/$(BUILD)/$(TARGET)" $^
    mkdir -p "bin/$(BUILD)" && touch "bin/$(BUILD)/$(TARGET)"
.PHONY: GENRULE

Again, a test run:

$ for target in bin/{debug,release}/{A,B,C}; do make -Bs "$target"; done

gcc -o bin/debug/A obj/debug/A.o obj/debug/dep1.o obj/debug/dep2.o
gcc -o bin/debug/B obj/debug/B.o
gcc -o bin/debug/C obj/debug/C.o obj/debug/dep2.o
gcc -o bin/release/A obj/release/A.o obj/release/dep1.o obj/release/dep2.o
gcc -o bin/release/B obj/release/B.o
gcc -o bin/release/C obj/release/C.o obj/release/dep2.o

The objects have all been created:

$ find bin obj

bin
bin/debug
bin/debug/A
bin/debug/B
bin/debug/C
bin/release
bin/release/A
bin/release/B
bin/release/C
obj
obj/debug
obj/debug/A.o
obj/debug/B.o
obj/debug/C.o
obj/debug/dep1.o
obj/debug/dep2.o
obj/release
obj/release/A.o
obj/release/B.o
obj/release/C.o
obj/release/dep1.o
obj/release/dep2.o

For simple variables (non-dependencies) here's a simple example using the $@ and $(@F) automatic variables:

EXTRA="--standardopts"
A_FLAGS="--a-opts"
B_FLAGS="--b-opts"

.SECONDEXPANSION:
    
debug/% release/%: EXTRA+=$($(@F)_FLAGS)

%/A %/B %/C:
    echo building $@ with $(EXTRA)
like image 148
sehe Avatar answered Dec 24 '25 10:12

sehe



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!