我假设您想按原样使用第三方库,而无需您进行任何自定义修改。我的回答也将针对 GNU/Linux,但由于您正在编写自己的 Makefiles,我认为这是适合您的相关平台。
虽然将库转储到您的存储库中似乎是一种方便的快速修复,但这并不是一个好的做法。理想情况下,您的存储库应该只包含您的项目负责的手写文件。这保持了关注点的清晰分离。
如果您的项目需要 libfoo,我感兴趣的另一个项目可能也需要它,因此我可能决定在我的系统上安装 libfoo 并将其用于所有需要它的项目。我可以通过手动下载、构建和安装 libfoo 来做到这一点,或者,如果我幸运的话,我可以通过我的包管理器安装它。手动构建和安装时,至少有两个选项。我只能为我的用户在系统范围内或本地安装它。另一方面,如果我认为我不再需要 libfoo 或者您的项目需要特定版本的 libfoo 而不是我通常想要使用的版本,我可能更愿意在本地目录中构建 libfoo 而不是安装完全没有。
总而言之,您的用户的选项是。
- 使用系统的包管理器在系统范围内安装库(例如在
/usr/lib/ 中)。
- 在系统范围内手动下载、构建和安装库(例如
/usr/local/lib/)。
- 仅为我的本地用户手动下载、构建和安装库(例如
~/lib/)。
- 手动下载并构建库但不要安装它(例如
~/src/foo-1.42/)。
应该由您的用户决定选择哪个选项。但是,虽然您不应该对他们强加任何东西,但您应该尽一切可能帮助他们。
处理外部依赖项的第一件事是记录它们。在您的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 脚本,用于下载并选择性地配置和构建所有外部依赖项。请清楚地记录哪些操作将从 Internet 下载内容,并确保用户知道他们想要下载的内容。此外,请让您的脚本在对下载的包进行任何操作之前验证它们的校验和。不这样做是攻击者的潜在切入点,您不希望您的用户通过使用您的软件而被利用。在这种情况下,您的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 脚本和Makefiles 是乏味的,而且你可能会弄错。您应该认真考虑为此使用自动化框架。 GNU 软件包通常使用 GNU Autotools,即Autoconf 和Automake。如果你想了解更多关于这些工具的信息,我可以推荐 John Calcote 的 book“Autotools – A Practioner's Guide to GNU Autoconf, Automake, 和 Libtool”。