第一次寫Makefile就...放棄(誤)

太久沒寫Makefile,寫一篇廢文把複習過程記錄下來

目標

  1. obj 資料夾要保持 src 的結構
  2. 每個 source code 要編譯成獨立的 object 檔案

過程

實作之前

Makefile 基礎語法為:

1
2
tagret: prerequires
command

用樹的 Postorder Traversal 來理解就是,每個 target 都是 node,prerequires 則是要往下拜訪的 node,command 則是拜訪 child node 完之後要執行的動作,例如:

1
2
3
4
5
all: compile
gcc -o main main.o helloworld.o
compile:
gcc -c helloworld.c
gcc -c main.c

執行 make 指令後,makefile 在沒有指定的情況下,預設從第一個 target 作展開。

  1. all 底下還有 compile 節點,往下拜訪 compile

  2. compile 底下沒有節點,執行 compile 裡的 command。

    1
    2
    gcc -c helloworld.c
    gcc -c main.c
  3. 底下的節點都拜訪並執行完 command 了,回到上一層節點 all ,執行 command

    1
    gcc -o main main.o helloworld.o
  4. 輸出大概長這樣

    1
    2
    3
    gcc -c helloworld.c
    gcc -c main.c
    gcc -o main main.o helloworld.o

其他語法

因為要展開整個專案底下的 source code,編譯階段如果是手動加入每個檔案不如直接寫腳本,還有寫死參數會造成日後維護困難,所以要用上其他語法

  1. 變數

    指定編譯器,之後要替換可以不用一個一個改,也不用擔心漏改或改錯等問題

    1
    COMPILER := gcc
  2. 字串操作

    1. 列出 src 資料夾以下所有 *.c 檔案

      執行 shell find 來尋找檔案

      1
      2
      3
      SRCDIR := src
      SRCEXT := c
      SOURCES := $(shell find $(SRCDIR) -type f -name *.$(SRCEXT))
    2. 替換 *.c 檔案的路徑作為 *.o 檔案的路徑

      1. 將含 *.c 結尾的字串都轉換成 *.o 結尾

        1
        $(SOURCES:.$(SRCEXT)=.$(OBJEXT)
      2. 替換資料夾

        語法為 $(patsubst pattern,replacement,text),將 test 中符合 pattern 的字串用 replacement 取代。下列語法的意思是將 $(SOURCES:.$(SRCEXT)=.$(OBJEXT) 內含 src 的字串都替換成 obj

        1
        2
        3
        4
        5
        SRCDIR := src
        BUILDDIR := obj
        SRCEXT := c
        OBJEXT := o
        OBJECTS := $(patsubst $(SRCDIR)/%, $(BUILDDIR)/%, $(SOURCES:.$(SRCEXT)=.$(OBJEXT)))

整合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#Compiler and Linker
CC := gcc

#The Target Binary Program
TARGET := main

#The Directories, Source, Includes, Objects, Binary and Resources
SRCDIR := src
BUILDDIR := obj
TARGETDIR := bin
SRCEXT := c
OBJEXT := o

#Flags, Libraries and Includes
CFLAGS := -Wall -O3 -g
LIB :=

#---------------------------------------------------------------------------------
#DO NOT EDIT BELOW THIS LINE
#---------------------------------------------------------------------------------
SOURCES := $(shell find $(SRCDIR) -type f -name *.$(SRCEXT))
OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))

#Defauilt Make
all: directories $(TARGET)

directories:
@mkdir -p $(TARGETDIR)
@mkdir -p $(BUILDDIR)

clean:
@$(RM) -rf $(TARGETDIR)
@$(RM) -rf $(BUILDDIR)

#Link
$(TARGET): $(OBJECTS)
$(CC) -o $(TARGETDIR)/$(TARGET) $^ $(LIB)

#Compile
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
# 建立對應的資料夾
@mkdir -p $(dir $@)
# 編譯為 object 檔案
$(CC) $(CFLAGS) -c $< -o $@

#Non-File Targets
.PHONY: all clean

參考連結

  1. How to place object files in separate subdirectory