将不同目录中的单独目标文件链接到二进制文件

时间:2013-01-17 13:40:35

标签: makefile

我一直在研究Makefile,它将在所有子目录中搜索包含源代码文件的src目录并编译对象。稍后它会将对象链接到与Makefile相同的目录中的bin目录中的二进制文件。每个二进制文件都以它链接目标文件的子目录命名。对不起,如果这听起来有些令人困惑......

这是一个显示我的意思的图表:

 Makefile
 app1-\
    src-\
      main.c
    obj-\
      main.o
 app2-\
    src-\
      main.c
    obj-\
      main.o
 bin-\
   app1
   app2

目前,每当我运行Makefile时,它都会很好地编译目标文件,但是当涉及链接它们时,它会尝试将所有这些文件链接到第一个二进制文件中。

错误:

Generating dependencies for problem2.1/src/2-1.c...
Compiling problem2.1/src/2-1.c...
Generating dependencies for problem2.1/src/2-1.c...
Compiling problem2.1/src/2-1.c...
Linking bin/problem2.1...
./problem2.2/obj/2-2.o: In function `main':
/cygdrive/c/Users/Hans/git/opsys/task_01/problem2.1/src/2-1.c:9: multiple definition of `_main'
./problem2.1/obj/2-1.o:/cygdrive/c/Users/Hans/git/opsys/task_01/problem2.1/src/2-1.c:9: first defined here
collect2: ld returned 1 exit status
Makefile:57: recipe for target `bin/problem2.1' failed
make: *** [bin/problem2.1] Error 1

我认为这里的主要问题是我误解了Makefiles,有什么方法可以做我想做的事情吗?

到目前为止,唯一与此类似的是使用make的递归功能,这是唯一的方法吗?

我的Makefile:

SRCEXT   = c
SRCDIR   = src
OBJDIR   = obj
BINDIR   = bin

SUBDIRS := $(shell find . -type d -name '*$(SRCDIR)*' -exec dirname {} \; | uniq)
SRCDIRS := $(shell find $(SUBDIRS) -name '*.$(SRCEXT)' -exec dirname {} \; | uniq)
OBJDIRS := $(subst src,obj,$(SRCDIRS))

SRCS    := $(shell find $(SRCDIRS) -name '*.$(SRCEXT)')
OBJREF  := $(subst src,obj,$(SRCS))
OBJS    := $(patsubst %.$(SRCEXT),%.o,$(OBJREF))
APPS    := $(subst ./,,$(SUBDIRS))

DEBUG    = -g
CFLAGS   = -Wall -pedantic -ansi -c $(DEBUG) $(INCLUDES)

ifeq ($(SRCEXT), cpp)
CC       = $(CXX)
else
CFLAGS  += -std=gnu99
endif

.PHONY: all clean distclean

all: $(BINDIR)/$(APPS)

$(BINDIR)/$(APPS): buildrepo $(OBJS)
    @mkdir -p `dirname $@`
    @echo "Linking $@..."
    @$(CC) $(OBJS) $(LDFLAGS) -o $@

$(OBJS): $(SRCS)
    @echo "Generating dependencies for $<..."
    @$(call make-depend,$<,$@,$(subst .o,.d,$@))
    @echo "Compiling $<..."
    @$(CC) $(CFLAGS) $< -o $@

clean:
    $(RM) -r $(OBJDIRS)

distclean: clean
    $(RM) -r $(BINDIR)

buildrepo:
    @$(call make-repo)

define make-repo
    for dir in $(OBJDIRS); \
    do \
            mkdir -p $$dir; \
    done
endef

# usage: $(call make-depend,source-file,object-file,depend-file)
define make-depend
    $(CC) -MM -MF $3 -MP -MT $2 $(CFLAGS) $1
endef

2 个答案:

答案 0 :(得分:0)

你的makefile有几个问题。

  • 如果您的find支持,则可以使用-printf '%h '代替-exec dirname {} \;

    这样可以为每个目录保存一个单独的进程(dirname)。

  • APPS := $(subst ./,,$(SUBDIRS))提供app1 app2$(BINDIR)/$(APPS)提供bin/app1 app2 bin/app1 bin/app2

    您可以使用APPS := $(subst ./,$(BINDIR),$(SUBDIRS))代替all: $(APPS)

  • @mkdir -p `dirname $@`相当于@mkdir -p $(BINDIR)
  • 您可以使用make-depend创建依赖项,但不要在生成文件中包含生成的依赖项文件。

    你必须在makefile中的某个地方include $(wildcard *.d)或类似的东西。

  • $(BINDIR)/$(APPS): buildrepo $(OBJS)表示所有应用程序都依赖于所有对象。

    当您最终将应用程序与$(CC) $(OBJS) $(LDFLAGS) -o $@链接时,它会尝试将所有对象合并到一个应用程序中,从而产生错误消息。

您可以通过在子目录中使用多个makefile并使用

循环它们来解决此问题
$(SUBDIRS):
    for d in $(SUBDIRS); do $(MAKE) -C $$d; done

另一种解决方案可能是使用make-depend分别为每个应用程序创建依赖关系和规则。

答案 1 :(得分:0)

这种“构建你能找到的一切”方法有点危险,而且解决方案也很棘手。你可能最好维护一个目标列表,每个目标都有一个源列表。但如果你真的想这样做......

基本问题在于:

$(BINDIR)/$(APPS): buildrepo $(OBJS)
    ...
    @$(CC) $(OBJS) $(LDFLAGS) -o $@

(目标是错误的,但没关系。)

目标可能是bin/app1,但$(OBJS)是“./app1/obj/main.o ./app2/obj/main.o”,因此这是链接器尝试链接的内容。 (另外,./app2/obj/main.o是先决条件。)我们需要的是针对目标定制的对象列表,如下所示:

bin/app1: ./app1/obj/main.o

这个应该很容易(通过从$(OBJS)筛选“app1”),但不是,因为Make的模式匹配非常差。

如果我们事先知道“./app1”,我们可以构建规则:

$(BINDIR)/$(notdir ./app1): $(subst .c,.o,$(subst /src/,/obj/,$(shell find ./app1/src -name '*.$(SRCEXT)')))

我们可以像以下那样将丑陋与“./app1”区分开来:

template = $(BINDIR)/$(notdir $(1)): $(subst .c,.o,$(subst /src/,/obj/,$(shell find $(1)/src -name '*.$(SRCEXT)')))

$(eval $(call template,./app1))

我们不希望为每个二进制文件执行此操作。但我们可以让 Make 为每个二进制文件执行此操作:

template = $(BINDIR)/$(notdir $(1)): $(subst .c,.o,$(subst /src/,/obj/,$(shell find $(1)/src -name '*.$(SRCEXT)')))

$(foreach dir,$(SUBDIRS),$(eval $(call template,$(dir)))

现在我们所需要的只是规则的主体:

$(BINDIR)/%:
    @mkdir -p `dirname $@`
    @echo "Linking $@..."
    @$(CC) $^ $(LDFLAGS) -o $@

和正确的默认规则:

all: $(addprefix $(BINDIR)/, $(APPS))