当前位置: 首页 > news >正文

OPenWrt编译文件解读makefile

当执行make V=99时调用得时MakeFile。那么MakeFile的作用是什么呢?

1.简化程序的编译过程,尽量少做重复劳动

2.组织管理源代码

学习主要目的是想通过分析MakeFile,了解openwrt编译过程。着重关注以下几点:

  1. openwrt目录结构
  2. 主Makefile的解析过程,各子目录的目标生成。
  3. kernel编译过程
  4. firmware的生成过程
  5. 软件包的编译过程

openwrt目录结构

上图是openwrt目录结构,其中第一行是原始目录,第二行是编译过程中生成的目录。各目录的作用是:

  • tools - 编译时需要一些工具, tools里包含了获取和编译这些工具的命令。里面是一些Makefile,有的可能还有patch。每个Makefile里都有一句 $(eval $(call HostBuild)),表示编译这个工具是为了在主机上使用的。
  • toolchain - 包含一些命令去获取kernel headers, C library, bin-utils, compiler, debugger
  • target - 各平台在这个目录里定义了firmware和kernel的编译过程。
  • package - 包含针对各个软件包的Makefile。openwrt定义了一套Makefile模板,各软件参照这个模板定义了自己的信息,如软件包的版本、下载地址、编译方式、安装地址等。
  • include - openwrt的Makefile都存放在这里。
  • scripts - 一些perl脚本,用于软件包管理。

  • dl - 软件包下载后都放到这个目录里
  • build_dir - 软件包都解压到build_dir/里,然后在此编译
  • staging_dir - 最终安装目录。tools, toolchain被安装到这里,rootfs也会放到这里。
  • feeds -
  • bin - 编译完成之后,firmware和各ipk会放到此目录下。

开始openwrt根目录下的Makefile

判断路径

TOPDIR:=${CURDIR}  #指定OpenWrt项目的根目录
LC_ALL:=C  #设置所有分类的本地化类别
LANG:=C  #设置默认的语言环境。
TZ:=UTC #设置时区
#export 命令用于将变量声明为环境变量,使其在子进程中可用。
export TOPDIR LC_ALL LANG TZ#检查 TOPDIR变量中是否包含空格,并在包含空格的情况下输出错误信息
empty:=
space:= $(empty) $(empty)
$(if $(findstring $(space),$(TOPDIR)),$(error ERROR: The path to the OpenWrt directory must not include any spaces))

第一层逻辑

world:export PATH:=$(TOPDIR)/staging_dir/host/bin:$(PATH)#条件判断:如果 OPENWRT_BUILD 不等于1,则执行下面的代码块
ifneq ($(OPENWRT_BUILD),1)
#设置 _SINGLE 变量,用于在构建过程中使用 make -j1 命令,确保单线程构建。_SINGLE=export MAKEFLAGS=$(space);
#设置 OPENWRT_BUILD 变量,并将其导出为环境变量。override OPENWRT_BUILD=1export OPENWRT_BUILD
#清除 GREP_OPTIONS 和 CDPATH 变量,并导出。GREP_OPTIONS=export GREP_OPTIONSCDPATH=export CDPATH

上面这段是主Makefile的结构,可以得知:

  1. 执行make时,若无任何目标指定,则默认目标是world
  2. 执行make时,无参数指定,则会进入第一个逻辑。如果执行命令make OPENWRT_BUILD=1,则直接进入第二个逻辑。
  override OPENWRT_BUILD=1export OPENWRT_BUILD#debug.mk定义了调试相关的配置和规则include $(TOPDIR)/include/debug.mk
#定义了依赖关系管理的规则include $(TOPDIR)/include/depends.mk
#定义了顶层构建规则include $(TOPDIR)/include/toplevel.mk

 这里我们看到变量OPENWRT_BUILD被置为1,然后包含了3个.mk文件。
这里稍微解释下.mk文件,它们一般没有什么执行动作,都是一些变量的定义还有依赖关系的说明,可以类比于C语言的头文件来理解。

debug.mk:可以通过定义DEBUG的值来控制编译过程。

depends.mk:主要定义了rdep这个变量。

toplevel.mk:这个是我们跟踪编译过程的重要的文件,这个文件在源码根目录下的include文件夹下。

核心部分:

%::@+$(PREP_MK) $(NO_TRACE_MAKE) -r -s prereq@( \cp .config tmp/.config; \./scripts/config/conf --defconfig=tmp/.config -w tmp/.config Config.in > /dev/null 2>&1; \if ./scripts/kconfig.pl '>' .config tmp/.config | grep -q CONFIG; then \printf "$(_R)WARNING: your configuration is out of sync. Please run make menuconfig, oldconfig or defconfig!$(_N)\n" >&2; \fi \)@+$(ULIMIT_FIX) $(SUBMAKE) -r $@ $(if $(WARN_PARALLEL_ERROR), || { \printf "$(_R)Build failed - please re-run with -j1 to see the real error message$(_N)\n" >&2; \false; \} )

除了少数在toplevel中被定义的目标外,其他编译目标都会走到这里,将之简化后(执行命令为: make V=s):

%::@make V=s -r -s prereq@make -w -r world

首先执行prereq,然后再执行我们指定的目标或者默认目标world

prereq整理后的依赖关系如下:

其中,staging_dir/host/.prereq-build:

将会执行一系列主机检查,是否安装了必要的软件。

prepare-tmpinfo:

根据scan.mk,扫描target/linuxpackage目录,生成packageinfo和targetinfo。

总之,顶层完成一系列必要的准备工作。对于绝大多数的目标而言,顶层是必经之路。当然,在toplevel.mk中,我们也可以看到目标menuconfig。也就是说对于目标menuconfig而言,将不会执行到第二层的逻辑。

第二层逻辑

在上面执行完make prereq之后,将执行make world。
还记得我们进入顶层后修改了变量OPENWRT_BUILD么?当再次执行make world的时候,由于条件不满足,我们将直接进入第二层来执行。

else
#包含其他makefile文件include rules.mk
#定义通用构建规则include $(INCLUDE_DIR)/depends.mk
#定义子目录构建规则include $(INCLUDE_DIR)/subdir.mk
#定义目标平台构建的顶层规则include target/Makefile
#定义包构建的顶层规则include package/Makefile
#定义工具链构建的顶层规则include tools/Makefile
#定义工具链构建的具体规则。include toolchain/Makefile

rules.mk:很重要的一个mk文件,其中规定了很多有用的变量,包括各种目录路径的定义,交叉编译器等等。其中:

ifeq ($(DUMP),)-include $(TOPDIR)/.config
endif

就是包含了我们的配置文件。对于Makefile而言,.config文件就是一大串变量的定义,Makefile可以直接读取这些定义,从而控制编译过程。

subdir.mk:这个是读懂我们整个编译过程的关键所在,其中主要定义了两个函数:subdirstampfile,我们稍后加以解释。

接下来,包含了4个Makefile文件。我们以target/Makefile为例.该文件位于target目录下。
核心部分为:

$(eval $(call stampfile,$(curdir),target,prereq,.config))
$(eval $(call stampfile,$(curdir),target,compile,$(TMP_DIR)/.build))
$(eval $(call stampfile,$(curdir),target,install,$(TMP_DIR)/.build))$(eval $(call subdir,$(curdir)))

这里调用了subdir.mk中定义的stampfile函数。将会生成target/stamp-prereq,target/stamp-compile,target/stamp-install三个变量。
target/stamp-prereq为例,执行部分为make target/prereq。同理target/stamp-compile,执行部分为make target/compile

最后又调用了sbudir函数,这个函数规定了目标和各子文件夹之间的依赖关系。如果有一定的Makefile基础可以去读读subdir.mk文件。
举例而言就是:

当执行目标为target/compile,这个目标将依赖于target/linux/compile
当执行目标为package/compile,这个目标将依赖于package目录下各子文件夹的compile

下面就是规定了一系列的依赖关系,我已经将他们梳理了出来,如下图:

编译过程中的一些变量可能会造成一些困扰,这里将它们的真实值记录下来,以执行make V=s为例:

1 $(PREP_MK) => OPENWRT_BUILD= QUIET=0
2 
3 $(NO_TRACE_MAKE) => make V=ss
4 
5 $(_SINGLE) => export MAKEFLAGS= ;
6 
7 $(ULIMIT_FIX) => _limit=1024; [  = unlimited -o  -ge 1024 ] || ulimit -n 1024;

 


http://www.mrgr.cn/news/39890.html

相关文章:

  • spring 实用小技巧
  • 【C++】set详解
  • 【PHP陪玩系统源码】游戏陪玩系统app,陪玩小程序优势
  • uniapp学习(003-1 vue3学习 Part.1)
  • 使用RestTemplate调用EMQX API查询MQTT客户端列表信息
  • 什么是reactor以及其三种版本
  • vxe-grid给单元格加上触发事件
  • 快速幂算法
  • 【新闻转载】Storm-0501:勒索软件攻击扩展到混合云环境
  • 【Ubuntu】PlantUML工具 | 安装 | 语法 | 使用工具画序列图
  • 未来前端发展方向:深度探索与技术前瞻
  • VS2022 Git功能的使用
  • 【数据驱动预测控制2】Willems基本引理
  • Ubuntu开机进入紧急模式处理
  • 数据中心解决方案
  • 电影《749局》酷燃首映 苗苗神秘感大片释出氛围感拉满
  • CMU 10423 Generative AI:lec10(few-shot、提示工程、上下文学习)
  • 使用Python实现图形学的路径追踪算法
  • 序列化方式四——Hessian
  • zi2zi-chain: 中国书法字体图片生成和字体制作的一站式开发