如何将目标文件放置在单独的子目录中

我在尝试使用 make 将目标文件放在单独的子目录中时遇到了麻烦,这可能是一种非常基本的技术。我已经尝试使用这个页面中的信息: Http://www.gnu.org/software/hello/manual/make/prerequisite-types.html#prerequisite-types

我从 make 得到以下输出:

make: *** No rule to make target `ku.h', needed by `obj/kumain.o'.  Stop.

然而,ku.h 是一个依赖项,而不是一个目标(尽管显然在 c 源文件中包含了 #)。当我不尝试为目标文件使用一个子目录时(例如,忽略 OBJDIR 部分) ,它工作得很好。为什么让我觉得 KU 是目标?

我的 makefile 是这样的: (样式是在阅读各种信息源之后)

.SUFFIXES:
.SUFFIXES: .c .o


CC=gcc
CPPFLAGS=-Wall
LDLIBS=-lhpdf
VPATH=%.c src
VPATH=%.h src
VPATH=%.o obj
OBJDIR=obj


objects= $(addprefix $(OBJDIR)/, kumain.o kudlx.o kusolvesk.o kugetpuz.o kuutils.o \
kurand.o kuASCboard.o kuPDFs.o kupuzstrings.o kugensud.o \
kushapes.o )


ku : $(objects)
$(CC) $(CPPFLAGS) -o ku $(objects) $(LDLIBS)


$(objects) : ku.h kudefines.h kuglobals.h kufns.h | $(OBJDIR)


$(OBJDIR):
mkdir $(OBJDIR)


.PHONY: clean
clean :
rm $(objects)

编辑: 我将更改应用于使用 vpath 指令。我的版本是 VPATH = xxx 和 VPATH% 的糟糕混合。C.然而,我现在得到了另一个问题(这是在我添加错误的 vpath 之前的原始问题)。这是现在的输出:

    gcc  -o ku -lhpdf obj/kumain.o obj/kudlx.o obj/kusolvesk.o ..etc
gcc: obj/kumain.o: No such file or directory
gcc: obj/kudlx.o: No such file or directory
gcc: obj/kusolvesk.o: No such file or directory
gcc: obj/kugetpuz.o: No such file or directory
gcc: obj/kuutils.o: No such file or directory
gcc: obj/kurand.o: No such file or directory
gcc: obj/kuASCboard.o: No such file or directory
gcc: obj/kuPDFs.o: No such file or directory
gcc: obj/kupuzstrings.o: No such file or directory
gcc: obj/kugensud.o: No such file or directory
gcc: obj/kushapes.o: No such file or directory
make: *** [ku] Error 1

看起来 make 并没有为对象文件应用隐式规则,尽管手册中说 “隐式规则告诉 make 如何使用习惯技术,这样当你想使用它们时,你就不必详细指定它们。例如,有一个用于 C 编译的隐式规则。文件名决定运行哪些隐式规则。例如,C 编译通常采用。文件,并创建一个。档案。因此,当看到这种文件名结尾的组合时,make 应用了 C 编译的隐式规则。”还有“在 VPATH 中指定的目录或使用 VPATH 进行搜索时,也会考虑隐式规则(参见使用隐式规则)。”

这里再说一遍“例如,当一个文件 foo.o 没有显式规则时,make 考虑隐式规则,比如如果该文件存在,则编译 foo.c 的内置规则。如果工作目录中缺少这样的文件,就会搜索相应的目录。如果 foo.c 在任何目录中存在(或在 makefile 中提到) ,则应用 C 编译的隐式规则。”

如果您能帮助获得为我的 makefile 工作的隐式规则,我将不胜感激。

编辑2: 感谢 Jack Kelly,我制定了一个明确的规则来编译。文件,因为我不能得到任何地方试图使用隐式规则。还要感谢 al _ miro 提供的 vpath 信息。

下面是工作的 makfile:

.SUFFIXES:
.SUFFIXES: .c .o


CC=gcc
CPPFLAGS=-Wall
LDLIBS=-lhpdf
OBJDIR=obj
vpath %.c src
vpath %.h src


objects = $(addprefix $(OBJDIR)/, kumain.o kudlx.o kusolvesk.o kugetpuz.o kuutils.o \
kurand.o kuASCboard.o kuPDFs.o kupuzstrings.o kugensud.o \
kushapes.o )


ku : $(objects)
$(CC) $(CPPFLAGS) -o ku $(objects) $(LDLIBS)


$(OBJDIR) obj/%.o : %.c ku.h kudefines.h kuglobals.h kufns.h
$(CC) -c $(CPPFLAGS) $< -o $@


.PHONY : clean
clean :
rm $(objects)
155515 次浏览

The VPATH lines are wrong, they should be

vpath %.c  src
vpath %.h  src

i.e. not capital and without the = . As it is now, it doesn't find the .h file and thinks it is a target to be made.

Since you're using GNUmake, use a pattern rule for compiling object files:

$(OBJDIR)/%.o: %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<

In general, you either have to specify $(OBJDIR) on the left hand side of all the rules that place files in $(OBJDIR), or you can run make from $(OBJDIR). VPATH is for sources, not for objects.

Take a look at these two links for more explanation, and a "clever" workaround.

Build from the output directory

Instead of building from the top-level directory, build from the output directory. You can access the source directories by setting the vpath. This option has the advantage that the built-in rules can be used.

build.sh

#!/bin/bash
mkdir -p obj
cp Makefile.template obj/Makefile
cd obj
make "$*"

Makefile

.SUFFIXES:
.SUFFIXES: .c .o


CC=gcc
CPPFLAGS=-Wall
LDLIBS=-lhpdf
VPATH=%.c ../src
VPATH=%.h ../src


objects=kumain.o kudlx.o kusolvesk.o kugetpuz.o kuutils.o \
kurand.o kuASCboard.o kuPDFs.o kupuzstrings.o kugensud.o \
kushapes.o


ku : $(objects)


$(objects) : ku.h kudefines.h kuglobals.h kufns.h


.PHONY: clean
clean :
rm $(objects)

The disadvantage is that error messages do not match the CWD. This can be solved by skipping build.sh and directly building from the obj directory.

Another advantage of this approach is that it's somewhat popular. cmake works in a similar fashion.

Create Rule based on output option

The following solution isn't nice in my opinion, as I really love the built-in rules. However, GNU make doesn't support something like vpath for output directories. And the built-in rules cannot match, as the % in %.o would match obj/foo of obj/foo.o, leaving make with a search in vpath %.c src/ for stuff like src/obj/foo.c, but not src/foo.c.

But this is as close to the built-in rules as you can get, and therefore to my best knowledge the nicest solution that's available.

$(OBJDIR)/%.o: %.c
$(COMPILE.c) $(OUTPUT_OPTION) $<

Explanation: $(COMPILE.c) $(OUTPUT_OPTION) $< actually is how .c.o is implemented, see http://git.savannah.gnu.org/cgit/make.git/tree/default.c (and it's even mentioned in the manual)

Besides, if $(OBJDIR) would only ever contain auto-gererated files, you could create it on-the-fly with an order-only prerequisite, making the clean rule slightly simpler:

$(OBJDIR):
mkdir -p $(OBJDIR)


$(OBJDIR)/%.o: %.c | $(OBJDIR)
$(COMPILE.c) $(OUTPUT_OPTION) $<


.PHONY: clean
clean:
$(RM) -r $(OBJDIR)

This requires that the feature order-only is available, which you can check using $(filter order-only, $(.FETAURES)). I've checked on Kubuntu 14.04 GNU make 3.81 and OpenSUSE 13.1 GNU make 3.82. Both were built with order-only enabled, and am now left puzzled why Kubuntu 14.04 comes with an older version of GNU make than OpenSUSE 13.1. Anyways, gonna download make 4.1 now :)

This is the makefile that I use for most of my projects,

It permits putting source files, headers and inline files in subfolders, and subfolders of subfolders and so-forth, and will automatically generate a dependency file for each object This means that modification of headers and inline files will trigger recompilation of files which are dependent.

Source files are detected via shell find command, so there is no need to explicitly specify, just keep coding to your hearts content.

It will also copy all files from a 'resources' folder, into the bin folder when the project is compiled, which I find handy most of the time.

To provide credit where it is due, the auto-dependencies feature was based largely off Scott McPeak's page that can be found HERE, with some additional modifications / tweaks for my needs.

Example Makefile

#Compiler and Linker
CC          := g++-mp-4.7


#The Target Binary Program
TARGET      := program


#The Directories, Source, Includes, Objects, Binary and Resources
SRCDIR      := src
INCDIR      := inc
BUILDDIR    := obj
TARGETDIR   := bin
RESDIR      := res
SRCEXT      := cpp
DEPEXT      := d
OBJEXT      := o


#Flags, Libraries and Includes
CFLAGS      := -fopenmp -Wall -O3 -g
LIB         := -fopenmp -lm -larmadillo
INC         := -I$(INCDIR) -I/usr/local/include
INCDEP      := -I$(INCDIR)


#---------------------------------------------------------------------------------
#DO NOT EDIT BELOW THIS LINE
#---------------------------------------------------------------------------------
SOURCES     := $(shell find $(SRCDIR) -type f -name *.$(SRCEXT))
OBJECTS     := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT)))


#Defauilt Make
all: resources $(TARGET)


#Remake
remake: cleaner all


#Copy Resources from Resources Directory to Target Directory
resources: directories
@cp $(RESDIR)/* $(TARGETDIR)/


#Make the Directories
directories:
@mkdir -p $(TARGETDIR)
@mkdir -p $(BUILDDIR)


#Clean only Objecst
clean:
@$(RM) -rf $(BUILDDIR)


#Full Clean, Objects and Binaries
cleaner: clean
@$(RM) -rf $(TARGETDIR)


#Pull in dependency info for *existing* .o files
-include $(OBJECTS:.$(OBJEXT)=.$(DEPEXT))


#Link
$(TARGET): $(OBJECTS)
$(CC) -o $(TARGETDIR)/$(TARGET) $^ $(LIB)


#Compile
$(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT)
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) $(INC) -c -o $@ $<
@$(CC) $(CFLAGS) $(INCDEP) -MM $(SRCDIR)/$*.$(SRCEXT) > $(BUILDDIR)/$*.$(DEPEXT)
@cp -f $(BUILDDIR)/$*.$(DEPEXT) $(BUILDDIR)/$*.$(DEPEXT).tmp
@sed -e 's|.*:|$(BUILDDIR)/$*.$(OBJEXT):|' < $(BUILDDIR)/$*.$(DEPEXT).tmp > $(BUILDDIR)/$*.$(DEPEXT)
@sed -e 's/.*://' -e 's/\\$$//' < $(BUILDDIR)/$*.$(DEPEXT).tmp | fmt -1 | sed -e 's/^ *//' -e 's/$$/:/' >> $(BUILDDIR)/$*.$(DEPEXT)
@rm -f $(BUILDDIR)/$*.$(DEPEXT).tmp


#Non-File Targets
.PHONY: all remake clean cleaner resources

For all those working with implicit rules (and GNU MAKE). Here is a simple makefile which supports different directories:

#Start of the makefile


VPATH = ./src:./header:./objects


OUTPUT_OPTION = -o objects/$@


CXXFLAGS += -Wall -g -I./header


Target = $(notdir $(CURDIR)).exe


Objects := $(notdir $(patsubst %.cpp,%.o,$(wildcard src/*.cpp)))






all: $(Target)


$(Target): $(Objects)
$(CXX) $(CXXFLAGS) -o $(Target) $(addprefix objects/,$(Objects))




#Beware of -f. It skips any confirmation/errors (e.g. file does not exist)


.PHONY: clean
clean:
rm -f $(addprefix objects/,$(Objects)) $(Target)

Lets have a closer look (I will refer to the current Directory with curdir):

This line is used to get a list of the used .o files which are in curdir/src.

Objects := $(notdir $(patsubst %.cpp,%.o,$(wildcard src/*.cpp)))
#expands to "foo.o myfoo.o otherfoo.o"

Via variable the output is set to a different directory (curdir/objects).

OUTPUT_OPTION = -o objects/$@
#OUTPUT_OPTION will insert the -o flag into the implicit rules

To make sure the compiler finds the objects in the new objects folder, the path is added to the filename.

$(Target): $(Objects)
$(CXX) $(CXXFLAGS) -o $(Target) $(addprefix objects/,$(Objects))
#                                    ^^^^^^^^^^^^^^^^^^^^

This is meant as an example and there is definitly room for improvement.

For additional Information consult: Make documetation. See chapter 10.2

Oracle: Programming Utilities Guide

You can specify the -o $@ option to your compile command to force the output of the compile command to take on the name of the target. For example, if you have:

  • sources: cpp/class.cpp and cpp/driver.cpp
  • headers: headers/class.h

...and you want to place the object files in:

  • objects: obj/class.o obj/driver.o

...then you can compile cpp/class.cpp and cpp/driver.cpp separately into obj/class.o and obj/driver.o, and then link, with the following Makefile:

CC=c++
FLAGS=-std=gnu++11


INCS=-I./headers
SRC=./cpp
OBJ=./obj
EXE=./exe


${OBJ}/class.o:    ${SRC}/class.cpp
${CC} ${FLAGS} ${INCS} -c $< -o $@


${OBJ}/driver.o:    ${SRC}/driver.cpp ${SRC}/class.cpp
${CC} ${FLAGS} ${INCS} -c $< -o $@


driver: ${OBJ}/driver.o ${OBJ}/class.o
${CC} ${FLAGS} ${OBJ}/driver.o ${OBJ}/class.o -o ${EXE}/driver

For anyone that is working with a directory style like this:

project
> src
> pkgA
> pkgB
...
> bin
> pkgA
> pkgB
...

The following worked very well for me. I made this myself, using the GNU make manual as my main reference; this, in particular, was extremely helpful for my last rule, which ended up being the most important one for me.

My Makefile:

PROG := sim
CC := g++
ODIR := bin
SDIR := src
MAIN_OBJ := main.o
MAIN := main.cpp
PKG_DIRS := $(shell ls $(SDIR))
CXXFLAGS = -std=c++11 -Wall $(addprefix -I$(SDIR)/,$(PKG_DIRS)) -I$(BOOST_ROOT)
FIND_SRC_FILES = $(wildcard $(SDIR)/$(pkg)/*.cpp)
SRC_FILES = $(foreach pkg,$(PKG_DIRS),$(FIND_SRC_FILES))
OBJ_FILES = $(patsubst $(SDIR)/%,$(ODIR)/%,\
$(patsubst %.cpp,%.o,$(filter-out $(SDIR)/main/$(MAIN),$(SRC_FILES))))


vpath %.h $(addprefix $(SDIR)/,$(PKG_DIRS))
vpath %.cpp $(addprefix $(SDIR)/,$(PKG_DIRS))
vpath $(MAIN) $(addprefix $(SDIR)/,main)


# main target
#$(PROG) : all
$(PROG) : $(MAIN) $(OBJ_FILES)
$(CC) $(CXXFLAGS) -o $(PROG) $(SDIR)/main/$(MAIN)


# debugging
all : ; $(info $$PKG_DIRS is [${PKG_DIRS}])@echo Hello world


%.o : %.cpp
$(CC) $(CXXFLAGS) -c $< -o $@


# This one right here, folks. This is the one.
$(OBJ_FILES) : $(ODIR)/%.o : $(SDIR)/%.h
$(CC) $(CXXFLAGS) -c $< -o $@


# for whatever reason, clean is not being called...
# any ideas why???
.PHONY: clean


clean :
@echo Build done! Cleaning object files...
@rm -r $(ODIR)/*/*.o

By using $(SDIR)/%.h as a prerequisite for $(ODIR)/%.o, this forced make to look in source-package directories for source code instead of looking in the same folder as the object file.

I hope this helps some people. Let me know if you see anything wrong with what I've provided.

BTW: As you may see from my last comment, clean is not being called and I am not sure why. Any ideas?

None of these answers seemed simple enough - the crux of the problem is not having to rebuild:

makefile

OBJDIR=out
VPATH=$(OBJDIR)


# make will look in VPATH to see if the target needs to be rebuilt
test: moo
touch $(OBJDIR)/$@

example use

touch moo
# creates out/test
make test
# doesn't update out/test
make test
# will now update test
touch moo
make test