Makefile: 如何链接来自不同子目录的目标文件并包含不同的搜索路径

12

我想要更改我的测试代码(tsnnls_test_DKU.c)的位置,但是我无法在makefile中进行更改以正确反映此文件夹的更改。希望能得到一些帮助。

我有两个问题: 1)如何链接来自不同子目录的对象文件 2)包括不同的搜索路径(在我的示例中有3个搜索路径)。

在我的原始设置中,makefile正常工作,我将我的测试代码tsnnls_test_DKU.c放在以下位置(第三方库内):

Dir1 = /home/dkumar/libtsnnls-2.3.3/tsnnls

我链接到的所有目标文件都在此处

OBJDir = /home/dkumar/libtsnnls-2.3.3/tsnnls

此外,tsnnls_test_DKU.c中包含的一些包含文件位于以下三个搜索路径中:

Dir1 = /home/dkumar/libtsnnls-2.3.3/tsnnls  
Dir2 = /home/dkumar/libtsnnls-2.3.3
Dir3 = /home/dkumar/libtsnnls-2.3.3/tsnnls/taucs_basic

我的makefile工作正常。

然而,我想将测试代码的位置更改为:

Dir4 = /home/dkumar/CPP_ExampleCodes_DKU/Using_tsnnls_DKU/

以下是我的makefile的样子(在另一个用户的输入后进行了更新):

# A sample Makefile

VPATH = -L/home/dkumar/libtsnnls-2.3.3/tsnnls
INC_PATH  = -I/home/dkumar/libtsnnls-2.3.3/ -I/home/dkumar/libtsnnls-2.3.3/tsnnls/  -I/home/dkumar/libtsnnls-2.3.3/tsnnls/taucs_basic/

# Here is a simple Make Macro.
LINK_TARGET     = tsnnls_test_DKU
OBJS_LOC    = tsnnls_test_DKU.o

# Here is a Make Macro that uses the backslash to extend to multiple lines.
OBJS =  libtsnnls_la-taucs_malloc.o libtsnnls_la-taucs_ccs_order.o \
    libtsnnls_la-taucs_ccs_ops.o libtsnnls_la-taucs_vec_base.o \
    libtsnnls_la-taucs_complex.o libtsnnls_la-colamd.o \
    libtsnnls_la-amdbar.o libtsnnls_la-amdexa.o \
    libtsnnls_la-amdtru.o libtsnnls_la-genmmd.o \
    libtsnnls_la-taucs_timer.o libtsnnls_la-taucs_sn_llt.o \
    libtsnnls_la-taucs_ccs_base.o libtsnnls_la-tlsqr.o \
    libtsnnls_la-tsnnls.o libtsnnls_la-lsqr.o   \
    $(OBJS_LOC)

REBUILDABLES = $(LINK_TARGET) 

all : $(LINK_TARGET)
    echo All done

clean : 
    rm -f $(REBUILDABLES)   
    echo Clean done

#Inclusion of all libraries
RANLIB = ranlib
STATICLIB= /usr/local/lib/taucs_full/lib/linux/libtaucs.a 

tsnnls_test_LDADD = $(LDADD)
LIBS = -largtable2 -llapack -lblas -lquadmath -lm

$(LINK_TARGET) : $(OBJS)   $(tsnnls_test_LDADD) $(LIBS)  $(STATICLIB)
gcc -g ${INC_PATH} -o $@ $^

尝试运行"$make"时出现的错误。
make: *** No rule to make target `libtsnnls_la-taucs_malloc.o', needed by `tsnnls_test_DKU'.  Stop.

显然,我无法正确使用VPATH。

更新: 感谢Mike Kinghan回答我的问题。


你的 Makefile 似乎没有引用任何包含路径(因为它只显式地进行链接阶段)。如果你想要链接不同子目录中驻留的对象,你可能需要查看 VPATH 文档。 - mfro
@mfro,你有VPATH的工作示例吗?我谷歌了一下,找到了很多链接,但没有一个适合我的。我无法弄清楚如何将VPATH传递给编译器。此外,我正在尝试包含INC_PATH,其中包含头文件和其他代码。请查看我在更新的makefile中的尝试。那也不起作用。 - Garima Singh
如果我理解您的问题正确,在“${OBJS}”中列出的文件名可以位于多个目录中,因此无法告诉“gcc”这些文件在哪里(这不是Makefile的问题,而是gcc命令行的问题)。 如果这是实际问题,我恐怕“${VPATH}”帮助不大,因为它仅由“make”用于定位先决条件。 解决方法之一是使用来自不同目录的“OBJS”列表的不同变量,并在目标先决条件中添加完整路径。 - Come Raczy
@ComeRaczy '${OBJS}' 中的所有文件都在一个文件夹中。此外,我的测试代码需要的头文件位于三个不同的文件夹中,这些文件夹都列在 INC_PATH 中。 - Garima Singh
@GarimaSingh 那实际问题是什么(错误信息是什么)? - Come Raczy
显示剩余4条评论
2个回答

38

Q1: 如何链接来自不同子目录的目标文件?

假设您的程序 prog 是一个 C 程序,将从编译到子目录 obj 的目标文件 file0.ofile1.o 中进行链接。以下是您通常需要在 makefile 中执行该链接所需的内容。

$(OBJS) = $(patsubst %.o,obj/%.o,file0.o file1.o)

prog: $(OBJS)
    gcc -o $@ $(CFLAGS) $(OBJS) $(LDFLAGS) $(LDLIBS)

这样就让 $(OBJS) 等于 obj/file0.o, obj/file1.o,然后你只需要把这些目标文件传递给链接命令。关于 patsubst 的文档,请参考这里

注意:如果在编译一个对象文件到 obj 子目录时该目录不存在,这是不够的。你必须自己创建它或学习如何让 make 自己创建。

问题2:怎样包含不同的搜索路径?

这是一个模糊的问题——其中的歧义让你感到困惑——我们必须将其分解成问题2.a、问题2.b和问题2.c

问题2.a:如何指定预处理器在源代码中使用的被#include的头文件的不同搜索路径?

默认情况下,预处理器将使用内置的一组标准搜索路径来查找头文件。你可以通过以 verbose 模式运行预处理器(例如cpp -v,按CTRL-C终止)来看到它们。输出将包含类似以下内容:

#include "..." search starts here:
#include <...> search starts here:
 /usr/lib/gcc/x86_64-linux-gnu/4.8/include
 /usr/local/include
 /usr/lib/gcc/x86_64-linux-gnu/4.8/include-fixed
 /usr/include/x86_64-linux-gnu
 /usr/include
End of search list.

假设您有一些自己的头文件存放在子目录 incinc/foo/bar 中,并且希望预处理器也能够搜索这些目录。那么,您需要传递以下预处理器选项:

-I inc -I inc/foo/bar

将预处理器选项添加到您的编译命令中。通常将预处理器选项分配给make变量CPPFLAGS,例如:

CPPFLAGS = -I inc -I inc/foo/bar

(以及所需的任何其他预处理器选项),并通过编译命令配方中的此变量传递,例如。
gcc -c -o $@ $(CPPFLAGS) $(CFLAGS) $<

注意: 认为CPPFLAGS是C++编译器标志的常规make变量是一个常见错误。C++编译器标志的常规make变量是CXXFLAGS

您可以通过运行以下命令来查看-I选项的效果:

mkdir -p inc/foo/bar # Just to create the path
cpp -v -I inc -I inc/foo/bar

(按CTRL-C退出)。现在输出将包含如下内容:

#include "..." search starts here:
#include <...> search starts here:
 inc
 inc/foo/bar
 /usr/lib/gcc/x86_64-linux-gnu/4.8/include
 /usr/local/include
 /usr/lib/gcc/x86_64-linux-gnu/4.8/include-fixed
 /usr/include/x86_64-linux-gnu
 /usr/include
End of search list.

Q2.b: 如何指定链接器查找库文件的不同搜索路径?

假设您有一个名为libfoobar.a的库需要与prog链接,并且它位于距离您的makefile两级目录的lib目录中。那么您需要传递以下链接器选项:

-L ../../lib

并且

-lfoobar

针对您的链接命令,其中第一个选项将告诉链接器 ../../lib 是查找库文件的路径之一。通常情况下,您可以通过 LDFLAGS 在链接命令中传递此选项。第二个选项将告诉链接器搜索名为 libfoobar.a(静态库)或 libfoobar.so(动态库)的某些库。通常情况下,您可以通过 LDLIBS 在链接命令中传递此选项。

就像预处理器有默认的搜索路径列表一样,链接器也有默认的搜索路径列表。您可以运行以下命令查看它们:

gcc -v -Wl,--verbose 2>&1 | grep 'LIBRARY_PATH'

输出结果将类似于:
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.8/:/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../x86_64-linux-gnu/: /usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/: /usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../:/lib/:/usr/lib/
当需要链接其中一个标准库,例如数学库“libm”,该库位于默认的搜索路径之一时,您不需要传递任何“-L”选项。“-lm”就可以了。
Q2.c:如何指定不同的搜索路径,使得make将查找目标的先决条件?
注:这是关于make的问题,而不是有关预处理器、编译器或链接器的问题。
我们已经假设所有的目标文件都会被编译到子目录“obj”中。为了让它们在那里被编译,只需使用模式规则即可简单地实现:
obj/%.o:%.c
    gcc -c -o $@ $(CPPFLAGS) $(CFLAGS) $<

这告诉make,例如obj/file0.o是由配方制成的file0.c

gcc -c -o obj/file0.o $(CPPFLAGS) $(CFLAGS) file0.c

同样适用于任何文件obj/*.o和匹配文件*.c

只要file0.c与makefile在同一个目录中,这是没有问题的,但是假设你的*.c文件在其他地方呢?假设你的源文件组织在子目录中,如foo/file0.cbar/file1.c。那么make将无法满足该模式规则,并且会说“没有找到目标为obj/file0.o”的规则等。

为解决此问题,请使用VPATH,这是一个具有特殊含义的make变量。如果您将由“:”分隔的目录名称列表赋给VPATH,那么make将在每个列出的目录中搜索前提条件,无论何时它在当前目录中找不到它。因此:

VPATH = foo:bar

这将使make在查找匹配模式规则的.c文件时,先查找当前目录,然后是foobar。它将成功满足规则并编译必要的源文件。

N.B.您在发布的代码中错误使用了VPATH

VPATH = -L/home/dkumar/libtsnnls-2.3.3/tsnnls

您已经为 链接器 分配了一个搜索路径,使用链接器选项-L,但这个选项不应该出现在那里。
总之:
  • 寻找头文件的预处理器搜索路径,是使用预处理器的-I<dirname>选项指定的。将这些选项传递给编译过程中的CPPFLAGS

  • 用于查找库的链接器搜索路径是使用链接器的-L<dirname>选项指定的。将这些选项传递给链接过程中的LDFLAGS

  • make规则先决条件相关联的搜索路径是在make变量VPATH中指定的,作为一个冒号分隔的目录列表。


感谢您提供如此详细的回复。这正是我所需要的。我真的无法表达我的感激之情。 - Garima Singh
如果可以的话,我会给予+10分。这个答案总结了我对Makefile的所有问题。我将永远把它作为参考。谢谢! - Fuzzyma

0

我的回答部分问题 我可以找到如何链接来自不同子目录/子目录的目标文件;但是,仍然无法弄清楚如何使用不同的搜索路径

我的当前makefile是:

    # A sample Makefile

OBJS_PATH = /home/dkumar/libtsnnls-2.3.3/tsnnls/
C_INCLUDE_PATH  = -I/home/dkumar/libtsnnls-2.3.3/ -I/home/dkumar/libtsnnls-2.3.3/tsnnls/  -I/home/dkumar/libtsnnls-2.3.3/tsnnls/taucs_basic/
CPATH  = -I/home/dkumar/libtsnnls-2.3.3/ -I/home/dkumar/libtsnnls-2.3.3/tsnnls/  -I/home/dkumar/libtsnnls-2.3.3/tsnnls/taucs_basic/

# Here is a simple Make Macro.
LINK_TARGET     = tsnnls_test_DKU
OBJS_LOC    = tsnnls_test_DKU.o

# Here is a Make Macro that uses the backslash to extend to multiple lines.
OBJS =  libtsnnls_la-taucs_malloc.o libtsnnls_la-taucs_ccs_order.o \
    libtsnnls_la-taucs_ccs_ops.o libtsnnls_la-taucs_vec_base.o \
    libtsnnls_la-taucs_complex.o libtsnnls_la-colamd.o \
    libtsnnls_la-amdbar.o libtsnnls_la-amdexa.o \
    libtsnnls_la-amdtru.o libtsnnls_la-genmmd.o \
    libtsnnls_la-taucs_timer.o libtsnnls_la-taucs_sn_llt.o \
    libtsnnls_la-taucs_ccs_base.o libtsnnls_la-tlsqr.o \
    libtsnnls_la-tsnnls.o libtsnnls_la-lsqr.o   

 ## adding "$(OBJS_PATH)" to each word in "$(OBJS)"
# which in our case is basically to add the same folder in front of all "*.o" object files.
OBJS2 = $(addprefix $(OBJS_PATH)/, $(OBJS)) 


# OBJS_LOC is in current working directory,
OBJS_ALL = $(OBJS2) \
    $(OBJS_LOC)

## DKU IS COPING THIS FROM ORIGINAL MAKEFILE THAT ARE GENERATED USING /home/dkumar/libtsnnls-2.3.3/tsnnls/MAKEFILE.AM
RANLIB = ranlib
STATICLIB= /usr/local/lib/taucs_full/lib/linux/libtaucs.a 

tsnnls_test_LDADD = $(LDADD)
LIBS = -largtable2 -llapack -lblas -lquadmath -lm


REBUILDABLES = $(OBJS_LOC) $(LINK_TARGET) 


all : $(LINK_TARGET)
    echo All done

clean : 
    rm -f $(REBUILDABLES)   
    echo Clean done

# Here is a Rule that uses some built-in Make Macros in its command:
$(LINK_TARGET) : $(OBJS_ALL)   $(tsnnls_test_LDADD) $(LIBS)  $(STATICLIB)
    gcc -g -o $@ $^

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接