Skip to content

Latest commit

 

History

History

makefile

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

mekefile 编写

参考

Linux下使用 autoconf和automake 自动构建 项目 make file文件

makefile 中加入shell语句 if shell

参考

foo.bak: foo.bar
    echo "foo"
    if [ -d "~/Dropbox" ]; then echo "Dir exists"; fi
Or
foo.bak: foo.bar
    echo "foo"
    if [ -d "~/Dropbox" ]; then \
        echo "Dir exists"; \
    fi

Makefile中的wildcard用法 获取所有源文件

在Makefile规则中,通配符会被自动展开。但在变量的定义和函数引用时,通配符将失效。这种情况下如果需要通配符有效,就需要使用函数“wildcard”,它的用法是:$(wildcard PATTERN...) 。在Makefile中,它被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表。如果不存在任何符合此模式的文件,函数会忽略模式字符并返回空。需要注意的是:这种情况下规则中通配符的展开和上一小节匹配通配符的区别。

一般我们可以使用“$(wildcard *.c)”来获取工作目录下的所有的.c文件列表。复杂一些用法;可以使用“$(patsubst %.c,%.o,$(wildcard *.c))”,首先使用“wildcard”函数获取工作目录下的.c文件列表;之后将列表中所有文件名的后缀.c替换为.o。这样我们就可以得到在当前目录可生成的.o文件列表。因此在一个目录下可以使用如下内容的Makefile来将工作目录下的所有的.c文件进行编译并最后连接成为一个可执行文件:



#sample Makefile

objects := $(patsubst %.c,%.o,$(wildcard *.c))



foo : $(objects)

cc -o foo $(objects)

这里我们使用了make的隐含规则来编译.c的源文件。对变量的赋值也用到了一个特殊的符号(:=)。



1、wildcard : 扩展通配符
2、notdir : 去除路径
3、patsubst :替换通配符

例子:
建立一个测试目录,在测试目录下建立一个名为sub的子目录
$ mkdir test
$ cd test
$ mkdir sub

在test下,建立a.c和b.c2个文件,在sub目录下,建立sa.c和sb.c2 个文件

建立一个简单的Makefile
src=$(wildcard *.c ./sub/*.c)
dir=$(notdir $(src))
obj=$(patsubst %.c,%.o,$(dir) )

all:
@echo $(src)
@echo $(dir)
@echo $(obj)
@echo "end"

执行结果分析:
第一行输出:
a.c b.c ./sub/sa.c ./sub/sb.c

wildcard把 指定目录 ./ 和 ./sub/ 下的所有后缀是c的文件全部展开。

第二行输出:
a.c b.c sa.c sb.c
notdir把展开的文件去除掉路径信息

第三行输出:
a.o b.o sa.o sb.o

在$(patsubst %.c,%.o,$(dir) )中,patsubst把$(dir)中的变量符合后缀是.c的全部替换成.o,
任何输出。
或者可以使用
obj=$(dir:%.c=%.o)
效果也是一样的。

这里用到makefile里的替换引用规则,即用您指定的变量替换另一个变量。
它的标准格式是
$(var:a=b) 或 ${var:a=b}
它的含义是把变量var中的每一个值结尾用b替换掉a




今天在研究makefile时在网上看到一篇文章,介绍了使用函数wildcard得到指定目录下所有的C语言源程序文件名的方法,这下好了,不用手工一个一个指定需要编译的.c文件了,方法如下:

SRC = $(wildcard *.c)

等于指定编译当前目录下所有.c文件,如果还有子目录,比如子目录为inc,则再增加一个wildcard函数,象这样:

SRC = $(wildcard *.c) $(wildcard inc/*.c)

也可以指定汇编源程序:
ASRC = $(wildcard *.S) 

示例

# 变量定义 ( = or := )
# 其中 = 和 := 的区别在于, 
# := 只能使用前面定义好的变量, = 可以使用后面定义的变量
# +=变量追加值 SRCS += programD.c

# 工程名
# 定义变量PROJ为 challenge ,在后面 handin 中使用了这个变量,将其插入生成的压缩包名字中
# 可能是用来让同学改为学号等信息对提交的作业进行区分
PROJ	:= challenge
# 生成空格
EMPTY	:=
SPACE	:= $(EMPTY) $(EMPTY)
# 斜杠 /   反斜杠\ back slash
SLASH	:= /

# 后面没有使用上面的3个变量

# @放在行首,表示不打印此行信息, at符号
V       := @
# 显示信息
#V       :=
# 为了显示所有执行的命令,我一开始是把Line6的“V:=@”改为“V:=”,然后make。
# 不过后来在网上看到,可以通过执行make "V=" 来达到目的。
# make qemu "V="
#变量V=@,后面大量使用了V
#@的作用是不输出后面的命令,只输出结果
#在这里修改V即可调整输出的内容
#也可以 make "V=" 来完整输出

# 不输出警告信息
W:=
# 输出警告信息
# W:= -Wall
#为了不输出warning,我自己加的


# 编译器=========================

#need llvm/clang-3.5+
#USELLVM := 1
#若要使用LLVM则去掉前面一行的#即可
#LLVM 是LLVM基金会开发的编译器架构,Clang是其开发的C++,C,ObjectiveC,Ojc++编译器。

# 这里是在选择交叉编译器。
# try to infer the correct GCCPREFX
# 检查环境变量 GCCPREFIX 是否被设置(通常是没有的)
ifndef GCCPREFIX
# 如果没有被设置(if not define),那么判断运行环境,自行定义变量 GCCPREFIX
# grep 在文本信息中查找 
# 0 是一个文件描述符,表示标准输入(stdin) == keyboard 键盘输入,并返回在前端 =========
# 1 是一个文件描述符,表示标准输出(stdout)== monitor 正确返回值 输出到前端  ====
# 2 是一个文件描述符,表示标准错误(stderr)== monitor 错误返回值 输出到前端  ====
# >和>> 都是重定向输出=== > 会覆盖已有的文件内容,而 >> 会附加到已有内容之后====
# 1>   指 标准信息输出路径(也就是默认的输出方式)   "1>" 通常可以省略成 ">". 
# 2>   指 错误信息输出路径                      
# 2>&1 指将 标准错误 指定 为标准输出(错误合并到输出) &1 表示 标准输出通道
# 1>&2 正确返回值传递给 2输出通道 &2表示 输出错误通道 (正确合并到错误)
# 如果此处错写成 1>2, 就表示把1输出重定向到文件2中. 
# <和<<都是重定向输入===========
# <0指标准输入路径
# 4<&0 指的是将文件描述符4指定为标准输入(实际可选4到9之间任意一个数字)

GCCPREFIX := $(shell if i386-elf-objdump -i 2>&1 | grep '^elf32-i386$$' >/dev/null 2>&1; \
	then echo 'i386-elf-'; \
	elif objdump -i 2>&1 | grep 'elf32-i386' >/dev/null 2>&1; \
	then echo ''; \
	else echo "***" 1>&2; \
	echo "*** Error: Couldn't find an i386-elf version of GCC/binutils." 1>&2; \
	echo "*** Is the directory with i386-elf-gcc in your PATH?" 1>&2; \
	echo "*** If your i386-elf toolchain is installed with a command" 1>&2; \
	echo "*** prefix other than 'i386-elf-', set your GCCPREFIX" 1>&2; \
	echo "*** environment variable to that prefix and run 'make' again." 1>&2; \
	echo "*** To turn off this error, run 'gmake GCCPREFIX= ...'." 1>&2; \
	echo "***" 1>&2; exit 1; fi)
endif

# 硬件模拟环境====================
# QEMU是一款优秀的模拟处理器,使用方便,比virtualbox更适合进行实验。
# try to infer the correct QEMU
# 检查环境变量 QEMU 是否被设置(通常是没有的)
ifndef QEMU
# 如果没有被设置(if not define),那么自行定义变量 QEMU
# /dev/null 哑型设备  无用信息收集器 (不会打印信息,哑巴)
# which 查找 并 显示 给定 命令 的绝对路径
QEMU := $(shell if which qemu-system-i386 > /dev/null; \
	then echo 'qemu-system-i386'; exit; \
	elif which i386-elf-qemu > /dev/null; \
	then echo 'i386-elf-qemu'; exit; \
	elif which qemu > /dev/null; \
	then echo 'qemu'; exit; \
	else \
	echo "***" 1>&2; \
	echo "*** Error: Couldn't find a working QEMU executable." 1>&2; \
	echo "*** Is the directory containing the qemu binary in your PATH" 1>&2; \
	echo "***" 1>&2; exit 1; fi)
endif

# 使用伪目标.SUFFIXES 定义自己的后缀列表
# 当前makefile内 支持 文件后缀 的类型列表
# eliminate default suffix rules
.SUFFIXES: .c .S .h

# 如果编译出错,或者编译中断,删除已经生成的目标文件
# delete target files if there is an error (or make is interrupted)
.DELETE_ON_ERROR:

# 设置编译器选项
# define compiler and flags 编译器和编译选项
ifndef  USELLVM
# 未定义 USELLVM 则使用 gcc编译器
# /dev/null 哑型设备  无用信息收集器 (不会打印信息,哑巴)

# hostcc是给主机用的编译器,按照主机格式。

HOSTCC		:= gcc

 
# -g 是为了gdb能够对程序进行调试  GNU  debug
# -Wall 生成警告信息
# -O2 优化处理(0,1,2,3表示不同的优化程度,O0为不优化)
HOSTCFLAGS	:= -g -Wall -O2

# cc 是 i386、elf32 格式的编译器 (交叉编译器)。

CC		:= $(GCCPREFIX)gcc
 
# -fno-builtin 不接受非“__”开头的 内建函数 buildin function
# -Wall 生成警告信息
# -ggdb 让 gcc 为 gdb 生成 比较丰富 的 调试信息
# -m32  编译32位程序 
# -gstabs 此选项以 stabs 格式声称调试信息, 但是不包括gdb调试信息
# -nostdinc 不在 标准系统 目录中搜索头文件, 只在-I指定的目录中搜索   no standard include
# DEFS是未定义量。可用来对CFLAGS进行扩展。
CFLAGS	:= -fno-builtin -Wall -ggdb -m32 -gstabs -nostdinc  $(DEFS)


 
# 这句话的意思是,如果-fno-stack-protector(无堆栈保护??)选项存在,就添加它。过程蛮复杂的。
# -fstack-protector-all 启用 堆栈保护, 为所有函数 插入保护代码
# -E 仅作预处理,不进行编译、汇编和链接
# -x c 指明使用的语言为 c语言
# 前一个 /dev/null 用来指定 目标文件
# >/dev/null 2>&1 将标准输出与错误输出重定向到 /dev/null(哑型设备  无用信息收集器 (不会打印信息,哑巴))
# /dev/null是一个 垃圾桶 一样的东西
# ‘&&’之前的半句表示,试着对一个垃圾 跑一下这个命令,所有的 输出 都作为垃圾,为了快一点,开了-E。
# 如果不能运行,那么&&前面的条件不成立,后面的就被忽视。======================s
# 如果可以运行,那么&&后面的句子得到执行,于是 CFLAGS += -fno-stack-protector
CFLAGS	+= $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector)

else
# 如果定义了 USELLVM 则使用 clang编译器
# LLVM 是LLVM基金会开发的编译器架构,Clang是其开发的 C++,C,ObjectiveC, Ojc++编译器。

HOSTCC		:= clang
HOSTCFLAGS	:= -g -Wall -O2
CC		:= clang
CFLAGS	:= -fno-builtin -Wall -g -m32 -mno-sse -nostdinc $(DEFS)
CFLAGS	+= $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector)
endif

# 源文件类型为 c 和 S=======
# .c文件 .S汇编文件
CTYPE	:= c S

# 一些链接选项========
LD      := $(GCCPREFIX)ld
# #ld -V命令会输出连接器的版本与支持的模拟器。在其中搜索grep elf_i386, 错误信息到 哑型设备
# 若支持,则LDFLAGS := -m elf_i386
LDFLAGS	:= -m $(shell $(LD) -V | grep elf_i386 2>/dev/null)

# -nostdinc 不在 标准系统 目录中搜索头文件, no standard include
# 只把指定的文件传递给连接器
LDFLAGS	+= -nostdlib

# objcopy把一种目标文件中的内容复制到另一种类型的目标文件中. 
OBJCOPY := $(GCCPREFIX)objcopy
# objdump命令是Linux下的反汇编目标文件或者可执行文件的命令
OBJDUMP := $(GCCPREFIX)objdump
# shell 指令
COPY	:= cp#复制
MKDIR   := mkdir -p#创建文件夹  make directory
MV	:= mv      #移动文件    move 
RM	:= rm -f   #删除文件    remove 
AWK	:= awk     #逐行逐列处理      字符串解析
SED	:= sed     #sed常常一整行处理 
SH	:= sh      #bash解析命令      
TR	:= tr      #对来自标准输入的字符进行替换、压缩和删除
TOUCH	:= touch -c#-c 如果文件不存在,则不要进行创建

OBJDIR	:= obj# 目标地址目标
BINDIR	:= bin# 二进制文件地址

# 可执行文件
ALLOBJS	:=
# 依赖
ALLDEPS	:=
# 目标
TARGETS	:=

#包含另外一个Makefile文件
#function.mk中定义了大量的函数。
#.mk中每个函数都有注释。
include tools/function.mk

#call函数:call func,变量1,变量2,...
#listf函数在function.mk中定义,列出某地址(变量1)下某些类型(变量2)文件
#listf_cc函数即列出某地址(变量1)下.c与.S文件
listf_cc = $(call listf,$(1),$(CTYPE))

# for cc
# add_files:(#files, cc[, flags, packet, dir])
# add_files_cc:(#files, packet, flags, dir) flags已添加,这个变量仅用以扩展
# 添加文件到目标
add_files_cc = $(call add_files,$(1),$(CC),$(CFLAGS) $(3),$(2),$(4))
create_target_cc = $(call create_target,$(1),$(2),$(3),$(CC),$(CFLAGS))

# for hostcc
add_files_host = $(call add_files,$(1),$(HOSTCC),$(HOSTCFLAGS),$(2),$(3))
create_target_host = $(call create_target,$(1),$(2),$(3),$(HOSTCC),$(HOSTCFLAGS))


#patsubst替换通配符
#cgtype(filenames,type1,type2)
#把文件名中 后缀是 type 1的改为 type2, 如*.c改为*.o
cgtype = $(patsubst %.$(2),%.$(3),$(1))
 
# 列出所有.o文件
objfile = $(call toobj,$(1))
# .o改为.asm
asmfile = $(call cgtype,$(call toobj,$(1)),o,asm)
# .o改为.out
outfile = $(call cgtype,$(call toobj,$(1)),o,out)
# .o改为.sym
symfile = $(call cgtype,$(call toobj,$(1)),o,sym)

# for match pattern
match = $(shell echo $(2) | $(AWK) '{for(i=1;i<=NF;i++){if(match("$(1)","^"$$(i)"$$")){exit 1;}}}'; echo $$?)

# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# include kernel/user
#库文件 
# +=变量追加值
INCLUDE	+= libs/

CFLAGS	+= $(addprefix -I,$(INCLUDE))

LIBDIR	+= libs

$(call add_files_cc,$(call listf_cc,$(LIBDIR)),libs,)

# -------------------------------------------------------------------
# kernel  内核源文件
# 生成 bin/kernel
# 头文件
KINCLUDE	+= kern/debug/ \
		   kern/driver/ \
		   kern/trap/ \
		   kern/mm/
# 源文件
KSRCDIR		+= kern/init \
		   kern/libs \
		   kern/debug \
		   kern/driver \
		   kern/trap \
		   kern/mm

KCFLAGS		+= $(addprefix -I,$(KINCLUDE))

$(call add_files_cc,$(call listf_cc,$(KSRCDIR)),kernel,$(KCFLAGS))

#应为所有的编译后的目标文件路径都保存在__temp_packet中,则该函数直接引用,用来最后的链接工作
KOBJS	= $(call read_packet,kernel libs)

# create kernel target  系统内核目标文件 obj/kernel
kernel = $(call totarget,kernel)

# 最终的目标文件的规则
$(kernel): tools/kernel.ld
# 命令前缀 
# 前缀 @   :: 只输出命令执行的结果, 出错的话停止执行
# 前缀 -   :: 命令执行有错的话, 忽略错误, 继续执行
$(kernel): $(KOBJS)
	@echo + ld $@
# 链接 obj/libs/* 和 obj/kernel/init/* ... 所有的目标文件生成 elf-i386 的内核文件,并且使用kernel.ld链接器脚本
	$(V)$(LD) $(LDFLAGS) -T tools/kernel.ld -o $@ $(KOBJS)
# 最终的内核文件应该去除符号表等信息,并输出符号表信息,汇编文件信息,和输出信息
	@$(OBJDUMP) -S $@ > $(call asmfile,kernel)
	@$(OBJDUMP) -t $@ | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call symfile,kernel)

$(call create_target,kernel)

# -------------------------------------------------------------------

# create bootblock 引导区
# 生成 bin/bootblock
# 启动扇区的编译,过程与内核差不多唯一的区别是需要对编译后的启动扇区进行签名,即有效启动扇区,最后字节为0x55aa。
bootfiles = $(call listf_cc,boot)# boot 下的文件 
$(foreach f,$(bootfiles),$(call cc_compile,$(f),$(CC),$(CFLAGS) -Os -nostdinc))

bootblock = $(call totarget,bootblock)

$(bootblock): $(call toobj,$(bootfiles)) | $(call totarget,sign)
	@echo + ld $@
	$(V)$(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 $^ -o $(call toobj,bootblock)
	@$(OBJDUMP) -S $(call objfile,bootblock) > $(call asmfile,bootblock)
	@$(OBJCOPY) -S -O binary $(call objfile,bootblock) $(call outfile,bootblock)
	@$(call totarget,sign) $(call outfile,bootblock) $(bootblock)
# boot/bootasm.S  ----> bootasm.o
# boot/bootmain.c ----> bootmain.o
# bootasm.o + bootmain.o ------> bootblock
$(call create_target,bootblock)

# -------------------------------------------------------------------
# 生成 tools/sign.c ----> bin/sign
# create 'sign' tools 创建符合规定的硬盘引导扇区  bootblock.out + 0x55AA -> bootblock (512字节)
# 在内核工具目录中,sign.c, 用来给扇区签名的小工具, 为什么这而使用host呢,
# 是因为该工具是在特定操作系统下的工具,所以编译过程跟内核编译过程完全不同,
# 最显著的就是 nostdlibc 内核是必须的编译选项,
# 而应用软件一般都是依赖C库,并且内核代码为了精简,
# 也没有栈溢出保护 --no-stack-protector
$(call add_files_host,tools/sign.c,sign,sign)
$(call create_target_host,sign,sign)
# 生成 一个被系统认为是符合规范的硬盘主引导扇区
# 主引导区大小为512字节且最后两个字节为 0x55 和 0xAA,只要达到这两个条件即符合规范。
# buf[510] = 0x55
# buf[511] = 0xAA
 

# -------------------------------------------------------------------
# 生成 bin/ucore.img
#最后把编译出的二进制文件和bootloader都写进一个大文件中,用来模拟硬盘。使用linux下dd块命令
# create ucore.img  创建虚拟硬盘文件
# 引导扇区bootblock + 系统内核kernel -> ucore.img
UCOREIMG	:= $(call totarget,ucore.img)

$(UCOREIMG): $(kernel) $(bootblock)
	$(V)dd if=/dev/zero of=$@ count=10000
	$(V)dd if=$(bootblock) of=$@ conv=notrunc
	$(V)dd if=$(kernel) of=$@ seek=1 conv=notrunc

$(call create_target,ucore.img)

# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# 收尾工作/定义变量
$(call finish_all)

IGNORE_ALLDEPS	= clean \
		  dist-clean \
		  grade \
		  touch \
		  print-.+ \
		  handin

ifeq ($(call match,$(MAKECMDGOALS),$(IGNORE_ALLDEPS)),0)
-include $(ALLDEPS)
endif

# files for grade script
# 定义各种make目标
TARGETS: $(TARGETS)

.DEFAULT_GOAL := TARGETS

.PHONY: qemu qemu-nox debug debug-nox
#下面的很多命令是qemu的参数,其中一些网上不太好查,有兴趣的话可以去 http:https://wiki.qemu.org/Manual 查阅

#终端模式打开qemu   -monitor
qemu-mon: $(UCOREIMG)
	$(V)$(QEMU)  -no-reboot -monitor stdio -hda $< -serial null

#新窗口下打开qemu   -parallel
qemu: $(UCOREIMG)
	$(V)$(QEMU) -no-reboot -parallel stdio -hda $< -serial null

 
#运行并生成log文件
log: $(UCOREIMG)
	$(V)$(QEMU) -no-reboot -d int,cpu_reset  -D q.log -parallel stdio -hda $< -serial null

 
#禁止图形界面,转到终端
qemu-nox: $(UCOREIMG)
	$(V)$(QEMU)   -no-reboot -serial mon:stdio -hda $< -nographic
TERMINAL        :=gnome-terminal


#调试
# 利用make debug来观察BIOS的单步执行
# 首先是对qemu进行的操作
# sleep 2 等待一段时间
# 针对 gdbinit 文件进行的调试
debug: $(UCOREIMG)
	$(V)$(QEMU) -S -s -parallel stdio -hda $< -serial null &
	$(V)sleep 2
	$(V)$(TERMINAL) -e "gdb -q -tui -x tools/gdbinit"

#在终端打开qemu进行调试,现在终端会陷入死循环QAQ
debug-nox: $(UCOREIMG)
	$(V)$(QEMU) -S -s -serial mon:stdio -hda $< -nographic &
	$(V)sleep 2
	$(V)$(TERMINAL) -e "gdb -q -x tools/gdbinit"

.PHONY: grade touch

GRADE_GDB_IN	:= .gdb.in
GRADE_QEMU_OUT	:= .qemu.out
HANDIN			:= proj$(PROJ)-handin.tar.gz

TOUCH_FILES		:= kern/trap/trap.c

MAKEOPTS		:= --quiet --no-print-directory

grade:
	$(V)$(MAKE) $(MAKEOPTS) clean
	$(V)$(SH) tools/grade.sh

touch:
	$(V)$(foreach f,$(TOUCH_FILES),$(TOUCH) $(f))

print-%:
	@echo $($(shell echo $(patsubst print-%,%,$@) | $(TR) [a-z] [A-Z]))

.PHONY: clean dist-clean handin packall tags
# 清理
clean:
	$(V)$(RM) $(GRADE_GDB_IN) $(GRADE_QEMU_OUT) cscope* tags
	-$(RM) -r $(OBJDIR) $(BINDIR)
#把压缩包也删除
dist-clean: clean
	-$(RM) $(HANDIN)

#打包并输出一句话
handin: packall
	@echo Please visit http:https://learn.tsinghua.edu.cn and upload $(HANDIN). Thanks!

#打包
packall: clean
	@$(RM) -f $(HANDIN)
	@tar -czf $(HANDIN) `find . -type f -o -type d | grep -v '^\.*$$' | grep -vF '$(HANDIN)'`

#可能是输出所有tags,要cscope工具
tags:
	@echo TAGS ALL
	$(V)rm -f cscope.files cscope.in.out cscope.out cscope.po.out tags
	$(V)find . -type f -name "*.[chS]" >cscope.files
	$(V)cscope -bq 
	$(V)ctags -L cscope.files


通用makefile

#########################################################################
######################## Configuration Area #############################
CC = gcc
CXX = g++
OBJDUMP = objdump

INCLUDES +=
CFLAGS += -g
CXXFLAGS += -g
LDFLAGS +=
LIBS +=
DUMP_FLAGS = -D

TARGET_OUTPUT_DIR = bin
DUMP_OUTPUT_DIR = ${TARGET_OUTPUT_DIR}
#########################################################################

ifneq (${INCLUDES}, )
	CFLAGS += -I${INCLUDES}
	CXXFLAGS += -I${INCLUDES}
endif


C_SRC = $(wildcard *.c)
C_TARGET = $(patsubst %.c, ${TARGET_OUTPUT_DIR}/%, ${C_SRC})
C_DUMP= $(patsubst %.c, ${DUMP_OUTPUT_DIR}/%.dump, ${C_SRC})
CXX_SRC = $(wildcard *.cpp)
CXX_TARGET = $(patsubst %.cpp, ${TARGET_OUTPUT_DIR}/%, ${CXX_SRC})
CXX_DUMP= $(patsubst %.cpp, ${DUMP_OUTPUT_DIR}/%.dump, ${CXX_SRC})
TARGET = ${C_TARGET} ${CXX_TARGET}
DUMP = ${C_DUMP} ${CXX_DUMP}

all: ${TARGET}

dump: ${DUMP} ${TARGET}

clean:
	@rm -f ${TARGET} ${DUMP}

${C_TARGET}: ${TARGET_OUTPUT_DIR}/%: %.c
	@mkdir -p `dirname $@`
	${CC} -o $@ $< ${CFLAGS} ${LDFLAGS} ${LIBS}

${CXX_TARGET}: ${TARGET_OUTPUT_DIR}/%: %.cpp
	@mkdir -p `dirname $@`
	${CXX} -o $@ $< ${CXXFLAGS} ${LDFLAGS} ${LIBS}

${DUMP}: ${DUMP_OUTPUT_DIR}/%.dump: ${TARGET_OUTPUT_DIR}/%
	@mkdir -p `dirname $@`
	${OBJDUMP} ${DUMP_FLAGS} $< > $@