2010年12月9日 星期四

讓 VIM 記住上次游標的位置

如果安裝的vim每次開檔案的時候,游標都不在上次編輯的位置,那請編輯檔案 /etc/vim/vimrc 。

將下面這幾行前的註解拿掉
" Uncomment the following to have Vim jump to the last position when
" reopening a file
if has("autocmd")
au BufReadPost * if line("'\"") > 1 && line("'\"") <= line("$") | exe "normal! g'\"" | endif
endif

2010年12月4日 星期六

GNU Makefile 入門

常使用 linux/unix 平台來發展軟體的程式撰寫者常常需要使用 Makefile 來管理和編輯程式碼,在這邊就簡單介紹一下。

會用到Makefile通常就是程式碼越寫越大,檔案數越來越多,為了編譯方便而使用 Makefile 來管理。下面的例子先從沒有Makefile的方法來寫程式,然後開始使用。

開始發展程式內容

先寫一個C程式 test.c 如下
// The content of test.c
#include <stdio.h>
int main(int argc,char **argv)
{
printf( Hello World!\n );
return 0;
}

// The content of a.c
void a(void)
{
printf( Hi there, it s a good day.\n );
}
怎麼編譯?
一般使用cc compiler 的使用方法如下:
cc {CFLAGS} -c -o {cfile.o} {cfile.c}
cc -o {executive_file} {objfile1.o} {objfile2.o}
按照 cc compiler 的使用發法,編譯test.c 的方法和執行結果如下:
[~/code]# cc -c -o test.o test.c

[~/code]# cc -o test test.o
[~/code]# ./test
Hello World!

第一個 makefile

加入檔案Makefile內容如下,在這邊特別注意一下,指令前方要用Tab來空格
# Comment # , This is a Makefile
# Notice that the space before cc must be a TAB, NOT four spaces !!
test:
[TAB]cc -o test test.o
目前我們已經有兩個檔案 test.c 和 Makefile了,在命令列直接打make指令,就會參考Makefile內容來編譯test.c ,執行內容如下:
[~/code]# make

cc -c -o test1.o test1.c
cc -o test1 test1.o
指令 make 會先找目前目錄底下的 Makefile,然後找 makefile ,否則會顯示以下錯誤訊息。
make: *** No targets specified and no makefile found. Stop.
你也可以指定makefile檔案名稱,請依照以下方法:
make -f {Your Makefile Name}
指令 make 後面可以指定規則 rule ,規則指的是 makefile 裡的執行項目。
make rule1 rule2

規則初探 (Rule)

規則 (rule) 的格式寫在 makefile 裡頭,格式如下,注意所有指令前方都要用一個tab來空格:

RuleName: Dependent_Rule1 Dependent_Rule2
[TAB]Your_Command_Here

假設我們寫一個 makefile 內容如下,包含兩個規則,分別只印出一則訊息。
rule1:
    @echo "Hello Rule1"
rule2: rule1
    @echo "Hello Rule2"
執行結果
[~/code]# make
Hello Rule1
[~/code]# make rule2
Hello Rule1
Hello Rule2
參照結果,你會發現 (1) 如果 make 指令不指定規則,那就會找檔案開始的第一個規則,(2) 執行目標規則之前,會先執行相依的規則。所以 make rule1 就只先執行 rule1的命令,而make rule2 則會先跑 rule1的指令再跑 rule2。

使用變數

為了方便起見,makefile裡頭也可以使用變數,在 makefile裡頭變數是字串的集合,字串之間用空白分隔。因此變數可以擁有零個、一個到多個字串。

VARIABLE = STR1 STR2 STR3

定義一個變數CC,並在規則中使用。
# It s a Makefile. Define and use variable.
CC = gcc
# First Rule
# Second Rule with one dependency
test.o:
    $(CC) -c -o test.o test.c
test: test.o
    $(CC) -o test test.o
執行結果
[~/code] # make test
gcc -c -o test.o test.c
gcc -o test test.o
[~/code] # make test.o
gcc -c -o test.o test.c
[~/code] # make
gcc -c -o test.o test.c

make test 執行了第二個規則(test)前,先執行相依規則(test.o)。當然我們也可以個別執行每個規則。

這邊我們注意到變數的使用是在變數名稱前加上錢字號 $,但是因為變數名稱是多個字母組合在一起,所以使用時用左右括號把名稱包起來。如果沒用左右括號則 $ 號後只會把第一個字母認作變數名稱。如下面範例所示,$AB 其實就是把變數A改成內容字串a加上後面的B,輸出的結果變成aB。
# 變數使用範例
A = a
AB = ab
all:
    @echo "$AB"
    @echo "$(AB)"
執行結果
[~/code] # make
aB
ab
makefile中也有一些用在規則內的自動變數,我列出三個比較常用的,等等會有範例展示用法。

$@ 表示目前的規則名稱
$< 表示第一個相依規則名稱
$^ 表示所有的相依規則名稱


萬用字元(wildcard)

makefile 中所用的萬用字元是 % ,代表所有可能的字串,前後可以接指定的字串來表示某些固定樣式的字串。例如%.c 表示結尾是 .c 的所有字串。因此我們改寫 makefile 如下
# Using pattern
CC = gcc
OBJS = a.o b.o c.o

all: test

%.o: %.c
    $(CC) -c -o $@ $<

test: $(OBJS)
    $(CC) -o test $^
當我們執行make,執行all然後是test,相依的規則是OBJS變數指定的字串 a.o b.o c.o,因此會尋找 a.o b.o c.o 這三個規則去執行,而 %.o 這個規則符合需求。執行結果如下:
[~/code] # make
gcc -c -o a.o a.c
gcc -c -o b.o b.c
gcc -c -o c.o c.c
gcc -o test a.o b.o c.o
這個例子中剛好也使用到自動變數 $@ $< $^ ,請參考前面的定義。

內建變數和規則(Implicit Variables and Rules)

因為makefile是為了幫助我們編譯程式,所以有些制式變數和規則就直接內建了,我們不需要在makefile裡面言明。如果想要覆蓋原本的定義,那麼在makefile中用相同的名稱來定義自己的變數和規則就可以了。
# 一個 implicit rule 例子
%.o:%.c
    $(CC) $(CFLAGS) -c -o $@ $<

# 一些內建變數
AR = ar
AS = as
CC = cc
CXX = g++
RM = rm -f
ARFLAGS = rv
CFLAGS =
CXXFLAGS =

使用函數(Functions)

當然makefile也提供了一些function可以使用,這邊提供幾個常用的。
wildcard 使用方法如下,注意的是wildcard裡面的參數就跟我們在命令列用的一樣,用*.c 表示所有.c結尾的檔名。請看下面範例,filelist1 等於所有 .c 結尾的檔名,例如 "a.c b.c c.c",但是變數 filelist2 中,實際表示的是字串 "*.c" ,
# function $(wildcard *.c)
# filelist1 would be "test1.c test2.c test3.c ...
# List all .c files in current directory
filelist1 = $(wildcard *.c)
# filelist2 would be "*.c"
filelist2 = *.c

Function patsubst 字串名稱取代
# Find matched strings in variable “filelist1” and change them into strings end with “.o”
# objsfilelist1 would be “test1.o test2.o test3.o …”
objsfilelist1 := $(patsubst %.c,%.o,$(filelist1))
# This statement is equal to the previous one
objsfilelist2 := $(filelist1:%.c=%.o)

Function subst 一樣是取代
# Set VPATH as our included paths. All paths are sperated by “;”
VPATH_STR = /mips/uclib.mips;/mips/ffmpeg/include;~/

# Find “;” in VPATH_STR and change them into “ “
# VPATH would be “/mips/uclib.mips /mips/ffmpeg/include ~/”
VPATH = $(subst ;, ,$(VPATH_STR))

# INCLUDES would be “-I/mips/uclib.mips -I/mips/ffmpeg/include -I~/”
INCLUDES= $(patsubst %,-I%, $(VPATH))

Example

觀摩範例是學習最好的方法,這邊是一個Makefile範例。

寫一個 Makefile 編譯目錄中所有 .c 檔,並且針對編譯出的.o檔 objdump(輸出反組譯資訊)。
# Makefile example. Compile all .c files and build execution file
QUIET = @
ECHO = echo
CC = gcc
RM = rm -rf
OBJDUMP= objdump -S –l

CFLAGS = -Os
LDFLAGS = -lm
TARGET = a.out
OBJS = $(patsubst %.c,%.o,$(wildcard *.c))
OBJSDUMP_LOG= $(patsubst %.c,%.objdump,$(wildcard *.c))

all: info $(TARGET)

$(TARGET): $(OBJS) $(OBJSDUMP_LOG)
    $(QUIET)$(ECHO) "Building " $@
    $(QUIET)$(CC) -o $@ $(OBJS) $(LDFLAGS)

%.o: %.c
    $(QUIET)$(ECHO) "Compliling " $<
    $(QUIET)$(CC) $(CFLAGS) -c $< -o $@

%.objdump: %.o
    $(QUIET)$(ECHO) "Objdumping " $<
    $(QUIET)$(OBJDUMP) $< > $@

info:
    $(QUIET)$(ECHO) "CC=$(CC)"
    $(QUIET)$(ECHO) "INFO: CFLAGS=$(CFLAGS)"
    $(QUIET)$(ECHO) "LDFLAGS=$(LDFLAGS)"

clean:
    $(QUIET)$(RM) $(OBJS) $(TARGET)
執行結果
[~/code] make
CC=gcc
INFO: CFLAGS=-Os
LDFLAGS=-lm
Compliling main.c
Compliling test1.c
Compliling test2.c
Objdumping main.o
Objdumping test1.o
Objdumping test2.o
Building a.out

參考資料
gnu make