关于编译的一些事儿:从头开始整理一套编译框架(一)

前言

    但凡接触过Makefile的同学,我想陈皓的《跟我一起写Makefile》必不陌生,其概述中的一句话我印象比较深,“会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力”。这两年以来,我一直在做产品支撑方面(系统级项目)的开发和维护,无论是底层驱动(Boot/BSP/Kernel)还是上层应用,编译方面的事情也参与了不少,认识上也是大有长进。对于我所接触的项目而言,整个系统的编译甚至已经上升到一个工程的维度,要兼顾易使用、易维护、可扩展,尤其注重编译效率,已经远不止是编译出一两个简单进程文件而已了。年前,因为开发调试过几个系统子应用,稍有收获,于是想着可以尝试从编译子应用的角度去理解整个系统项目的编译工程框架,是以从头开始做一番整理。

Demo应用

    出于示例需要,该Demo只是对我的系统子应用的简单抽象,不具有实际意义。
    目录结构:
    关于编译的一些事儿:从头开始整理一套编译框架(一)_第1张图片
    源码内容:
  ./public/main.c                                                        ./public/public.h
  
  1 #include "public.h"                                               |  1 #ifndef __PUBLIC_H__
  2                                                                   |  2 #define __PUBLIC_H__
  3 int main()                                                        |  3 
  4 {                                                                 |  4 
  5     printf("hello, fun.\n");                                      |  5 #include "stdio.h"
  6                                                                   |  6 #include "lib.h"
  7     lib_func();                                                   |  7 #include "demo.h"
  8                                                                   |  8 
  9     demo_func();                                                  |  9 
 10                                                                   | 10 
 11     return 0;                                                     | 11 #endif
 12                                                                   | 12 
 13 }                                                                 | 13 
   ./lib/lib.c                                                        ./lib/lib.h
  
  1 #include "lib.h"                                                  |  1 #ifndef __LIB_H__
  2                                                                   |  2 #define __LIB_H__
  3 void lib_func()                                                   |  3 
  4 {                                                                 |  4 #include "public.h"
  5     printf("This is a lib func.\n");                              |  5     
  6                                                                   |  6 void lib_func();
  7     return;                                                       |  7     
  8 }                                                                 |  8 #endif
   ./demo/demo.c                                                        ./demo/demo.h
   
  1 #include "demo.h"                                                 |  1 #ifndef __DEMO_H__
  2                                                                   |  2 #define __DEMO_H__ 
  3 void demo_func()                                                  |  3 
  4 {                                                                 |  4 #include "public.h"
  5     printf("This is a demo func.\n");                             |  5 
  6                                                                   |  6 void demo_func();
  7     return;                                                       |  7 
  8 }                                                                 |  8 #endif

第一个Makefile

    先按最简单的目标,仅仅为了编译出ELF文件。
# ---------------------------------------------------------------------------
#                        
#                           Make for demo
# 
# ---------------------------------------------------------------------------
SUB_DIRS = public lib demo
ALL_SRCS = $(foreach dir, $(SUB_DIRS), $(wildcard $(dir)/*.c))
ALL_OBJS = $(ALL_SRCS:.c=.o)

INCLUDE = -I./public -I./lib -I./demo

CFLAGS = -g

.PHONY: all clean $(SUB_DIRS) 

all: demo_app

demo_app: $(ALL_OBJS)  
  @echo Build demo_prj start...
  gcc $(ALL_OBJS) -o $@
  @echo Done!!!

%.o: %.c
  @echo "Compiling $<..."
  gcc $(INCLUDE) $(CFLAGS) -c -o $@ $<

clean:
  @rm -rf (shell find ./ -name "*.o")
  @rm -rf demo_app 
  @echo Clean demo_prj over... 
    编译后的结果:
    关于编译的一些事儿:从头开始整理一套编译框架(一)_第2张图片
   
    这个Makefile的编写参照了网上的万能通用模版,简单实在就不注释了,支持并行编译(即make -j),对于编译简单的小程序绰绰有余了。但作为一个搞嵌入式软件的程序员,从实际应用来说,是存在一些小问题的,如下:
  1. 交叉编译工具链的设置不方便;(易用性)
  2. .o目标文件与源文件不分离;(易维护性)
  3. 随着源码目录结构的变动,每一次变化都需要重新调试修改该Makefile,费劲;(可扩展性)
    针对上面的前两个小问题,不涉及大动作,稍作改动,修改后的Makefile如下:
# ---------------------------------------------------------------------------
#                        
#                           Make for demo
#                       
# ---------------------------------------------------------------------------
# CROSS_COMPILE ?=  xxx-linux-gnu- 交叉编译链的默认设置,用“?=”是为了防止在make时,参数传入CROSS_COMPILE
CROSS_COMPILE ?=
CC = $(CROSS_COMPILE)gcc
CXX = $(CROSS_COMPILE)g++
AR = $(CROSS_COMPILE)ar
LD = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy

# ---------------------------------------------------------------------------
# Compiler dir_env define
# ---------------------------------------------------------------------------
TOP_DIR := $(shell pwd)
BUILD_DIR := $(TOP_DIR)/build
BIN_DIR := $(BUILD_DIR)/bin
OBJS_DIR := $(BUILD_DIR)/objs
# Attempt to create a output target directory. 创建obj目标文件及elf文件输出目录
$(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR} && mkdir -p ${OBJS_DIR} && mkdir -p ${BIN_DIR})

# ---------------------------------------------------------------------------
# OBJS include the necessary directories and the source files 
# ---------------------------------------------------------------------------
SUB_DIRS = public lib demo
ALL_SRCS = $(foreach dir, $(SUB_DIRS), $(wildcard $(dir)/*.c))
TMP_OBJS = $(notdir $(patsubst %.c, %.o, $(ALL_SRCS)))
ALL_OBJS = $(addprefix $(OBJS_DIR)/, $(TMP_OBJS))

INCLUDE = -I./public -I./lib -I./demo

CFLAGS = -g

.PHONY: all clean $(SUB_DIRS)

all: $(BIN_DIR)/demo_app

$(BIN_DIR)/demo_app: $(ALL_OBJS)
    @echo Build demo_prj start...
    $(CC) $(ALL_OBJS) -o $@
    @echo Done!!!

$(ALL_OBJS): $(ALL_SRCS)
    @echo "Compiling $(filter %$(*F).c, $(ALL_SRCS))..."
	$(CC) $(INCLUDE) $(CFLAGS) -c -o $@ $(filter %$(*F).c, $(ALL_SRCS))
	
    # $(filter %$(*F).c, $(ALL_SRCS))有点取巧的意味,这里从所有的.c源码文件过滤出对应.o的.c文件
	# 注意!!!这里有个非常大的弊端,因为目标文件依赖所有的.c源码文件,所以只要其中任一文件有更新,
	# 那么所有的.o都需要重编。这种编译行为,对于大型的编译工程而言是不可接受的!!!
	

clean:
    @rm -rf $(shell find ./ -name "*.o")
    @rm -rf $(BIN_DIR)/demo_app
    @echo clean demo_prj over... 
    编译后的结果:
    关于编译的一些事儿:从头开始整理一套编译框架(一)_第3张图片
   
    以上,只是为了编译出Demo的ELF文件而写的首个Makefile,单纯从结果导向而言,目的达到。但是,随着Demo功能需求的开发,编译工作随之也会遇到很多问题。这样,我们就需要对Makefile做出相应调整了,最重要的一点即是从可扩展的角度出发,这个下一篇整理。




 





你可能感兴趣的:(关于编译的一些事儿:从头开始整理一套编译框架(一))