0

I have a program, set up for experimental purposes, that can be build in different variants, controlled by preprocessor switches. At the moment, my makefile is setup such that it generates the binaries build/[flags], where flags is a letter-encoding of the different variants to be build.

build/[flags]:
    linker commands

build/[flags]_objects/[filename].o: [filename].cpp
    compiler commands

I use secondary expansion to resolve all this, i.p., to decode the flag string and turn it into command line switches to the compiler. This is a bit hard to maintain, and I would like to know if there are better alternatives to that approach.

Edit. I admit that I wasn't very clear. Here is an example. My program is supposed to be compiled with and without -DFLAG. My binary will be called d (like default) for the version without and f for the version with the flag. My makefile goes roughly like this:

.SECONDEXPANSION:

# Used for using $% in secondary expansion
PERCENT=%

# Decode the flag in the target name
decode_flag=$(if $(findstring f,$(1)),-DFLAG,)

# Build the executable
build/%.out: $$(patsubst $$(PERCENT).cpp,build/$$*/$$(PERCENT).o,$$(SRC))
    $(CXX) $(LDFLAGS) -o $@ $^

# Build the object files; the objects for binary build/x* in build/x/
build/%.o: $$(*F).cpp
    @mkdir -p $(@D)
    $(CXX) -c $(CXXFLAGS) $(call decode_flag, $(*D)) -o $@ $<

I haven't specified CXX, CXXFLAGS, LDFLAGS and SRC; they have the usual meaning.

I guess you'll admit that all this patsubst-stuff is hard to read. In reality, the decode_flag is slightly more complicated, but the principle stays the same.

Bubaya
  • 615
  • 3
  • 13

2 Answers2

0

It sounds like you can just do a for/eval loop. Here's an example:

POSSIBLE_FLAGS := "d f"

CFLAGS_d = ""
CFLAGS_f = "-DFLAG"

SRC := ...

#
# flag_rules:
#   $1 - flag
#
define flag_rules

OBJS_$1 := $$(patsubst %.cpp,build_$1/%.o,$$(SRC))

# Build the executable
build_$1/%.out: $$(OBJS_$1)
    $$(CXX) $$(LDFLAGS) -o $$@ $$^

# Build the object files 
build_$1/%.o: %.cpp
    @mkdir -p $$(@D)
    $$(CXX) -c $$(CXXFLAGS) $$(CFLAGS_$1) -o $$@ $$<

endef

$(foreach flag,$(POSSIBLE_FLAGS), \
   $(eval $(call flag_rules,$(flag))))

(Disclaimer -- I didn't actually run this, so I might have missed something)

Be careful to not put spaces around the $(flag) inside of the $(call ...) statement. I switched up your conventions slightly -- all outputs for a particular flag appear in a directory called build_$(flag). Outside of this, the names appear the same. With this, you can do make build_f/blah to build with flag f, or make build_d/blah to build with flag d

HardcoreHenry
  • 5,909
  • 2
  • 19
  • 44
0

Personally, I would be a bit more specific for the sake of readability, like so:

$ cat Makefile
SRC := src/main.cpp src/moduleA/moduleA.cpp src/moduleB/moduleB.cpp
OBJ := $(SRC:.cpp=.o)

.SECONDEXPANSION:

# Customized linking flags per target
build/f.out: LDFLAGS += -Wl,--build-id

# Generic rule for linking
build/%.out: $$(addprefix build/$$*/,$(OBJ))
        echo $(LINK.cc) $(OUTPUT_OPTION) $^

# Section for d variant (dependency rule only)
$(addprefix build/d/,$(OBJ)): build/d/%.o: %.cpp

# Section for f variant (dependency rule and customizations)
$(addprefix build/f/,$(OBJ)): build/f/%.o: %.cpp
$(addprefix build/f/,$(OBJ)): CXXFLAGS += -DFLAG

# Generic rule for compilation
build/%.o:
        echo $(COMPILE.cc) $(OUTPUT_OPTION) $<

This approach uses static pattern rules to explicitly define which object file is supposed to be created from which source file, while keeping compilation recipe once only. It also uses target-specific variable assignment to keep flags set only for specific objects without complicating compilation recipe.

The explicit dependency rules are added for the sake of readability; this of course might be evaluated with second expansion if one wants to sacrifice readability for brevity.

Output (in parallel to show there is no interference):

$ make -s -j2 build/f.out build/d.out
g++ -DFLAG -c -o build/f/src/main.o src/main.cpp
g++ -DFLAG -c -o build/f/src/moduleA/moduleA.o src/moduleA/moduleA.cpp
g++ -DFLAG -c -o build/f/src/moduleB/moduleB.o src/moduleB/moduleB.cpp
g++ -c -o build/d/src/main.o src/main.cpp
g++ -c -o build/d/src/moduleA/moduleA.o src/moduleA/moduleA.cpp
g++ -c -o build/d/src/moduleB/moduleB.o src/moduleB/moduleB.cpp
g++ -Wl,--build-id -o build/f.out build/f/src/main.o build/f/src/moduleA/moduleA.o build/f/src/moduleB/moduleB.o
g++ -o build/d.out build/d/src/main.o build/d/src/moduleA/moduleA.o build/d/src/moduleB/moduleB.o
raspy
  • 3,995
  • 1
  • 14
  • 18