It takes a little more than one line. In fact, it takes some
collaboration from the compiler, and some shell behind it. But
it does work, and it works correctly (unlike most other tools
I've seen), because it depends on the compiler to find the
dependencies, rather than trying to reimplement the compiler's
logic internally.
Yes, it actually takes ~4 lines of makefile, not just 1. I
exaggerated, but not by much. And yes, it does require using compiler
support to do it in so little lines. (This is using gcc 3.4.3.)
Here's a snippet from such a make system I'm been playing around with
on the side as a demonstration to my company that GNU Make is superior
to Maven. I'm sure this is not the best, just a demonstration that I
whipped up mostly myself. (I would first attempt to cache calls to
shell and use Make built-ins for stuff like realpath, except those
appear to be broken my version of GNU Make.)
(Yes I pass -fno-strict-aliasing to gcc. I'm aware that code should be
written to not require that. Some of my company's code does.)
(Yes, the makefile will be munged from line wrapping that the
submission process does automatically. Hopefully it'll still be
readable.)
#Input is:
# CPP_FILENAMES a list of file names (without directory)
to compile into object files
# INCLUDE_PATHS a list of include paths
# CPP_DIR folder containing *.cpp files
# OBJ_DIR folder where to put the .o files and
other temp files like .d files
# SHARED_LIBRARY_BUILD if "yes", then will build with -fPIC or
similar option to allow
# use of .o files in shared libraries.
# RELEASE_BUILD if "yes", then will build with release
options, otherwise no
ifneq ($(filter %.cpp, $(CPP_FILEPATHS)),)
$(error $(filter %.cpp, $(CPP_FILEPATHS)) do not have .cpp as their
extension)
endif
..PHONY : all
all :
UNAME := $(shell uname)
build_scripts_dir := $(dir $(word $(words $(MAKEFILE_LIST)), $
(MAKEFILE_LIST)))
#create the directories if they don't exist
FORCE := $(foreach x,$(INCLUDE_PATHS),$(shell $(build_scripts_dir)/
mkdir_p.sh $(x)))
FORCE := $(shell $(build_scripts_dir)/mkdir_p.sh $(CPP_DIR))
FORCE := $(shell $(build_scripts_dir)/mkdir_p.sh $(OBJ_DIR))
# basically realpath, which doesn't work for some reason
INCLUDE_PATHS := $(foreach x,$(INCLUDE_PATHS),$(shell cd $(dir $(x))
&& pwd)/$(notdir $(x)))
CPP_DIR := $(shell cd $(dir $(CPP_DIR)) && pwd)/$(notdir $(CPP_DIR))
OBJ_DIR := $(shell cd $(dir $(OBJ_DIR)) && pwd)/$(notdir $(OBJ_DIR))
# Get absolute path names to use instead of file names
OBJ_FILEPATHS := $(patsubst %.cpp,$(OBJ_DIR)/%.o,$(CPP_FILENAMES))
DEP_FILEPATHS := $(patsubst %.cpp,$(OBJ_DIR)/%.d,$(CPP_FILENAMES))
####
# compile rules
ifeq ($(UNAME),Linux)
# The compile-rule generates makefile fragements for each .cpp file.
# These makefile fragments add a dependency of every included header
# on the object file. Include these dependencies here.
# (If the file is not yet generated, for example a clean build, the
# "-" in "-include" means ignore it if the file doesn't exist.)
ifeq ($(DEP_FILEPATHS),)
$(error Must compile at least one cpp file)
endif
-include $(DEP_FILEPATHS)
# Target specific variable definitions, to not conflict if this
makefile is included again
$(OBJ_FILEPATHS) : OBJ_DIR := $(OBJ_DIR)
# Target specific variable definitions, to not conflict if this
makefile is included again
$(OBJ_FILEPATHS) : INCLUDE_OPTIONS := $(addprefix -I,$
(INCLUDE_PATHS))
# Target specific variable definitions, to not conflict if this
makefile is included again
ifeq ($(RELEASE_BUILD),yes)
$(OBJ_FILEPATHS) : OTHER_COMPILE_OPTIONS := -O3 -finline-
limit=10000 -Wuninitialized
else
$(OBJ_FILEPATHS) : OTHER_COMPILE_OPTIONS := -g
endif
ifeq ($(SHARED_LIBRARY_BUILD),yes)
$(OBJ_FILEPATHS) : OTHER_COMPILE_OPTIONS += -fPIC
endif
$(OBJ_FILEPATHS) : $(OBJ_DIR)/%.o : $(CPP_DIR)/%.cpp
rm -f $@ $(OBJ_DIR)/$*.d
g++ -c \
-std=c++98 -pedantic-errors -Wall -Wabi -Wtrigraphs -Wpointer-
arith -Wdisabled-optimization \
$(OTHER_COMPILE_OPTIONS) \
-fno-strict-aliasing \
-MP -MMD -MF $(OBJ_DIR)/$*.d \
-o $@ \
$< \
$(INCLUDE_OPTIONS)
@echo
else
$(error Platform $(UNAME) not suppoted in makefile.)
endif
####
# all rule
..PHONY : all
all : $(OBJ_FILEPATHS)
####
# clean rules
FAKE_CLEAN_TARGETS := $(addsuffix .clean,$(OBJ_FILEPATHS) $
(DEP_FILEPATHS))
..PHONY : clean
clean : $(FAKE_CLEAN_TARGETS)
..PHONY : $(FAKE_CLEAN_TARGETS)
$(FAKE_CLEAN_TARGETS) : %.clean : ; rm -f $*