将静态库添加到C或C ++项目的常用方法是什么?

时间:2016-03-06 18:12:20

标签: c++ c makefile

基本问题 - 我正在学习用C和C ++编程。我不确定如何处理这些语言中的依赖关系管理。

我想在项目中添加一个静态库。通常的方式是说明项目需要该库,并将库包含在项目中?

我很想知道如何在基于makefile的项目中做到这一点(不是用CMake)。对于我所看到的,CMake允许您使用ExternalProject_Add函数自动化依赖关系管理,但我感兴趣的是在不使用CMake的情况下执行此操作的常用方法。

感谢您的帮助!

1 个答案:

答案 0 :(得分:1)

我假设您希望按原样使用第三方库,而无需进行任何自定义修改。我的回答也将特定于GNU / Linux,但由于您正在编写自己的Makefile,我认为这是适合您的相关平台。

将库转储到您的存储库似乎是一个方便的快速修复,但这不是一个好习惯。理想情况下,您的存储库应该只包含项目负责的手写文件。这可以保持关注点的清晰分离。

如果您的项目需要libfoo,我感兴趣的另一个项目可能也需要它,所以我可能决定在我的系统上安装libfoo并将其用于需要它的所有项目。我可以通过手动下载,构建和安装libfoo来实现这一点,或者,如果我很幸运,我可以通过我的包管理器安装它。手动构建和安装时,至少有两个选项。我可以在系统范围内或本地安装它仅供我的用户使用。另一方面,如果我认为我再也不需要libfoo或者你的项目需要特定版本的libfoo而不是我通常想要使用的版本,我可能更喜欢在本地目录中构建libfoo并且根本不安装它。

总而言之,您的用户的选项是。

  1. 使用系统的软件包管理器在系统范围内安装库(例如在/usr/lib/中)。
  2. 在系统范围内手动下载,构建和安装库(例如/usr/local/lib/)。
  3. 仅为我的本地用户手动下载,构建和安装库(例如~/lib/)。
  4. 手动下载并构建库但不安装它(例如~/src/foo-1.42/)。
  5. 决定选择哪个选项应留给您的用户。但是,虽然你不应该强加任何东西,但你应该尽一切努力帮助

    与外部依赖关系的第一件事是记录它们。在您的README文件中,列出项目的所有依赖项,并将URL指向相应项目的下载页面。如果您知道相关操作系统已经打包了库,那么还要提及人们必须为该系统安装的软件包的名称。我所知道的所有GNU / Linux发行版都有一个网站,您可以在其中搜索其包索引,以便您可以为更受欢迎的版本执行此操作。如果您的项目需要特定版本的库,请不要忘记提及这一点。

    如果您的用户决定使用选项(1),则无需执行任何操作。他们将使用包管理器来安装库,Makefile将从LIBS变量引用它(例如-lfoo)。

    如果您的用户决定(或必须,因为图书馆没有为他们的系统打包),请使用选项(2)或(3),情况并没有太大差异。他们将下载,构建和安装库,一旦完成,您的Makefile将再次获取它。但是,如果他们将库安装在非标准位置,则链接器可能无法直接找到它。因此,您的Makefile使用LDFLAGS变量非常重要,这样用户才能为其添加相应的选项(例如-L${HOME}/lib/)。如果您的代码引用了包中的标头,则包含路径(例如-I${HOME}/include/)也是如此。这些选项属于CPPFLAGS变量。

    如果您的用户使用选项(4),他们肯定必须使用链接器标志来找到库。例如,如果将libfoo下载并构建在子目录foo中,则会将-L./foo/添加到LDFLAGS。但是,在这种情况下,您可以让用户的生活更轻松一些。在项目的顶级目录中放置一个小shell脚本,下载并可选地配置和构建所有外部依赖项。请清楚地记录从互联网上下载内容的操作,并确保用户将使用他们想要下载的内容。此外,请在执行任何操作之前让您的脚本验证下载的包的校验和。如果不这样做是攻击者可能的切入点,并且您不希望使用您的软件来利用您的用户。在这种情况下,您的Makefile(或更好,您的configure脚本)应该检测到包是在本地构建的并使用它(即添加相应的-I…-L…标志)。您还可以无条件地添加标志,因为预处理器和链接器将静默忽略不存在的目录。

    编写帮助程序脚本并不困难。一个简单的版本可能看起来像这样。

    #! /bin/bash -eu
    
    packages=(foo bar)
    declare -A urls
    urls['foo']='https://download.foo.org/foo-1.42.tar.gz'
    urls['bar']='https://download.bar.org/bar-2.50.tar.gz'
    
    cat <<'EOF' > dependency-checksums.sha1
    0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33  foo-1.42.tar.gz
    62cdb7020ff920e5aa642c3d4066950dd1f01f4d  bar-2.50.tar.gz
    EOF
    
    for pkg in "${packages[@]}"
    do
        wget "${urls[${pkg}]}" || exit
    done
    
    sha1sum --check dependency-checksums.sha1 || {
        echo "ALERT: Verification of package integrity failed.  Stop." >&2
        exit 1
    }
    
    for pkg in "${packages[@]}"
    do
        ar="${urls[${pkg}]##*/}"
        dir="${ar%.tar*}"
        tar -xf "${ar}"
        ln -s "${dir}/" "${pkg}"
        ( cd "${dir}" && ./configure && make )
    done
    

    您可以考虑稍微抛光一下。例如,让它理解--help选项并仅提供下载但不构建包。当然,您不必编写shell脚本。您可以提供Perl或Python脚本,甚至是Make脚本。事实上,使用花哨的Bash功能是一个坏主意,就像我在上面的例子中所做的那样,因为许多人不使用现代的Bash shell。

    如果您将软件作为源存档发布,那么提供已包含所有外部依赖关系的替代 tarball没有任何问题,因此您的用户不必单独下载它们。但是你应该只提供这个替代品,而不是唯一的选择。对于tarball而言, repository - 如前所述 - 应该尽可能地保留第三方内容。

    虽然编写一个体面的“下载我的依赖项”脚本很容易,但编写灵活的configure脚本和Makefile是很乏味的,你可能会弄错它。您应该认真考虑使用自动化框架。 GNU包通常使用GNU Autotools,即AutoconfAutomake。如果您想了解有关这些工具的更多信息,我可以推荐John Calcote的book“Autotools - GNU Autoconf,Automake和Libtool的实用工具指南”。