I've this Makefile
framework:
#$(MAKE) -C $(HIGGSBASESELECTOR)
#$(MAKE) -C $(MATRIX4)
#$(MAKE) -C $(SUBSELECTOR)
#$(MAKE) -C $(MCSUBSELECTOR)
#$(MAKE) -C $(MATRIXSUBSELECTOR)
#$(MAKE) -C $(CHECKSUBSELECTOR)
...
if I run
make -j framework
it uses only one process. What is the best (and fast) way to refactor this Makfile to parallize the compilation?
Your rule executes the $(MAKE) commands in sequence, so it's not surprising that you see only one process at a time.
A quick and dirty approach (if nothing's waiting for the results of these calls) is
framework:
#$(MAKE) -C $(HIGGSBASESELECTOR) &
#$(MAKE) -C $(MATRIX4) &
#$(MAKE) -C $(SUBSELECTOR) &
#$(MAKE) -C $(MCSUBSELECTOR) &
#$(MAKE) -C $(MATRIXSUBSELECTOR) &
#$(MAKE) -C $(CHECKSUBSELECTOR) &
If that makes your blood run cold (and it should) you can do it this way:
framework: $(HIGGSBASESELECTOR) $(MATRIX4) $(SUBSELECTOR) ...
do things after the calls
.PHONY: $(HIGGSBASESELECTOR) $(MATRIX4) $(SUBSELECTOR) ...
$(HIGGSBASESELECTOR) $(MATRIX4) $(SUBSELECTOR) ...:
#$(MAKE) -C $#
Related
My currently working makefile uses gcc to compile and link in one step. It is 600 lines long so I have cut it down to just show you the 'compile and link' and hex stages (very cut down code here!)
$(PROGRAM_ELF): \
$(BSP_DIR)/install/lib/$(CONFIGURATION)/libmetal.a \
$(BSP_DIR)/install/lib/$(CONFIGURATION)/libmetal-gloss.a \
$(BSP_DIR)/metal.$(LINK_TARGET).lds
mkdir -p $(dir $#)
$(MAKE) -C $(SRC_DIR) $(basename $(notdir $#)) \
PORT_DIR=$(PORT_DIR) \
AR=$(RISCV_AR) \
CC=$(RISCV_GCC) \
CXX=$(RISCV_GXX) \
ASFLAGS="$(RISCV_ASFLAGS)" \
CCASFLAGS="$(RISCV_CCASFLAGS)" \
CFLAGS="$(RISCV_CFLAGS)" \
CXXFLAGS="$(RISCV_CXXFLAGS)" \
XCFLAGS="$(RISCV_XCFLAGS)" \
LDFLAGS="$(RISCV_LDFLAGS)" \
LDLIBS="$(RISCV_LDLIBS)" \
PROJ_SRC="$(PROJ_SRC)"
$(PROGRAM_HEX): \
$(PROGRAM_ELF)
$(RISCV_OBJCOPY) -O ihex $(PROGRAM_ELF) $#
mv $(PROGRAM_HEX) $(PROGRAM_TMP)
$(RISCV_OBJCOPY) -O verilog $(PROGRAM_ELF) $#
cp $(PROGRAM_HEX) $(PROGRAM_MEM)
mv $(PROGRAM_TMP) $(PROGRAM_HEX)
However, I need the 'compile and link stage' to be in 2 steps now as I'll be using a different compiler which has separate compile and link exes. How would I do this ? So the above would need to be split into 2. Examples online are a bit vague.
at its simplest, you use the gcc (or g++) -c option to compile the source without linking. This will generate an object file - which you can use in the linker state. Here is a simple example:
SOURCE = test.cpp
OBJECT = test.o
OUTPUT = run
all:
#g++ $(SOURCE) -c -o $(OBJECT) <------ Compile test.cpp, produces test.o
#g++ $(OBJECT) -o $(OUTPUT) <------ Links test.o into executable `run`
clean:
#$(RM) -rf $(OBJECT) $(OUTPUT)
That's it...
I did this which seemed to work:
#list of all files
PROJ_SRC = $(SRC_DIR)/plsi2c_riscv.c \
$(SRC_DIR)/SimSpi.c \
etc
#assume BUILD_FOLDER defined already
OBJ_FILES = $(addprefix $(BUILD_FOLDER)/,$(PROJ_SRC:.c=.o))
$(BUILD_FOLDER)/%.o: %.c
#Create the folder structure for the output file
#mkdir -p $(dir $#)
$(MY_CC) $(CCFLAGS) $< -o $#
$(BUILD_FOLDER)/example: $(OBJ_FILES)
mkdir -p $(dir $#)
#echo Linking $(notdir $#)
$(MY_LINK) $(LFLAGS) $^ $(LDLIBS) -o $#
I want to compile the project and run it in the debugger. There is the following line in the Makefile:
all: build_libs $(TARGET)
Can i specify -g option of gcc in this line? Or it should be somewhere else?
All contents of the Makefile are bellow.
SDK_PATH:=$(shell pwd)/../../ARDroneLib
PC_TARGET=yes
USE_LINUX=yes
ifdef MYKONOS
include $(ARDRONE_CUSTOM_CONFIG)
include $(ARDRONE_BUILD_CONFIG)
else
include $(SDK_PATH)/Soft/Build/custom.makefile
include $(SDK_PATH)/Soft/Build/config.makefile
endif
ifeq "$(RELEASE_BUILD)" "yes"
ARDRONE_TARGET_DIR=$(shell pwd)/../../Build/Release
else
ARDRONE_TARGET_DIR=$(shell pwd)/../../Build/Debug
endif
TARGET=video_opencv
SRC_DIR:=$(shell pwd)/../Sources
# Define application source files
GENERIC_BINARIES_SOURCE_DIR:=$(SRC_DIR)
GENERIC_BINARIES_COMMON_SOURCE_FILES+=\
Video/pre_stage.c\
Video/post_stage.c\
Video/display_stage.c\
image_processing.c\
controls.c
GENERIC_INCLUDES+= \
$(SRC_DIR) \
$(LIB_DIR) \
$(SDK_PATH)/Soft/Common \
$(SDK_PATH)/Soft/Lib
GENERIC_TARGET_BINARIES_PREFIX=
GENERIC_TARGET_BINARIES_DIR=$(ARDRONE_TARGET_DIR)
GENERIC_BINARIES_SOURCE_ENTRYPOINTS+= \
ardrone_testing_tool.c
GENERIC_INCLUDES:=$(addprefix -I,$(GENERIC_INCLUDES))
GENERIC_INCLUDES+=`pkg-config --cflags opencv`
GENERIC_LIB_PATHS=-L$(GENERIC_TARGET_BINARIES_DIR)
GENERIC_LIBS=-lpc_ardrone -lrt -lgtk-x11-2.0 -lcairo -lgobject-2.0 -lgdk-x11-2.0 -lm `pkg-config --libs opencv`
SDK_FLAGS+="USE_APP=yes"
SDK_FLAGS+="APP_ID=linux_video_demo"
export GENERIC_CFLAGS
export GENERIC_LIBS
export GENERIC_LIB_PATHS
export GENERIC_INCLUDES
export GENERIC_BINARIES_SOURCE_DIR
export GENERIC_BINARIES_COMMON_SOURCE_FILES
export GENERIC_TARGET_BINARIES_PREFIX
export GENERIC_TARGET_BINARIES_DIR
export GENERIC_BINARIES_SOURCE_ENTRYPOINTS
# Bug fix ...
export GENERIC_LIBRARY_SOURCE_DIR=$(GENERIC_BINARIES_SOURCE_DIR)
.PHONY: $(TARGET) build_libs
all: build_libs $(TARGET)
$(TARGET):
#$(MAKE) -C $(SDK_PATH)/VP_SDK/Build $(TMP_SDK_FLAGS) $(SDK_FLAGS) $(MAKECMDGOALS) USE_LINUX=yes
mv $(ARDRONE_TARGET_DIR)/ardrone_testing_tool $(TARGET)
mv $(TARGET) $(ARDRONE_TARGET_DIR)/
$(MAKECMDGOALS): build_libs
#$(MAKE) -C $(SDK_PATH)/VP_SDK/Build $(TMP_SDK_FLAGS) $(SDK_FLAGS) $(MAKECMDGOALS) USE_LINUX=yes
build_libs:
#$(MAKE) -C $(SDK_PATH)/Soft/Build $(TMP_SDK_FLAGS) $(SDK_FLAGS) $(MAKECMDGOALS) USE_LINUX=yes
It seems like if you add GENERIC_CFLAGS = -g to this makefile, it should work. However, the real work of compilation is not being done by this makefile, it's being done by a makefile in the subdirectory $(SDK_PATH)/VP_SDK/Build (note how the $(TARGET) rule re-invokes make in that subdirectory with the -C flag). So we can't know exactly what needs to be done by looking at this makefile.
I'm very new to makefiles. I'm reading the GNU-make manual, but I'm still unclear about how to set the parameters for the compiler and the linker when they are executed by an implicit rule.
This is part of the makefile, note there is no explicit declaration of how to compile and link everything:
.PHONY: $(TARGET) build_libs
all: build_libs $(TARGET)
$(TARGET):
#echo "============> building target: $(TARGET)"
#$(MAKE) -C $(SDK_PATH)/VP_SDK/Build $(TMP_SDK_FLAGS) $(SDK_FLAGS) $(MAKECMDGOALS) USE_LINUX=yes
mv $(ARDRONE_TARGET_DIR)/ardrone_testing_tool $(TARGET)
mv $(TARGET) $(ARDRONE_TARGET_DIR)/
#echo "============> end building target: $(TARGET)"
$(MAKECMDGOALS): build_libs
#echo "============> making cmd goals"
#$(MAKE) -C $(SDK_PATH)/VP_SDK/Build $(TMP_SDK_FLAGS) $(SDK_FLAGS) $(MAKECMDGOALS) USE_LINUX=yes
#echo "============> end making cmd goals"
build_libs:
#echo "============> building libs"
#$(MAKE) -C $(SDK_PATH)/Soft/Build $(TMP_SDK_FLAGS) $(SDK_FLAGS) $(MAKECMDGOALS) USE_LINUX=yes
#echo "============> end building libs"
That makefile builds an executable from the source files and library. But I want to compile them into a shared library. Because of that I (think I) have to add the -fPIC parameter to cc and the -shared and -soname parameters to ld. I've tried with CFLAGS=-fPIC and LDFLAGS=-shared -soname foo, which didn't work. Has anybody suggestions on how to get a shared libarary? If you need more information please just ask. Thanks in advance!
UPDATE: The makefile in $(SDK_PATH)/Soft/Build:
GEN_CUSTOM_HEADER:=../Common/generated_custom.h
include custom.makefile
include config.makefile
GNUTOOLS_PATH=/usr/local/$(GNUTOOLS_VERSION)/bin
define ADD_RULE_TEMPLATE
TO_BUILD+=build_$(1)
endef
# Add rule for each target
$(foreach target,$(TARGETS),$(eval $(call ADD_RULE_TEMPLATE,$(target))))
.PHONY: linux_sample svn_update $(TO_BUILD) build_libs $(MAKECMDGOALS)
all: $(GEN_CUSTOM_HEADER) build_libs $(TO_BUILD)
$(GEN_CUSTOM_HEADER): custom.makefile
#echo "#ifndef _GENERATED_CUSTOM_CONFIGURATION_H_" > $#
#echo "#define _GENERATED_CUSTOM_CONFIGURATION_H_" >> $#
#echo >> $#
#echo "#if defined(BR2_PACKAGE_BCM4318_AP)" >> $#
#echo "# define AP" >> $#
#echo "#else" >> $#
#echo "# define STA" >> $#
#echo "#endif" >> $#
#echo "#define CURRENT_NUM_VERSION_SOFT \"$(MAJOR_VERSION).$(MINOR_VERSION).$(MODIF_VERSION)\"" >> $#
#echo "#define CURRENT_BUILD_DATE \"$(shell date +%F\ %H:%M)\"" >> $#
#echo >> $#
ifeq ("$(VIDEO_YUV)","yes")
#echo "#define USE_VIDEO_YUV" >> $#
endif
ifeq ("$(RECORD_VISION_DATA)","yes")
#echo "#define RECORD_VISION_DATA" >> $#
endif
#echo >> $#
#echo "#define WIFI_NETWORK_NAME \"$(WIFI_NETWORK_NAME)\"" >> $#
#echo "#define WIFI_BROADCAST \"$(WIFI_BROADCAST)\"" >> $#
#echo "#define WIFI_ARDRONE_IP \"$(WIFI_ARDRONE_IP)\"" >> $#
#echo >> $#
#echo "#if defined(__linux__) || defined(USE_MINGW32)" >> $#
#echo "# define WIFI_MOBILE_IP \"$(WIFI_MOBILE_IP)\"" >> $#
#echo "# define WIRED_ITFNAME \"$(WIRED_ITFNAME)\"" >> $#
#echo "#endif // ! __linux__" >> $#
#echo >> $#
#echo >> $#
#echo "#endif // ! _GENERATED_CUSTOM_CONFIGURATION_H_" >> $#
ifneq "$(MAKECMDGOALS)" ""
ifneq "$(MAKECMDGOALS)" "clean"
ifneq "$(MAKECMDGOALS)" "update"
$(MAKECMDGOALS):
#echo -e "\nCannot make what you ask me to do :-("
else
$(MAKECMDGOALS): svn_update
endif
endif
endif
$(MAKECMDGOALS): build_libs $(TO_BUILD)
checkpackages:
ifeq ($(IPHONE_MODE),yes)
sh $(shell pwd)/check_dependencies.sh iphone RELEASE_BUILD=$(RELEASE_BUILD) $(MAKECMDGOALS)
else
ifeq ($(USE_LINUX),yes)
sh $(shell pwd)/check_dependencies.sh static RELEASE_BUILD=$(RELEASE_BUILD) $(MAKECMDGOALS)
else
ifeq ($(USE_ANDROID),yes)
sh $(shell pwd)/check_dependencies.sh android_no_neon RELEASE_BUILD=$(RELEASE_BUILD) $(MAKECMDGOALS)
endif
endif
endif
define GENERIC_RULES_TEMPLATE
build_$(1):
#$(MAKE) -C $(1) $(MAKECMDGOALS)
endef
$(foreach target,$(TARGETS),$(eval $(call GENERIC_RULES_TEMPLATE,$(target))))
build_libs: checkpackages
#$(MAKE) PC_TARGET=yes USE_ARDRONE_TOOL=yes TARGET=pc_ USE_MINGW32=no -C ../Lib/Build $(MAKECMDGOALS)
#$(MAKE) PC_TARGET=yes USE_ARDRONE_TOOL=no TARGET=pc_ USE_MINGW32=no -C ../Lib/Build $(MAKECMDGOALS)
ifeq ("$(MINGW32_MODE)","yes")
ifeq ($(shell which i586-mingw32msvc-gcc 2> /dev/null),)
$(warning You need MinGW32 to compile My Ardrone lib for Windows if you want. (under Debian: apt-get install mingw32))
else
# #$(MAKE) PC_TARGET=yes TARGET=mingw32_ USE_MINGW32=yes TMP_SDK_FLAGS="USE_MINGW32=yes NO_COM=yes USE_BLUEZ=no" -C ../Lib/Build $(MAKECMDGOALS)
# #$(MAKE) PC_TARGET=yes TARGET=emb_mingw32_ USE_MINGW32=yes CONTROL_DLL=yes TMP_SDK_FLAGS="USE_MINGW32=yes NO_COM=yes USE_BLUEZ=no" -C ../Lib/Build $(MAKECMDGOALS)
endif
endif
ifeq ($(WIIMOTE_SUPPORT),yes)
# #$(MAKE) PC_TARGET=yes TARGET=pc_ TMP_SDK_FLAGS="USE_BLUEZ=yes" -C ../Lib/libcwiid $(MAKECMDGOALS)
endif
define svn_update_template
cd ../.. ; \
echo "Checking out tag $(1) of $(2) ..." ; \
if [ $(1) != head ] ; then \
svn co -r $(1) https://svn.ardrone.org/repo/ARDrone_API/$(2) ; \
else \
svn co https://svn.ardrone.org/repo/ARDrone_API/$(2) ; \
fi ; \
cd Soft/Build ;
endef
svn_update:
#-$(call svn_update_template,$(SDK_VERSION),ARDroneLib)
If custom.makefile and config.makefile are needed you can find them here: http://pastebin.com/H8PNKKhu
UPDATE 2: I just discovered this, located in $(SDK_PATH)/VP_SDK/Build: http://pastebin.com/3knnSkmy
Since you haven't showed us the makefile in $(SDK_PATH)/Soft/Build, I'm going to take a guess.
In the GNU Make manual, there is the list of variables used by implicit rules:
CFLAGS: Extra flags to give the C compiler.
LDFLAGS: Extra flags to give to compiler when they are supposed to invoke the linker 'ld'.
In addition, the catalogue of implicit rules mentions the LDLIBS variable that names libraries used in the implicit link rule. (The implicit link rule is fine when you have a single source file being linked or where one of the object files is the name of the final executable, otherwise you need to write an explicit linking rule.)
This isn't coming into focus. These appear to be generated makefiles, so they're hard to trace, there's clearly more going on than we're seeing (and I don't blame you for not posting everything), and I don't see where the build_libs rule leads, or any use of the linker, or anything that could build a static library. We could show you how to write an explicit rule to build a shared library, but without a list of sources or objects we wouldn't know what to put into that library.
This is getting desperate, but could you try the following:
1) make -C $(SDK_PATH)/Soft/Build (putting in the appropriate value for $(SDK_PATH)) and verify that this produces the static library.
2) make -n, capture the torrent of output and put it in pastebin. We'll try to see what rules it's using, which might lead to what we need.
I did it. Finally!!!
The solution: in $(SDK_PATH)/VP_SDK/Build there was a file named generic.makefile containing all the compiler and linker invocations. I had to add the -shared and -fPIC arguments to some of them. Sounds really simple, but nothing of that was documented so I first had to look for that hidden compiler/linker invocations...
Suppose I have a project with two or more subfolders foo, bar, etc. I have a Makefile at the root of the project, and also in each subdirectory.
I would like to have certain targets (e.g. all, clean, etc) to run recursively in each subdirectory. My top-level Makefile looks like this:
all:
$(MAKE) -C foo all
$(MAKE) -C bar all
clean:
$(MAKE) -C foo clean
$(MAKE) -C bar clean
Seems to me there's a lot of duplication going on here. Is there a way I can avoid such tedious duplication in my Makefiles?
How about this:
SUBDIRS=foo bar
all clean:
for dir in $(SUBDIRS) ; do \
$(MAKE) -C $$dir $# ; \
done
A bit scary:
SUBDIRS=foo bar
SUBDIR_TARGETS=all clean
define subdir_rule
$(2): $(1)-$(2)
$(1)-$(2):
make -C $(1) $(2)
endef
$(foreach targ,$(SUBDIR_TARGETS),\
$(foreach dir,$(SUBDIRS),\
$(eval $(call subdir_rule,$(dir),$(targ)))))
Here's how I'd do it:
SUBDIRS=foo bar baz
TARGETS = clean all whatever
.PHONY:$(TARGETS)
# There really should be a way to do this as "$(TARGETS):%:TARG=%" or something...
all: TARG=all
clean: TARG=clean
whatever: TARG=whatever
$(TARGETS): $(SUBDIRS)
#echo $# done
.PHONY: $(SUBDIRS)
$(SUBDIRS):
#$(MAKE) -s -C $# $(TARG)
So I followed the Advanced Auto-Dependency Generation paper --
Makefile:
SRCS := main.c foo.c
main: main.o foo.o
%.o: %.c
$(CC) -MMD -MG -MT '$# $*.d' -c $< -o $#
cp $*.d $*.tmp
sed -e 's;#.*;;' -e 's;^[^:]*: *;;' -e 's; *\\$$;;' \
-e '/^$$/d' -e 's;$$; :;' < $*.tmp >> $*.d
rm $*.tmp
clean::
-rm *.o *.d main
-include $(SRCS:.c=.d)
main.c:
#include "foo.h"
int main(int argc, char** argv) {
foo() ;
return 0 ;
}
foo.h:
#ifndef __FOO_H__
#define __FOO_H__
void foo() ;
#endif
-- and it works like a charm.
But when foo.h becomes a generated file --
Makefile:
...
HDRS := foo.h
$(HDRS):
mk_header.sh $*
clean::
-rm $(HDRS)
...
mk_header.sh:
#!/bin/bash
UP=$(tr "[:lower:]" "[:upper:]" <<< $1)
cat <<EOF > $1.h
#ifndef __${UP}_H__
#define __${UP}_H__
void $1() ;
#endif
EOF
The 1st time I run make, main.d is not yet generated, and thus foo.h is not considered a prerequisite, and thus isn't been generated:
$ ls
foo.c main.c Makefile mk_header.sh*
$ make
cc -MMD -MG -MT 'main.o main.d' -c main.c -o main.o
cp main.d main.tmp
sed -e 's;#.*;;' -e 's;^[^:]*: *;;' -e 's; *\\$;;' \
-e '/^$/d' -e 's;$; :;' < main.tmp >> main.d
rm main.tmp
cc -MMD -MG -MT 'foo.o foo.d' -c foo.c -o foo.o
cp foo.d foo.tmp
sed -e 's;#.*;;' -e 's;^[^:]*: *;;' -e 's; *\\$;;' \
-e '/^$/d' -e 's;$; :;' < foo.tmp >> foo.d
rm foo.tmp
cc main.o foo.o -o main
$ ls
foo.c foo.d foo.o
main* main.c main.d main.o
Makefile mk_header.sh*
Only in the 2nd invocation of make, the foo.h is generated, and as a result another build cascades.
$ make
./mk_header.sh foo
cc -MMD -MG -MT 'main.o main.d' -c main.c -o main.o
cp main.d main.tmp
sed -e 's;#.*;;' -e 's;^[^:]*: *;;' -e 's; *\\$;;' \
-e '/^$/d' -e 's;$; :;' < main.tmp >> main.d
rm main.tmp
cc main.o foo.o -o main
$ ls
foo.c foo.d foo.h foo.o
main* main.c main.d main.o
Makefile mk_header.sh*
And only after that make realizes that:
$ make
make: `main' is up to date.
So my question is: Is there a way to extend the recipe suggested by the paper above, to allow for generated header files, without the elimination of the performance gain realized by not having to re-evaluate the entire make tree when including the *.d fragments?
The problem is that the *.d Makefile-fragments generation must be performed after all the header generation is complete. Putting it this way, one can use the make dependencies to force the right order:
SRCS := main.c foo.c
HDRS := foo.h
main: main.o foo.o
%.o: %.c | generated_headers
$(CC) -MMD -MG -MT '$# $*.d' -c $< -o $#
cp $*.d $*.tmp
sed -e 's;#.*;;' -e 's;^[^:]*: *;;' -e 's; *\\$$;;' \
-e '/^$$/d' -e 's;$$; :;' < $*.tmp >> $*.d
rm $*.tmp
-include $(SRCS:.c=.d)
$(HDRS):
mk_header.sh $*
generated_headers: $(HDRS)
clean:
-rm $(HDRS) *.o *.d main
.PHONY: clean generated_headers
Notes:
I use an order-only dependency.
This solution is fairly scalable: Each generate-header rule, needs only to be a prerequisite of the generated_headers .PHONY target. Assuming that the header generation rule is written properly, once it has been generated correctly, satisfying the generated_headers target should be a no-op.
One can't compile a single object, even if that object does not require any generated headers, without generating all the generated headers of the project first. While this is technically sound, your developers will complain.
So you should think about having a FAST_AND_LOOSE flag, that will turn this feature off:
%.o: %.c | $(if $(FAST_AND_LOOSE),,generated_headers)
...
Thus a developer may issue:
make FAST_AND_LOOSE=1 main.o
The makefile in the original question doesn't work for me with gcc 4.8.2:
cc -MMD -MG -MT main.d -c main.c -o main.o
cc1: error: -MG may only be used with -M or -MM
I guess gcc changed the behaviour of -MG at some point in the last 4 years.
It seems that if you want to support generated header files, there is no longer
any way to generate the ".d" file and the ".o" file at the same time, without
invoking the C preprocessor twice.
So I've updated the recipe to:
%.o: %.c
$(CC) -MM -MG -MP -MT $*.o -MF $*.d $<
$(CC) -c $< -o $#
(Note also that gcc now has -MP to generate phony targets for each header,
so you no longer need to run sed on gcc's output.)
We still have the same problem as the original question -- running make the
first time fails to generate foo.h:
$ make
cc -MM -MG -MP -MT main.o -MF main.d main.c
cc -c main.c -o main.o
main.c:1:17: fatal error: foo.h: No such file or directory
#include "foo.h"
^
compilation terminated.
Makefile:7: recipe for target 'main.o' failed
make: *** [main.o] Error 1
Running it again works:
$ make
./mk_header.sh foo
cc -MM -MG -MP -MT main.o -MF main.d main.c
cc -c main.c -o main.o
cc main.o -o main
Since we have to run the C preprocessor twice anyway, let's generate the .d
file in a separate rule:
%.d: %.c
$(CC) -MM -MG -MP -MT $*.o -MF $# $<
%.o: %.c
$(CC) -c $< -o $#
Now it generates the header file correctly:
$ make clean
rm -f *.o *.d main foo.h
$ make
cc -MM -MG -MP -MT main.o -MF main.d main.c
./mk_header.sh foo
cc -c main.c -o main.o
cc main.o -o main
Does this suffer from the performance issue that the original question was
trying to avoid? This is essentially the "Basic Auto-Dependencies" solution
described in the Advanced Auto-Dependency
Generation paper.
That paper claims 3 problems with this solution:
We re-exec make if anything changes.
Ugly but harmless warning: "main.d: No such file or directory"
Fatal error "no rule to make targe foo.h" if foo.h file is removed, even
if mention of it is removed from the .c file.
Problem 2 is solved by using -include instead of include. As far as I can
tell, this is orthogonal to the paper's technique for avoiding re-exec of
make. At least I haven't been able to cause any problems by using -include
instead of include.
Problem 3 is solved by GCC's -MP (or the equivalent sed script) -- this is
also orthogonal to the technique for avoiding re-exec of make.
Problem 1 can be perhaps ameliorated somewhat by something like this:
%.d: %.c
$(CC) -MM -MG -MP -MT $*.o -MF $#.new $<
cmp $#.new $# 2>/dev/null || mv $#.new $#; rm -f $#.new
Before that change:
$ make clean
rm -f *.o *.d main foo.h
$ make -d 2>&1 | grep Re-executing
Re-executing[1]: make -d
$ make -d 2>&1 | grep Re-executing
$ touch main.c; make -d 2>&1 | grep Re-executing
Re-executing[1]: make -d
After that change:
$ make clean
rm -f *.o *.d main foo.h
$ make -d 2>&1 | grep Re-executing
Re-executing[1]: make -d
$ make -d 2>&1 | grep Re-executing
$ touch main.c; make -d 2>&1 | grep Re-executing
Slightly better. Of course if a new dependency is introduced, make will still
need to re-execute. Maybe there's nothing that can be done to improve this; it seems to be a tradeoff between correctness and speed.
All of the above was tested with make 3.81.
Short answer: no. The recipe described in the paper is very clever, one of my favorites, but it's a sophisticated use of a crude tool. It takes advantage of the usual scheme in which all needed headers exist; what it tries to solve is the problem of determining which headers, if recently modified, require the given object file to be rebuilt. In particular, if the object file doesn't exist then it must be rebuilt-- and in that case there's no reason to worry about the header files because the compiler will surely find them.
Now header files are generated. So foo.h may not exist, so somebody will have to run the script to generate it, and only Make can do that. But Make can't know that foo.h is necessary without performing some analysis of main.c. But that really can't happen until Make starts to execute main-related rules (e.g main.o or main.o.d), which it cannot execute until after it has decided which targets it is going to build.
So we will have to use... recursive make! [Dun-dun-dunnnn!]
We can't achieve the paper's goal of avoiding reinvocation of Make, but we can at least avoid (some) unnecessary rebuilding. You could do something like the "Basic Auto-Dependencies" described in the paper; the paper describes the problems of that approach. Or you could use a command like the one in the "Advanced" recipe to generate a list of headers, then pass that to $(MAKE); this approach is tidy, but might call Make many times on the same header, depending on what your code tree looks like.
You could create an explicit dependency rule for your generated header:
main.o: foo.h
If the generated header is directly included in a small number of files, this may be a workable approach.