I'm trying to create a generic makefile to build static libraries that my project uses. I've used the expertise on this site, as well as the GNU Make manual to help write the following makefile. It is working well apart from one annoying problem.
# Generic makefile to build/install a static library (zlib example)
ARCH = linux
CFLAGS = -O3 -Wall
# List of source code directories
SOURCES = src test utils
# List of header files to install
INCLUDES = src/zlib.h src/zconf.h
# Library to create
LIBNAME = libz.a
############################################################
BUILD_DIR = build/$(ARCH)
# Full path to the built library
TARGET = $(BUILD_DIR)/$(LIBNAME)
prefix = ../..
exec_prefix = prefix
libdir = $(prefix)/lib/$(ARCH)
includedir = $(prefix)/include
INSTALL_PROGRAM = install -D
INSTALL_DATA = $(INSTALL_PROGRAM) -m 644
CFILES := $(foreach dir,$(SOURCES),$(wildcard $(dir)/*.c))
OBJECTS := $(addprefix $(BUILD_DIR)/,$(CFILES:.c=.o))
DEPENDS := $(OBJECTS:.o=.d)
.PHONY: all installdirs install uninstall clean
# Default
all: $(DEPENDS) $(TARGET)
# Build the dependency files
# (GNU Make Manual 4.14 Generating Prerequisites Automatically)
$(BUILD_DIR)/%.d: $(BUILD_DIR)
#echo "build dep for $*.c as $#"
#$(CC) -M $(CFLAGS) $*.c > $#.tmp
#sed s~.*:~"$(BUILD_DIR)/$*.o $#:"~ $#.tmp > $#
#rm $#.tmp
# Link all changed object files into static library
$(TARGET): $(OBJECTS)
$(AR) -rc $(TARGET) $?
# Compile C source to object code
$(BUILD_DIR)/%.o: %.c
$(CC) $(CFLAGS) -c $< -o $#
# Create the necessary directory tree for the build
$(BUILD_DIR):
#for p in $(SOURCES); do mkdir -p $(BUILD_DIR)/$$p; done
# Create the necessary directory tree for installation
installdirs:
#mkdir -p $(libdir)
#mkdir -p $(includedir)
# Install the library and headers
install: all installdirs
$(INSTALL_DATA) $(TARGET) $(libdir)
for p in $(INCLUDES); do $(INSTALL_DATA) $$p $(includedir); done
# Remove the library and headers
uninstall:
rm -f $(libdir)/$(LIBNAME)
for p in $(notdir $(INCLUDES)); do rm -f $(includedir)/$$p; done
# Remove all build files
clean:
rm -fr $(BUILD_DIR)
# Pull in the dependencies if they exist
# http://scottmcpeak.com/autodepend/autodepend.html
-include $(DEPENDS)
The problem is that the dependency files are built when they don't need to be. e.g. make install shown below rebuilds the .d files before installing.
$make --version
GNU Make 3.81
$make
build dep for utils/utils.c as build/linux/utils/utils.d
build dep for test/test.c as build/linux/test/test.d
build dep for src/zutil.c as build/linux/src/zutil.d
...
{ continues on making the other .d files, then the .o files }
...
cc -O3 -Wall -c src/zutil.c -o build/linux/src/zutil.o
cc -O3 -Wall -c test/test.c -o build/linux/test/test.o
cc -O3 -Wall -c utils/utils.c -o build/linux/utils/utils.o
ar rc { ... .o files ... }
All good up to this point! But a 'make install' now will rebuild the .d files
$make install
build dep for utils/utils.c as build/linux/utils/utils.d
build dep for test/test.c as build/linux/test/test.d
build dep for src/zutil.c as build/linux/src/zutil.d
{ ... }
install -D -m 644 build/linux/libz.a ../../lib/linux
for p in src/zlib.h src/zconf.h; do install -D -m 644 $p ../../include; done
I tried to 'touch' the .d files when the objects are built, so the update time is newer than the .o files, but that had no effect. What's wrong with my makefile?
The problem is that you include the dependency files (whatever.d), and you also have a rule for building these files. So every time you use this makefile, no matter what target you specify, it will rebuild them, include them, and execute again.
Try this for an approach to dependency handling that solves this problem. Basically, you don't need a separate rule for foo.d, just make it a side effect of the foo.o rule (it takes some thought to realize that this will work).
Related
I don't understand the dependencies very well
TARGET = prog1.exe
SRC = src
# dir. with *.cpp
INC = inc
# dir. with *.hpp
BIN = bin
# dir. with *.o, *.out,*.exe
SOURCE = $(wildcard $(SRC)/*.cpp)
OBJECT = $(patsubst %, $(BIN)/%, $(notdir $(SOURCE:.cpp=.o)))
CXX = g++
CXXFLAGS = -g -I$(INC)
ifneq ($(ARGS),'')
GDBI := --args $(BIN)/$(TARGET) $(ARGS)
else
GDBI := $(BIN)/$(TARGET)
endif
$(BIN)/$(TARGET): $(OBJECT)
$(CXX) -o $# $^
$(BIN)/%.o: $(SRC)/%.cpp
$(CXX) $(CXXFLAGS) -c $< -o $#
.PHONY: help run clean depurate
run: $(BIN)/
$(BIN)/$(TARGET) ${ARGS}
clean:
rm -f $(OBJECT) $(BIN)/$(TARGET)
depurate:
gdb $(GDBI)
help:
#echo "Para ejecutar: \`make run' o \`make ARGS=\"<argumentos>\" run'."
Why is Make not checking changes with the headers files .hpp in the directory inc/?
Is right this solution for headers without source files?
I mean: x.cpp includes x.hpp, and x.hpp includes a.hpp and b.hpp.
$(BIN)/%.o: $(SRC)/%.cpp $(INC)/%.hpp
$(CXX) $(CXXFLAGS) -c $< -o $#
Make isn't doing dependency checking on your header files because they're not explicitly listed as a dependency anywhere.
If you want to make your source files depend on all of the header files, you can do something like this:
HEADERS:=$(wildcard $(INC)/*.hpp)
$(BIN)/%.o: $(SRC)/%.cpp $(HEADERS)
That will rebuild all of your source files whenever any header changes, which works but is probably overkill. If you only want a source file to depend on those header files it actually uses, you'll want to use the compiler's automatic dependency generation capabilities:
CXXFLAGS = -g -I$(INC) -MMD -MF $(<:.cpp=.d)
include $(wildcard $(SRC)/*.d)
$(BIN)/%.o: $(SRC)/%.cpp
$(CXX) $(CXXFLAGS) -c $< -o $#
clean:
rm -f $(OBJECT) $(BIN)/$(TARGET) $(SRC)/*.d
Now, when the compiler compiles a .cpp file, it will create a matching .d file alongside it. This file is a tiny fragment of a makefile that lists the headers that .cpp file uses. The next time you build, the include line pulls in all of those .d files and uses that information to help calculate dependencies. The blog post mentioned by Tim in the comments has a great detailed explanation of how this works.
I am trying to create a Makefile and I am a bit stuck.
So far I have been compiling my 3 files (2 headers and one main program) as such:
gcc -c phypages.c -o phypages.o
gcc -c pagetable.c -o pagetable.o
gcc -c analysis.c -o analysis.o
gcc analysis.o phypages.o pagetable.o -o analysis
I would like to make a Makefile to help me out with this. When I compile and link the files without a Makefile everything works fine, however when I try to make a Makefile I get a bunch of errors. Could you give me some tips on how to go about making a basic Makefile?
This is a good start:
# Binary name
NAME = analysis
# Compiler settings
CC = gcc
CFLAGS += -Wall -Wextra
CFLAGS += -Werror
# Source files
SRCS = phypages.c \
pagetable.c \
analysis.c
# Object files
OBJS = $(SRCS:.c=.o)
RM = rm -f
# Default rule, it should call all your rules that create
# an executable, or build a library...
# Here, it calls only the $(NAME) rule, that builds `analysis`
all: $(NAME)
# Rule to build your object files and link them into a binary
$(NAME): $(OBJS)
$(CC) -o $(NAME) $(OBJS)
# Rule to remove object files
clean:
$(RM) $(OBJS)
# Rule to remove binary, calls the 'clean' rule first
fclean: clean
$(RM) $(NAME)
# Rule to remove object files and binary, then re-build everything
re: fclean all
# Rules that are not linked with a filename should be listed here
.PHONY: all clean fclean re
You can then run make analysis or simply make to build your program.
Next time you change a file, run make again and only the file you changed will be re-compiled, instead of the whole project.
I'm trying to build a new package for using in buildroot this useful program to power on/off the different USB ports from the raspberry pi.
The GIT repository is on this site:
https://github.com/codazoda/hub-ctrl.c
And this is the hub-ctrl.mk I've built:
################################################################################
#
# hub-ctrl
#
################################################################################
HUB_CTRL_VERSION = 42095e522859059e8a5f4ec05c1e3def01a870a9
HUB_CTRL_SITE = https://github.com/codazoda/hub-ctrl.c
HUB_CTRL_SITE_METHOD = git
HUB_CTRL_LICENSE = GPLv2+
define HUB_CTRL_BUILD_CMDS
$(TARGET_MAKE_ENV) $(MAKE) $(TARGET_CONFIGURE_OPTS) -C $(#D)
endef
define HUB_CTRL_INSTALL_TARGET_CMDS
# Install predictead application
$(INSTALL) -m 4755 -D $(#D)/hub-ctrl $(TARGET_DIR)/usr/sbin/hub-ctrl;
endef
$(eval $(generic-package))
Up to now, everything is ok. But I realize that the repository it doesn't have a Makefile, so I decided to build one on my own, but I have errors. I don't know how to link the include and library folder. I'm not an expert building makefiles so I need some help. This is my Makefile:
PROJECT_ROOT = .
OUTDIR = $(PROJECT_ROOT)/bin
BASE_NAME = hub-ctrl
NAME = $(BASE_NAME)$(D)
OBJ = $(BASE_NAME).o
INC =
LIBS = -lusb
MKDIR = mkdir -p
MV = mv
# Master rule
.PHONY: all
all: $(NAME)
# Output binary
$(NAME): $(OBJ)
$(CC) $(CFLAGS) $(INC) $(OBJ) -o $(BASE_NAME) $(LIBS)
-#$(MV) $(BASE_NAME) $(OUTDIR)/$(BASE_NAME)
rm *.o
# Intermediate object files
$(OBJ): %.o: %.c
#$(MKDIR) $(OUTDIR)
$(CC) $(CFLAGS) $(LIBS) $(INC) -c $<
# Cleanup intermediate objects
.PHONY: clean_obj
clean_obj:
rm -f $(OBJ)
#echo "obj cleaned up!"
# Cleanup everything
.PHONY: clean
clean: clean_obj
rm -rf $(OUTDIR)/$(BASE_NAME)
#echo "all cleaned up!"
This is the error I've got:
hub-ctrl.c:12:17: fatal error: usb.h: No such file or directory
#include
^
compilation terminated.
Any suggestion?
Best regards.
I've followed the indications of Peter Korsgaard. I'm using the package uhubctl from buildroot-2017.11-rc1 and I've added this package to my buildroot-2016.02 custom platform. Everything is working fine, and I'm able to power off the usb ports in my HW platform.
Thank you very much to Peter Korsgaard for his advice.
I searched on the inet but I did not find any clear answer. Could you point me in the right direction on how to convert a Makefile into a CMakeLists?
I want to do that because I am new both to makefile and to cmake. In my job CMake is more used and since I need to start using one of them I prefer having everything in CMake. I know CMake is generating a Makefile but for me CMake is way easier to read than a Makefile.
I have the following Makefile:
PREFIX ?= /usr/local
CC = gcc
AR = ar
CFLAGS = -std=gnu99 -fPIC -Wall -Wno-unused-parameter -Wno-unused-function -I. -O4
APRILTAG_SRCS := $(shell ls *.c common/*.c)
APRILTAG_HEADERS := $(shell ls *.h common/*.h)
APRILTAG_OBJS := $(APRILTAG_SRCS:%.c=%.o)
TARGETS := libapriltag.a libapriltag.so
# LIBS := -Lusr/include/flycapture
.PHONY: all
all: $(TARGETS)
#$(MAKE) -C example all
.PHONY: install
install: libapriltag.so
#chmod +x install.sh
#./install.sh $(PREFIX)/lib libapriltag.so #this should be the line that install the library
#./install.sh $(PREFIX)/include/apriltag $(APRILTAG_HEADERS)
#sed 's:^prefix=$$:prefix=$(PREFIX):' < apriltag.pc.in > apriltag.pc
#./install.sh $(PREFIX)/lib/pkgconfig apriltag.pc
#rm apriltag.pc
#ldconfig
libapriltag.a: $(APRILTAG_OBJS)
#echo " [$#]"
#$(AR) -cq $# $(APRILTAG_OBJS)
libapriltag.so: $(APRILTAG_OBJS)
#echo " [$#]"
#$(CC) -fPIC -shared -o $# $^
%.o: %.c
#echo " $#"
#$(CC) -o $# -c $< $(CFLAGS)
.PHONY: clean
clean:
#rm -rf *.o common/*.o $(TARGETS)
#$(MAKE) -C example clean
I am not asking you to do my job but I would like to have some kind of guide or a good link where to look.
The project contains both C and C++ programming languages.
I started creating a new CMakeLists.txt file, but it is still not working. It gives me the following errors:
You have called ADD_LIBRARY for library librapriltag.a without any source files. This typically indicates a problem with your CMakeLists.txt file
-- Configuring done
CMake Error: Cannot determine link language for target "librapriltag.a".
CMake Error: CMake can not determine linker language for target: librapriltag.a
-- Generating done
-- Build files have been written to: .....
The CMakeLists.txt I started creating is the following:
project( apriltag2 C CXX)
cmake_minimum_required(VERSION 2.8)
set(CMAKE_C_FLAGS "-std=gnu99 -fPIC -Wall -Wno-unused-parameter -Wno-unused-function -I. -O4")
include_directories("/home/fschiano/Repositories/apriltag2")
include_directories("/home/fschiano/Repositories/apriltag2/common")
add_library( librapriltag.a )
The CMakeLists.txt which works is the following:
project( apriltag2 )
cmake_minimum_required(VERSION 2.8)
set(CMAKE_C_FLAGS "-std=gnu99 -fPIC -Wall -Wno-unused-parameter -Wno-unused-function -I. -O4")
message("CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}")
file(GLOB apriltag_SRC "*.c")
file(GLOB apriltag_HEADERS "*.h")
set(APRILTAG_SRCS ${apriltag_SRC})
set(APRILTAG_HEADERS ${apriltag_HEADERS})
message(STATUS "CMAKE_CURRENT_LIST_DIR=${CMAKE_CURRENT_LIST_DIR}")
add_library(apriltag STATIC ${APRILTAG_SRCS})
target_include_directories(apriltag PUBLIC ${CMAKE_SOURCE_DIR})
target_compile_options(apriltag PUBLIC -fPIC -Wall -Wno-unused-parameter -Wno-unused-function -O4)
install(TARGETS apriltag
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib)
install(DIRECTORY CMAKE_CURRENT_LIST_DIR/include/
DESTINATION CMAKE_CURRENT_LIST_DIR/include/
FILES_MATCHING PATTERN *.h)
EDIT:
Something is still not right. If I want to change something in my library, like something which is in /home/fschiano/Repositories/apriltag2/common
If I use the Makefile which I had before doing all these modifications and I do:
make
do some modifications in the files I wanted to modify
sudo make install, which would give me the following output:
/usr/local/lib/libapriltag.so
/usr/local/include/apriltag/apriltag.h
/usr/local/include/apriltag/common/g2d.h
/usr/local/include/apriltag/common/getopt.h
/usr/local/include/apriltag/common/homography.h
/usr/local/include/apriltag/common/image_f32.h
/usr/local/include/apriltag/common/image_u8.h
/usr/local/include/apriltag/common/image_u8x3.h
/usr/local/include/apriltag/common/matd.h
/usr/local/include/apriltag/common/math_util.h
/usr/local/include/apriltag/common/pnm.h
/usr/local/include/apriltag/common/postscript_utils.h
/usr/local/include/apriltag/common/string_util.h
/usr/local/include/apriltag/common/svd22.h
/usr/local/include/apriltag/common/thash_impl.h
/usr/local/include/apriltag/common/timeprofile.h
/usr/local/include/apriltag/common/time_util.h
/usr/local/include/apriltag/common/unionfind.h
/usr/local/include/apriltag/common/workerpool.h
/usr/local/include/apriltag/common/zarray.h
/usr/local/include/apriltag/common/zhash.h
/usr/local/include/apriltag/common/zmaxheap.h
/usr/local/include/apriltag/tag16h5.h
/usr/local/include/apriltag/tag25h7.h
/usr/local/include/apriltag/tag25h9.h
/usr/local/include/apriltag/tag36artoolkit.h
/usr/local/include/apriltag/tag36h10.h
/usr/local/include/apriltag/tag36h11.h
/usr/local/lib/pkgconfig/apriltag.pc
/sbin/ldconfig.real: /usr/lib/libstdc++.so.5 is not a symbolic link
and the modifications would take effect.
Now, if I remove the Makefile and I do:
cmake .
make
do some modifications in the files I wanted to modify
sudo make install, it gives me the following output:
Install the project...
-- Install configuration: ""
-- Up-to-date: /usr/local/lib/libapriltag.a
So it seems that the install part of the CMakeLists.txt is not right!
The file install.sh is the following.
#!/bin/sh -e
# Usage: install.sh TARGET [RELATIVE PATHS ...]
#
# e.g. ./install.sh /usr/local foo/file1 foo/file2 ...
# This creates the files /usr/local/foo/file1 and /usr/local/foo/file2
TARGETDIR=$1
shift
for src in "$#"; do
dest=$TARGETDIR/$src
mkdir -p $(dirname $dest)
cp $src $dest
echo $dest
done
Could you try to help me?
Thanks
Let's go through that step-by-step:
PREFIX ?= /usr/local
We ignore that, as it's the default. Can be overwritten by CMAKE_INSTALL_PREFIX.
CC = gcc
AR = ar
Ignore these as well. Use CMAKE_C_COMPILER and CMAKE_CXX_COMPILER to forcibly switch the compiler.
CFLAGS = -std=gnu99 -fPIC -Wall -Wno-unused-parameter -Wno-unused-function -I. -O4
They are pretty special for gcc-like compilers. Set them conditionally for CMAKE_C_COMPILER_ID MATCHES GNU further down after defining the target.
The standard is set by set(C_STANDARD 98) and set(CXX_STANDARD 98).
APRILTAG_SRCS := $(shell ls *.c common/*.c)
Define a variable listing all the source files individually: set(APRILTAG_SRCS ...)
APRILTAG_HEADERS := $(shell ls *.h common/*.h)
Define a variable listing all the header file individually: set(APRILTAG_HEADERS ...). However, you don't really need them anywhere (unless you want Visual Studio to list them).
APRILTAG_OBJS := $(APRILTAG_SRCS:%.c=%.o)
In most cases, you don't need that. For those rare cases there are Object Libraries.
TARGETS := libapriltag.a libapriltag.so
# LIBS := -Lusr/include/flycapture
We define our libraries here with add_library:
add_library(apriltag ${APRILTAG_SRCS})
target_include_directories(apriltag PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include/apriltag)
target_compile_options(apriltag PUBLIC -fPIC -Wall -Wno-unused-parameter -Wno-unused-function -O4)
The switch between static and shared is done via BUILD_SHARED_LIBS on invocation of CMake.
.PHONY: all
all: $(TARGETS)
#$(MAKE) -C example all
Nothing to do here. CMake will automatically create that.
.PHONY: install
install: libapriltag.so
#chmod +x install.sh
#./install.sh $(PREFIX)/lib libapriltag.so #this should be the line that install the library
#./install.sh $(PREFIX)/include/apriltag $(APRILTAG_HEADERS)
#sed 's:^prefix=$$:prefix=$(PREFIX):' < apriltag.pc.in > apriltag.pc
#./install.sh $(PREFIX)/lib/pkgconfig apriltag.pc
#rm apriltag.pc
#ldconfig
CMake will ease this up by a magnitude:
install(TARGETS apriltag
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib)
install(DIRECTORY include/
DESTINATION include/
FILES_MATCHING PATTERN *.h)
That will install the library static and shared library (whatever exists) and the header files.
libapriltag.a: $(APRILTAG_OBJS)
#echo " [$#]"
#$(AR) -cq $# $(APRILTAG_OBJS)
libapriltag.so: $(APRILTAG_OBJS)
#echo " [$#]"
#$(CC) -fPIC -shared -o $# $^
%.o: %.c
#echo " $#"
#$(CC) -o $# -c $< $(CFLAGS)
All this is not needed.
.PHONY: clean
clean:
#rm -rf *.o common/*.o $(TARGETS)
#$(MAKE) -C example clean
You don't need that. CMake will generate a clean target automatically.
Judging from TARGETS := libapriltag.a libapriltag.so, you'll defintely need add_library command to create targets.
Instead of gathering souces to be compiled using wildcards like APRILTAG_SRCS := $(shell ls *.c common/*.c) it is recommended to list them explicitly in add_library call. But if you really want to list them automatically, see file(GLOB ...) command. (There are some important things to be aware of, though, see Specify source files globally with GLOB?).
The clean target would be generated automatically by CMake.
Finally, see the documentation for install() command to create install rules.
Compiler flags are set using set(CMAKE_C_FLAGS "blabla"), or appended using set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} blabla").
I'm having some trouble understanding how to design my makefile to build my project the way I want to. Specifically, I can't figure out how to keep all source files in a src directory, while putting all binaries in a bin directory except the linked executable, which goes in the project root.
This is my makefile:
# Compiler options
FC := mpif90
FFLAGS := -O3 -g -Wall -Warray-bounds -ffixed-line-length-none -fbounds-check
VPATH := src
BINDIR := bin
# Define file extensions
.SUFFIXES:
.SUFFIXES: .f .o .mod
# All modules
OBJS := $(BINDIR)/ratecoeffs.o $(BINDIR)/interpolation.o $(BINDIR)/io.o $(BINDIR)/eedf.o $(BINDIR)/single_particle.o $(BINDIR)/physics.o $(BINDIR)/random.o $(BINDIR)/mpi.o $(BINDIR)/precision.o $(BINDIR)/populations.o
# Build rules
all: runner | $(BINDIR)
$(BINDIR):
mkdir -p $(BINDIR)
$(BINDIR)/%.o: $(VPATH)/%.f | $(BINDIR)
$(FC) $(FFLAGS) -c $^ -o $#
runner: $(OBJS)
clean:
#rm -rf $(BINDIR)
Running make builds everything allright - it finds all source files in src and puts all .o files in bin - but the module files (.mod) that are generated by the compiler are put in the project root instead of in the bin directory. I realize I could just specify a rule to place them there, but that messes with the build order, and will sometimes break the build.
What is the "correct" way to get this behavior?
And yes, I've looked at autotools and automake, but I've never used them before and they seem to be overkill for this project. As I couldn't find any good tutorials on how they work (no, I didn't like the tutorial on gnu.org) I'd prefer if I could avoid having to learn this tool just to get this work...
Assuming your underlying Fortran compiler is gfortran, use the -J command line option.
$(FC) $(FFLAGS) -c $^ -o $# -J$(BINDIR)
With an eye to the future, you may be better off creating a MODDIR or similar variable, that you use instead of BINDIR. Object code (*.o) and mod files have different roles to play in later compilation and linking steps - in larger projects they are often kept separate.
It would be probably more in the sense of the make system to change into the obj-directory and do the compilation from there. Via the VPATH option you can let make to find your source files automatically. You could easily call your makefile recursively from the right directory. Below you find a trivial example which would be straightforward to adapt to your case. Please note, that it only works with GNU make.
ifeq (1,$(RECURSED))
VPATH = $(SRCDIR)
########################################################################
# Project specific makefile
########################################################################
FC = gfortran
FCOPTS =
LN = $(FC)
LNOPTS =
OBJS = accuracy.o eqsolver.o io.o linsolve.o
linsolve: $(OBJS)
$(LN) $(LNOPTS) -o $# $^
%.o: %.f90
$(FC) $(FCOPTS) -c $<
.PHONY: clean realclean
clean:
rm -f *.mod *.o
realclean: clean
rm -f linsolve
accuracy.o:
eqsolver.o: accuracy.o
io.o: accuracy.o
linsolve.o: accuracy.o eqsolver.o io.o
else
########################################################################
# Recusive invokation
########################################################################
BUILDDIR = _build
LOCALGOALS = $(BUILDDIR) distclean
RECURSIVEGOALS = $(filter-out $(LOCALGOALS), $(MAKECMDGOALS))
.PHONY: all $(RECURSIVE_GOALS) distclean
all $(RECURSIVEGOALS): $(BUILDDIR)
+$(MAKE) -C $(BUILDDIR) -f $(CURDIR)/GNUmakefile SRCDIR=$(CURDIR) \
RECURSED=1 $(RECURSIVEGOALS)
$(BUILDDIR):
mkdir $(BUILDDIR)
distclean:
rm -rf $(BUILDDIR)
endif
The principle is simple:
In the first part you write your normal makefile, as if you would create the object files in the source directory. However, additionally you add the VPATH option to make sure the source files are found (as make will be in the directory BUILDDIR when this part of the makefile is processed).
In the second part (which is executed first, when the variable RECURSED is not set yet), you change to the BUILDIR directory and invoke your makefile from there. You pass some helper variables (e.g. the current directory) and all make goals, apart of those, which must be executed from outside BUILDDIR (e.g. distclean and the one creating BUILDDIR itself). The rules for those goals you specify also in the second part.