【NUJ PA2】Read a Makefile

这里是NJU的PA2.2里面要求读懂的Makefile,是abstract-machine的。这里会放一些与读懂这个Makefile有关的知识。

下面是用ChatGPT解释的代码。只做大致的了解,写Makefile的时候还是要具体去看官方手册。

官方手册:make.pdf
(gnu.org)

python 复制代码
# Makefile for AbstractMachine Kernels and Libraries

### *Get a more readable version of this Makefile* by `make html` (requires python-markdown)
html:
    cat Makefile | sed 's/^\([^#]\)/    \1/g' | markdown_py > Makefile.html
.PHONY: html


    ## 1. Basic Setup and Checks

    ## 创建一个裸机内核镜像
    ### Default to create a bare-metal kernel image
    ifeq ($(MAKECMDGOALS),)
      MAKECMDGOALS  = image
      .DEFAULT_GOAL = image
    endif

    ### Override checks when `make clean/clean-all/html`
    ifeq ($(findstring $(MAKECMDGOALS),clean|clean-all|html),)

    ### Print build info message
    $(info # Building $(NAME)-$(MAKECMDGOALS) [$(ARCH)])

    ### Check: environment variable `$AM_HOME` looks sane
    ifeq ($(wildcard $(AM_HOME)/am/include/am.h),)
      $(error $$AM_HOME must be an AbstractMachine repo)
    endif

    ### Check: environment variable `$ARCH` must be in the supported list
    ARCHS = $(basename $(notdir $(shell ls $(AM_HOME)/scripts/*.mk)))
    ifeq ($(filter $(ARCHS), $(ARCH)), )
      $(error Expected $$ARCH in {$(ARCHS)}, Got "$(ARCH)")
    endif

    ### Extract instruction set architecture (`ISA`) and platform from `$ARCH`. Example: `ARCH=x86_64-qemu -> ISA=x86_64; PLATFORM=qemu`
    ARCH_SPLIT = $(subst -, ,$(ARCH))
    ISA        = $(word 1,$(ARCH_SPLIT))
    PLATFORM   = $(word 2,$(ARCH_SPLIT))

    ### Check if there is something to build
    ifeq ($(flavor SRCS), undefined)
      $(error Nothing to build)
    endif

    ### Checks end here
    endif

    ## 2. General Compilation Targets

    ### Create the destination directory (`build/$ARCH`)
    WORK_DIR  = $(shell pwd)
    DST_DIR   = $(WORK_DIR)/build/$(ARCH)
    $(shell mkdir -p $(DST_DIR))

    ### Compilation targets (a binary image or archive)
    IMAGE_REL = build/$(NAME)-$(ARCH)
    IMAGE     = $(abspath $(IMAGE_REL))
    ARCHIVE   = $(WORK_DIR)/build/$(NAME)-$(ARCH).a

    ### Collect the files to be linked: object files (`.o`) and libraries (`.a`)
    OBJS      = $(addprefix $(DST_DIR)/, $(addsuffix .o, $(basename $(SRCS))))
    LIBS     := $(sort $(LIBS) am klib) # lazy evaluation ("=") causes infinite recursions
    LINKAGE   = $(OBJS) \
      $(addsuffix -$(ARCH).a, $(join \
        $(addsuffix /build/, $(addprefix $(AM_HOME)/, $(LIBS))), \
        $(LIBS) ))

    ## 3. General Compilation Flags

    ### (Cross) compilers, e.g., mips-linux-gnu-g++
    AS        = $(CROSS_COMPILE)gcc
    CC        = $(CROSS_COMPILE)gcc
    CXX       = $(CROSS_COMPILE)g++
    LD        = $(CROSS_COMPILE)ld
    AR        = $(CROSS_COMPILE)ar
    OBJDUMP   = $(CROSS_COMPILE)objdump
    OBJCOPY   = $(CROSS_COMPILE)objcopy
    READELF   = $(CROSS_COMPILE)readelf

    ### Compilation flags
    INC_PATH += $(WORK_DIR)/include $(addsuffix /include/, $(addprefix $(AM_HOME)/, $(LIBS)))
    INCFLAGS += $(addprefix -I, $(INC_PATH))

    ARCH_H := arch/$(ARCH).h
    CFLAGS   += -O2 -MMD -Wall -Werror $(INCFLAGS) \
                -D__ISA__=\"$(ISA)\" -D__ISA_$(shell echo $(ISA) | tr a-z A-Z)__ \
                -D__ARCH__=$(ARCH) -D__ARCH_$(shell echo $(ARCH) | tr a-z A-Z | tr - _) \
                -D__PLATFORM__=$(PLATFORM) -D__PLATFORM_$(shell echo $(PLATFORM) | tr a-z A-Z | tr - _) \
                -DARCH_H=\"$(ARCH_H)\" \
                -fno-asynchronous-unwind-tables -fno-builtin -fno-stack-protector \
                -Wno-main -U_FORTIFY_SOURCE
    CXXFLAGS +=  $(CFLAGS) -ffreestanding -fno-rtti -fno-exceptions
    ASFLAGS  += -MMD $(INCFLAGS)
    LDFLAGS  += -z noexecstack

    ## 4. Arch-Specific Configurations

    ### Paste in arch-specific configurations (e.g., from `scripts/x86_64-qemu.mk`)
    -include $(AM_HOME)/scripts/$(ARCH).mk

    ### Fall back to native gcc/binutils if there is no cross compiler
    ifeq ($(wildcard $(shell which $(CC))),)
      $(info #  $(CC) not found; fall back to default gcc and binutils)
      CROSS_COMPILE :=
    endif

    ## 5. Compilation Rules

    ### Rule (compile): a single `.c` -> `.o` (gcc)
    $(DST_DIR)/%.o: %.c
        @mkdir -p $(dir $@) && echo + CC $<
        @$(CC) -std=gnu11 $(CFLAGS) -c -o $@ $(realpath $<)

    ### Rule (compile): a single `.cc` -> `.o` (g++)
    $(DST_DIR)/%.o: %.cc
        @mkdir -p $(dir $@) && echo + CXX $<
        @$(CXX) -std=c++17 $(CXXFLAGS) -c -o $@ $(realpath $<)

    ### Rule (compile): a single `.cpp` -> `.o` (g++)
    $(DST_DIR)/%.o: %.cpp
        @mkdir -p $(dir $@) && echo + CXX $<
        @$(CXX) -std=c++17 $(CXXFLAGS) -c -o $@ $(realpath $<)

    ### Rule (compile): a single `.S` -> `.o` (gcc, which preprocesses and calls as)
    $(DST_DIR)/%.o: %.S
        @mkdir -p $(dir $@) && echo + AS $<
        @$(AS) $(ASFLAGS) -c -o $@ $(realpath $<)

    ### Rule (recursive make): build a dependent library (am, klib, ...)
    $(LIBS): %:
        @$(MAKE) -s -C $(AM_HOME)/$* archive

    ### Rule (link): objects (`*.o`) and libraries (`*.a`) -> `IMAGE.elf`, the final ELF binary to be packed into image (ld)
    $(IMAGE).elf: $(OBJS) am $(LIBS)
        @echo + LD "->" $(IMAGE_REL).elf
        @$(LD) $(LDFLAGS) -o $(IMAGE).elf --start-group $(LINKAGE) --end-group

    ### Rule (archive): objects (`*.o`) -> `ARCHIVE.a` (ar)
    $(ARCHIVE): $(OBJS)
        @echo + AR "->" $(shell realpath $@ --relative-to .)
        @$(AR) rcs $(ARCHIVE) $(OBJS)

    ### Rule (`#include` dependencies): paste in `.d` files generated by gcc on `-MMD`
    -include $(addprefix $(DST_DIR)/, $(addsuffix .d, $(basename $(SRCS))))

    ## 6. Miscellaneous

    ### Build order control
    image: image-dep
    archive: $(ARCHIVE)
    image-dep: $(OBJS) am $(LIBS)
        @echo \# Creating image [$(ARCH)]
    .PHONY: image image-dep archive run $(LIBS)

    ### Clean a single project (remove `build/`)
    clean:
        rm -rf Makefile.html $(WORK_DIR)/build/
    .PHONY: clean

    ### Clean all sub-projects within depth 2 (and ignore errors)
    CLEAN_ALL = $(dir $(shell find . -mindepth 2 -name Makefile))
    clean-all: $(CLEAN_ALL) clean
    $(CLEAN_ALL):
        -@$(MAKE) -s -C $@ clean
    .PHONY: clean-all $(CLEAN_ALL)

1. Basic Setup and Checks

Default to create a bare-metal kernel image

MAKECMDGOALS

Make会将特殊变量MAKECMDGOALS设置为您在命令行上指定的目标列表。如果在命令行上没有指定目标,则此变量为空。请注意,这个变量只应在特殊情况下使用。

适当使用的一个例子是在清理规则中避免包含.d文件(参见第4.13节[自动先决条件],第42页),这样make就不会创建这些文件,只是立即删除它们。

换句话说,MAKECMDGOALS变量可以用于根据命令行上指定的目标来执行特定的操作或规则。在清理规则中,您可以检查MAKECMDGOALS变量,以决定是否需要清理.d文件。这样可以避免不必要的创建和删除.d文件的开销。

这段代码片段展示了一个示例的

Makefile 开头部分。 首先,定义了一个变量sources,其中包含了要编译的源文件列表,例如 foo.cbar.c。接下来,使用条件语句 ifeq 来检查 MAKECMDGOALS 变量中是否包含了clean 目标。MAKECMDGOALS 变量存储了在命令行上指定的目标列表。 如果MAKECMDGOALS 中不包含 clean 目标,那么会执行 include语句。这里使用了变量替换和模式替换,将 sources 中的 .c 后缀替换为.d 后缀,然后包含这些 .d 文件。.d文件通常是由编译器生成的依赖关系文件,用于跟踪源文件之间的依赖关系。通过这段代码,可以在编译程序之前自动包含并处理依赖关系文件,以确保在源文件或头文件发生更改时,相关的文件会被重新编译。这有助于提高构建的效率和准确性。

ifeq ($(MAKECMDGOALS),)
  MAKECMDGOALS  = image
  .DEFAULT_GOAL = image
endif

如果MAKECMDGOALS 变量为空,即没有在命令行上指定目标,则会执行里面的代码,将MAKECMDGOALS、.DEFAULT_GOAL都指向image这个镜像。

Override checks when make clean/clean-all/html

ifeq ($(findstring $(MAKECMDGOALS),clean|clean-all|html),)

也是说我们输入make clean/make clean-all/makehtml,可以避免在执行清理或生成 HTML等特定操作时,执行不必要的检查(也就是下面的Check)或设置。

Printbuild info message

pass,没啥好说的。

Check: environment variable$AM_HOME looks sane

ifeq ($(wildcard $(AM_HOME)/am/include/am.h),)
  $(error $$AM_HOME must be an AbstractMachine repo)
endif

用于检查环境变量 $AM_HOME 是否指向 Abstract-Machine 仓库

Check:environment variable $ARCH must be in the supported list

检查是否是支持的ISA

Extract instruction set architecture (ISA)and platform from $ARCH. Example:

ARCH=x86_64-qemu -> ISA=x86_64; PLATFORM=qemu没啥好说的,也就是做了一点类似字符串的截取的工作。

Check if there

is something to build 检查是否需要build。

2. General Compilation Targets

Create the destination directory (build/$ARCH)

生成目标的文件路径,创建文件。 ### Compilation targets (a binary image

or archive)

IMAGE_REL = build/$(NAME)-$(ARCH)
IMAGE     = $(abspath $(IMAGE_REL))
ARCHIVE   = $(WORK_DIR)/build/$(NAME)-$(ARCH).a

这段代码片段定义了编译目标的变量。 首先,定义了一个变量IMAGE_REL,它表示编译生成的二进制镜像的相对路径。路径由build/$(NAME)(名称变量)和 $(ARCH)(架构变量)组成。接下来,定义了一个变量 IMAGE,它使用 abspath 函数将 IMAGE_REL的相对路径转换为绝对路径。这样可以确保在不同的工作目录中也能正确地引用生成的二进制镜像。最后,定义了一个变量 ARCHIVE,它表示编译生成的静态库的路径。路径由$(WORK_DIR)(工作目录变量)、build/$(NAME)$(ARCH) 组成。通过这些变量,可以方便地引用编译生成的二进制镜像和静态库的路径,以便后续的操作和使用。

生成了一个库文件。

Collect the files to be linked: object files (.o) and libraries (.a)

这段代码片段用于收集需要链接的文件,包括目标文件(.o)和库文件(.a)。首先,定义了一个变量 OBJS,它使用 addprefixaddsuffixbasename 函数来生成目标文件的路径。addprefix函数用于在每个文件名前添加目标目录的路径 $(DST_DIR)addsuffix函数用于在每个文件名后添加 .o 后缀,basename函数用于去除源文件的扩展名。通过这些函数的组合,可以生成目标文件的完整路径。接下来,使用 sort 函数对变量 LIBS 进行排序,并将 amklib添加到 LIBS 变量中。这里使用了 := 运算符,表示对 LIBS变量进行延迟求值,避免无限递归的问题。 最后,定义了一个变量LINKAGE,它包含了需要链接的文件列表。这个列表由目标文件 $(OBJS)和库文件组成。库文件的路径由 $(AM_HOME)$(LIBS)-$(ARCH).a组合而成。通过 join 函数和字符串操作,可以生成每个库文件的完整路径。通过这些变量,可以方便地收集需要链接的目标文件和库文件,并在链接过程中使用它们。

3. General Compilation Flags

(Cross) compilers, e.g.,mips-linux-gnu-g++

进行交叉编译。通过这些变量,可以方便地配置和切换不同的编译器和工具链,以适应特定的编译环境和目标平台。

Compilation flags

定义了一些编译标志(Compilation flags)。

4. Arch-Specific Configurations

Paste in arch-specific configurations(e.g., from scripts/x86_64-qemu.mk)

这段代码片段用于包含特定架构的配置文件。使用 -include 指令,将$(AM_HOME)/scripts/$(ARCH).mk 文件包含到当前的 Makefile

中。这个文件路径由 $(AM_HOME)(AbstractMachine 仓库路径)和$(ARCH)(架构变量)组成。通过包含特定架构的配置文件,可以在构建过程中应用特定的架构相关配置。这样可以根据不同的架构定制编译和链接的行为,以适应特定的目标平台和需求。这方式可以使Makefile 更加灵活和可配置,以适应不同的架构和配置需求。 ### Fall backto native gcc/binutils if there is no cross compiler这段代码片段用于在没有交叉编译器的情况下,回退到本地的 gcc 和 binutils。

5. Compilation Rules - 定义编译规则,将源文件编译为目标文件。 -

  • 定义编译规则,将源文件编译为目标文件。
  • 定义递归规则,构建依赖库(am、klib 等)的静态库。
  • 定义链接规则,将目标文件和库文件链接为最终的 ELF 二进制文件。
  • 定义归档规则,将目标文件打包为静态库。
  • 定义 .d 文件的包含规则,用于处理头文件的依赖关系。

6. Miscellaneous

这段代码片段控制了构建的顺序和清理操作。 首先,定义了目标image,它依赖于目标 image-dep。这表示在构建 image 之前,需要先构建image-dep。同样地,定义了目标 archive,它依赖于变量$(ARCHIVE),表示在构建 archive 之前,需要先生成 $(ARCHIVE)。在目标 image-dep 中,定义了依赖关系,它依赖于目标 $(OBJS)am$(LIBS)。这表示在构建 image-dep 之前,需要先生成目标文件$(OBJS)、构建 am 和构建 $(LIBS)。 在目标 clean中,定义了清理操作,它会删除 Makefile.html 文件和 $(WORK_DIR)/build/目录。 在目标 clean-all 中,定义了清理所有子项目的操作。通过使用find 命令找到所有深度为 2 的 Makefile文件,并提取出对应的目录。然后,通过递归调用 $(MAKE)命令在每个子项目中执行 clean 目标。通过这些目标和规则,可以控制构建的顺序,并提供清理操作以清除构建生成的文件。

END

  1. 里面对于make clean、make html、make
    clean-all进行了处理,使得它们不需要进行检查和下面的操作,节省了很多时间;
  2. 对于交叉编译做了处理,如果没有交叉编译的环境则回退到原环境;
  3. Arch-Specific Configurations的Paste in arch-specific configurations
    (e.g., from scripts/x86_64-qemu.mk)使用 -include 指令,将$(AM_HOME)/scripts/$(ARCH).mk 文件包含到当前的 Makefile
    中。也是就是包含了特定架构的配置文件,可以在构建过程中应用特定的架构相关配置。使得一个n*m的问题变成了n + m!

Answered by ChatGPT:

这个 Makefile 是用于构建 AbstractMachine 内核和库的。下面对其中的内容进行解释:

1. Basic Setup and Checks(基本设置和检查):
   - 检查默认目标,并设置 `MAKECMDGOALS` 和 `.DEFAULT_GOAL`。
   - 检查环境变量 `$AM_HOME` 是否指向 AbstractMachine 仓库。
   - 检查环境变量 `$ARCH` 是否在支持的架构列表中。
   - 提取 `$ARCH` 中的指令集架构 (`ISA`) 和平台信息。

2. General Compilation Targets(通用编译目标):
   - 创建目标目录 `build/$ARCH`。
   - 定义编译目标:`IMAGE_REL`(二进制镜像路径)和 `ARCHIVE`(静态库路径)。
   - 收集需要链接的文件:目标文件(`.o`)和库文件(`.a`)。

3. General Compilation Flags(通用编译标志):
   - 定义编译器和工具链的变量。
   - 定义编译标志,包括包含路径、宏定义和其他编译选项。

4. Arch-Specific Configurations(特定架构的配置):
   - 根据 `$ARCH` 包含特定架构的配置文件(例如 `scripts/x86_64-qemu.mk`)。
   - 如果没有交叉编译器,则使用本地的 gcc 和 binutils。

5. Compilation Rules(编译规则):
   - 定义编译规则,将源文件编译为目标文件。
   - 定义递归规则,构建依赖库(am、klib 等)的静态库。
   - 定义链接规则,将目标文件和库文件链接为最终的 ELF 二进制文件。
   - 定义归档规则,将目标文件打包为静态库。
   - 定义 `.d` 文件的包含规则,用于处理头文件的依赖关系。

6. Miscellaneous(其他):
   - 定义构建顺序控制的目标。
   - 定义清理目标,用于清除构建生成的文件和目录。

这个 Makefile 提供了一套通用的编译规则和目标,用于构建 AbstractMachine 内核和库。根据不同的架构和目标,可以通过修改和扩展这个 Makefile 来满足具体的构建需求。
相关推荐
LCG元8 小时前
【面试问题】JIT 是什么?和 JVM 什么关系?
面试·职场和发展
m0_7482455212 小时前
吉利前端、AI面试
前端·面试·职场和发展
kkflash316 小时前
提升专业素养的实用指南
学习·职场和发展
sinat_3070215317 小时前
大数据政策文件——职业道德(山东省大数据职称考试)
大数据·职场和发展
Sudo_Wang20 小时前
力扣150题
算法·leetcode·职场和发展
呆呆的猫1 天前
【LeetCode】9、回文数
算法·leetcode·职场和发展
测试老哥1 天前
Python自动化测试图片比对算法
自动化测试·软件测试·python·测试工具·程序人生·职场和发展·测试用例
破-风1 天前
leetcode---mysql
算法·leetcode·职场和发展
Zhu_S W1 天前
Java web的发展历史
面试·职场和发展
破-风2 天前
leetcode----mysql
算法·leetcode·职场和发展