最后更新:
多亏了@Useless,问题解决了!
将$(EXET): $(OBJS)
更改为$(EXET): $(OSRC)
。
更新:2020 年 9 月 29 日
按照@MadScientist的建议,我编辑并从Makefile中删除了不必要的部分。但是,行OSRC := $(addprefix $(ODIR)/, $(OBJS))
似乎没有任何影响,因为*.o
对象文件现在已编译到工作区文件夹,而不是专用./obj
文件夹中,这是我的源问题。
#############################################################
## THIS IS A TEST PROJECT ##
#############################################################
vpath %.cpp src
vpath %.h hdr
#############################################################
## BUILD TASKS ##
#############################################################
HDIR := hdr
#SRCS := src
ODIR := obj
EXET := a
OBJS := main.o mainhdr.o testcode.o
OSRC := $(addprefix $(ODIR)/, $(OBJS))
CXX := g++
CXXFLAGS := -I$(HDIR) -g -Wall -std=c++17
## BUILD DIRECTIVE:
all: $(EXET)
$(EXET): $(OBJS)
$(CXX) $(CXXFLAGS) $^ -o $@
$(ODIR)/%.o: %.cpp
$(CXX) -c $(CXXFLAGS) $^ -o $@
#############################################################
## CLEAN TASK ##
#############################################################
.PHONY: clean
clean:
rm -r $(EXET) *.o
原始问题:
我一直在拔头发,试图让 Make 从特定目录链接预构建的对象,但无济于事。我让我的 Makefile 将目标文件编译到不同的目录中,但在那之后我基本上卡住了。
#############################################################
## THIS IS A TEST PROJECT ##
#############################################################
.SUFFIXES:
.SUFFIXES: .o .h .cpp
vpath %.cpp src
vpath %.h hdr
vpath %.o out
%.h : %.cpp
#############################################################
## BUILD TASKS ##
#############################################################
HDIR := hdr
#SRCS := src #UNUSED
ODIR := obj
EXE := a
OBJS := main.o mainhdr.o testcode.o
## This line added in reference to this thread:
## https://stackoverflow.com/questions/9178285/how-can-makefile-use-separate-directories-for-source-code-and-binaries
## But it doesn't seem to work either
OSRC := $(addprefix $(ODIR)/, $(OBJS))
CPP := g++
CPPFLAGS := -I$(HDIR) -c -g -Wall -std=c++17
## BUILD DIRECTIVE:
all: $(EXE)
## PROBLEM: Doesn't seem to work when prefixed with the output dir:
#$(EXE): $(ODIR)/$(OBJS)
$(EXE): $(OBJS)
$(CPP) -o %@ %^
## This compiles the objects into the output dir just fine.
%.o: %.cpp
# $(CPP) $(CPPFLAGS) $< -o $@
$(CPP) $(CPPFLAGS) $< -o $(ODIR)/$@
#############################################################
## CLEAN TASK ##
#############################################################
.PHONY: clean
clean:
rm -r $(EXE) $(ODIR)/*.o
## End of Makefile ##########################################
到目前为止调试工作:
我将
.o
文件类型添加到.SUFFIXES:
但我怀疑它是否做任何事情。Make 似乎只关心标头和源文件。我做了更多的研究,后来我添加了这部分:
## This line added in reference to this thread:
## https://stackoverflow.com/questions/9178285/how-can-makefile-use-separate-directories-for-source-code-and-binaries
## But it doesn't seem to work either
OSRC := $(addprefix $(ODIR)/, $(OBJS))
但是,它似乎根本没有任何效果。制作也找不到对象目录。
- 尽管它有效,但这行不知何故困扰着我:
$(CPP) $(CPPFLAGS) $< -o $(ODIR)/$@
难道不应该以某种方式修复它以与$(addprefix)
指令保持一致吗?
来自 GNU Make 的快速参考,以防我错过了什么:
$@ target
%< first prerequisite
$^ prerequisites
以下是简单的测试代码:
// cat hdr/mainhdr.h
#ifndef MAINHDR_H
#define MAINHDR_H
#include "testcode.h"
#define BIGCONST 777
void testFunc();
#endif
// cat hdr/testcode.h
#ifndef TESTCODE_H
#define TESTCODE_H
#include <iostream>
#include <string>
#define SMALLCONST 25
class NameTag {
private:
std::string f;
std::string l;
public:
NameTag(std::string first="", std::string last="");
void setFirstN(std::string );
void setLastN(std::string );
std::string getFirstN() const;
std::string getLastN() const;
void operator=(const NameTag &);
};
#endif
// cat src/main.cpp
#include "mainhdr.h"
int main () {
testFunc();
return 0;
}
// cat src/mainhdr.cpp
#include "mainhdr.h"
void testFunc() {
std::cout << "THIS IS A TEST STRING!n";
std::cout << "BIGCONST = " << BIGCONST << std::endl;
std::cout << "SMALLCONST = " << SMALLCONST << std::endl;
NameTag someDude("Good", "Dude");
std::cout << someDude.getFirstN() << " " << someDude.getLastN() << std::endl;
}
// cat src/testcode.cpp
#include "testcode.h"
NameTag::NameTag(std::string first, std::string last) {
f = first;
l = last;
}
void NameTag::setFirstN(std::string first) {
f = first;
}
void NameTag::setLastN(std::string last) {
l = last;
}
std::string NameTag::getFirstN() const {
return f;
}
std::string NameTag::getLastN() const {
return l;
}
void NameTag::operator=(const NameTag &right) {
f = right.f;
l = right.l;
}
这里有许多问题,一些真正的问题和一些没有用的东西。
.SUFFIXES:
.SUFFIXES: .o .h .cpp
这些行是无用的,因为您使用的是模式规则。 仅当您编写后缀规则时才使用.SUFFIXES
。
vpath %.o out
这是没有用的。 您永远不应该使用 vpath 来查找目标。 您只能使用它来查找源文件。 此外,您似乎从未在生成文件的其他任何地方使用out
。
%.h : %.cpp
我不确定这是想做什么,但它实际上什么也没做。
OBJS := main.o mainhdr.o testcode.o
OSRC := $(addprefix $(ODIR)/, $(OBJS))
这很好,但它毫无用处,因为您从未在 makefile 中的其他任何地方实际使用该变量OSRC
。
CPP := g++
CPPFLAGS := -I$(HDIR) -c -g -Wall -std=c++17
仅供参考,makefile 中的常见做法是对C++编译器使用CXX
,而不是CPP
。CPP
(通常的makefile用语)是指C预处理器。 当然,这只是一个建议。
## PROBLEM: Doesn't seem to work when prefixed with the output dir:
#$(EXE): $(ODIR)/$(OBJS)
当然这行不通。 所以$(ODIR)
是obj
,$(OBJS)
是main.o mainhdr.o testcode.o
.这意味着:
$(ODIR)/$(OBJS)
显然扩展到这个:
obj/main.o mainhdr.o testcode.o
这显然不是你想要的。 上面的版本,使用addprefix
将目录附加到变量中的每个单词前面,而不仅仅是将其放在整个变量扩展的开头,这就是您想要的。
$(EXE): $(OBJS)
$(CPP) -o %@ %^
你把OSRC
设置为正确的事情,但你仍然在这里使用$(OBJS)
,所以它对你没有帮助。
另外,显然你的意思是这里的$@
和$^
而不是%@
和%^
.
## This compiles the objects into the output dir just fine.
%.o: %.cpp
$(CPP) $(CPPFLAGS) $< -o $(ODIR)/$@
不,它没有。 我的意思是,是的,它确实将对象编译到输出目录中,但不是"很好"。 任何时候你有一个配方构建了一些不完全$@
的其他文件,那个配方是错误的。 在这里,你的食谱是$(ODIR)/$@
,而不是$@
,所以这绝对是错误的。
你想这样写:
$(ODIR)/%.o: %.cpp
$(CPP) $(CPPFLAGS) $< -o $@
另外请注意,IMO 将像-c
这样的输出控制标志放入CPPFLAGS
(或使用标准名称,CXXFLAGS
)是一种糟糕的做法。 您应该将这样的标志直接放入配方中,而不是在变量中。
通常,明智的做法是在链接行和编译行中包含$(CXXFLAGS)
变量,以便看到与链接器相关的任何编译器标志。 如果变量也包含-c
,则不起作用。