如何使用子目录中的源代码为C和C ++创建一个makefile

时间:2016-10-20 21:25:02

标签: c++ c makefile

我有一个目前在OS X上构建Xcode的项目。我正在尝试构建一个makefile,以允许它在其他Un * x系统上构建。我是编写makefile的新手,所以我一直在用网络上的各种例子拼凑一个makefile,也许不出所料,我无法得到结果。我已经在SO上查看了其他类似的问题,并从中获取了我能得到的信息,但我的情况似乎有点不同,因为

  1. 我有大量的源文件,因此明确指定所有源和对象是不切实际的,我需要使用通配符
  2. 我的一些来源位于子目录中,并且可以在不同的子目录中使用相同的文件名,因此需要将对象放置在文件层次结构中以避免冲突
  3. 我在构建C和C ++并在最后将它们链接在一起
  4. 我希望能够构建两个不同的目标,名为" slim"和" eidos",使用目录中源文件的不同子集。
  5. 所以请不要将此标记为重复,除非另一个问题真正满足所有这些要求;谢谢。

    我还想避免使用makefile生成工具,部分原因是它增加了我目前无法理解的另一层次的复杂性,部分是因为它似乎有点矫枉过正,因为我对此并不感兴趣处理标头依赖关系等(见下面的讨论),部分是因为我需要最大程度的可移植性(我不想依赖特定工具,而不是香草make)。

    我从make -n得到的错误很简单,所以我的错误可能非常愚蠢:

    make: *** No rule to make target `gsl/complex/*.c.o', needed by `slim'.  Stop.
    

    但在我看来*.c.o目标应该由我的%.c.o规则处理,所以我感到困惑。我的makefile:

    SHELL = /bin/sh
    CC = gcc
    CXX = g++
    INCLUDES = -iquote./eidos -iquote./gsl -iquote./gsl/rng -iquote./gsl/randist -iquote./gsl/sys -iquote./gsl/specfunc -iquote./gsl/complex
    CCFLAGS = -O3 -v $(INCLUDES)
    CXXFLAGS = -O3 -v $(INCLUDES) -std=c++11
    
    OUTPUTDIR = ./bin/
    MKDIR = mkdir -p $(OUTPUTDIR)
    
    CSOURCES = ./gsl/*/*.c
    SLIM_CXXSOURCES = ./core/*.cpp ./eidos/*.cpp
    EIDOS_CXXSOURCES = ./eidostool/*.cpp ./eidos/*.cpp
    
    COBJECTS = $(patsubst %.c, %.c.o, $(CSOURCES))
    SLIM_CXXOBJECTS = $(patsubst %.cpp, %.cpp.o, $(SLIM_CXXSOURCES))
    EIDOS_CXXOBJECTS = $(patsubst %.cpp, %.cpp.o, $(EIDOS_CXXSOURCES))
    
    all: slim eidos FORCE
    
    slim: $(COBJECTS) $(SLIM_CXXOBJECTS) FORCE
        $(MKDIR)
        $(CXX) $(COBJECTS) $(SLIM_CXXOBJECTS) -o ./bin/slim
    
    eidos: $(COBJECTS) $(EIDOS_CXXOBJECTS) FORCE
        $(MKDIR)
        $(CXX) $(COBJECTS) $(EIDOS_CXXOBJECTS) -o ./bin/eidos
    
    %.cpp.o : %.cpp
        $(CXX) -c $(CXXFLAGS) -o $@ $<
    
    %.c.o : %.c
        $(CC) -c $(CCFLAGS) -o $@ $<
    
    clean: FORCE
        $(RM) -rf $(OUTPUTDIR)
        $(RM) ./*.o
    
    # Would use .PHONY, but we don't want to depend on GNU make.
    # See http://www.gnu.org/software/make/manual/make.html#Phony-Targets
    # and http://www.gnu.org/software/make/manual/make.html#Force-Targets
    FORCE:
    

    我没有尝试建立每个文件头依赖项或类似的任何智能。相反,我只是在每次构建一个顶级目标(slim,eidos,all)时尝试使用FORCE强制完全重建。这是因为我不希望人们在开发过程中使用这个makefile;所有开发都在Xcode的OS X上完成。因此,它不需要极小/高效,只需要可靠地工作以生产最终产品。我不确定我是否正确使用FORCE。

    我也有几个问题。

    1. 我从网上的示例makefiles获取了两个编译命令的$<结尾,但我不知道他们做了什么。那里发生了什么?它看起来像是一个重定向,但它并不是我熟悉的重定向命令,当然Google对于搜索这样的符号事物毫无用处。
    2. patsubst业务有点棘手,而且我不知道如何判断它是否正常工作。我尝试make -np打印出make的内部信息,但它似乎显示了原始定义的变量,而不是其已解析的定义,因此我无法说明最终值是make实际用于我的变量。我该怎么调试呢? patsubstmake的标准部分吗?是否有更好的方法来做我想做的事情?
    3. 感谢。很抱歉忙碌的多部分问题。顺便说一句,我真正想做的就是将以前存在的makefile转换为单独构建源文件,生成.o目标文件,然后在最后进行链接。以前存在的makefile在一次调用g ++时构建了所有内容,由于内存使用和构建时间过长,导致某些用户出现问题。这是旧的Makefile:

      SHELL = /bin/sh
      CC = g++
      CFLAGS = -O3 -Wno-deprecated-register
      INCLUDES = -iquote./eidos -iquote./gsl -iquote./gsl/rng -iquote./gsl/randist -iquote./gsl/sys -iquote./gsl/specfunc -iquote./gsl/complex
      ALL_CFLAGS = $(CFLAGS) $(INCLUDES) -std=c++11
      
      all: slim eidos FORCE
      
      slim: FORCE
          mkdir -p ./bin
          $(CC) $(ALL_CFLAGS) ./core/*.cpp ./eidos/*.cpp ./gsl/*/*.c -o ./bin/slim
      
      eidos: FORCE
          mkdir -p ./bin
          $(CC) $(ALL_CFLAGS) ./eidostool/*.cpp ./eidos/*.cpp ./gsl/*/*.c -o ./bin/eidos
      
      clean: FORCE
          rm -f ./bin/slim
          rm -f ./bin/eidos
      
      # Would use .PHONY, but we don't want to depend on GNU make.
      # See http://www.gnu.org/software/make/manual/make.html#Phony-Targets
      # and http://www.gnu.org/software/make/manual/make.html#Force-Targets
      FORCE:
      

      除了前面提到的缓慢和使用大量内存的问题之外,这种方法还可以。

2 个答案:

答案 0 :(得分:1)

make的基本概念实际上非常简单:对于您希望能够构建的每个文件,您可以指定其先决条件,以及如何根据这些先决条件构建目标文件。 Make可以弄清楚如何将多个构建步骤链接在一起,以便从实际存在的资源中创建最终目标,如果事实上有任何方法可以这样做。

虽然GNU make确实功能强大且已被广泛移植,但我通常建议避免使用特定于make版本的扩展。仅仅因为你可以获取 GNU make几乎任何机器都不会减轻混淆,当它不是给定机器上的默认make时,也不会避免惊慌失措实际上,在构建软件之前,必须先获取并安装GNU make。并且您的特定项目具有足够简单的结构(尽管有源文件的数量),您不需要GNU扩展。

关于你的资格:

  

我有大量的源文件,因此明确指定所有源和对象是不切实际的,我需要使用通配符

在评论中你澄清说“大量的源文件”意味着“超过100”,我猜这意味着“大”是在旁观者的眼中。你所描述的当然不小,但它仍然相当温和。我为具有数百个源文件的软件套件构建了基于make的构建系统。

  

我的一些源代码位于子目录中,并且可以在不同的子目录中使用相同的文件名,因此需要将对象放置在文件层次结构中以避免冲突

这根本不是问题。让自己不知道目录;它主要用于字符串,并让它执行的shell命令适当地解释它们。 target和prerequiste名称包含相对或甚至绝对路径没有特殊问题。

  

我正在构建C和C ++并在最后将它们链接在一起

同样,这不是一个大问题,或者至少不是make特有的问题。这里的问题是你如何将C链接到C ++可能依赖于底层的工具链(编译器和链接器)。

  

我希望能够使用目录中不同的源文件子集构建两个名为“slim”和“eidos”的不同目标

完全没问题。这很常见。

你也注意到了

  

我只是在每次构建一个顶级目标(slim,eidos,all)时尝试使用FORCE强制完全重建。这是因为我不希望人们在开发过程中使用这个makefile;所有开发都在Xcode的OS X上完成。因此,它不需要极小/高效,只需要可靠地工作以生产最终产品。我不确定我是否正确使用FORCE。

对不起,但这没有意义。如果人们在开发期间没有使用makefile,那么预期的用例是什么都没有构建,所以不需要强制。另一方面,通过强制获得了什么?不多。强制使用的唯一理由是,有关项目或make文件的内容使make无法正确评估需要重建的目标。无论如何,如果你提供了一个合适的clean目标,那么只要这是他们想要的,用户就可以轻松地获得一个干净的构建。为用户提供投资力量。它得到了回报。

  

我拿了$&lt;来自web上的示例makefile的两个编译命令的结尾,但我不知道他们做了什么。那里发生了什么?

Make提供一些自动变量;其中一个名为<。在构建规则中,通过语法$<访问的值将扩展为当前规则的第一个先决条件的名称。

  

patsubst业务有点棘手,我不知道如何判断它是否正常工作。

您不需要它,但您可以通过在echo其结果的构建规则中包含命令来测试它是否正常工作。

这是makefile的模板,可能适合您:

CC = gcc
CXX = g++

INCLUDES = -Ieidos -Igsl -Igsl/rng -Igsl/randist -Igsl/sys -Igsl/specfunc -Igsl/complex
CFLAGS = -O3 -Wno-deprecated-register
CXXFLAGS = $(CFLAGS) -std=c++11

CSOURCES = \
    gsl/subdir/something.c \
    gsl/subdir/something_else.c \
    gsl/dir2/important.c

COMMON_CXXSOURCES = \
    eidos/file1.cpp \
    eidos/file2.cpp

SLIM_CXXSOURCES = \
    core/slim1.cpp \
    core/slim2.cpp \
    core/slim3.cpp

EIDOS_CXXSOURCES = \
    eidostool/et.cpp \
    eidostool/et42.cpp \
    eidostool/phone_home.cpp

COMMON_OBJS = $(CSOURCES:.c=.o) $(COMMON_CXXSOURCES:.cpp=.o)
SLIM_OBJS = $(SLIM_CXXSOURCES:.cpp=.o)
EIDOS_OBJS = $(EIDOS_CXXSOURCES:.cpp=.o)

all: slim eidos

# NOTE: commands in build rules must start with a tab character, which
# will not be conveyed via the web representation of what follows.

# How to link object files together to build slim, using the C++ compiler as
# the linker driver
slim: $(SLIM_OBJS) $(COMMON_OBJS)
    $(CXX) $(CXXFLAGS) -o $@ $^

# How to link object files together to build eidos, using the C++ compiler as
# the linker driver
eidos: $(EIDOS_OBJS) $(COMMON_OBJS)
    $(CXX) $(CXXFLAGS) -o $@ $^

# remove all built files
clean:
    rm -f slim eidos $(COMMON_OBJS) $(SLIM_OBJS) $(EIDOS_OBJS)

# You might not actually need anything below, because `make` has built-in
# rules for building object files from source files of various kinds, based
# on their extensions.

# How to build an object file (x.o) from a corresponding C source file (x.c)
.c.o:
    $(CC) $(INCLUDES) $(CFLAGS) -c -o $@ $<

# How to build an object file from a corresponding C++ source file (x.cpp)
.cpp.o:
    $(CXX) $(INCLUDES) $(CXXFLAGS) -c -o $@ $<

关于通配符的注意事项:我建议不要使用它们,而是明确指定需要在构建中包含哪些源。是的,这成为您必须维护的东西,但它也允许您在树中拥有对构建没有贡献的源文件。这些可以是备份副本,工作副本等。只要您在发布之前通过make测试构建项目,就应该很容易发现遗漏。

答案 1 :(得分:1)

不完全确定这对你有用,你的设置中可能会有一些惊喜并不是很明显。

但是,这应该让您了解如何构建Makefile,使用GNU Make选择不同的源/对象集。

CC = gcc
CXX = g++
INCLUDES = -iquote./eidos -iquote./gsl -iquote./gsl/rng -iquote./gsl/randist -iquote./gsl/sys -iquote./gsl/specfunc -iquote./gsl/complex
CCFLAGS = -O3 
CXXFLAGS = -O3 -std=c++11
CPPFLAGS = $(INCLUDES)

OUTPUTDIR = ./bin/
MKDIR = mkdir -p $(OUTPUTDIR)

# shell find command is good for multiple level
# directory searching
CSOURCES := $(shell find gsl -name "*.c")

# wildcard selects all matching files
SLIM_SOURCES = \
    $(CSOURCES) \
    $(wildcard core/*.cpp) \
    $(wildcard eidos/*.cpp)

# filter selects one type of file for substitution
# I use it here to prevent source files that don't match the 
# pattern from being included
SLIM_OBJECTS := \
    $(patsubst %.c,%.c.o,$(filter %.c,$(SLIM_SOURCES))) \
    $(patsubst %.cpp,%.cpp.o,$(filter %.cpp,$(SLIM_SOURCES)))

EIDOS_SOURCES = \
    $(CSOURCES) \
    $(wildcard eidostool/*.cpp) \
    $(wildcard eidos/*.cpp)

EIDOS_OBJECTS := \
    $(patsubst %.c,%.c.o,$(filter %.c,$(EIDOS_SOURCES))) \
    $(patsubst %.cpp,%.cpp.o,$(filter %.cpp,$(EIDOS_SOURCES)))

all: $(OUTPUTDIR)/slim $(OUTPUTDIR)/eidos

# I often find a show target useful for complex makefiles
# to figure out if you are setting the variables correctly
show:
    @echo "SLIM_SOURCES: $(SLIM_SOURCES)"
    @echo "SLIM_OBJECTS: $(SLIM_OBJECTS)"
    @echo "EIDOS_SOURCES: $(EIDOS_SOURCES)"
    @echo "EIDOS_OBJECTS: $(EIDOS_OBJECTS)"

$(OUTPUTDIR)/slim: $(SLIM_OBJECTS)
    $(CXX) -o $@ $^ $(LDFLAGS)

$(OUTPUTDIR)/eidos: $(EIDOS_OBJECTS)
    $(CXX) -o $@ $^ $(LDFLAGS)

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

%.c.o : %.c
    $(CC) -c $(CCFLAGS) $(CPPFLAGS) -o $@ $<

clean: 
    $(RM) $(OUTPUTDIR)/slim $(OUTPUTDIR)/eidos $(SLIM_OBJECTS) $(EIDOS_OBJECTS)