返回

Minix 2 Makefile 提示 No targets provided? 原因与解决方法

Linux

Minix 2 环境下 Makefile 报 'No targets provided near line X' 错误的排查与解决

写 Makefile 时遇到错误是家常便饭,但有些错误提示会让人摸不着头脑。比如在 Minix 2 这种比较老的系统上编译 C 代码,make 命令有时会抛出一个错误:make: No targets provided in Makefile near line X。奇怪的是,错误指向的那一行(比如第 17 行)明明看起来是规则中的一条命令,并非没有目标。

咱们来看看这个具体的例子:

用户在 Minix 2 上尝试编译几个 C 文件,Makefile 内容如下:

CC = cc
CFLAGS = -O -D_MINIX -D_POSIX_SOURCE

PROG1 = main
PROG2 = functions
PROG3 = superblock
PROG4 = inode

OBJ1 = main.o
OBJ2 = functions.o
OBJ3 = superblock.o
OBJ4 = inode.o

all: $(PROG1) $(PROG2) $(PROG3) $(PROG4)

$(PROG1): $(OBJ1)
    $(CC) $(CFLAGS) -o $(PROG1) $(OBJ1)   <--- line 17 据称报错在此附近

$(PROG2): $(OBJ2)
    $(CC) $(CFLAGS) -o $(PROG2) $(OBJ2)

$(PROG3): $(OBJ3)
    $(CC) $(CFLAGS) -o $(PROG3) $(OBJ3)

$(PROG4): $(OBJ4)
    $(CC) $(CFLAGS) -o $(PROG4) $(OBJ4)

main.o: main.c
    $(CC) $(CFLAGS) -c main.c

functions.o: functions.c
    $(CC) $(CFLAGS) -c functions.c

superblock.o: superblock.c
    $(CC) $(CFLAGS) -c superblock.c

inode.o: inode.c
    $(CC) $(CFLAGS) -c inode.c

clean:
    rm -f *.o $(PROG1) $(PROG2) $(PROG3) $(PROG4)

.PHONY: all clean

运行 make 后,却收到错误:make: No targets provided in Makefile near line 17。这就怪了,明明 $(PROG1) (也就是 main) 是一个有效的目标,它依赖于 $(OBJ1) (main.o),并且后面跟着一条编译链接命令。这行命令怎么会让 make 觉得“没有提供目标”呢?

问题分析

这个错误提示确实有点迷惑人。通常,“No targets provided” 意味着你直接运行 make,但 Makefile 里没有定义默认目标(比如名为 all 的目标),或者你 make 了一个不存在的目标。

但是,错误信息指向了第 17 行,也就是 $(PROG1) 规则下的第一条命令。这暗示问题可能不是缺少目标定义,而是 make 在解析文件时,在第 17 行附近遇到了语法障碍,导致它无法正确理解当前的规则结构,甚至可能误以为规则定义已经结束,后面跟着的是无效内容。

哪些情况可能导致这种解析混乱呢?

  1. 缩进问题 :Makefile 对命令行的缩进有严格要求。
  2. 隐藏的特殊字符 :从别处复制代码可能带入肉眼不可见的非法字符。
  3. 文件编码或换行符问题 :虽然少见,但特定系统环境下也可能引发问题。
  4. make 程序本身的怪癖 :Minix 2 上的 make 版本可能比较老旧,行为与现代 GNU Make 略有差异。

可能的罪魁祸首与解决方案

下面我们来逐一排查这些可能性,并给出具体的解决步骤。

1. 检查缩进:必须是 Tab 字符!

这是 Makefile 新手最常犯的错误,没有之一。

  • 原理和作用
    Makefile 规定,在每个规则(如 $(PROG1): $(OBJ1)) 下方的命令(如 $(CC) ...必须 以一个硬 Tab 字符 (\t) 开头,不能 使用空格来缩进。如果使用了空格,make 就不会把这行识别为命令,从而可能导致解析错误,有时就会报出像“No targets provided”这样看似不相关的错误,因为它可能认为规则定义不完整或后面紧跟了非法内容。

  • 操作步骤与代码示例
    你可以用几种方法来检查缩进:

    • 使用 cat -Acat -et :这些命令可以显示包括 Tab 在内的特殊字符。Tab 会显示为 ^I

      cat -A Makefile
      # 或者
      cat -et Makefile
      

      仔细观察第 17 行以及其他所有命令行的开头。如果看到的是空格(或者 cat -A 显示为普通空格,cat -et 中显示为空白但行末没有 $ 标记 Tab),那就说明缩进用错了。

    • 使用文本编辑器
      大多数现代的代码编辑器(如 VS Code, Vim, Emacs, Sublime Text)都可以配置显示空格和 Tab 字符。打开 Makefile 文件,检查命令行的行首空白。

      • 在 Vim 中,可以设置 :set list 来显示特殊字符,Tab 会显示为 ^I,行尾是 $
      • 在 VS Code 中,可以通过设置 "editor.renderWhitespace": "all" 来显示所有空白字符。
  • 修正方法
    如果发现是空格,手动将每条命令行开头的连续空格替换为一个 Tab 字符。或者使用编辑器的查找替换功能(注意要区分行首空格和行内空格)。也可以尝试用 sed 命令(注意备份!这个命令会直接修改文件):

    # 假设原来是用 8 个空格缩进的,替换为 Tab
    # 注意:这里的 \t 可能需要特殊处理,或者直接输入一个字面上的 Tab
    # 在 Bash/Zsh 等 shell 中,可以按 Ctrl+V 然后按 Tab 键输入字面 Tab
    # sed -i 's/^        /\t/' Makefile  # 用实际的 Tab 字符替换 \t
    # 更稳妥的方式可能是逐行手动修正
    
  • 安全建议/进阶技巧

    • 配置你的文本编辑器,使其在编辑 Makefile 时自动使用 Tab 进行缩进,或者在保存时自动转换空格为 Tab。
    • 使用 .editorconfig 文件在项目中统一代码风格,包括缩进方式。

2. 清理不可见字符

有时候,从网页、文档或其他地方复制代码粘贴到 Makefile 中,可能会带入一些不可见的、非标准的字符(比如零宽空格、非标准的换行符等),这些字符会干扰 make 的解析。

  • 原理和作用
    make 解析器期望的是标准的 ASCII 或 UTF-8 文本,包含特定的控制字符(如 Tab 和换行符)。非标准字符可能导致解析中断或产生意外行为。

  • 操作步骤与代码示例
    检查和清理这些字符:

    • 使用 cat -vet :这个命令比 cat -A 更彻底,能显示更多非打印字符。

      cat -vet Makefile
      

      留意是否有看起来像空格但不是 ^I (Tab) 或普通空格的东西,或者奇怪的符号。

    • 使用 od -c :以八进制和字符形式显示文件内容,能暴露所有字节。

      od -c Makefile
      

      仔细检查第 17 行及其附近的内容表示。正常的空格显示为空格,Tab 显示为 \t,换行符为 \n。任何不认识的转义序列都值得怀疑。

    • 使用十六进制编辑器 :如 hexedit 或在线工具,直接查看文件的字节码。

  • 修正方法

    • 最好的方法往往是找到问题字符所在的行,手动删除它们,然后重新输入该行内容,确保只使用标准键盘字符和 Tab。

    • 如果怀疑是换行符问题(例如 Windows 的 \r\n 被带到了 Unix 环境),可以使用 dos2unix 工具转换:

      # 如果系统里有 dos2unix
      dos2unix Makefile
      
  • 安全建议/进阶技巧

    • 设置编辑器始终以 Unix 风格(LF,\n)保存文件。
    • 从外部复制代码时,先粘贴到纯文本编辑器中“清洗”一下格式,再粘贴到 Makefile。

3. 简化与逐步测试

当错误难以定位时,可以尝试简化 Makefile,逐步排查。

  • 原理和作用
    通过移除部分代码,创建一个能正常工作的最小版本,然后逐步加回原来的内容,看错误在哪一步重新出现,以此定位问题区域。

  • 操作步骤与代码示例

    1. 备份 当前的 Makefile:cp Makefile Makefile.bak

    2. 最小化测试 :创建一个极简的 Makefile,比如只编译 main

      CC = cc
      CFLAGS = -O -D_MINIX -D_POSIX_SOURCE
      
      PROG1 = main
      OBJ1 = main.o
      
      all: $(PROG1)
      
      $(PROG1): $(OBJ1)
          $(CC) $(CFLAGS) -o $(PROG1) $(OBJ1) # 确保这里的缩进是 Tab
      
      main.o: main.c
          $(CC) $(CFLAGS) -c main.c         # 确保这里的缩进是 Tab
      
      clean:
          rm -f *.o $(PROG1)
      
      .PHONY: all clean
      

      运行 make 看是否还报错。如果不报错,说明问题出在被删除的部分。

    3. 逐步恢复 :如果最小版本没问题,慢慢把 PROG2, PROG3, PROG4 相关的目标和规则加回来,每次添加后都运行 make 测试。例如,先加回 PROG2 的部分:

      # ... (前面不变) ...
      PROG2 = functions
      OBJ2 = functions.o
      # ...
      all: $(PROG1) $(PROG2) # 添加 $(PROG2)
      
      # ... (PROG1 规则不变) ...
      
      $(PROG2): $(OBJ2) # 添加 PROG2 规则
          $(CC) $(CFLAGS) -o $(PROG2) $(OBJ2) # 确保 Tab
      
      # ... (main.o 规则不变) ...
      
      functions.o: functions.c # 添加 functions.o 规则
          $(CC) $(CFLAGS) -c functions.c  # 确保 Tab
      
      # ...
      clean:
          rm -f *.o $(PROG1) $(PROG2) # 更新 clean
      
      .PHONY: all clean
      

      如果在添加某部分后错误重现,就重点检查刚刚添加的代码块,尤其是缩进和特殊字符。

  • 进阶技巧

    • 使用 make -dmake --debug 选项。这会输出非常详细的调试信息,显示 make 是如何解析 Makefile 和决定执行哪些命令的。虽然输出量很大,但仔细分析往往能找到解析中断的地方。Minix 2 的 make 是否支持 -d 或类似选项需要查证,但 GNU make 支持。

4. 检查 make 版本与行为

考虑到是在 Minix 2 这个相对较旧的系统上操作,make 程序本身的行为可能和我们习惯的 GNU Make 有细微差别。

  • 原理和作用
    不同版本的 make(例如 BSD make vs GNU make,或者非常古老的版本)可能对语法的容忍度不同,或者有特定的限制或 bug。

  • 操作步骤

    • 尝试查看 make 的版本信息(如果支持的话):

      make --version
      # 或者 man make 查看手册,了解其类型和选项
      
    • 查阅 Minix 2 的相关文档,了解其自带 make 的特性和限制。

    • 将这个 Makefile(确保语法本身无误后)在一个装有现代 GNU Make 的系统(如 Linux)上运行,看是否还会报同样的错误。如果在现代系统上工作正常,那问题很可能与 Minix 2 的 make 实现有关。这种情况下,可能需要调整 Makefile 语法以适应 Minix 2 的 make,或者寻找 Minix 2 下的 GNU Make 包(如果存在)。

  • 修正方法
    如果确认是 Minix 2 make 的限制或 bug 导致的,并且无法轻易解决,你可能需要:

    • 简化 Makefile 结构,避免使用可能引起问题的特性。
    • 手动执行编译命令,绕过 make
    • 尝试在 Minix 2 上安装或编译一个兼容性更好的 make 版本(如 GNU Make)。

5. 文件名与路径核对

虽然不太可能报这个特定的行号错误,但基础检查永远是必要的。

  • 原理和作用
    make 需要能找到 Makefile 文件本身,以及规则中引用的源文件。
  • 操作步骤
    • 确认你运行 make 命令的当前目录是包含 Makefile 和所有 .c 源文件的目录。使用 pwd 查看当前路径,使用 ls 查看文件列表。
    • 确认 Makefile 的文件名是 Makefilemakefile (大小写敏感性取决于具体系统和 make 版本,但 Makefile 通常是首选)。
    • 确认 main.c, functions.c, superblock.c, inode.c 这些源文件都存在于当前目录。

优化 Makefile(附加建议)

虽然与报错不直接相关,但既然我们分析了这份 Makefile,可以顺便提一下如何让它更简洁、更地道。Makefile 支持一些内置规则和自动化变量,可以减少重复代码。

  • 使用自动化变量
    • $@:代表规则中的目标文件名。
    • $^:代表规则中的所有依赖文件名。
    • $<:代表规则中的第一个依赖文件名(通常用在隐含规则或模式规则中)。
  • 使用模式规则 :对于 .c.o 的编译,可以使用模式规则简化。

改进后的 Makefile 可能看起来像这样:

CC = cc
CFLAGS = -O -D_MINIX -D_POSIX_SOURCE
# CFLAGS += -Wall -Wextra # 建议加上警告选项

# 定义程序名列表
PROGS = main functions superblock inode

# 根据程序名列表生成对应的 .o 文件列表
OBJS = $(PROGS:=.o) # 例如, 结果是 main.o functions.o ...

# 默认目标:构建所有程序
all: $(PROGS)

# 链接各个主程序
# 注意:这个示例假设每个 C 文件都直接编译成一个独立的可执行文件
# 如果它们是互相调用的库函数和主程序,结构会不同
main: main.o
	$(CC) $(CFLAGS) -o $@ $^

functions: functions.o
	$(CC) $(CFLAGS) -o $@ $^

superblock: superblock.o
	$(CC) $(CFLAGS) -o $@ $^

inode: inode.o
	$(CC) $(CFLAGS) -o $@ $^

# 编译 .c 文件为 .o 文件的模式规则
%.o: %.c
	$(CC) $(CFLAGS) -c -o $@ 
CC = cc
CFLAGS = -O -D_MINIX -D_POSIX_SOURCE
# CFLAGS += -Wall -Wextra # 建议加上警告选项

# 定义程序名列表
PROGS = main functions superblock inode

# 根据程序名列表生成对应的 .o 文件列表
OBJS = $(PROGS:=.o) # 例如, 结果是 main.o functions.o ...

# 默认目标:构建所有程序
all: $(PROGS)

# 链接各个主程序
# 注意:这个示例假设每个 C 文件都直接编译成一个独立的可执行文件
# 如果它们是互相调用的库函数和主程序,结构会不同
main: main.o
	$(CC) $(CFLAGS) -o $@ $^

functions: functions.o
	$(CC) $(CFLAGS) -o $@ $^

superblock: superblock.o
	$(CC) $(CFLAGS) -o $@ $^

inode: inode.o
	$(CC) $(CFLAGS) -o $@ $^

# 编译 .c 文件为 .o 文件的模式规则
%.o: %.c
	$(CC) $(CFLAGS) -c -o $@ $<

# 清理规则
clean:
	rm -f *.o $(PROGS)

# 声明伪目标
.PHONY: all clean
lt;
# 清理规则 clean: rm -f *.o $(PROGS) # 声明伪目标 .PHONY: all clean

注意 :上面的优化版本假设每个 .c 文件(如 functions.c)本身就能编译成一个独立运行的程序(functions),这与原始 Makefile 的意图一致,但可能不是真实的程序结构。如果 functions.c, superblock.c, inode.c 实际上是库或模块,它们应该被链接到 main 程序中,Makefile 的结构需要相应调整(例如,main 依赖于所有 .o 文件)。

排查 Makefile 的问题时,耐心和细致非常重要。特别是像“No targets provided”却指向命令行的错误,多半是隐藏的语法细节在作祟。从最常见的 Tab 缩进和特殊字符入手,通常能解决大部分这类问题。