make与makefile

make命令

make是linux系统中的一个编译源码的命令。

通常在执行后,会成功生成一个elf的可执行文件。

make 是一个构建管理工具,主要用于自动化编译程序和其他文件。它通过读取一个名为 Makefile 的文件来执行任务。Makefile 是一个包含规则和命令的文本文件,描述了如何编译和链接一个程序或者执行其他类型的任务。在 Makefile 中,你可以定义目标(target)、依赖关系(dependencies)和操作命令(commands),make 工具根据 Makefile 中定义的规则来决定需要重新编译哪些文件,以确保所有的文件都是最新的。

如何编写makefile

编写 Makefile 可以帮助你自动化编译和管理项目的过程,以下是编写 Makefile 的基本步骤和语法说明:

基本语法

  1. 目标(Targets)

    • 目标是 Makefile 中的主要部分,它们指定了要执行的操作。
  2. 依赖关系(Dependencies)

    • 每个目标可以有零个或多个依赖项,这些依赖项是目标执行所必需的文件或其他目标。
  3. 命令(Commands)

    • 每个目标可以有一个或多个命令,用于执行特定操作。命令必须以 Tab 键开始。

示例 Makefile

假设有一个简单的 C 语言项目,包含两个源文件 main.chelper.c,以及它们对应的头文件 helper.h。我们可以编写一个 Makefile 来编译这个项目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Makefile for a simple C program

# 定义编译器
CC = gcc
# 定义编译选项
CFLAGS = -Wall -g

# 默认目标
default: myprogram

# 目标:myprogram
myprogram: main.o helper.o
$(CC) $(CFLAGS) -o myprogram main.o helper.o

# 目标:main.o
main.o: main.c helper.h
$(CC) $(CFLAGS) -c main.c

# 目标:helper.o
helper.o: helper.c helper.h
$(CC) $(CFLAGS) -c helper.c

# 清理目标:clean
clean:
rm -f *.o myprogram

解释和说明

  • CC: 定义了编译器为 gcc
  • CFLAGS: 定义了编译选项,这里包括 -Wall(显示所有警告)和 -g(生成调试信息)。
  • default: 这是一个伪目标,定义了默认的目标,这里是 myprogram
  • myprogram: 目标 myprogram 的依赖是 main.ohelper.o,执行命令是将它们链接为可执行文件 myprogram
  • main.ohelper.o: 分别是 main.chelper.c 的编译目标,每个都有对应的依赖和编译命令。
  • clean: 清理目标,用于删除编译生成的 .o 文件和可执行文件 myprogram

使用方法

在命令行中,你可以运行 make 命令来执行 Makefile 中定义的目标。比如:

  • make:执行默认目标 myprogram,编译整个项目。
  • make clean:执行清理目标,删除所有编译生成的文件。

注意事项

  • 目标和依赖关系中的文件名区分大小写。
  • 命令必须以 Tab 键开始,而不是空格。
  • 可以在 Makefile 中使用变量来简化和参数化编译过程。
  • Makefile 是根据目标和依赖关系来判断是否需要重新编译文件,从而提高了项目的编译效率。

编写 Makefile 可以根据具体项目的需要灵活调整,例如添加更多目标来执行测试、文档生成等其他任务。

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
BINARY_NAME=mythic-cli
LOCAL_PATH=$(shell pwd)
BUILDER_IMAGE=ghcr.io/its-a-feature/mythic_cli:v0.0.3.53
.PHONY: default
default: build_linux ;

# pull in build and env options from global settings
-include ../build.env
-include ../.env

export

copy_binary_linux:
docker run -v ${LOCAL_PATH}/copy_file/:/copy_file/ --rm ${BUILDER_IMAGE} sh -c "cp /mythic-cli_linux /copy_file/mythic-cli"
mv ./copy_file/${BINARY_NAME} . && rm -rf ./copy_file && chmod +x ${BINARY_NAME}

copy_binary_macos:
docker run -v ${LOCAL_PATH}/copy_file/:/copy_file/ --rm ${BUILDER_IMAGE} sh -c "cp /mythic-cli_macos /copy_file/mythic-cli"
mv ./copy_file/${BINARY_NAME} . && rm -rf ./copy_file && chmod +x ${BINARY_NAME}

build_local:
cd src && go build -o ../../mythic-cli .

build_linux_docker:
docker build -t mythic-cli-builder -f Dockerfile .
docker run -v ${LOCAL_PATH}/copy_file/:/copy_file/ --rm mythic-cli-builder sh -c "cp /mythic-cli_linux /copy_file/mythic-cli"
mv ./copy_file/${BINARY_NAME} . && rm -rf ./copy_file && chmod +x ${BINARY_NAME}

build_macos_docker:
docker build -t mythic-cli-builder -f Dockerfile .
docker run -v ${LOCAL_PATH}/copy_file/:/copy_file/ --rm mythic-cli-builder sh -c "cp /mythic-cli_macos /copy_file/mythic-cli"
mv ./copy_file/${BINARY_NAME} . && rm -rf ./copy_file && chmod +x ${BINARY_NAME}

build_linux: copy_binary_linux
build_macos: copy_binary_macos

这个 Makefile 文件定义了一些规则和目标,用来构建一个名为 mythic-cli 的命令行工具。

  1. 变量定义

    • BINARY_NAME=mythic-cli:定义生成的可执行文件的名称为 mythic-cli
    • LOCAL_PATH=$(shell pwd):获取当前工作目录的路径,并存储在 LOCAL_PATH 变量中。
    • BUILDER_IMAGE=ghcr.io/its-a-feature/mythic_cli:v0.0.3.53:定义了用于构建的 Docker 镜像名称和版本。
  2. 默认目标及依赖

    • .PHONY: default:声明 default 是一个伪目标,即不对应实际文件。
    • default: build_linux ;:默认目标是 build_linux,即执行 make 命令时默认会执行 build_linux 目标。
  3. 包含外部文件

    • -include ../build.env:尝试包含上级目录的 build.env 文件。
    • -include ../.env:尝试包含上级目录的 .env 文件。
  4. 目标规则

    • copy_binary_linuxcopy_binary_macos:这两个目标使用 Docker 容器来复制生成的 Linux 和 macOS 版本的 mythic-cli 可执行文件,并将其重命名为 mythic-cli。然后将其移动到当前目录下,并赋予执行权限。
    • build_local:在 src 目录下使用 Go 编译器编译源代码,生成 mythic-cli 可执行文件,并将其移动到上一级目录。
    • build_linux_dockerbuild_macos_docker:这两个目标使用 Docker 容器来构建 Linux 和 macOS 版本的 mythic-cli 可执行文件。它们分别构建一个临时的 mythic-cli-builder 容器,从中复制生成的可执行文件,并进行后续的文件操作。
  5. 依赖关系

    • build_linuxbuild_macos:这两个目标分别依赖于 copy_binary_linuxcopy_binary_macos 目标,因此执行 make build_linuxmake build_macos 时,会先执行相应的复制操作,然后才完成构建。

这个 Makefile 使用了 Docker 来隔离和执行编译任务,确保了构建过程的环境一致性和可重复性。

用Docker 来隔离和执行编译任务

使用 Docker 来隔离和执行编译任务在现代软件开发中非常常见。以下是一些使用 Docker 来执行编译任务的常见优点和场景:

  1. 环境一致性:Docker 可以确保在不同的开发环境中具有一致的编译和构建环境,避免了因环境差异而导致的构建失败或不一致的问题。

  2. 依赖管理:通过将所有依赖项(如编译器、库文件)封装在 Docker 镜像中,可以简化项目的依赖管理,减少了安装和配置软件所需的手动步骤。

  3. 隔离性:使用 Docker 可以将编译过程隔离在容器中,防止其影响主机系统的其他部分,同时也减少了与操作系统版本和配置相关的问题。

  4. 跨平台:Docker 镜像可以在不同的操作系统上运行,从而使得开发团队可以在不同的操作系统上使用相同的构建环境。

  5. 持续集成和持续部署(CI/CD):Docker 可以集成到 CI/CD 流水线中,用于自动化构建、测试和部署流程,提高了开发团队的生产力和发布的可靠性。

因此,使用 Docker 来隔离和执行编译任务已经成为许多现代软件项目的标准实践,特别是对于需要跨平台开发、复杂依赖关系或需要确保环境一致性的项目。