如何在 autotools 中处理库的函数文件(不是 header 文件)?

How to handle library's function files (not header files) in autotools?

所以最近我一直在尝试使用 autotools 来构建 C++ 库。最初我只是在研究纯自定义 类,这很容易 #include 然后用 g++ 编译。但是当我想为库编写一些自定义函数时,我了解到在 header 文件中编写函数是一种不好的做法,而是我应该在 header 中声明它然后将其写入单独的 .cpp 文件。后来我用 g++ ./lib/**/*.cpp src/main.cpp.

尝试并成功编译

所以基本上我的项目结构是代码放在 src/ 下,header 放在 include/ 下,函数放在 lib/ 下。以下是我的 src/Makefile.am

AUTOMAKE_OPTIONS = subdir-objects foreign
bin_PROGRAMS = main
include_HEADERS = -I../include/**/*.hpp 
main_SOURCES = -I../lib/**/*.cpp main.cpp

对于端点目录(在 lib/ 下),我有如下内容

main_SOURCES = stdout.cpp

但是报错说没有名为main的程序,所以我想也许所有这些函数文件都必须先编译,所以我把它们改成了

noinst_PROGRAMS = stdout
stdout_SOURCES = stdout.cpp

但是他们给了我以下错误

/usr/sbin/ld: /usr/lib/gcc/x86_64-pc-linux-gnu/11.1.0/../../../../lib/Scrt1.o: in function `_start':
(.text+0x24): undefined reference to `main'
collect2: error: ld returned 1 exit status

我知道报错的意思是文件里没有写main(),但是由于是库函数文件,并不意味着要有main(),本来就是被其他文件调用(如 main.cpp)。到这里我被困住了。

我试图在网上找到文档,但似乎大多数都是针对 C 程序而不是 C++,我不确定这些步骤是否兼容。我记得 C++ 库被编译成 .so.o 文件,而大多数教程似乎都使用 .la 文件。


MWE

src/main.cpp

#include"../include/conn/bash/stdout.hpp"
#include<string>
#include<iostream>

int main() {
        std::string  o=d::conn::bash::exec("ls");
        std::cout << o << std::endl;
        return 0;
}

include/conn/bash/stdout.hpp

#ifndef __CONN_BASH_O__
#define __CONN_BASH_O__
#include<string>
namespace d { namespace conn { namespace bash {
        std::string exec(const char*);
}}}
#endif

lib/conn/bash/stdout.cpp

#include"../../../include/conn/bash/stdout.hpp"
#include <cstdio>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <array>

std::string d::conn::bash::exec(const char* cmd) {
    std::array<char, 128> buffer;
    std::string result;
    std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
    if (!pipe) {
        throw std::runtime_error("popen() failed!");
    }
    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
        result += buffer.data();
    }
    return result;
}
// 

使用 g++ ./lib/**/*.cpp src/main.cpp

编译和测试

如果您只是为您的库定义一个方便的目标,您应该能够通过将其声明为 noinst_LIBRARIES = libmine.a 然后将其声明为 stdout_LIBADD.[=15= 来实现。 ]

但最有可能的是,如果您不想将其作为 已安装 库,您只想直接在单个非递归列表中列出源 Makefile.am 文件,参见 https://autotools.io/automake/nonrecursive.html

如果您确实希望您的库可安装,那么您想使用 libtool:https://autotools.io/libtool/index.html

I've tried to find documentation online, but it seems that most of them are targeted at C programs instead of C++,

“大多数”表明您正在搜索教程。 文档 将是 the Automake manual,建议您阅读。它涵盖了这些主题以及您在继续使用 Autotools 时想要了解的更多内容,尤其是 Automake。至少,您应该知道如何找到该手册以便在需要时查阅。教程和指南可能会有帮助,但它们应被视为手册的补充,而不是主要来源。

which I'm not sure if those steps are compatible.

他们大多是。需要注意的主要事情是,C 编译器的命令行选项是通过 CFLAGS 主参数指定的(见下文),而 C++ 编译器的命令行选项是通过 CXXFLAGS 主参数指定的。

And I remember C++ libraries are compiled into .so or .o file while most tutorials seems to be using .la files.

你似乎很困惑。与这些扩展关联的格式不是特定于语言的,并且 .o 根本不指定库。

.o 是编译单个源文件时产生的目标文件的常规扩展名。这些可以 link 编辑成可执行文件,或者可以将它们聚集到一个库中,但它们本身不是库。

常规 UNIX 库扩展是 .a 静态库和 .so 共享库。这适用于可用于构建通用 link 库的任何语言,包括 C、C++、Fortran 和其他各种语言。

.la 扩展是另外一回事。它指定一个 libtool 库。 Libtool 是另一个自动工具,专注于抽象系统特定的构建和使用库的细节。这又是独立于语言的。构建 libtool 库将导致相应的静态库、相应的共享库或两者都被构建和安装,具体取决于 Autotooling 和/或 configure 命令行中指定的选项。

如果您使用 Autotools 构建共享库,则应使用 libtool。如果您只构建静态库 (.a),您也可以使用它,但在这种情况下,将 libtool 排除在外会更简单一些。


至于你提出的具体问题,你写:

And for the endpoints' directory (under lib/) I have something like following

main_SOURCES = stdout.cpp

But it gave me error that no program named main,

始终显示错误消息的实际文本(作为文本),而不是释义。如果您必须在这里提出问题,那么您对消息的理解不完整或错误的风险很大,因此您的释义具有误导性。

在这种情况下,我比较确信Automake的抱怨是没有target命名为“main”。您给出的行指定了此类目标的源列表,但 Automake 找不到该 Makefile.am 文件中定义的目标。 “程序”和“目标”之间的区别有点微妙,但意义重大。

假设您正在尝试构建一个便利库 -- 即,一个将函数分组以在编译时用于构建其他目标的库,但不打算作为独立库安装 *——你可以用类似的东西得到它:

noinst_LIBRARIES = libendpoints.a

由以下主要部分组成:

  • LIBRARIES 是“主要”,指定给出什么样的定义。在本例中,它是项目中包含的静态库目标列表的定义。
  • libendpoints.a 指定(静态库)目标的名称。这是一个外部名称:构建将在构建树中使用此名称生成一个文件。请养成为此使用标准命名约定的习惯,例如我演示的那样。
  • noinst 指定 make install 将安装构建目标的位置。此特定值是一个特殊值,表示根本不会安装目标。如果您希望它安装在配置的 libdir 中,那么您应该说 lib.

该目标的属性必须由其他定义给出,基于其名称的修改形式。例如,它的源列表可能如下所示:

libendpoints_a_SOURCES = stdout.cpp

同样由三部分组成:

  • SOURCES 又是主要的。在这种情况下,它表示该定义为某些目标提供了源列表。
  • libendpoints_a 标识正在提供其源列表的目标。它是目标名称的变形形式,变量名称中不能出现的字符被“_”字符替换。
  • stdout.cpp 是(单元素)源列表。

要使用它,主程序的属性还需要指定库应该 linked。这意味着在 Makefile.am 中定义了主程序目标:

main_LDADD = lib/endpoints/libendpoints.a

这又是一个由三部分组成的定义,我将其解释留作练习。


*并且如果您想要一个确实安装到系统中的静态库,那么您可以将“noinst”换成标识安装位置的前缀,例如“图书馆”。以 libtool 为中介的库(包括共享库)的形式略有不同。