2016-08-18 82 views
5

我想用GNU make工具爲我的微控制器構建一個C程序。我想以一種乾淨的方式做到這一點,這樣我的源代碼就不會在構建之後與目標文件和其他東西混雜在一起。所以,想象一下,我有一個項目文件夾中,它被稱爲「myProject的」兩個文件夾:使用GNU make編譯「源代碼樹」C程序

- myProject 
    | 
    |---+ source 
    | 
    '---+ build 

build文件夾中只包含一個makefile。下圖顯示了當我運行GNU make工具應該發生什麼:

enter image description here

所以GNU化妝應爲每個.c的源代碼創建一個對象文件,文件,它可以在源文件夾中找到。目標文件應該構建在與源文件夾中的結構相似的目錄樹中。

對於每個.c源文件,GNU make還應該生成一個.d依賴文件(實際上,一個依賴文件是某種makefile本身)。依賴文件是在GNU描述進行手動章節4.14「生成自動的先決條件」:

對於每一個源文件name.c有一個makefile name.d列出 哪些文件,目標文件name.o取決於。

從下面的問題#1 About the GNU make dependency files *.d,我瞭解到,添加的選項-MMD-MP到GNU gcc編譯器的CFLAGS可以幫助自動執行。

所以現在出現了這個問題。有沒有人有一個執行這種源代碼構建的示例生成文件?或者有關如何開始的一些很好的建議?

我很肯定,大多數寫過這樣的makefile的人都是Linux人。但是微控制器項目也應該在Windows機器上構建。無論如何,即使你的makefile是純Linux,它提供了一個很好的起點;-)

PS:我想避免像CMake,Autotools或任何與IDE有關的額外工具。只是純粹的GNU製造。

我將不勝感激:-)


更新相關文件
請看看這個問題:What is the exact chain of events when GNU make updates the .d files?

+1

你在問一個非常簡單的makefile,所以如果有人在這裏給你的話,我不會感到驚訝。但通常我們提供免費的幫助,而不是免費的代碼。 – Beta

+0

是的你是對的。 –

+0

這種簡單的Makefile實際上可以很好地匹配新的文檔功能,但對於Q/A而言,它有點太寬泛。 – Tim

回答

10

下面是我添加到文檔的Makefile(正在審覈,所以我會在這裏發佈):

# Set project directory one level above the Makefile directory. $(CURDIR) is a GNU make variable containing the path to the current working directory 
PROJDIR := $(realpath $(CURDIR)/..) 
SOURCEDIR := $(PROJDIR)/Sources 
BUILDDIR := $(PROJDIR)/Build 

# Name of the final executable 
TARGET = myApp.exe 

# Decide whether the commands will be shown or not 
VERBOSE = TRUE 

# Create the list of directories 
DIRS = Folder0 Folder1 Folder2 
SOURCEDIRS = $(foreach dir, $(DIRS), $(addprefix $(SOURCEDIR)/, $(dir))) 
TARGETDIRS = $(foreach dir, $(DIRS), $(addprefix $(BUILDDIR)/, $(dir))) 

# Generate the GCC includes parameters by adding -I before each source folder 
INCLUDES = $(foreach dir, $(SOURCEDIRS), $(addprefix -I, $(dir))) 

# Add this list to VPATH, the place make will look for the source files 
VPATH = $(SOURCEDIRS) 

# Create a list of *.c sources in DIRS 
SOURCES = $(foreach dir,$(SOURCEDIRS),$(wildcard $(dir)/*.c)) 

# Define objects for all sources 
OBJS := $(subst $(SOURCEDIR),$(BUILDDIR),$(SOURCES:.c=.o)) 

# Define dependencies files for all objects 
DEPS = $(OBJS:.o=.d) 

# Name the compiler 
CC = gcc 

# OS specific part 
ifeq ($(OS),Windows_NT) 
    RM = del /F /Q 
    RMDIR = -RMDIR /S /Q 
    MKDIR = -mkdir 
    ERRIGNORE = 2>NUL || true 
    SEP=\\ 
else 
    RM = rm -rf 
    RMDIR = rm -rf 
    MKDIR = mkdir -p 
    ERRIGNORE = 2>/dev/null 
    SEP=/ 
endif 

# Remove space after separator 
PSEP = $(strip $(SEP)) 

# Hide or not the calls depending of VERBOSE 
ifeq ($(VERBOSE),TRUE) 
    HIDE = 
else 
    HIDE = @ 
endif 

# Define the function that will generate each rule 
define generateRules 
$(1)/%.o: %.c 
    @echo Building [email protected] 
    $(HIDE)$(CC) -c $$(INCLUDES) -o $$(subst /,$$(PSEP),[email protected]) $$(subst /,$$(PSEP),$$<) -MMD 
endef 

# Indicate to make which targets are not files 
.PHONY: all clean directories 

all: directories $(TARGET) 

$(TARGET): $(OBJS) 
    $(HIDE)echo Linking [email protected] 
    $(HIDE)$(CC) $(OBJS) -o $(TARGET) 

# Include dependencies 
-include $(DEPS) 

# Generate rules 
$(foreach targetdir, $(TARGETDIRS), $(eval $(call generateRules, $(targetdir)))) 

directories: 
    $(HIDE)$(MKDIR) $(subst /,$(PSEP),$(TARGETDIRS)) $(ERRIGNORE) 

# Remove all objects, dependencies and executable files generated during the build 
clean: 
    $(HIDE)$(RMDIR) $(subst /,$(PSEP),$(TARGETDIRS)) $(ERRIGNORE) 
    $(HIDE)$(RM) $(TARGET) $(ERRIGNORE) 
    @echo Cleaning done ! 

主要特點

  • 在指定的文件夾
  • 多個源文件夾
  • 多個對應的目標文件夾對象和依賴文件的C源自動檢測
  • 自動生成規則爲每個目標文件夾
  • 目標文件夾不存在時創建
  • 依賴管理與gcc:只建什麼是必要的
  • 文選UnixDOS系統
  • 而立GNU Make

如何使用這個Makefile

適應這個Makefile對你的項目你必須:

  1. 更改TARGET變量在SOURCEDIRSourcesBuild文件夾的名稱相匹配的目標名稱
  2. 變化BUILDDIR
  3. 變化make all VERBOSE=FALSE Makefile文件在Makefile本身或發出呼叫的詳細級別( )
  4. 更改DIRS中文件夾的名稱以匹配您的源代碼和生成文件夾
  5. 如果需要,更改編譯器和標記

在這個Makefile Folder0Folder1Folder2是等同於你的FolderAFolderBFolderC

請注意,我目前還沒有機會在Unix系統上測試它,但它在Windows上正常工作。


說明了幾個棘手的部分:

忽略的Windows的mkdir錯誤

ERRIGNORE = 2>NUL || true 

這有兩種作用: 第一個,2>NUL是重定向錯誤輸出NUL,所以它不在控制檯中。

第二個,|| true可防止命令上升錯誤級別。這是與Makefile無關的Windows東西,因爲Windows'mkdir命令會提高錯誤級別,如果我們嘗試創建已存在的文件夾,而我們並不在意它是否存在,那很好。常見的解決方案是使用if not exist結構,但這不是UNIX兼容的,所以即使它很棘手,我也認爲我的解決方案更清晰。含有它們的正確路徑中的所有目標文件OBJS的


創作

OBJS := $(subst $(SOURCEDIR),$(BUILDDIR),$(SOURCES:.c=.o)) 

在這裏我們要OBJS包含與他們的系統的全部目標文件,而且我們已經有了源是包含了所有的源文件及其路徑。 $(SOURCES:.c=.o)對所有來源的* .o都進行* .c更改,但路徑仍是其中一個來源。 $(subst $(SOURCEDIR),$(BUILDDIR), ...)將簡單地用構建路徑減去整個源路徑,所以我們最終有一個包含.o文件及其路徑的變量。


與Windows和Unix風格路徑分隔符

SEP=\\ 
SEP =/
PSEP = $(strip $(SEP)) 

本交易的存在只是爲了讓Makefile文件在Unix和Windows的工作,自Windows路徑使用反斜槓,而其他人使用斜線, 。

SEP=\\此處使用雙反斜槓來轉義反斜槓字符,make通常將其視爲「忽略換行字符」以允許在多行上書寫。

PSEP = $(strip $(SEP))這將刪除已自動添加的SEP變量的空格字符。


自動生成的規則,每個目標文件夾

define generateRules 
$(1)/%.o: %.c 
    @echo Building [email protected] 
    $(HIDE)$(CC) -c $$(INCLUDES) -o $$(subst /,$$(PSEP),[email protected]) $$(subst /,$$(PSEP),$$<) -MMD 
endef 

這也許與您的用例中最相關的伎倆。這是一個可以使用$(eval $(call generateRules, param))生成的規則模板,其中param是您在模板中可以找到的$(1)。 這基本符合這樣的規則對每個目標文件夾填滿的Makefile:

path/to/target/%.o: %.c 
    @echo Building [email protected] 
    $(HIDE)$(CC) -c $(INCLUDES) -o $(subst /,$(PSEP),[email protected]) $(subst /,$(PSEP),$<) -MMD 
+0

非常感謝Tim!你的makefile文件非常優雅並且有很好的文檔。你的回覆對我來說是一個很大的幫助。我會在今晚測試它。謝謝,謝謝你,謝謝你:-) –

+0

我很高興你喜歡它,其實我很高興做到這一點。這有點具有挑戰性,它與Makefiles的第三條規則有關:「如果目標是建立在當前工作目錄中,則生活是最簡單的。」但誰想要簡單的生活? ;) – Tim

+0

它看起來不錯!我在答案的末尾添加了解釋,以便理解這個Makefile。對於參考,你可以簡單地鏈接到這個stackoverflow Q/A,這對我來說很好。順便說一下,我使用的大多數技巧都是在其他站點上找到的,或者在其他站點上找到。 – Tim

2

這還算簡單的Makefile應該做的伎倆:

VPATH = ../source 
OBJS = FolderA/fileA1.o FolderA/fileA2.o FolderB/fileB1.o 
CPPFLAGS = -MMD -MP 

all: init myProgram 

myProgram: $(OBJS) 
     $(CC) $(LDFLAGS) -o [email protected] $(OBJS) $(LDLIBS) 

.PHONY: all init 

init: 
     mkdir -p FolderA 
     mkdir -p FolderB 

-include $(OBJS:%.o=%.d) 

主要棘手的部分是確保臨時在嘗試運行將寫入它們的編譯器之前,在構建目錄中存在t FolderAFolderB。上面的代碼可以順序執行構建,但可能會在第一次運行時失敗,因爲一個線程中的編譯器可能會在另一個線程創建目錄之前嘗試打開輸出文件。它也有些不潔。通常在GNU工具中,你有一個配置腳本,它會在你嘗試運行make之前爲你創建這些目錄(和makefile)。autoconf和automake可以爲你構建。

應並行工作的另一種方法是建立一個將重新定義標準的規則編譯C文件:

VPATH = ../source 
OBJS = FolderA/fileA1.o FolderA/fileA2.o FolderB/fileB1.o 
CPPFLAGS = -MMD -MP 

myProgram: $(OBJS) 
     $(CC) $(LDFLAGS) -o [email protected] $(OBJS) $(LDLIBS) 

%.o: %.c 
     mkdir -p $(dir [email protected]) 
     $(CC) $(CFLAGS) $(CPPFLAGS) -c -o [email protected] $< 

-include $(OBJS:%.o=%.d) 

其中有缺點,你還需要重新定義內建規則,任何其他有種的資源文件要編譯

+0

Waw,非常感謝!你說它在第一次運行時不會和'-j2'一起工作。這個選項是並行化的構建,對吧?我認爲它不會在並行構建中工作,因爲在創建對象文件時文件夾FolderA和FolderB可能不存在。我對麼? –

+0

'VPATH'如何處理多個具有相同名稱的源文件? –

+1

@MaximEgorushkin:不能在同一個目錄中具有相同名稱的多個文件。子目錄(源代碼下)是名稱的一部分,因此不同子目錄中的相同基本文件名稱可以。對象和依賴項文件將具有相同的名稱(包括子目錄名稱),其擴展名已更改。所以你最終在'build'下的目錄結構與'source'下的一樣。 –

1

這裏有一個基本的我用所有的時間,這幾乎是一個骨架,因爲它是,但適用於簡單的項目完美的罰款。對於更復雜的項目,它肯定需要進行調整,但我始終以此爲出發點。

APP=app 

SRC_DIR=src 
INC_DIR=inc 
OBJ_DIR=obj 
BIN_DIR=bin 

CC=gcc 
LD=gcc 
CFLAGS=-O2 -c -Wall -pedantic -ansi 
LFLGAS= 
DFLAGS=-g3 -O0 -DDEBUG 
INCFLAGS=-I$(INC_DIR) 

SOURCES=$(wildcard $(SRC_DIR)/*.c) 
HEADERS=$(wildcard $(INC_DIR)/*.h) 
OBJECTS=$(SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o) 
DEPENDS=$(OBJ_DIR)/.depends 


.PHONY: all 
all: $(BIN_DIR)/$(APP) 

.PHONY: debug 
debug: CFLAGS+=$(DFLAGS) 
debug: all 


$(BIN_DIR)/$(APP): $(OBJECTS) | $(BIN_DIR) 
    $(LD) $(LFLGAS) -o [email protected] $^ 

$(OBJ_DIR)/%.o: | $(OBJ_DIR) 
    $(CC) $(CFLAGS) $(INCFLAGS) -o [email protected] $< 

$(DEPENDS): $(SOURCES) | $(OBJ_DIR) 
    $(CC) $(INCFLAGS) -MM $(SOURCES) | sed -e 's!^!$(OBJ_DIR)/!' >[email protected] 

ifneq ($(MAKECMDGOALS),clean) 
-include $(DEPENDS) 
endif 


$(BIN_DIR): 
    mkdir -p [email protected] 
$(OBJ_DIR): 
    mkdir -p [email protected] 

.PHONY: clean 
clean: 
    rm -rf $(BIN_DIR) $(OBJ_DIR) 
+0

Waw,非常感謝你分享這個文件。 –

-2

我會避免直接操作Makefile,而是使用CMake代替。 只需在CMakeLists.txt中描述源文件,如下所示:

創建文件MyProject/source/CMakeLists.txt contains;

project(myProject) 
add_executable(myExec FolderA/fileA1.c FolderA/fileA2.c FolderB/fileB1.c) 

在MyProject的/建造,運行

cmake ../source/ 

你會得到一個Makefile了。要建立,同樣構建/目錄下,

make 

你也可以切換到一個快如閃電的構建工具,忍者,只需增加一個開關如下。

cmake -GNinja .. 
ninja 
+1

歡迎來到Stack Overflow!這真是一個評論,而不是一個答案。有了更多的代表,[你將能夠發表評論](// stackoverflow.com/privileges/comment)。 – manetsus

+0

@manetsus感謝您的評論。我剛剛修改了我的答案。 – exavolt