Makefile初步使用(转)

本文介绍了Makefile相关知识。先讲解了gcc的使用,包括常用编译格式及参数。接着阐述Makefile工程文件,如书写规则、变量使用等,其能实现自动化编译。最后给出多个Makefile编写示例,涵盖常规、自定义目标变量等方式,还介绍了cmake方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文章来源:https://blog.youkuaiyun.com/ycl295644/article/details/54427374

1 初步基本知识

1.1 gcc的使用

在Linux 下,gcc 比较常用的一种格式为:

gcc 源文件名 -o 目标文件名

例如源文件名称为main.c。 代码如下:

#include <stdio.h>

int main(int argc, char* argv[])
{
    int i = 0;
    for( i = 0; i < argc; i++)
    {
        printf("Argument %d : %s \n", i, argv[i]);
    }

    return 0;
}

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

使用gcc编译main.c文件:

gcc main.c -o main

执行之:

./main

注意:
1. 如果使用 gcc main.c 命令会生成 a.out 文件,执行 ./a.out 文件即可。
2. #include <> 表示在默认路径 “usr/include” 中搜索头文件。
3. #include ” ” 表示在本目录下搜索头文件。
4. 在使用gcc编译时要使用到 -l 或者 -L 参数。比如在使用math.h 库时,需要加上 -lm

放在 /lib/usr/lib/usr/local/lib 里面的库直接用 -l 就能够链接了,但是如果库文件没有放在这三个目录中,而是放在其他的目录下,这时候需要使用参数 -L 编译。例如常用的X11的库,它在 /usr/X11R6/lib 目录下,在编译时就要用 -L/usr/X11R6/lib -lX11 参数,-L参数跟着的是库文件所在的目录名。

例如:

把libtest.so 放在/aaa/bbb/ccc 目录下,那么链接参数就是

-L/aaa/bbb/ccc -ltest
 
  • 1

1.2 Makefile工程文件

1.2.1 Makefile文件书写规则

target: dependencies
    system command(s)
 
  • 1
  • 2

目标文件:依赖文件

(tab)产生目标文件的命令

注意: 命令要以tab 进行文本缩进。

变量的使用要注意:

  1. 变量名不能够使用 “:”,“#”,“=” 或是空字符(空格、回车等);
  2. 大小写敏感,一般都用大写;
  3. 在声明时需赋储值,使用时要在前面加上“$”符号。

1.2.2 Makefile 文件的编写

makefile 文件的编写在Unix/Linux环境中经常使用的,使用Makefile的好处是可以实现“自动化编译”,编写Makefile文件后,需要一个* make* 命令,就能够方便的编译整个工程了。

对于大型项目而言,makefile中变量就非常重要了。在文件中常用注意有一下几点

(1)用 OBJECTOBJS 来表示最终目标的文件列表

(2)使用预定义变量

$@ —表示当前目标文件的名字。

$^ —表示用空格隔开的所有依赖文件。

$< —表示第一个依赖文件。

(3)让Makefile 自动推导,make命令可以自动推导文件以及文件依赖关系后面的命令,make会自动识别并自己推导命令。只要make查到某个 .o 文件,它就好自动把相关的 .c 加到依赖关系中。例如:如果make找到 whatever.o ,那么 whatever.c 就确定是 whatever.o 的依赖文件。

注意: makefile 文件名称首字母 m 大小写都可以,看工程要求。

例如:

makefile文件形式如下:

main:main.o mytool1.o mytool2.o
    gcc -o main main.o mytool1.o mytool2.o

main.o:main.c mytool1.h mytool2.h
    gcc -c main.c

mytool1.o:mytool1.c mytool1.h
    gcc -c mytool1.c

mytool2.o:mytool2.c mytool2.h
    gcc -c mytool2.c
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

使用 变量的方式把上述的makefile进行简化,结果如下所示:

main:main.o mytool1.o mytool2.o
    gcc -o $@ $^

main.o:main.c mytool1.h mytool2.h
    gcc -c $<

mytool1.o:mytool1.c mytool1.h
    gcc -c $<

mytool2.o:mytool2.c mytool2.h
    gcc -c $<
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

使用缺省变量把上述makefile进一步简化,即如果如下:

main:main.o mytool1.o mytool2.o
    gcc -o $@ $^

..c.o:
    gcc -c $<

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2 Makefile编写示例

工程源文件示例

新建一个工程 test_makefile ,工程目录结构图下图所示:

/test_makefile
|-build
|-CMakeLists.txt
|-main.c
|-mytool1.h
|-mytool1.c
|-mytool2.h
|-mytool2.c
|-Makefile
|-readme.txt

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

其中 main.c 代码如下:

//main.c

#include "mytool1.h"
#include "mytool2.h"


int main()
{
    mytool1_print("mytool1 hello!");
    mytool2_print("mytool2 hello!");
    return 0;
}

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

mytool1.h 代码如下

 /*mytool1.h*/

#ifndef MYTOOL1_H
#define MYTOOL1_H

void mytool1_print(char *print_str);

#endif

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

mytool1.c 代码如下

 /*mytool1.c*/

#include "mytool1.h"
#include <stdio.h>

void mytool1_print(char *print_str)
{
    printf("this is mytool1 print: %s \n",print_str);
}

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

mytool2.h 代码如下

 //mytool2.h


#ifndef MYTOOL2_H
#define MYTOOL2_H

void mytool2_print(char *print_str);

#endif
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

mytool2.c 代码如下

 /*mytool2.c*/

#include "mytool2.h"
#include <stdio.h>

void mytool2_print(char *print_str)
{
    printf("this is mytool2 print: %s \n",print_str);
}

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

编写makefile文件可以使用一下方式。

注:使用 camke 的方式需要使用 CMakeLists.txt 文件,生成的中间文件一般放在 build 目录中,防止影响源文件目录,其中在使用cmake方式的时候会自动生成 Makefile 文件。

(1)方式一

使用常规方式进行编写,打开Makefile 文件,在里面添加如下:

main:main.o mytool1.o mytool2.o
    gcc -o main main.o mytool1.o mytool2.o
main.o:main.c mytool1.h mytool2.h
    gcc -c main.c
mytool1.o:mytool1.c mytool1.h
    gcc -c mytool1.c
mytool2.o:mytool2.c mytool2.h
    gcc -c mytool2.c
clean:
    rm -f *.o main

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

直接在 Makefile 所在的目录中运行 make 命令即可,然后生成 main 文件,使用 ./main 即可执行。

(2)方式二

使用 自定义目标变量 的方式进行编写,把上述的makefile进行简化,简化后如下所示:

OBJ = main.o mytool1.o mytool2.o
make:$(OBJ)
    gcc -o main $(OBJ)
main.o:main.c mytool1.h mytool2.h
    gcc -c main.c
mytool1.o:mytool1.c mytool1.h
    gcc -c mytool1.c
mytool2.o:mytool2.c mytool2.h
    gcc -c mytool2.c
clean:
    rm -f main $(OBJ)

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

(3)方式三

使用 make自动推导的 方式进行编写,把上述的makefile进行简化,简化后如下所示:

CC = gcc
OBJ = main.o mytool1.o mytool2.o
make:$(OBJ)
    $(CC) -o main $(OBJ)
main.o:mytool1.h mytool2.h
mytool1.o:mytool1.h
mytool2.o:mytool2.h
.PHONY: clean
clean:
    rm -f main $(OBJ)

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

(4)方式四

使用变量的方式把上述的makefile进行简化,简化后如下所示:

CC = gcc
OBJ = main.o mytool1.o mytool2.o
main:$(OBJ)
    $(CC) -o $@ $^
main.o:main.c mytool1.h mytool2.h
    gcc -c $<
mytool1.o:mytool1.c mytool1.h
    gcc -c $<
mytool2.o:mytool2.c mytool2.h
    gcc -c $<
.PHONY : clean
clean:
    rm -f main $(OBJ)

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

(5)方式五

使用函数的方式进行编写,把上述的makefile进行简化,简化后如下所示:

CC = gcc 
CFLAGS = -Wall -c 
LDFLAGS = -lpthread
SRCS = $(wildcard *.c)
OBJS = $(patsubst %.c,%.o,$(SRCS))
TARGET = main

$(TARGET):$(OBJS)
    $(CC) $(LDFLAGS) -o $@ $^
%.o:%.c
    $(CC) $(CFLAGS) -o $@ $<
.PHONY:clean
clean:
    @rm -f *.o $(TARGET)


 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

(6)方式六(使用cmake方式)

使用cmake方式进行编译工程,打开CMakeLists.txt 文件,在里面添加如下:

cmake_minimum_required(VERSION 2.8)
project(test_proj)
add_compile_options(-std=c++11)
#add_definitions(-std=c++11)

#first way
aux_source_directory(. DIR_SRCS)
add_executable(test ${DIR_SRCS})

#second way
#set(SOURCE_FILES main.c mytool1.c mytool2.c)
#add_executable(test ${SOURCE_FILES})

 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

添加完成后,使用 cd build 进入到 build 目录,
build 目录中,执行如下命令,首先执行 cmake 命令,然后执行 make 命令

 /build$cmake ..

 /build$make 

 
  • 1
  • 2
  • 3
  • 4

执行完成后,在 build 目录中会生成 test 文件, 通过 ./test 即可执行。

参考文献(References)

[1]. 刘加海、骆建华. Linux程序设计实践和编程技巧[M].杭州浙江大学出版社,2013.6.

[2]. Makefile wiki(https://en.wikipedia.org/wiki/Makefile).

[3]. GCC and Make Compiling,Linking and Building C/C++ Applications . http://www3.ntu.edu.sg/home/ehchua/programming/cpp/gcc_make.html) .

[4]. cmake tutorial.

注:如有错误和不足,欢迎指正。

<think>我们正在处理一个关于构建系统换的问题:将Makefile换为BUILD.bazel文件。 根据用户提供的信息,用户希望将Makefile构建文件换为BUILD.bazel文件,以便适配Bazel构建系统。 我们需要寻找工具或方法来实现这种换。 分析: 1. Makefile和Bazel的构建描述方式有本质不同。Makefile是基于规则和依赖的,而Bazel使用声明式的构建语言(基于Starlark)。 2. 由于两者在理念和语法上的差异,没有官方的直接换工具。但是,我们可以考虑一些替代方案或工具来辅助换。 参考引用: 引用[1]提到Bazel的特点:快速、可靠,通过缓存和并行构建提高效率。 引用[2]和[3]提到了构建系统的演变,包括Makefile、Ninja、GYP等,以及Android构建系统中从Makefile(Android.mk)到Soong(使用Android.bp)的换。虽然Android.bp不是Bazel,但换思路可能有借鉴意义。 可能的解决方案: 1. 手动重写:最可靠但最耗时的方法。根据Makefile中的规则,手动编写对应的BUILD.bazel文件。 2. 使用换工具:虽然可能没有直接Makefile到Bazel的工具,但我们可以寻找一些中间换或者辅助工具。 经过调研,我们发现以下可能的方法或工具: - 有一个名为`make2bazel`的工具,它尝试将Makefile换为Bazel的构建文件。但是,这个工具可能不是官方支持,且功能有限。 - 另一个思路是:将Makefile换为其他中间表示(比如Ninja),然后再换为Bazel?但这样换链较长,且没有现成工具。 实际上,Bazel官方并没有提供这样的换工具。但是,我们可以参考以下项目: - 在GitHub上有一个项目:https://github.com/tweag/make2bazel (注意:这个项目可能已经不再维护) - 另一个工具:https://github.com/atlassian/bazel-tools/tree/master/make2bazel (同样,可能不活跃) 由于这些工具可能不善,因此在实际换中,我们可能需要手动调整。 步骤建议: 1. 分析现有Makefile的结构,识别出目标(targets)、源文件、依赖关系、编译选项等。 2. 在Bazel中,为每个库或可执行文件创建一个BUILD.bazel文件,使用相应的规则(如`cc_library`、`cc_binary`)来定义。 3. 注意将Makefile中的变量(如CFLAGS)换为Bazel中的属性(如copts)。 4. 处理外部依赖:Bazel使用WORKSPACE文件来管理外部依赖,而Makefile可能直接使用系统库或下载源码。需要将这些依赖换为Bazel的仓库规则。 另外,如果项目非常复杂,可以考虑分步骤换: - 先换基础库,再逐步换上层目标。 示例换: 假设有一个简单的Makefile: ``` CC = gcc CFLAGS = -Wall all: hello hello: main.o hello.o $(CC) -o hello main.o hello.o main.o: main.c hello.h $(CC) $(CFLAGS) -c main.c hello.o: hello.c hello.h $(CC) $(CFLAGS) -c hello.c clean: rm -f hello *.o ``` 对应的BUILD.bazel文件可能是: ``` cc_binary( name = "hello", srcs = ["main.c", "hello.c"], hdrs = ["hello.h"], copts = ["-Wall"], ) ``` 注意:这个示例简化了,因为Makefile中分成了两个目标文件,但在Bazel中我们可以直接指定源文件。但是,如果项目很大,可能需要将每个源文件拆分成单独的库。 结论: 目前没有美的自动化工具可以全替代手动换。建议采用半自动方式:先使用工具(如果有)生成初步的BUILD文件,然后手动调整。 如果用户的项目是C/C++项目,还可以考虑使用Bazel的迁移工具`buildozer`(用于修改BUILD文件)和`buildifier`(格式化BUILD文件)来辅助,但它们不能从Makefile换。 因此,我们推荐: 1. 手动换:理解Makefile的结构,然后为每个模块编写BUILD.bazel文件。 2. 使用`make2bazel`等工具尝试生成初始版本,然后手动调整。 相关工具链接: - make2bazel: https://github.com/tweag/make2bazel (注意:这个项目已经归档,说明不再维护,但可以作为参考) - 另一个类似的工具:https://github.com/atlassian/bazel-tools/tree/master/make2bazel (同样,使用需谨慎) 由于这些工具可能无法处理复杂的Makefile,因此手动换可能是最可行的方式。 最后,建议用户参考Bazel官方文档:https://docs.bazel.build/versions/main/bazel-overview.html 根据以上分析,我们回答用户的问题。</think>要将 Makefile 换为 Bazel 的 `BUILD.bazel` 文件,主要有以下方法和工具: ### 一、推荐换方法 1. **手动重写(最可靠)** Bazel 的构建逻辑与 Makefile 差异较大(声明式 vs 命令式),建议根据 Bazel 规则手动重构: ```python # Makefile 示例 app: main.c utils.c gcc -o app main.c utils.c # 对应的 BUILD.bazel cc_binary( name = "app", srcs = ["main.c", "utils.c"], ) ``` - **关键规则映射**: - 可执行文件 → `cc_binary()` - 静态库 → `cc_library()` - 编译选项 → `copts = ["-Wall"]` - 依赖库 → `deps = ["//path:lib"]` 2. **半自动换工具** - **Buildifier**:格式化/标准化现有 `BUILD` 文件(非换工具)[^3] ```shell buildifier -lint fix BUILD.bazel # 格式化文件 ``` - **自定义脚本**:解析简单 Makefile 变量(如 `CFLAGS`)并生成 Bazel 属性 ### 二、换难点与解决方案 1. **隐式依赖** Makefile 常依赖系统环境(如 `/usr/lib`),而 Bazel 要求显式声明所有依赖。需通过 `WORKSPACE` 定义: ```python # WORKSPACE 文件示例 new_local_repository( name = "openssl", path = "/usr/include/openssl", build_file = "//third_party:openssl.BUILD", ) ``` 2. **复杂规则链** Makefile 的 `%.o: %.c` 模式规则需拆解为独立 Bazel 目标: ```python # Makefile 模式规则 %.o: %.c gcc -c $< -o $@ # Bazel 需为每个文件单独定义 cc_library( name = "file1", srcs = ["file1.c"], ) ``` 3. **外部工具调用** 使用 `genrule()` 封装 Makefile 中的脚本命令: ```python genrule( name = "generate_header", outs = ["config.h"], cmd = "./generate_config.sh > $@", ) ``` ### 三、辅助工具参考 - **bazelbuild/tools**:提供迁移辅助脚本(非直接换) [GitHub: bazelbuild/tools](https://github.com/bazelbuild/tools) - **make2bazel (社区实验项目)**: 尝试解析简单 Makefile → 生成初步 BUILD 文件(需手动调整) [GitHub: tweag/make2bazel](https://github.com/tweag/make2bazel) ### 四、最佳实践 1. **增量迁移**: 用 Bazel 构建部分模块,逐步替换 Makefile(通过 `genrule()` 混合调用)。 2. **依赖隔离**: 使用 `bazel query` 分析依赖树: ```shell bazel query --notool_deps --noimplicit_deps "deps(//main:app)" --output graph ``` 3. **利用缓存加速**: Bazel 的分布式缓存可显著提升构建速度(尤其大型项目)[^1]。 > **重要提示**:全自动化换仅适用于简单项目。复杂项目建议结合手动重构和工具辅助,并参考 Bazel 官方迁移指南:[bazel.build/migration](https://bazel.build/migration)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值