mekefile 編寫
參考
Linux下使用 autoconf和automake 自動構建 項目 make file文件
makefile 中加入shell語句 if shell
參考
foo.bak: foo.barecho "foo"if [ -d "~/Dropbox" ]; then echo "Dir exists"; fi
Or
foo.bak: foo.barecho "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 Makefileobjects := $(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.cwildcard把 指定目錄 ./ 和 ./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/mmKCFLAGS += $(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-.+ \handinifeq ($(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://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 touchGRADE_GDB_IN := .gdb.in
GRADE_QEMU_OUT := .qemu.out
HANDIN := proj$(PROJ)-handin.tar.gzTOUCH_FILES := kern/trap/trap.cMAKEOPTS := --quiet --no-print-directorygrade:$(V)$(MAKE) $(MAKEOPTS) clean$(V)$(SH) tools/grade.shtouch:$(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://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 = objdumpINCLUDES +=
CFLAGS += -g
CXXFLAGS += -g
LDFLAGS +=
LIBS +=
DUMP_FLAGS = -DTARGET_OUTPUT_DIR = bin
DUMP_OUTPUT_DIR = ${TARGET_OUTPUT_DIR}
#########################################################################ifneq (${INCLUDES}, )CFLAGS += -I${INCLUDES}CXXFLAGS += -I${INCLUDES}
endifC_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} $< > $@