【问题标题】:How to place object files in separate subdirectory如何将目标文件放在单独的子目录中
【发布时间】:2011-07-07 21:07:25
【问题描述】:

我在尝试使用 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 是一个依赖而不是一个目标(尽管它显然是 #included 在 c 源文件中)。当我不尝试使用目标文件的子目录(即错过 OBJDIR 部分)时,它可以正常工作。为什么 make 认为 ku.h 是目标?

我的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 xxx 的错误混合。但是我现在遇到了另一个问题(这是我添加错误 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 编译通常需要一个 .c 文件并生成一个 .o 文件。因此,当 make 看到这种文件名结尾组合时,它会应用 C 编译的隐式规则。以及“在考虑隐式规则时也会通过 VPATH 或使用 vpath 指定的目录进行搜索(请参阅使用隐式规则)。”

再次在这里“例如,当文件 foo.o 没有显式规则时,make 会考虑隐式规则,例如如果该文件存在则编译 foo.c 的内置规则。如果这样的文件在当前目录,在适当的目录中搜索它。如果 foo.c 在任何目录中存在(或在 makefile 中提到),则应用 C 编译的隐式规则。"

如果能帮助我获得适用于我的 makefile 的隐式规则,我们将不胜感激。

编辑 2: 感谢 Jack Kelly,我已经制定了一个明确的规则来编译 .c 文件,因为我无法在任何地方尝试使用隐式规则。还要感谢 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)

【问题讨论】:

  • 样式说明:$(CPPFLAGS) 传统上用于 C 预处理器标志,而 $(CFLAGS) 用于编译器标志。

标签: makefile gnu-make


【解决方案1】:

由于您使用的是 GNUmake,因此请使用模式规则来编译目标文件:

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

【讨论】:

  • 这对我不起作用:它查找 $(OBJDIR)/%.c 而不是 %.c。知道如何在使用 $(OBJDIR) 作为目标的同时使用当前目录中的 C 文件作为依赖项吗?
  • @Lelanthran,上面获取C文件的解决方案在当前目录下。如果你使用“$(OBJDIR)/%.c”而不是“%.c”,你的C代码应该把它放在“$(OBJDIR)”中。
  • 如何创建$(OBJDIR)?如果我使用规则$(OBJDIR)/%.o: %.c $(OBJDIR) $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $&lt;,它每次都会从头开始重新编译
  • 尽管我很欣赏这些答案的简洁性,但作为 Makefile 的初学者,我只想知道将目标文件输出到不同目录的标准命令。所有隐含的快捷方式和诸如此类的答案方式更加令人困惑
【解决方案2】:

这是我用于大多数项目的 makefile,

它允许将源文件、头文件和内联文件放在子文件夹中,以及子文件夹的子文件夹等等,并且会为每个对象自动生成一个依赖文件 这意味着对头文件和内联文件的修改文件将触发重新编译依赖的文件。

源文件是通过shell find命令检测的,所以不需要明确指定,只要按你的心编写代码就行了。

它还会在项目编译时将“resources”文件夹中的所有文件复制到 bin 文件夹中,我觉得大部分时间都很方便。

为了提供应有的功劳,自动依赖功能主要基于 Scott McPeak 的页面,该页面可以在 HERE 找到,并根据我的需要进行了一些额外的修改/调整。

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

【讨论】:

  • 'mp' 表示 MacPorts。 g++-mp-4.7 是来自 GCC for MacPorts 的 C++ 编译器
  • 如果头文件发生变化,编译规则似乎不会触发,只有源文件。
  • 在 *.$(SRCEXT) 周围添加“”,以便 find 递归查找源文件(第 26 行)
  • 制作目标资源时使用cp -r递归复制复制$(RESDIR)/.没有 *,所以如果资源目录是空的,它仍然可以工作
【解决方案3】:

VPATH 行错了,应该是

vpath %.c  src
vpath %.h  src

即不是大写,也没有 = 。就像现在一样,它没有找到 .h 文件,并认为它是一个目标。

【讨论】:

    【解决方案4】:

    一般来说,您要么必须在将文件放置在$(OBJDIR) 的所有规则的左侧指定$(OBJDIR),要么可以从$(OBJDIR) 运行make。 VPATH 用于源,而不是对象。

    查看这两个链接以获得更多解释和“聪明”的解决方法。

    【讨论】:

    • 其实这是最好的答案。有趣的是,它的投票数为零(在我之前),而所有绝望的、不完整的、破碎的等尝试都拥有这一切……;)谢谢,Theo B.,为我节省了一些时间!
    【解决方案5】:

    从输出目录构建

    不是从顶级目录构建,而是从输出目录构建。您可以通过设置 vpath 来访问源目录。此选项的优点是可以使用内置规则。

    build.sh

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

    生成文件

    .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)
    

    缺点是错误消息与 CWD 不匹配。这可以通过跳过build.sh 并直接从obj 目录构建来解决。

    这种方法的另一个优点是它有点受欢迎。 cmake 以类似的方式工作。

    根据输出选项创建规则

    在我看来,以下解决方案并不好,因为我真的很喜欢内置规则。但是,GNU make 不支持像 vpath 这样的输出目录。并且内置规则无法匹配,因为%.o 中的% 将匹配obj/foo.oobj/foo,留下makevpath %.c src/ 中搜索src/obj/foo.c 之类的东西,但不是@987654336 @。

    但这与您可以获得的内置规则一样接近,因此据我所知,这是可用的最佳解决方案。

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

    说明:$(COMPILE.c) $(OUTPUT_OPTION) $&lt;实际上是.c.o的实现方式,见http://git.savannah.gnu.org/cgit/make.git/tree/default.c(手册中甚至提到过)

    此外,如果 $(OBJDIR) 只包含自动生成的文件,您可以使用仅限订单的先决条件即时创建它,从而使清理规则稍微简单一些:

    $(OBJDIR):
            mkdir -p $(OBJDIR)
    
    $(OBJDIR)/%.o: %.c | $(OBJDIR)
            $(COMPILE.c) $(OUTPUT_OPTION) $<
    
    .PHONY: clean
    clean:
            $(RM) -r $(OBJDIR)
    

    这要求 order-only 功能可用,您可以使用$(filter order-only, $(.FETAURES)) 进行检查。我检查了 Kubuntu 14.04 GNU make 3.81 和 OpenSUSE 13.1 GNU make 3.82。两者都是在启用 order-only 的情况下构建的,现在我很困惑为什么 Kubuntu 14.04 带有比 OpenSUSE 13.1 更旧的 GNU make 版本。不管怎样,现在要下载 make 4.1 :)

    【讨论】:

      【解决方案6】:

      适用于所有使用隐式规则(和 GNU MAKE)的人。这是一个支持不同目录的简单makefile:

      #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)
      

      让我们仔细看看(我会用 curdir 指代当前目录):

      此行用于获取 curdir/src 中已使用的 .o 文件的列表。

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

      通过变量将输出设置到不同的目录(curdir/objects)。

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

      为确保编译器在新对象文件夹中找到对象,将路径添加到文件名中。

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

      这只是一个例子,肯定有改进的余地。

      更多信息请咨询: Make documetation. See chapter 10.2

      或者: Oracle: Programming Utilities Guide

      【讨论】:

        【解决方案7】:

        您可以在编译命令中指定 -o $@ 选项以强制编译命令的输出采用目标名称。例如,如果您有:

        • 来源:cpp/class.cpp 和 cpp/driver.cpp
        • 标题:标题/class.h

        ...你想把目标文件放在:

        • 对象:obj/class.o obj/driver.o

        ...那么你可以将cpp/class.cpp和cpp/driver.cpp分别编译成obj/class.o和obj/driver.o,然后链接,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
        

        【讨论】:

          【解决方案8】:

          对于任何使用这样的目录样式的人:

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

          以下内容对我来说效果很好。我自己做的,使用 the GNU make manual 作为我的主要参考;尤其是this,它对我的​​最后一条规则非常有帮助,它最终对我来说是最重要的一条。

          我的 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
          

          通过使用$(SDIR)/%.h 作为$(ODIR)/%.o 的先决条件,这迫使make 在源代码包目录中查找源代码而不是在与目标文件相同的文件夹中查找。

          我希望这对某些人有所帮助。如果您发现我提供的内容有任何问题,请告诉我。

          顺便说一句:正如您从我上一条评论中看到的那样,没有调用 clean 并且我不知道为什么。有什么想法吗?

          【讨论】:

            【解决方案9】:

            这些答案似乎都不够简单——问题的症结在于不必重建:

            制作文件

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

            示例使用

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

            【讨论】:

              猜你喜欢
              • 2023-03-22
              • 1970-01-01
              • 1970-01-01
              • 2011-05-05
              • 2010-12-21
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多