V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
fyyz
V2EX  ›  C

这个 Makefile 下的生成最终二进制文件输出的环节有点问题

  •  
  •   fyyz · 2016-05-17 23:21:20 +08:00 · 1701 次点击
    这是一个创建于 3103 天前的主题,其中的信息可能已经有所发展或是发生改变。

    以前也写过一点 Makefile ,但是基本上都是有一个文件就写条编译命令,于是 Makefile 里面往往会弄得很乱。 双休日看了点 Makefile 的文档,直接上手用在一个小项目里了,但是会出现一点问题,实在搞不定。

    先看下目录结构:

    # tree
    .
    ├── bin
    ├── Makefile
    ├── objs
    └── src
        ├── Httpkit.cpp
        ├── Httpkit.h
        └── main.cpp
    
    

    然后是 Makefile 。其中我重新定义了 %.o:%.cpp 的规则,这样可以把生成的动态链接库文件放到 objs 目录下。

    CXX             = g++
    CXXFLAGS        = -std=c++11 -Wall -Werror
    CFLAGS          = -lcurl -lcurlpp
    SRC_DIR         = ./src
    OBJS_DIR        = ./objs
    BIN_DIR         = ./bin
    
    vpath %.cpp     $(SRC_DIR)
    vpath %.h       $(SRC_DIR)
    vpath %.o       $(OBJS_DIR)
    
    %.o:            %.cpp
            $(CXX) $(CXXFLAGS) -c -o $(OBJS_DIR)/$@ $^
    
    .PHONY:         default clean
    
    default:        HttpKit
    main.o:         main.cpp
    Httpkit.o:      Httpkit.cpp
    HttpKit:        main.o Httpkit.o
            $(CXX) $(CXXFLAGS) $(CFLAGS) -o $(BIN_DIR)/$@ $^
    
    clean:
            -rm -rf $(OBJS_DIR)/*
            -rm -rf $(BIN_DIR)/*
    

    执行 make 的结果:

    g++ -std=c++11 -Wall -Werror -c -o ./objs/main.o ./src/main.cpp
    g++ -std=c++11 -Wall -Werror -c -o ./objs/Httpkit.o ./src/Httpkit.cpp
    g++ -std=c++11 -Wall -Werror -lcurl -lcurlpp -o ./bin/HttpKit main.o Httpkit.o  #注意这行
    g++: error: main.o: No such file or directory
    g++: error: Httpkit.o: No such file or directory
    Makefile:21: recipe for target 'HttpKit' failed
    make: *** [HttpKit] Error 1
    

    上面我标注释的这行输出,虽然自动推导展开了依赖参数,但是依赖的路径完全没有按照 vpath 进行补全。但是奇怪的是,上面两个生成动态链接库的规则也是这样的写法,却完全正确地补全了!

    但是,此时 objs 目录下已经有了两个 .o 文件,再执行 make ,直接只有一行输出:

    g++ -std=c++11 -Wall -Werror -lcurl -lcurlpp -o ./bin/HttpKit ./objs/main.o ./objs/Httpkit.o
    

    WTF ?!?!?!

    现在我有两个问题: 1 ,究竟为什么第一次 make 无法按照 vpath 变量的值自动展开路径,第二次直接就可以了? 2 ,生成最终二进制文件的命令能否也定义成隐含规则自动推导(确实这个意义不大,但是主要还是学习下 Makefile ,并且最终二进制文件并没有后缀名)?

    第 1 条附言  ·  2016-05-18 14:47:38 +08:00

    感谢 @clarkok @arakashic,还有 @Maykiller 同学,经过大家的指导,现在已经可以全部自动化推导来实现了。

    CXX                             = g++ -std=c++11
    CXXFLAGS                        = -Wall -Werror
    CFLAGS                          = -lcurl -lcurlpp
    SRC_DIR                         = ./src
    OBJS_DIR                        = ./objs
    BIN_DIR                         = ./bin
    
    vpath %.cpp                     $(SRC_DIR)
    vpath %.h                       $(SRC_DIR)
    
    .PHONY:                         default clean
    
    $(OBJS_DIR)/%.o:                %.cpp
            $(CXX) $(CXXFLAGS) -c -o $@ $^
    
    $(BIN_DIR)/HttpKit:             $(OBJS_DIR)/main.o $(OBJS_DIR)/Httpkit.o
            $(CXX) $(CXXFLAGS) $(CFLAGS) -o $@ $^
    
    default:                        $(BIN_DIR)/HttpKit
    
    clean:
            @rm -rf $(OBJS_DIR)/*
            @rm -rf $(BIN_DIR)/*
    
    10 条回复    2016-05-18 12:01:02 +08:00
    fyyz
        1
    fyyz  
    OP
       2016-05-17 23:33:43 +08:00
    还有一个问题,怎么看 Make 的隐含规则?网上可以查到不少隐含规则,但是我仔细看了下,并不是与现在版本的 make 的隐含规则精确匹配的。
    clarkok
        2
    clarkok  
       2016-05-18 11:20:27 +08:00
    > HttpKit: main.o Httpkit.o

    这行应该改成

    > HttpKit: $(OBJS_DIR)/main.o $(OBJS_DIR)/Httpkit.o

    然后 `make -p` 可以让 make 输出所有的内部命令,所有的隐含规则都在这里面
    clarkok
        3
    clarkok  
       2016-05-18 11:22:00 +08:00
    另外,讲道理所有的目标都应该写成相对路径,比如

    > HttpKit: ......

    应该变成

    > $(BIN_DIR)/HttpKit: ......

    这样
    fyyz
        4
    fyyz  
    OP
       2016-05-18 11:31:17 +08:00
    @clarkok 能不能依赖里写清楚文件名之后,就不要在规则里再次写一遍文件名了?
    fyyz
        5
    fyyz  
    OP
       2016-05-18 11:32:07 +08:00
    @clarkok 我还想知道为什么第一次 make 报错,第二次就奇迹般地把路径补全对了?
    arakashic
        6
    arakashic  
       2016-05-18 11:33:55 +08:00
    1. VPATH 只解决依赖文件在哪里找的问题,并不添加依赖的前缀。所以 Httpkit 依赖的是 main.o 和 Httpkit.o ,而不是$(OBJS_DIR)/main.o ,$(OBJS_DIR)/Httpkit.o 。同理 main.o 和 Httpkit.o 依赖的是 main.cpp 和 Httpkit.cpp ,而不是$(SRC_DIR)/main.cpp ,$(SRC_DIR)/Httpkit.cpp 。
    2. make 的依赖处理是递归的。
    第一次 make 的时候,编译这一步是在找不到./main.o 和./Httpkit.o 的时候执行的。编译的这一步理应生成./main.o 和./Httpkit.o ,而不是$(OBJS_DIR)/main.o 和$(OBJS_DIR)/Httpkit.o 。所以到链接的时候就 fail 了。
    第二次 make 的时候,因为有了$(OBJS_DIR)/main.o 和$(OBJS_DIR)/Httpkit.o ,所以这个时候 VPATH 就发挥作用了。
    fyyz
        7
    fyyz  
    OP
       2016-05-18 11:38:20 +08:00
    @arakashic
    那么应该这样写,对吗?
    $(OBJS_DIR)/main.o: main.cpp
    $(OBJS_DIR)/Httpkit.o: Httpkit.cpp
    ...
    arakashic
        8
    arakashic  
       2016-05-18 11:47:05 +08:00
    嗯,还有$(BIN_DIR)/HttpKit: $(OBJS_DIR)/main.o $(OBJS_DIR)/Httpkit.o
    fyyz
        9
    fyyz  
    OP
       2016-05-18 11:49:46 +08:00
    @arakashic
    先谢谢你的解答
    还有个问题我不是很懂了,为什么 .cpp 和 .o 都在 vpath 里,但是 .cpp 不需要加 $(SRC_DIR),而 .o 就需要加 $(OBJS_DIR) 了?
    arakashic
        10
    arakashic  
       2016-05-18 12:01:02 +08:00
    简单来说,第一次的时候 make 认为到处(包括 VPATH )都找不到 main.o 和 Httpkit.o ,所以就要生成这俩。生成的时候需要 main.cpp 和 Httpkit.cpp ,而这俩正好是 VPATH 里能找到的,于是就正常编译了。 make 认为既然顺利经过了这一步, main.o 和 Httpkit.o 已经存在了,于是就不用找了。然而并没有,你修改的规则生成的是$(OBJS_DIR)/main.o 和$(OBJS_DIR)/Httpkit.o ,而不是 HttpKit 所依赖的 main.o 和 Httpkit.o 。所以就 fail 了。
    第二次的时候 make 在 VPATH 找到了 main.o 和 Httpkit.o ,所以就直接用了。

    Rule of Thumb : VPATH 是适合用来找源代码的,而不适合找目标文件。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3520 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 04:38 · PVG 12:38 · LAX 20:38 · JFK 23:38
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.