In Makefile why my phony target is execute between two different generic rule - gnu-make

My problem is as follows:
I have two targets in my Makefile, toad4 and toad5.
Depending on which target is built some different files and compiler flags need to be set.
This works for the actual build and goes like this:
SDCC = /Volumes/Partition2/Users/nyholku/sdcc340/bin/sdcc
SDCCFLAGS = "-Wl -f 0xffff" -DTOAD_HW_VERSION=${HWVERSION} --verbose --no-crt --ivt-loc=0x800 -V -L /Volumes/Partition2/Users/nyholku/sdcc340/non-free/lib/pic16 -Wa,-S,0 -Wl,-m,-s18f45k50.lkr -mpic16 -p18f45k50 --disable-warning 85 --std-sdcc99 --obanksel=3 --use-non-free
.PHONY: toad4
toad4: $(OBJDIR)/$(TARGET).hex
toad4: HI_SPEED_IRQ_ASM :=hi_speed_irq-hw4.asm
toad4: HWVERSION := HW4
toad4: BOOTLOADER := ../diolan-plus2-toad4/fw/bootloader.hex
.PHONY: toad5
toad5: HI_SPEED_IRQ_ASM :=hi_speed_irq.asm
toad5: HWVERSION := HW5
toad5: BOOTLOADER := ../diolan-plus2-toad5/fw/bootloader.hex
toad5: $(OBJDIR)/$(TARGET).hex
$(OBJDIR)/%.o: %.c $$(#D)/.f
#echo $(PATH)
$(SDCC) -c $(SDCCFLAGS) $< -o $#
But later in the Make file I have rule to produce the C code dependencies:
# First include the dependencies
include $(addprefix $(OBJDIR)/, $(SRCS:.c=.dep))
# Then recreate them
$(OBJDIR)/%.dep: %.c $$(#D)/.f
set -e; rm -f $#; \
$(SDCC) -c -M $(SDCCFLAGS) $< > $#.$$$$; \
sed -e '1 s,^,$(OBJDIR)/,' -e 's,\($*\)\.o[ :]*,\1.o $# : ,g' < $#.$$$$ > $#; \
rm -f $#.$$$$
This was adapted from the GNU Makefile manual and worked before I introduced the dual target scheme.
When I comment that dependency generation out, the make works, the flag HWVERSION gets set for the C compilation (through the SDCCFLAGS), just as they it should, depending on the target.
But if I leave that dependency generation in then apparently that rule is applied before the toad4 and toad5 rules because the HWVERSION is empty and the C-compilation to produce the dependencies fails.
So why is the other generic C compile rule applied before the toad4/toad5 rules and the dependency generating rules is applied before?
I'm also ok with suggestion on how to organise this better for the goal of building to different binaries from mostly same files with different settings. This should be quite common need.
I'm using GNU Make 3.81 on macOS 11.3.1 and the compiler is Small Device C Compiler ie SDCC
For completeness the complete Make file is here
#-------------------------------------------------------------------------------
#
# Copyright (c) 2011, Kustaa Nyholm / SpareTimeLabs
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# Redistributions of source code must retain the above copyright notice, this list
# of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright notice, this
# list of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# Neither the name of the Kustaa Nyholm or SpareTimeLabs nor the names of its
# contributors may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
# OF SUCH DAMAGE.
#
# Everyone should have this
.SUFFIXES:
# The output file name
TARGET = toad4
# This is necessary so that pk2cmd (used by 'load' script) is found and for the
# pk2cmd to find its support files (it seems to search current dir which is wrong)
PATH := ${PATH}
UPLOADER := java -cp ~/EazyCNC-Project/classes:/Volumes/Partition2/Users/nyholku/EazyCNC-Project/lib/purejavahidapi-0.0.12.jar:/Volumes/Partition2/Users/nyholku/EazyCNC-Project/lib/jna-5.6.0.jar diolanupdater.UpdateFirmware
ENCODER := /Volumes/Partition2/Users/nyholku/diolan-plus2/encoder/build/encoder
HEXMATE := /Applications/microchip/xc8/v2.32/pic/bin/hexmate
# The source files that make up the project go here
SRCS = main.c toad4.c usb_hid.c usb_core.c usb_pic_defs.c usb_user_config.c command_queue.c crt0iz_toad4.c swuart.c
# The libraries that are used go here
LIBS = libc18f.lib libm18f.lib libsdcc.lib
# Where to find the compiler
SDCC = /Volumes/Partition2/Users/nyholku/sdcc340/bin/sdcc
# Compiler flags go here
# --use-crt=crt0.o TOAD_HW_VERSION
SDCCFLAGS = "-Wl -f 0xffff" -DTOAD_HW_VERSION=${HWVERSION} --verbose --no-crt --ivt-loc=0x800 -V -L /Volumes/Partition2/Users/nyholku/sdcc340/non-free/lib/pic16 -Wa,-S,0 -Wl,-m,-s18f45k50.lkr -mpic16 -p18f45k50 --disable-warning 85 --std-sdcc99 --obanksel=3 --use-non-free
# Where to store the target/intermediate/temporary/object files
OBJDIR = ../obj
#-------------------------------------------------------------------------------
#
.PHONY: toad4
toad4: $(OBJDIR)/$(TARGET).hex
toad4: HI_SPEED_IRQ_ASM :=hi_speed_irq-hw4.asm
toad4: HWVERSION := HW4
toad4: BOOTLOADER := ../diolan-plus2-toad4/fw/bootloader.hex
.PHONY: toad5
toad5: HI_SPEED_IRQ_ASM :=hi_speed_irq.asm
toad5: HWVERSION := HW5
toad5: BOOTLOADER := ../diolan-plus2-toad5/fw/bootloader.hex
toad5: $(OBJDIR)/$(TARGET).hex
#
#-------------------------------------------------------------------------------
.PHONY: load
load:
$(UPLOADER) $(OBJDIR)/$(TARGET)-encoded.hex
#-------------------------------------------------------------------------------
.PHONY: comparebootloaders
comparebootloaders:
$(HEXMATE) -o$../bootloader-backedp.hex -fill=0xFF#0x0000:0x07FF r0000-07FF,/Volumes/Partition2/Users/nyholku/diolan-plus2/fw-backup-25.7.2017/bootloader.hex r000802-FFFFFF,/Volumes/Partition2/Users/nyholku/diolan-plus2/fw-backup-25.7.2017/bootloader.hex
$(HEXMATE) -o$../bootloader-toad4.hex -fill=0xFF#0x0000:0x07FF r0000-07FF,../diolan-plus2-toad4/fw/bootloader.hex r000802-FFFFFF,../diolan-plus2-toad4/fw/bootloader.hex
$(HEXMATE) -o$../bootloader-toad5.hex -fill=0xFF#0x0000:0x07FF r0000-07FF,../diolan-plus2-toad5/fw/bootloader.hex r000802-FFFFFF,../diolan-plus2-toad5/fw/bootloader.hex
#
#
#-------------------------------------------------------------------------------
#
# This ensures that the object directory exists and re-creates it if necessary
#
# This requires make 3.81 or later, delete this section and all expressions that
# refer to .f if you have an older make
#
.SECONDEXPANSION:
# Uses a .f file as a flag file in each directory
%/.f:
mkdir -p $(dir $#)
touch $#
# dont' let make remove the flag files automatically
.PRECIOUS: %/.f
#
#-------------------------------------------------------------------------------
#
# Actual rules
#
# Compile the C-files
$(OBJDIR)/%.o: %.c $$(#D)/.f
#echo $(PATH)
$(SDCC) -c $(SDCCFLAGS) $< -o $#
KEY=${TOAD4PLUS_DIALON_KEY2}
# Link the compiled files and libraries
$(OBJDIR)/$(TARGET).hex: $(addprefix $(OBJDIR)/, $(SRCS:.c=.o)) $(OBJDIR)/hi_speed_irq.o
$(SDCC) $(SDCCFLAGS) -o $(OBJDIR)/$(TARGET).hex $(addprefix $(OBJDIR)/, $(SRCS:.c=.o)) $(LIBS) $(OBJDIR)/hi_speed_irq.o
# normalize the code filling un-used code memory with 0xFF so that encoding always works on known data
$(HEXMATE) -o$(OBJDIR)/$(TARGET)-normalized.hex -fill=0xFF#0x0800:0x7FFF r0800-7FFF,$(OBJDIR)/$(TARGET).hex
# sanitise the bootloader by keeping only the first 2kB (there is an extra jump code at 0x800 which overlaps with firmware code)
$(HEXMATE) -o$(OBJDIR)/bootloader-normalized.hex -fill=0xFF#0x0000:0x07FF r0000-07FF,$(BOOTLOADER) r000802-FFFFFF,$(BOOTLOADER)
# combine the bootloader and firmware to one hex file that can be programmed with pickit ready to run
$(HEXMATE) -o$(OBJDIR)/$(TARGET)-pickit.hex -fill=0xFFFF#0xF00001:0xF000FF -fill=0xA5#0xF00000:0xF00000 -fill=0x00#0x300000:0x30000D $(OBJDIR)/$(TARGET)-normalized.hex ../obj/bootloader-normalized.hex
# encode the bootloader for bootloading purposes, suppress output so as NOT to reveal the secret key
$(ENCODER) -ix $(OBJDIR)/$(TARGET)-normalized.hex -ox $(OBJDIR)/$(TARGET)-encoded.hex -e ${TOAD4PLUS_DIALON_KEY2}
# upload the encoded hex file using the bootload process
$(UPLOADER) $(OBJDIR)/$(TARGET)-encoded.hex
# Compile the high speed interrupt asm file
$(OBJDIR)/hi_speed_irq.o: ${HI_SPEED_IRQ_ASM}
gpasm -D TOAD_HW_VERSION=${HWVERSION} -o $(OBJDIR)/hi_speed_irq.o -c ${HI_SPEED_IRQ_ASM}
#
#-------------------------------------------------------------------------------
#
# Automatic generation of dependencies
#
# This magic code fragment from GNU make manual uses the SDCC compiler -M option
# to create a Makefile fragment for each C-source file describing the dependencies.
#
# Traditionally these fragments have the type '.d' but SDCC seems to delete them
# when it compiles files, so I use '.dep' here.
#
# Also SDCC '-M' option produces wrong dependency for the file being compiled
# in the sense that it does not contain the path, only the filename. Hence
# the 'sed' command has been mangled to inject the missing path to the fragment.
#
# First include the dependencies
include $(addprefix $(OBJDIR)/, $(SRCS:.c=.dep))
# Then recreate them
$(OBJDIR)/%.dep: %.c $$(#D)/.f
set -e; rm -f $#; \
$(SDCC) -c -M $(SDCCFLAGS) $< > $#.$$$$; \
sed -e '1 s,^,$(OBJDIR)/,' -e 's,\($*\)\.o[ :]*,\1.o $# : ,g' < $#.$$$$ > $#; \
rm -f $#.$$$$
#------------------------------------------------------------------------------
#
# pretty standard default target
#
all: toad5
#
#-------------------------------------------------------------------------------
#
# pretty standard clean that attempts to delete all that this Makefile may left behind
#
clean:
rm -f $(OBJDIR)/*.rel
rm -f $(OBJDIR)/*.lnk
rm -f $(OBJDIR)/*.S19
rm -f $(OBJDIR)/*.map
rm -f $(OBJDIR)/*.mem
rm -f $(OBJDIR)/*.asm
rm -f $(OBJDIR)/*.rst
rm -f $(OBJDIR)/*.sym
rm -f $(OBJDIR)/*.lst
rm -f $(OBJDIR)/*.o
rm -f $(OBJDIR)/*.dep
rm -f $(OBJDIR)/*.hex
#
# cleanall deletes all in the object directory, do not use this if target dir == source dir
cleanall:
rm $(OBJDIR)/*
#-------------------------------------------------------------------------------

Target-specific variable inheritance flows through the prerequisite dependency graph. That's what you're relying on here: when a target is built as a prerequisite of toad4 it gets one set of options and when it's built as a prerequisite of toad5 it gets a different set of options.
However, when you are building the dependency file the recipe isn't run as a prerequisite of either toad4 or toad5: as described in the manual the dependency file is built by make as part of parsing the makefile, because you included those dependency files. Because of that, none of the target-specific variables are set.
Stepping back it's not clear to me how you imagine this will work anyway. You have two different sets of prerequisites but you are trying to keep them both in the same file: that can't ever work. The only way it would be correct is if you rebuilt all the prerequisites every time you invoked make, so that they would be accurate for whatever target this particular make would want to build, but then of course everything in the makefile would be out of date and so everything would be rebuilt every time.
The right answer for a makefile that wants to build two different sets of targets from the same makefile, is to build two different sets of targets, not the same set of targets with different flags. Specifically, you should have two different object directories and put the generated files from one build in one object directory (say, obj-toad4) and the generated files from the other build in the other object directory (say, obj-toad5). Then they won't get mixed up and make can tell which are out of date and which are not.
Also, the method of handling automatic prerequisites described in the GNU make manual is out of date. You should consider the method described here: http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/

Related

Debian Packaging Without Build Tool

I want to create a Debian package from a C program without the use of a build tool such as autotools or CMake. My debian/rules file:
#!/usr/bin/make -f
%:
dh $#
override_dh_auto_clean:
rm -f program
override_dh_auto_build:
gcc program.c -o program
override_dh_auto_install:
cp program /usr/local/bin
Upon running dpkg-buildpackage, I get:
dh: error: Unknown sequence application (choose from: binary binary-arch binary-indep build build-arch build-indep clean install install-arch install-indep)
It seems the issue was related to the fact that I was creating the file in a shell script heredoc that was expanding the $#, e.g.
cat <<EOF > debian/rules.temp
#!/usr/bin/make -f
%:
dh $#
EOF
Which should be:
all_symbol='$#'
cat <<EOF > debian/rules.temp
#!/usr/bin/make -f
%:
dh $all_symbol
EOF
An unrelated issue is to the location of the override_dh_auto_install To manually create Debian file hierarchy it should be:
override_dh_auto_install:
mkdir -p debian/PACKAGENAME/usr/bin
cp program debian/PACKAGENAME/usr/bin
Or, to have this done automatically:
cat <<EOF > debian/PACKAGENAME.install
program usr/bin
EOF

What's the difference between -a and -e in a zsh conditional expression?

I was looking up the meaning of flags like -a in zsh if statements, eg.
if [[ -a file.txt ]]; do
# do something
fi
and I found this
-a file
true if file exists.
-e file
true if file exists.
What is the difference between -a and -e? And if there is none, why do they both exist?
POSIX sheds some light on this.
tl;dr: Ksh traditionally used -a and several other shells followed suit. POSIX instead borrowed -e from Csh to avoid confusion. Now many shells support both.
The -e primary, possessing similar functionality to that provided by the C shell, was added because it provides the only way for a shell script to find out if a file exists without trying to open the file. Since implementations are allowed to add additional file types, a portable script cannot use:
test -b foo -o -c foo -o -d foo -o -f foo -o -p foo
to find out if foo is an existing file. On historical BSD systems, the existence of a file could be determined by:
test -f foo -o -d foo
but there was no easy way to determine that an existing file was a regular file. An early proposal used the KornShell -a primary (with the same meaning), but this was changed to -e because there were concerns about the high probability of humans confusing the -a primary with the -a binary operator.

How to create directories for dist files?

Here's my Makefile:
dist/%.js: src/%.js node_modules
$(NM)/babel $< -o $#
build: $(patsubst src/%,dist/%,$(wildcard src/**/*.js))
It runs a command like this:
node_modules/.bin/babel src/deep/foo.js -o dist/deep/foo.js
The problem is that if dist/deep doesn't exist, it errors:
Error: ENOENT: no such file or directory, open 'dist/deep/foo.js'
So what I want to do is add an extra dependency on the directory, which I was hoping I could do with something like this:
dist/%.js: src/%.js $(dir dist/%) node_modules
$(NM)/babel $< -o $#
dist/%/:
mkdir -p $#
build: $(patsubst src/%,dist/%,$(wildcard src/**/*.js))
But it doesn't work. $(dir dist/%) isn't filling in that % like I hoped. Running make --trace yields:
Makefile:10: update target 'dist/deep/foo.js' due to: src/deep/foo.js dist/ node_modules
i.e., you can see it has a dependency on dist/, but I was hoping it'd depend on dist/deep/ so that I could mkdir -p it.
Is there a way to achieve what I want?
First a subsidiary snag. Judging from:
$(wildcard src/**/*.js)
it seems you want this function to perform recursive globbing,
returning all *.js files that exist in src or any subdirectory
thereof.
I don't know what shell you've got, but they don't all do that by
default. The linux bash shell doesn't, though as of bash 4.0
it will do it if the shell option globstar is set.
And anyway, $(wildcard ...) won't do it (unless, possibly, the
operative shell does it by default, which I'm not in a position to
check out). So you can't dependably use $(wildcard ...) for that
purpose. You need make to be invoking a shell in which recursive
** globbing is enabled, and then call:
$(shell ls src/**/*.js)
So that's what I'll do now in showing how to solve your problem with
a simple example. I've got:
src/
one.js
a/
two.js
c/
four.js
b/
three.js
and I just want to each *.js file copied from beneath src to the
same relative name under dist, ensuring that dist and all
necessary subdirectories exist when required to. (Of course, this
could all be done at once with cp). Here is a makefile:
SHELL := /bin/bash
.SHELLFLAGS := -O globstar -c
SRCS := $(shell ls src/**/*.js)
DISTS := $(patsubst src/%,dist/%,$(SRCS))
DESTDIRS := $(dir $(DISTS))
.PHONY: all clean
all: $(DISTS)
dist/%.js: src/%.js | $(DESTDIRS)
cp $< $#
$(DESTDIRS):
mkdir -p $#
clean:
rm -fr dist
which runs like:
$ make
mkdir -p dist/a/c/
mkdir -p dist/b/
cp src/a/c/four.js dist/a/c/four.js
cp src/a/two.js dist/a/two.js
cp src/b/three.js dist/b/three.js
cp src/one.js dist/one.js
In that makefile,
| $(DESTDIRS)
makes each of the $(DESTDIRS) an order-only prerequisite
of any target dist/%.js. An order-only prequisite is not considered in determining whether its
target shall be made, but if it is determined that the target shall be made, then the
order-only prequisite will be made first.

GNU make dependency issue

To make it simple, say I have the following folders:
./src/ with many .c files
./obj/ with many .obj files
./output/ with my binaries I want to build
My makefile is as follows:
all: init mybin
# init commands
init:
mkdir obj
mkdir output
mybin: project1 project2 project3
$(CC) src/misc.c ... etc
$(LK) obj/first.obj obj/second.obj obj/third.obj obj/four.obj obj/five.obj obj/six.obj obj/seven.obj obj/eight.obj obj/nine.obj -o output/myapp.bin
project1: obj/first.obj obj/second.obj obj/third.obj
obj/first.obj: src/first.c
$(CC) first.c ... etc
obj/second.obj: src/second.c
$(CC) obj/second.c ... etc
obj/third.obj: src/third.c
$(CC) obj/third.c ... etc
project2: obj/four.obj obj/five.obj obj/six.obj
obj/four.obj: src/four.c
$(CC) four.c ... etc
obj/five.obj: src/five.c
$(CC) obj/five.c ... etc
obj/six.obj: src/six.c
$(CC) obj/six.c ... etc
project3: obj/seven.obj obj/eight.obj obj/nine.obj
obj/seven.obj: src/seven.c
$(CC) seven.c ... etc
obj/eight.obj: src/eight.c
$(CC) obj/eight.c ... etc
obj/nine.obj: src/nine.c
$(CC) obj/nine.c ... etc
The first time I ran make all, everything compiled find. Then I did:
$ touch src/four.c
$ make all
$
But make exits without compiling anything. I guess it did not detect that one of the .c files had changed, however I don't see what's wrong with my dependencies.
What I expected:
touching src/four.c should have marked obj/four.obj obsolete, and project2 aswell, hence marking mybin obsolete too. This chain should trigger a new compilation of src/four.c to obj/four.obj and then a new linkage of the whole project.
Did you specify the output file of compilation (likely the -o option)? By default (for most toolchains), compiling a .c file produces an .o file, not an .obj one.
UPD.
To get Make updating targets when some prerequisites change you have to provide an exact dependencies between files as far as Make use timestamps to determine whether a file has been changed.
That is, all and init could remain as so-called .PHONY targets, but it is a good practice to make the rest targets to be files.
OUT_DIR := ./output
SRC_DIR := ./src
OBJ_DIR := ./obj
MYBIN := $(OUT_DIR)/myapp.bin
OBJS := $(addprefix $(OBJ_DIR)/, \
first.obj \
second.obj \
third.obj \
four.obj \
five.obj \
six.obj \
seven.obj \
eight.obj \
nine.obj)
.PHONY : all mkdir-output mkdir-obj
all : $(MYBIN)
mkdir-output :
#mkdir -p $(OUT_DIR)
mkdir-obj :
#mkdir -p $(OBJ_DIR)
$(MYBIN) : $(OBJS) | mkdir-output
$(LK) $^ -o $#
$(OBJS) : | mkdir-out
$(OBJS) : $(OBJ_DIR)/%.obj : $(SRC_DIR)/%.c
$(CC) $< -object=$# $(CC_OPT)
The last rule is GNU Make's static pattern rule. And the mkdir-xxx prerequisites after a pipe sign | are order-only ones.

Recursive Make issue

I'm trying my hand at using recursive make to eliminate things like having multiple CFLAGS variables, one for each target. There is only one level of recursion, there is no crazy directory tree traversal happening here. I just want to dump my object files into their target specific folders.
So far I have come up with something that is really elegant (edit: alright it's more elegant than the single-makefile solution i had from before. Which had way too much repetition!) but unfortunately does not work.
I think that by posting the format here it will be apparent what it is that I am trying to do.
# ./makefile
.PHONY: all clean
export CC = g++
export INCLUDE = -I ../include/
export SRC = Main.cpp Graphics.cpp Thread.cpp Net.cpp Otherstuff.cpp
export LINKEROPT = -lglew32 -lopengl32 -lsdl -lws2_32 -lglu32 -lmorelibraries
test:
$(MAKE) -f make.unittest
all:
$(MAKE) -f make.unittest
$(MAKE) -f make.debug
$(MAKE) -f make.release
clean:
-rm -rf build_* *.exe
# I am on windows so the targets are .exe's
Here is the file make.debug:
### sub-makefile for the debug build target. Contains target specific build settings.
DIRNAME = build_debug
TARGETNAME = program_debug
TARGETDESCR = DEBUG
CFLAGS = -Wextra -Wall -O0 -g3 -DDEBUG
### EVERYTHING AFTER THIS POINT IS A TEMPLATE
# my goal is to have as much of my makefile code being "reusable" as possible
# so that I can easily add targets.
OBJ = $(patsubst %.cpp,$(DIRNAME)/%.o,$(SRC))
DEPS = $(patsubst %.cpp,$(DIRNAME)/%.d,$(SRC))
-include $(DEPS)
# default behavior. Set up the build directory. Then build the debug target.
all: $(DIRNAME) $(TARGETNAME)
# this is the build dir
$(DIRNAME):
mkdir $(DIRNAME)
$(DIRNAME)/%.o: %.cpp
#echo -e "Compiling for $(TARGETDESCR): $< --> $#"
$(CC) $(CFLAGS) $(INCLUDE) -c $< -o $#
#echo -e "Generating dependencies: $< --> $(patsubst %.o,%.d,$#)"
$(CC) $(CFLAGS) $(INCLUDE) -MM -MT $# -MF $(patsubst %.o,%.d,$#) $<
# I realize there is a way to generate the deps while compiling in one pass
# but I'll figure it out later
$(TARGETNAME): $(OBJ)
#echo -e "Linking $(TARGETDESCR): $#.exe"
$(CC) -L ../lib/win32/ -o $# $(OBJ) $(LINKEROPT)
As you can see, I can very quickly add a new build target with its own set of CFLAGS by copying over the sub-makefile and lightly modifying it, and then adding a few entries to the main makefile.
So the problem here is that it doesn't recognize changes in files. Only when I edit Main.cpp will it recompile build_debug/Main.o. I'm really unsure about where I can start to figure out what's incorrect.
I've created a monster.
When I suggested recursive make in that other post it was to deal with a specific -- and very strange -- problem of inclusion. I do not subscribe to the dogma that recursive make is pure evil, but it does have disadvantages, and should not be used as a cure-all.
First, let's eliminate a bad (and ongoing) source of redundancy. In you main makefile, instead of
$(MAKE) -f make.debug
use
$(MAKE) -f makefile.sub DIRNAME = build_debug TARGETNAME = program_debug TARGETDESCR = DEBUG CFLAGS = -Wextra -Wall -O0 -g3 -DDEBUG
where makefile.sub is the "template" part of your various sub-makefiles. I know that doesn't look like an improvement, but this way you have only one sub-makefile, not N. (I also think you're using too many target-specific variables, but we can discuss that later.)
Once you're comfortable with this, you can use target-specific variables within makefile.sub, so that in the main makefile you can replace
all:
$(MAKE) -f makefile.sub DIRNAME=build_unittest ...
$(MAKE) -f makefile.sub DIRNAME=build_debug ...
$(MAKE) -f makefile.sub DIRNAME=build_release ...
with
all:
$(MAKE) -f makefile.sub unittest debug release
It's not clear to me what problem you're having with recognizing changes in files (when should Make rebuild build_debug/Main.o, if Main.cc hasn't changed?), but it's probably a direct consequence of using make recursively (see "disadvantages", above) and we can probably fix it without too much grief.
EDIT:
In your sub-makefile, put the all rule before the -include line, so that it will be the default rule. When you call $(MAKE), you don't specify a target, so Make chooses the default target, which is (usually) whichever one comes first. The contents of the %.d files have the form of rules, so if you include them first, one of them (i.e. build_debug/Main.o: Main.cpp) will win.

Resources