Skip to content

LV070-指定生成文件路径

一、文件准备

1. 目录结构

各文件所在目录的结构如下:

c
.
├── main.c
├── Makefile
├── test1.c
├── test1.h
├── test2.c
└── test2.h

0 directories, 6 files

2. 源码

2.1 main.c

c
#include <stdio.h>
#include "test1.h"
#include "test2.h"

int main(int argc, const char *argv[])
{
	printf("This is main file!\n");
	test1Fun();
	test2Fun();
	return 0;
}

2.2 test1.c

c
#include <stdio.h>

void test1Fun()
{
    printf("This is test1.c file\n");
}

2.3 test2.c

c
#include <stdio.h>

void test2Fun()
{
    printf("This is test2.c file\n");
}

2.4 test1.h

c
#ifndef __TEST1_H__
#define __TEST1_H__

void test1Fun();

#endif

2.5 test2.h

c
#ifndef __TEST2_H__
#define __TEST2_H__

void test2Fun();

#endif

二、生成文件路径

1. 指定生成文件路径

我们每次生成的一大堆的文件都是存放在当前目录下的,文件多了之后,就会看起来很乱,我们能否指定生成文件路径呢?当然是可以的啦。这里直接以实例来说明。

1.1 文件准备

首先我们新建一个 obj 文件夹用于存放生成的 .o 中间文件。

shell
mkdir obj

我们使用上边的文件搜索相关测试文件来测试,我们新建两个目录,并将两个相关文件移动到指定目录:

shell
mkdir test1 test2 user
mv test1.c test1.h test1
mv test2.c test2.h test2
mv main.c user

所以现在的目录结构为:

shell
.
├── Makefile
├── obj
├── test1
   ├── test1.c
   └── test1.h
├── test2
   ├── test2.c
   └── test2.h
└── user
    └── main.c

4 directories, 6 files

1.2 两次试错

1.2.1 测试1

我们编写 Makefile 内容如下:

makefile
OBJ     := main.o  test1.o  test2.o

INCLUDE := -I test1 -I test2
OBJDIR  := ./obj
vpath %.c user test1 test2

# 可执行程序 main
main: $(OBJ)
	gcc $(OBJ) -o main $(INCLUDE)

$(OBJ):$(OBJDIR)/%.o: %.c
	gcc -c $< -o $@ $(INCLUDE)

print:
	@echo "OBJ = $(OBJ)"
.PHONY: clean
clean:
	rm -rf $(OBJDIR)/*.o main

然后我们执行 make 命令,会发现报了下边的错误:

shell
Makefile:11: target 'main.o' doesn't match the target pattern
Makefile:11: target 'test1.o' doesn't match the target pattern
Makefile:11: target 'test2.o' doesn't match the target pattern
gcc -c  -o main.o -I test1 -I test2
gcc: fatal error: no input files
compilation terminated.
Makefile:12: recipe for target 'main.o' failed
make: *** [main.o] Error 1

我们看报错的说明,就是生成 main 的时候,找不到它所依赖的 main.o 、 test1.o 和 test2.o ,并且在模式规则的匹配中,匹配也会有问题,我们来分析一下,我们拆开上边的模式规则,并将变量进行替换,可以得到:

makefile
OBJ     := main.o  test1.o  test2.o

INCLUDE := -I test1 -I test2
OBJDIR  := ./obj
vpath %.c user test1 test2

# 可执行程序 main
main: main.o  test1.o  test2.o
	gcc main.o  test1.o  test2.o -o main $(INCLUDE)

# =============================================================================
# 以下是模式规则的展开形式(但这种写法是错误的,仅用于演示问题)
# =============================================================================

# 规则1: main.o 的编译规则
# 目标: main.o (位于当前目录)
# 依赖: ./obj/%.o (模式匹配,期望在obj目录下)
#      %.c (源文件,使用vpath搜索)
# 问题: 目标文件名 main.o 与依赖模式 ./obj/%.o 不匹配!
#       % 会匹配 "main",但依赖路径会是 ./obj/main.o,而实际目标是 main.o
main.o:./obj/%.o:%.c
	gcc -c $< -o $@ $(INCLUDE)
	# $< = 源文件路径 (如 user/main.c)
	# $@ = 目标文件路径 (如 main.o,但这里期望是 ./obj/main.o)

# 规则2: test1.o 的编译规则(与上面同样的问题)
# 目标: test1.o
# 依赖: ./obj/%.o (期望在obj目录下)
#      %.c (源文件)
# 问题: 目标路径与依赖路径不一致
test1.o:./obj/%.o:%.c
	gcc -c $< -o $@ $(INCLUDE)

# 规则3: test2.o 的编译规则(与上面同样的问题)
# 目标: test2.o
# 依赖: ./obj/%.o (期望在obj目录下)
#      %.c (源文件)
# 问题: 目标路径与依赖路径不一致
test2.o:./obj/%.o:%.c
	gcc -c $< -o $@ $(INCLUDE)

# =============================================================================
# 总结:这种写法的错误在于
# 1. 目标文件名 (main.o) 与依赖模式 (./obj/%.o) 不匹配
# 2. 编译输出的目标文件应该在 ./obj/ 目录下,但这里指定的是当前目录
# 3. 正确的写法应该是: ./obj/main.o:./obj/%.o:%.c
# =============================================================================

.PHONY: clean
clean:
	rm -rf $(OBJDIR)/*.o main

我们会发现, % 匹配的时候,前边的目标文件格式与后边匹配的不太一致,并且我们生成的 .o 文件是存放在 obj 目录下的, gcc 编译的时候是找不到这几个文件的。

1.2.2 测试2

我们可以修改如下:

makefile
OBJ     := main.o  test1.o  test2.o

INCLUDE := -I test1 -I test2
OBJDIR  := ./obj
vpath %.c user test1 test2

# 可执行程序 main
main: ./obj/main.o  ./obj/test1.o  ./obj/test2.o
	gcc ./obj/main.o  ./obj/test1.o  ./obj/test2.o -o main $(INCLUDE)

./obj/main.o:./obj/%.o:%.c
	gcc -c $< -o $@ $(INCLUDE)

./obj/test1.o:./obj/%.o:%.c
	gcc -c $< -o $@ $(INCLUDE)

./obj/test2.o:./obj/%.o:%.c
	gcc -c $< -o $@ $(INCLUDE)


.PHONY: clean
clean:
	rm -rf $(OBJDIR)/*.o main

然后我们再执行 make 命令,会发现有如下提示:

shell
gcc -c user/main.c -o obj/main.o -I test1 -I test2
gcc -c test1/test1.c -o obj/test1.o -I test1 -I test2
gcc -c test2/test2.c -o obj/test2.o -I test1 -I test2
gcc ./obj/main.o  ./obj/test1.o  ./obj/test2.o -o main -I test1 -I test2

此时文件结构如下:

shell
.
├── main
├── Makefile
├── obj
   ├── main.o
   ├── test1.o
   └── test2.o
├── test1
   ├── test1.c
   └── test1.h
├── test2
   ├── test2.c
   └── test2.h
└── user
    └── main.c

发现,生成的中间文件全部进入了 obj 目录。

1.3 正确的格式

经过前边的试错,我们可以将 Makefile 写成如下内容:

makefile
INCLUDE := -I test1 -I test2
OBJDIR  := ./obj
OBJ     := $(OBJDIR)/main.o  $(OBJDIR)/test1.o  $(OBJDIR)/test2.o
TARGET  := main
vpath %.c user test1 test2

# 可执行程序 main
$(TARGET): $(OBJ)
	gcc $(OBJ) -o $@ $(INCLUDE)

$(OBJ):$(OBJDIR)/%.o:%.c
	gcc -c $< -o $@ $(INCLUDE)

# 创建 obj 目录
$(OBJDIR):
	@mkdir -p $@

.PHONY: clean
clean:
	rm -rf $(OBJDIR)/*.o main

这样的 Makefile 就简洁了很多了。

2. 最终目标路径

上边我们已经把中间文件的路径设置过了,那还有最终生成的目标文件的,但其实也没什么必要,毕竟最终生成的文件只有一个,也不会很乱,不过这里还是记录一下吧,我们只需要在写目标文件的时候加上路径就可以了:

makefile
INCLUDE := -I test1 -I test2
OBJDIR  := ./obj
OBJ     := $(OBJDIR)/main.o  $(OBJDIR)/test1.o  $(OBJDIR)/test2.o
TARGET  := ./bin/main
vpath %.c user test1 test2

# 可执行程序 main
$(TARGET): $(OBJ)
	gcc $(OBJ) -o $@ $(INCLUDE)

$(OBJ):$(OBJDIR)/%.o:%.c
	gcc -c $< -o $@ $(INCLUDE)


.PHONY: clean
clean:
	rm -rf $(OBJDIR)/*.o main

这样我们最终生成目标文件为 main ,并且,它将会被存放到 ./bin 目录下。