C++编译之(2)-make及makefile编译过程

news/2024/11/29 2:37:03/

引言

前面我们介绍了c++的编译工具,使用g++实现对单个文件,多个文件,静态库动态库的编译;我们继续以该项目为例讲解;

g++ 的编译使用入门教程-点这里查看

我们继续以前面的目录解构为例,这里给出上一节的目录如下:

- mutilFilesDemo- include // 头文件目录- HelloTools.h- Prints.h- libs // 库子项目目录- ToolLibs.h- ToolLibs.cpp- libToolLibs.a // 静态库- src // 源码目录- module // 源码模块- Prints.cpp // Prints类- HelloTools.cpp // HelloTools类- main.cpp // main类

我们简单再回顾直接使用g++的编译上面项目的流程;该项目包含一个自项目静态库;所以我们需要首先编译子项目mutilFilesDemo/libs生成库文件libToolLibs.a,然后再进行编译主项目

step.1 编译子项目

# 进入子项目
cd mutilFilesDemo/libs
g++ -c ToolLibs.cpp
ar crv libToolLibs.a ToolLibs.o

step.编译主项目

# 回到主项目
cd mutilFilesDemo
# 编译并链接到静态库
g++ main.cpp ./src/HelloTools.cpp ./src/modules/Prints.cpp -L ./libs -l ToolLibs -o main

这样我们就完成整个编译了;

倘若,我们开发一个大工程,有很多的模块,很多子项目、代码文件,同时依赖很多静态库、动态库的话 ,显然采用这种方式工作量是非常大的。

于是我们创造出了一个make的工具及Makefile文件,Makefile文件记录编译相关的策略及配置项,我们只需要一个make命令即可完成编译,是不是很优雅?

Makefile如何编写?

首先,Makefile的本质其实可以理解为一个构建shell配置脚本;说白了,就是将我们前面写的g++ xxx.cpp -o xxx等一长串命令,预先写好,然后输入make命令,就自动执行了

理论上来说,你写shell脚本也可以实现c++的编译,再退一万步,只要你愿意,你可以一条条g++地输入

我们先看一个Makefile的最简单例子

Makefile文件

help :@echo "help info"dist :@mkdir distclean :@rm -rf dist

我们执行一下make,输入help info字符串

$ make
help info

其实就是执行了@echo help info这条命令(前面加了@,表示不显示命令字符串自本身)

这个Makefile没啥用,然而,确实就是一个合法的Makefile文件,所以其实我们可以从侧面窥探出这个Makefile其实并没有什么高深的技术,它本质就是执行一系列的脚本,而不管你执行的是啥东西(当然,make工具添加许多非常方便的脚本函数,都是在g++手动编译过程中遇到的麻烦事转摸索过来的经验工具函数);Makefile更多的是一种自动化构建思想;

对于Makefile的规则,始终默认执行文件中的第一个标签的脚本,这里即是help;如果希望执行其他标签的脚本可在make命令的后面跟上相应的标签,如:make dist

实际上,Makefile官方介绍中,是这样介绍这个基本规则的,Makefile的编写基本规则如下

target: prerequisitesrecipe

target: 通常是程序生成(输出)的一个或多个文件名,例如:可执行文件或目标文件;它也可以是要执行任务的名称,例如用于清理生成文件的 clean 任务

prerequisites: 先决条件是用于生成 target 文件的输入文件或是完成 target 任务前需要先执行的任务 。一个 target 可以没有先决条件,也可以有一个或多个先决条件(比如,编译的依赖文件,这里还隐藏着的规则是,如果这些依赖发生了改变,则目标文件将会重新编译,着这个规则可以大大减少我们重新编译的效率)

recipe: 中文翻译为菜谱,它是 make 用于生成 target 文件或完成 target 任务而执行一系列 shell 命令。这些命令可以放在同一行里,也可以每个命令占一行。值得注意的是,recipe 默认以制表符开头,而不是空格

其实写到这里,你已经有办法可以写一个简单的符合Makefile的c++构建脚本了;
我们把Makefile再改造成如下内容:

build :@echo "开始编译c++"g++ main.cpp ./src/HelloTools.cpp ./src/modules/Prints.cpp -L ./libs -l ToolLibs -o main@echo "end"

执行一下make命令

$ make
开始编译c++
g++ main.cpp ./src/HelloTools.cpp ./src/modules/Prints.cpp -L ./libs -l ToolLibs -o main
end

编译成功!

当然Makefile的功能远不止这些,我们继续学习一下Makefile的基本语法规则

基本语法规则

1、变量
  • 变量的定义
    make的变量与shell的变量很像,但是make的变量可以出现许多特殊字符,包括空格(=除外),如下所示
objs = main.o model.o view.o controller.o

注意变量的赋值有两种,一种是取直接赋值(:=),领外一种是最后计算赋值(=);遗憾的我们熟悉的编程赋值语法是这一种最后计算赋值=;这意味着如果赋值内容存在变量,而该变量在后面的脚本中,被改变了,那边这个赋值是采用最终值来计算赋值的;所以搞不清楚用法的话,最好变量赋值最好只做一次赋值,避免踩坑

  • 变量的使用
    make中的变量采用$()的方式使用,如下所示
build: $(objs)g++ $(objs) -o app

变量的其他用法

$^ 表示所有的依赖文件

$@ 表示生成的目标文件

$< 代表第一个依赖文件

  • 一些常用的全局变量

这些变量与前面自定义的变量本质一样,都是可定义修改的,只不过这些变量全局的环境变量,可全局使用;

CFLAGSCXXFLAGSCPPFLAGS

选项说明
-c用于把源码文件编译成 .o 对象文件,不进行链接过程
-o用于连接生成可执行文件,在其后可以指定输出文件的名称
-g用于在生成的目标可执行文件中,添加调试信息,可以使用GDB进行调试
-Idir用于把新目录添加到include路径上,可以使用相对和绝对路径,“-I.”、“-I./include”、“-I/opt/include”
-Wall生成常见的所有告警信息,且停止编译,具体是哪些告警信息,请参见GCC手册,一般用这个足矣!
-w关闭所有告警信息
-O表示编译优化选项,其后可跟优化等级0\1\2\3,默认是0,不优化
-fPIC用于生成位置无关的代码
-v(在标准错误)显示执行编译阶段的命令,同时显示编译器驱动程序,预处理器,编译器的版本号

LDFLAGS

选项说明
-llibrary链接时在标准搜索目录中寻找库文件,搜索名为liblibrary.a 或 liblibrary.so
-Ldir用于把新目录添加到库搜索路径上,可以使用相对和绝对路径,“-L.”、“-L./include”、“-L/opt/include”
-Wl,option把选项 option 传递给连接器,如果 option 中含有逗号,就在逗号处分割成多个选项
-static使用静态库链接生成目标文件,避免使用共享库,生成目标文件会比使用动态链接库大

LIBS
如: LIBS = -lpthread -lm -lpthread -liconv

2、函数

make 中的函数用于处理 Makefile 文件中的文本,例如:计算操作的文件列表,“菜谱”中使用的命令等。

  • 函数的调用
    函数的调用与变量的使用有点像,格式如下所示:
$(function arguments)

例如常用的函数实例

SRC = $(wildcard *.cpp)
OBJ = $(patsubst %.cpp, %.o, $(SRC))
build:@echo $(subst o,q,hello world)

我们一起来看看上面的函数代表的函数:
第一条指令,wildcard *.cpp 获取所有源文件,并最终赋值给SRC变量;
第二条指令,patsubst %.cpp, %.o, $(SRC),把SRC字符串中,所有.cpp替换成.o,结果赋值给OBJ变量
第三条指令,把hello world字符串中的o替换成q

make还有一些更简单的规则,如一些常用的编译命令,像下面这样写是完整的写法

app : main.o utils.og++ -o app main.o utils.omain.o : main.c utils.hg++ -c main.cutils.o : utils.cg++ -c utils.c

但是,实际上可以简写成如下所示

app : main.o utils.occ -o app main.o utils.omain.o : main.c utils.hutils.o : utils.c
3、嵌套执行Makefile

如果我们有子项目,那么我们可以采用嵌套Makefile来执行,如我们前面的libs静态库子项目

subsystem:cd subdir && $(MAKE)

等价于

subsystem:$(MAKE) -C subdir

为啥用一个变量MAKE表示呢,也许我们的make需要一些参数,所以定义成一个变量比较有利于维护;

这两种方式都会先进入subdir目录后,再执行make命令

make -C <dir> 表示在<dir>目录下执行make命令;即使用<dir>目录下的Makefile文件执行;当然,你也可以使用-f path/to/you/Makefile完全直接指定Makefile路径

有时,我们希望/不希望传递一下变量到下一级的Makefile中,那么可以使用这样的声明
export/unexport 来声明变量,或者直接使用export把所有变量往下传递

export var1 var2 var3

Makefile实战练习

前面讲了这么多,那我们开始提到的项目,到底该如何写这个Makefile呢?请看下面的目录

- mutilFilesDemo- include // 头文件目录- HelloTools.h- Prints.h- libs // 库子项目目录- ToolLibs.h- ToolLibs.cpp- Makefile  // 子项目Makefile- src // 源码目录- module // 源码模块- Prints.cpp // Prints类- HelloTools.cpp // HelloTools类- main.cpp // main类- Makefile  // 主项目Makefile

我们分别来看这两个Makefile怎么写,首先我们来看子项目的Makefile

# 子项目Makefile
CC=g++
# 获取当前工作路径
WORK_DIR:=$(CURDIR)
# 设置目标名
Target:=libToolLibs.a# 统一的文件目录命名
DEFAULT_BUILD_DIR?=build
DEFAULT_TMP_DIR?=tmp
DEFAULT_BIN_DIR?=bin# ===========
# 各种目录设置
# ===========
# 设置源文件目录,可设置多个
SRC_PATH:=$(WORK_DIR)
# 兼容:直接编译的编译目录|作为子项目时的编译目录
WORK_BUILD_DIR:=$(if $(TOP_DIR),$(TOP_DIR)/$(DEFAULT_BUILD_DIR)/sub_projects/$(notdir $(WORK_DIR)),$(WORK_DIR))
# 设置编译目录
BUILD_PATH:=$(if $(TOP_DIR),$(WORK_BUILD_DIR),$(WORK_BUILD_DIR)/$(DEFAULT_BUILD_DIR))
# 设置编译临时目录
OBJ_PATH:=$(BUILD_PATH)/$(DEFAULT_TMP_DIR)
# 设置编译最终文件目录
BIN_PATH:=$(BUILD_PATH)/$(DEFAULT_BIN_DIR)# ===================
# 各种文件名字符串的处理
# ===================
# 获取源文件目录下所有带路径的cpp文件列表
SRC:=$(foreach dir,$(SRC_PATH),$(wildcard $(dir)/*.cpp))
# 获取纯cpp文件名(不带路路径,去掉cpp文件目录)
SRC_FILES:=$(notdir $(SRC))
# 生成.o文件名列表(不带路径)
OBJ_FILES:=$(patsubst %.cpp,%.o,$(SRC_FILES))
# 为.o文件列表加上编译目录(完整文件路径)
OBJ_WITH_PATH_FILE:=$(addprefix $(OBJ_PATH)/,$(OBJ_FILES))# 显示信息
$(info ============ sub =============)
$(info "Target => $(Target)")
$(info "WORK_DIR => $(WORK_DIR)")
$(info "BUILD_PATH => $(BUILD_PATH)")
$(info "OBJ_WITH_PATH_FILE => $(OBJ_WITH_PATH_FILE)")
$(info "CPPFLAGS => $(CPPFLAGS)")
$(info "LDFLAGS => $(LDFLAGS)")
$(info "LIBS => :$(LIBS)")
$(info ------------------------------)# ================
# 各编译目标任务处理
# ================
# 编译目标
all:build_prepare $(Target)
# 连接目标
$(Target):$(OBJ_WITH_PATH_FILE) ar crv $(BIN_PATH)/$@ $^
# 主项目编译:所有的*.cpp编译生成相应的*.o文件
$(OBJ_PATH)/%.o:%.cpp$(CC) -c -o $@ $<
# 创建编译目录
build_prepare:@if [ ! -d $(BUILD_PATH) ]; then \mkdir -p $(OBJ_PATH); \mkdir -p $(BIN_PATH); \fi.PHONY:cleanclean:-rm -rf $(BIN_PATH)/$(Target) $(OBJ_WITH_PATH_FILE)

子这个Makefile我把他设计成,即可以独立编译,又可与主项目集成编译
上面的代码,几乎每条指令都有注释,我们直接对子项目的Makefile文件执行一下make看看效果

$ cd mutilFilesDemo/libs
$ make
============ sub =============
"Target => libToolLibs.a"
"WORK_DIR => /home/compMutilFilesMakeDemo/libs"
"BUILD_PATH => /home/compMutilFilesMakeDemo/libs/build"
"OBJ_WITH_PATH_FILE => /home/compMutilFilesMakeDemo/libs/build/tmp/ToolLibs.o"
"CPPFLAGS => "
"LDFLAGS => "
"LIBS => :"
------------------------------
g++ -c -o /home/compMutilFilesMakeDemo/libs/build/tmp/ToolLibs.o ToolLibs.cpp
ar crv /home/compMutilFilesMakeDemo/libs/build/bin/libToolLibs.a /home/compMutilFilesMakeDemo/libs/build/tmp/ToolLibs.o
a - /home/compMutilFilesMakeDemo/libs/build/tmp/ToolLibs.o

执行成功后,多了一个build目录,所有构建的临时文件及最终的目标文件都在这个目录下

再上主项目的Makefile文件内容如下:

CC=g++
# 获取当前工作路径
WORK_DIR:=$(CURDIR)
TOP_DIR:=$(CURDIR)
# 设置目标名
Target:=mainApp
# 设置子项目生成目标库名,可设置多个
SubPorjectLibName:=ToolLibs# 统一的文件目录命名
DEFAULT_BUILD_DIR:=build
DEFAULT_TMP_DIR:=tmp
DEFAULT_BIN_DIR:=bin
# 全局有效
export TOP_DIR DEFAULT_BUILD_DIR DEFAULT_TMP_DIR DEFAULT_BIN_DIR# ===========
# 各种目录设置
# ===========
# 设置子项目目录,可设置多个
SUB_PRO_PATH:=	$(WORK_DIR)/libs
# 设置源文件目录,可设置多个
SRC_PATH:=	$(WORK_DIR) \$(WORK_DIR)/src \$(WORK_DIR)/src/modules
# include头目录
INCLUDE_PATH=$(WORK_DIR)/include
# 设置编译目录
BUILD_PATH:=$(WORK_DIR)/$(DEFAULT_BUILD_DIR)
# 设置编译临时目录
OBJ_PATH:=$(BUILD_PATH)/$(DEFAULT_TMP_DIR)
# 设置编译最终文件目录
BIN_PATH:=$(BUILD_PATH)/$(DEFAULT_BIN_DIR)
# 子项目目录名称(去路径)
SUB_PRO_DIR_NAME:=$(notdir $(SUB_PRO_PATH))
# 子项目BIN目录,可用于主项目的链接库目录(加前后路径)
SUB_PRO_BIN_PATH:=$(addsuffix /$(DEFAULT_BIN_DIR),$(addprefix $(BUILD_PATH)/sub_projects/,$(SUB_PRO_DIR_NAME)))# ===================
# 各种文件名字符串的处理
# ===================
# 获取源文件目录下所有带路径的cpp文件列表
SRC:=$(foreach dir,$(SRC_PATH),$(wildcard $(dir)/*.cpp))
# 获取纯cpp文件名(不带路路径,去掉cpp文件目录)
SRC_FILES:=$(notdir $(SRC))
# 生成.o文件名列表(不带路径)
OBJ_FILES:=$(patsubst %.cpp,%.o,$(SRC_FILES))
# 为.o文件列表加上编译目录(完整文件路径)
OBJ_WITH_PATH_FILE:=$(addprefix $(OBJ_PATH)/,$(OBJ_FILES))# ====================
# 添加Makefile的全局变量
# ====================
# 添加头文件目录
CPPFLAGS+=$(addprefix -I,$(INCLUDE_PATH))
# 添加链接的库目录
LDFLAGS+=$(addprefix -L,$(SUB_PRO_BIN_PATH))
# 添加链接的库名
LIBS+=$(addprefix -l,$(SubPorjectLibName))
# 为g++添加源文件搜索目录
VPATH=$(SRC_PATH)# 显示信息
$(info =========== main ==============)
$(info "Target => $(Target)")
$(info "WORK_DIR => $(WORK_DIR)")
$(info "BUILD_PATH => $(BUILD_PATH)")
$(info "OBJ_WITH_PATH_FILE => $(OBJ_WITH_PATH_FILE)")
$(info "CPPFLAGS => $(CPPFLAGS)")
$(info "LDFLAGS => $(LDFLAGS)")
$(info "LIBS => $(LIBS)")
$(info ===============================)# ================
# 各编译目标任务处理
# ================
# 编译目标
all:build_prepare $(SUB_PRO_PATH) $(Target)
# 连接目标
$(Target):$(OBJ_WITH_PATH_FILE) $(CC) -o $(BIN_PATH)/$@ $^ $(LDFLAGS) $(LIBS) 
# 主项目编译:所有的*.cpp编译生成相应的*.o文件
$(OBJ_PATH)/%.o:%.cpp$(CC) -c -o $@ $<
# 创建编译目录
build_prepare:@if [ ! -d $(BUILD_PATH) ]; then \mkdir -p $(OBJ_PATH); \mkdir -p $(BIN_PATH); \fi
#子项目执行
$(SUB_PRO_PATH):ECHO_MSG@echo start compile:$@@make -C $@
ECHO_MSG:@echo Begin Sub Projects Compile@echo All Subs:$(SUB_PRO_PATH).PHONY:cleanclean:-rm -rf $(BIN_PATH)/$(Target) $(OBJ_WITH_PATH_FILE)cleanAll:-rm -rf $(BUILD_PATH)

同样,我们进入主项目,执行一下make

$ cd mutilFilesDemo
$ make
=========== main ==============
"Target => mainApp"
"WORK_DIR => /home/compMutilFilesMakeDemo"
"BUILD_PATH => /home/compMutilFilesMakeDemo/build"
"OBJ_WITH_PATH_FILE => /home/compMutilFilesMakeDemo/build/tmp/main.o /home/compMutilFilesMakeDemo/build/tmp/HelloTools.o /home/compMutilFilesMakeDemo/build/tmp/Prints.o"
"CPPFLAGS => -I/home/compMutilFilesMakeDemo/include"
"LDFLAGS => -L/home/compMutilFilesMakeDemo/build/sub_projects/libs/bin"
"LIBS => -lToolLibs"
===============================
Begin Sub Projects Compile
All Subs:/home/compMutilFilesMakeDemo/libs
start compile:/home/compMutilFilesMakeDemo/libs
make[1]: Entering directory '/home/compMutilFilesMakeDemo/libs'
============ sub =============
"Target => libToolLibs.a"
"WORK_DIR => /home/compMutilFilesMakeDemo/libs"
"BUILD_PATH => /home/compMutilFilesMakeDemo/build/sub_projects/libs"
"OBJ_WITH_PATH_FILE => /home/compMutilFilesMakeDemo/build/sub_projects/libs/tmp/ToolLibs.o"
"CPPFLAGS => "
"LDFLAGS => "
"LIBS => :"
------------------------------
g++ -c -o /home/compMutilFilesMakeDemo/build/sub_projects/libs/tmp/ToolLibs.o ToolLibs.cpp
ar crv /home/compMutilFilesMakeDemo/build/sub_projects/libs/bin/libToolLibs.a /home/compMutilFilesMakeDemo/build/sub_projects/libs/tmp/ToolLibs.o
a - /home/compMutilFilesMakeDemo/build/sub_projects/libs/tmp/ToolLibs.o
make[1]: Leaving directory '/home/compMutilFilesMakeDemo/libs'
g++ -c -o /home/compMutilFilesMakeDemo/build/tmp/main.o main.cpp
g++ -c -o /home/compMutilFilesMakeDemo/build/tmp/HelloTools.o /home/compMutilFilesMakeDemo/src/HelloTools.cpp
g++ -c -o /home/compMutilFilesMakeDemo/build/tmp/Prints.o /home/compMutilFilesMakeDemo/src/modules/Prints.cpp
g++ -o /home/compMutilFilesMakeDemo/build/bin/mainApp /home/compMutilFilesMakeDemo/build/tmp/main.o /home/compMutilFilesMakeDemo/build/tmp/HelloTools.o /home/compMutilFilesMakeDemo/build/tmp/Prints.o -L/home/compMutilFilesMakeDemo/build/sub_projects/libs/bin -lToolLibs 

最后,我们进入build/bin看看我们主项目的编译结果,并执行一下

$ cd compMutilFilesMakeDemo/build/bin
$ ./mainApp 
Hello world!
MAX_NUM+n:110
=================================
使用静态库-add(a,b)
结果为:a+b=500

结果与上一节的直接用g++一样,而后续的可维护性大大的增加,这就是make/m=Makefile的魅力所在;

尽管IDE很漂亮,但是掌握make/Makefile才是你走向程序员的巅峰的必由之路,尤其是linux下的开发更是如此!

这是一个非常棒的例子,里面包含了许多自动化构建项目的一些思路,供大家参考使用!

如此呕心沥血之作,必然离不开各个广大码友的贡献,这里最后附属一些参考文献!
C++编译之(1)-g++单/多文件/库的编译
跟我一起写Makefile
官网文档

make/Makefile很美好,燃鹅写Makefile的工程量也不小,于是还有大神继续砥砺前行,发明了cmake/CMakeLists.txt来自动制作Makefile,我们在下一节中继续介绍


http://www.ppmy.cn/news/19293.html

相关文章

基础知识一览3

这里写目录标题1.Servlet1.1 入门1.2 什么是Servlet1.3 Servlet的作用1.4 Servlet生命周期1.5 Servler的体系结构1.6 Servler的两种配置方式2.Filter2.1 Filter拦截路径配置2.2 过滤器链2.2 入门2.3 过滤器链2.4 过滤器生命周期3.Listener3.1 监听器分类3.1.1 一类监听器4.Serv…

使用ebpf 监控mysqld 内核

一、开发思路分析 我们使用ebpf 监控mysql的话有两个思路去做这件事情 1、kprobe -> hook 掉tcp_sendmsg 和 tcp_recvmsg 一类的内核函数去分析网络协议 2、uprobe -> hook 掉 mysqld 的api函数&#xff0c;然后在此基础上进行统计 我使用的是uprobe 去hook 掉mysql内…

MP-2平面烟雾气体传感器介绍

MP-2平面烟雾气体传感器简介MP-2烟雾检测气体传感器采用多层厚膜制造工艺&#xff0c;在微型Al2O3陶瓷基片的两面分别制作加热器和金属氧化物半导体气敏层&#xff0c;封装在金属壳体内。当环境空气中有被检测气体存在时传感器电导率发生变化&#xff0c;该气体的浓度越高&…

树,堆,二叉树的认识

1.树概念及结构 1.1树的概念 注意&#xff1a;树形结构中&#xff0c;子树之间不能有交集&#xff0c;否则就不是树形结构 1.2 树的相关概念 1.3 树的表示 树结构相对线性表就比较复杂了&#xff0c;要存储表示起来就比较麻烦了&#xff0c;既然保存值域&#xff0c;也要保存…

javascript中Math.random()产生随机数进行随机点名

Math.random()是令系统随机选取大于等于 0.0 且小于 1.0 的小数&#xff0c;即[0.0,1.0&#xff09;。 Math.floor()返回小于参数x的最大整数,即对浮点数向下取整&#xff0c;比如Math.floor(3.8)为3。 一、 在连续整数中取得一个随机整数 随机值 Math.floor(Math.random()…

16 Java网络编程(计算机网络+网络模型OSI/TCP/IP+通信协议等)

网络编程16.1 网络概述16.1.1 概念16.1.2 计算机网络16.1.3 网络模型16.1.3.1 OSI参考模型16.1.3.2 TCP/IP模型16.1.4 网络编程总结 【掌握】16.2 常见协议16.2.1 IP协议概述16.2.2 InetAddress类16.3 端口号16.3.1 端口号概述 【了解】16.4 通信协议16.4.1 通信协议概述 【掌握…

项目整体管理

项目整体管理过程: 一、制定项目章程,正式批准(授权)项目或项目阶段的开始。 项目章程作用: (1)宣布项目成立。(2)任命项目经理并授权。(3)粗略规定范围。 项目章程内容: 概括性的项目描述和项目产品描述 (项目产品描述)项目目的或批准项目的理由,即为什么要…

node文件上传与下载(基于express和multer实现)

node文件上传与下载 所需要的前置知识&#xff1a;基本的html标签&#xff0c;基本DOM, AJAX, express 等 创建基本的 express 项目 使用express-generator生成基本的项目结构 全局安装express-generator npm install -g express-generator 创建express 项目 express upload-…