学习从源代码编译内容(在 Unix/Linux/OSX 上)

学习从源代码编译内容(在 Unix/Linux/OSX 上)

虽然我尽可能从软件包(MacPorts / apt-get)安装软件,但我经常发现自己需要从源代码编译软件包。./configure && make && sudo make install通常就足够了,但有时它不起作用 - 当它不起作用时,我经常陷入困境。这几乎总是与其他库依赖关系有关。

我想了解以下内容:

  • 我如何确定要传递给什么参数./configure
  • 共享库在 OS X / Linux 下是如何工作的 - 它们在文件系统中的位置,如何./configure && make找到它们,当它们被链接到时实际上会发生什么
  • 共享库和静态链接库之间有什么实际区别?为什么我不能只静态链接所有内容(如今 RAM 和磁盘空间很便宜),从而避免奇怪的库版本冲突?
  • 我如何知道我安装了哪些库以及哪些版本?
  • 如何在不破坏正常系统的情况下安装一个库的多个版本?
  • 如果我在使用包管理的系统上从源代码安装东西,最干净的方法是什么?
  • 假设我设法从源代码编译出一些复杂的内容,那么我该如何将其打包,以便其他人不必经历同样的麻烦?特别是在 OS X 上......
  • 我需要掌握哪些命令行工具才能熟练掌握这些功能?例如 otool、pkg-config 等。

我愿意在这里投入相当多的时间和精力——我不一定想直接得到上述问题的答案,我更愿意得到关于我可以阅读的书籍/教程/常见问题解答的推荐,这些将为我提供我需要的知识,让我了解实际情况,从而自己解决问题。

答案1

我很抱歉直接回答所有问题,但我不知道任何有用的教程、常见问题解答等。基本上,接下来是 8 年制作桌面应用程序(我帮助分发)、挫折和谷歌搜索:

1. 我如何确定要传递给 ./configure 的参数?

真正地练习。Autotools 非常简单,因为它是一致的。但是有很多东西可以使用 cmake 或自定义构建脚本。通常,您不必将任何东西传递给 configure,它应该会确定您的系统是否可以构建 foo-tool。

配置和 GNU 工具都在 /、/usr 和 /usr/local 中查找依赖项。如果您在其他地方安装了任何东西(如果依赖项是由 MacPorts 或 Fink 安装的,那么事情就会变得很麻烦),您必须传递一个标志来配置或修改 shell 的环境,以帮助 GNU 工具找到这些依赖项。

2. 共享库在 OS X / Linux 下如何工作 - 它们在文件系统中的位置,./configure && make 如何找到它们,当它们被链接到

在 Linux 上,它们需要安装到动态链接器可以找到的路径,这由LD_LIBRARY_PATH环境变量和 /etc/ld.conf 的内容定义。在 Mac 上,大多数开源软件几乎总是如此(除非它是 Xcode 项目)。除了 env 变量DYLD_LIBRARY_PATH

链接器搜索库的默认路径是 /lib:/usr/lib:/usr/local/lib。

您可以使用 CPATH 变量、CFLAGS 或任何其他环境变量(非常复杂)来补充这一点。我建议使用 CFLAGS,如下所示:

导出 CFLAGS="$CFLAGS -L/new/path"

-L参数添加到链接路径。

现代软件使用 pkg-config 工具。您安装的现代软件还会安装一个 .pc 文件,该文件描述了库及其位置以及如何链接到它。这可以让生活更轻松。但它不随 OS X 10.5 一起提供,因此您还必须安装它。而且许多基本依赖项不支持它。

链接行为只是“在运行时解析这个函数”,实际上它是一个很大的字符串表。

3. 共享库和静态链接库之间有什么实际区别?为什么我不能只静态链接所有内容(如今 RAM 和磁盘空间很便宜),从而避免奇怪的库版本冲突?

当您链接到静态库文件时,代码将成为应用程序的一部分。这就像该库有一个巨大的 .c 文件,然后您将其编译到应用程序中。

动态库具有相同的代码,但是当应用程序运行时,代码会在运行时加载到应用程序中(简化解释)。

您可以静态链接到所有内容,但遗憾的是,几乎没有任何构建系统可以轻松实现这一点。您必须手动编辑构建系统文件(例如 Makefile.am 或 CMakeLists.txt)。但是,如果您经常安装需要不同版本库的东西,并且发现同时安装依赖项很困难,那么这可能值得学习。

诀窍是将链接行从 -lfoo 更改为 -l/path/to/static/foo.a

您可能可以找到并替换。然后使用 ldd foo 或 otool -L foo 检查该工具是否链接到 .so 或 dylib

另一个问题是并非所有库都会编译为静态库。很多库都会。但 MacPorts 或 Debian 可能决定不发布它。

4. 如何知道我安装了哪些库以及哪些版本?

如果您有这些库的 pkg-config 文件,那么很简单:

pkg-config --list-all

否则,您通常无法轻松获得。dylib 可能具有与库版本相同的 soname(即 foo.0.1.dylib,soname 为 0.1)。但这不是必需的。soname 是一种二进制可计算性功能,如果您更改库中函数的格式,则必须改变 soname 的主要部分。因此,您可以获得例如 2.0 库的版本 14.0.5 soname。尽管这并不常见。

我对这种事情感到沮丧,并在 Mac 上为此开发了一个解决方案,接下来我将讨论它。

5. 如何在不破坏正常系统的情况下安装一个库的多个版本?

我的解决方案如下:http://github.com/mxcl/homebrew/

我喜欢从源代码安装,并且想要一个可以轻松完成安装的工具,但需要一些包管理功能。因此,我使用 Homebrew 构建,例如从源代码构建 wget,但确保安装到特殊前缀:

/usr/local/地窖/wget/1.1.4

然后我使用 homebrew 工具将所有这些符号链接到 /usr/local,因此我仍然有 /usr/local/bin/wget 和 /usr/local/lib/libwget.dylib

稍后如果我需要不同版本的 wget,我可以并行安装它并只需更改链接到 /usr/local 树的版本。

6. 如果我在使用包管理的系统上从源代码安装东西,最干净的方法是什么?

我认为 Homebrew 的方式最简洁,因此请使用它或执行等效操作。安装到 /usr/local/pkgs/name/version 并将其余部分符号链接或硬链接进去。

请使用 /usr/local。现有的每个构建工具都会在那里搜索依赖项和标头。你的生活将会很多更轻松。

7. 假设我设法从源代码编译出一些复杂的程序,那么我该如何将其打包,以便其他人不必经历同样的麻烦?特别是在 OS X 上....

如果它没有依赖项,您可以将构建目录打包并交给其他人,然后由他们执行“make install”。但是,您只能对完全相同版本的 OS X 可靠地执行此操作。在 Linux 上,它可能适用于具有相同内核版本和 libc 次要版本的类似 Linux(例如 Ubuntu)。

在 Unix 上分发二进制文件并不容易,原因在于二进制兼容性。GNU 人员和其他所有人都经常更改他们的二进制接口。

基本上不要分发二进制文件。事情可能会以非常奇怪的方式发生。

在 Mac 上,最好的选择是制作一个 macports 包。每个人都使用 macports。在 Linux 上,有如此多不同的构建系统和组合,我认为没有比写一篇博客文章介绍如何在奇怪的配置中成功构建 x 工具更好的建议了。

如果您制作了软件包描述(针对 macports 或 homebrew),那么任何人都可以安装该软件包,并且它还解决了依赖性问题。然而,这通常并不容易,而且将您的 macports 配方包含在主 macports 树中也不容易。此外,macports 不支持奇特的安装类型,它们为所有软件包提供一种选择。

我对 Homebrew 未来的目标之一是让人们能够点击网站上的链接(例如 homebrew://blah,它将下载该 Ruby 脚本,安装该包的 deps,然后构建应用程序。但是,是的,还没有完成,但考虑到我选择的设计,这并不是太棘手。

8. 我需要掌握哪些命令行工具才能熟练掌握这些功能?例如 otool、pkg-config 等。

otool 实际上只是在之后才有用。它会告诉你构建的二进制文件链接到什么。当你弄清楚必须构建的工具的依赖项时,它就没用了。pkg-config 也是如此,因为在你使用它之前你已经安装了依赖项。

我的工具链是,阅读 README 和 INSTALL 文件,然后执行 configure --help。观察构建输出以检查其是否正常。解析任何构建错误。也许将来,可以在 serverfault 上询问 :)

答案2

这是一个很大的话题,所以让我们从 Linux 上的共享库(Linux 上的 ELF 和 OS X 上的 Mach-O)开始,Ulrich Drepper 有一个很好的编写 DSO 简介(动态共享对象)涵盖了 Linux 上共享库的一些历史,包括它们为何如此重要。

Ulrich 还解释了为什么静态链接被认为是有害的这里的一个关键点是安全更新。广泛静态链接的公共库(例如 zlib)中的缓冲区溢出可能会给发行版带来巨大的开销 - 这发生在 zlib 1.1.3 中(Red Hat 公告

极低频

链接器 ld.so 手册页

man ld.so 

解释运行时动态链接所涉及的基本路径和文件。在现代 Linux 系统上,您会看到通过 /etc/ld.so.conf.d/ 添加的其他路径,这些路径通常是通过 /etc/ld.so.conf 中的 glob include 添加的。

如果你想通过 ld.so 配置查看哪些内容是动态可用的,你可以运行

ldconfig -v -N -X

阅读 DSO 操作指南应该会给你提供良好的基本知识,然后继续了解这些原则如何应用于 OS X 上的 Mach-O。

马赫-O

在 OS X 上,二进制格式为 Mach-O。链接器的本地系统文档是

man dyld

Mach 格式文档可从 Apple 购买

UNIX 构建工具

常见的configure, make,make install进程通常由 GNU autotools 提供,它具有网上书籍涵盖了配置/构建分割和 GNU 工具链的一些历史。自动配置使用测试来确定目标构建系统上的功能可用性,它使用M4宏语言来推动这一点。自动制作基本上是一种 Makefile 的模板方法,模板通常被称为 Makefile.am,它输出一个 Makefile.in,而 autoconf(配置脚本)的输出会将其转换为 Makefile。

GNU 你好该程序是理解 GNU 工具链的一个很好的例子 - 并且手册包含自动工具文档。

答案3

Simon!我理解你的感受;我也在学习 Linux 的过程中遇到过这方面的困难。根据我自己的经验,我编写了一个教程,介绍了你提到的一些问题(主要是作为我自己的参考!): http://easyaspy.blogspot.com/2008/12/buildinginstalling-application-from.html。我想你会喜欢我关于 Python 应用程序构建/安装有多么简单的说明。:)

希望这能有所帮助!祝您编译愉快。

蒂姆·琼斯


在 Ubuntu Linux 中从源代码构建/安装应用程序

虽然 Ubuntu 存储库中塞满了出色的应用程序,但您总有一天会遇到存储库中没有的“必备”工具(或者没有 Debian 软件包),或者您需要比存储库中更新的版本。您会怎么做?好吧,您必须从源代码构建应用程序!别担心,这实际上并不像听起来那么复杂。以下是一些提示,基于我从业余爱好者变成专业人士的经验!(虽然我使用 Ubuntu 作为此示例,但一般概念应该适用于大多数 Unix/Linux 发行版,例如 Fedora,甚至 Windows 上的 Cygwin 平台。)

从源代码构建(编译)大多数应用程序的基本过程遵循以下顺序:配置 --> 编译 --> 安装。执行这些操作的典型 Unix/Linux 命令是:config--> make--> make install。在某些情况下,您甚至会发现网页显示所有这些命令都可以组合成一个命令:

$ config && make && make install

当然,这个命令假设这些步骤都没有问题。这就是乐趣所在!

入门

如果您之前没有在系统上从源代码编译过应用程序,您可能需要使用一些常规开发工具来设置它,例如gcc编译器套件、一些常用头文件(可以将其视为其他人已经编写的代码,供您正在安装的程序使用)和 make 工具。幸运的是,在 Ubuntu 中,有一个名为的元包build-essential可以安装它。要安装它(或者只是确保您已经拥有它!),请在终端中运行以下命令:

$ sudo apt-get install build-essential

现在您已完成基本设置,请下载应用程序源文件并将其保存到您有读/写权限的目录,例如您的“主”目录。通常,这些文件将位于文件扩展名为 或 的存档文件中.tar.gz.tar.bz2简单.tar地说,它是一个“磁带存档”,它是一组保留其相对目录结构的文件。.gz代表 gzip (GNU zip),这是一种流行的 Unix/Linux 压缩格式。同样, 代表.bz2bzip2,这是一种较新的压缩格式,与 gzip 相比,它提供更高的压缩率(压缩文件大小更小)。

下载源文件后,打开终端窗口(Ubuntu 菜单中的系统终端)并切换到保存文件的目录。(我将~/download在此示例中使用。此处,“~”是“主”目录的快捷方式。)使用 tar 命令从下载的存档文件中提取文件:

如果你的文件是 gzip 存档(例如以 结尾.tar.gz),请使用以下命令:

            $ tar -zxvf filename.tar.gz

如果您的文件是 bzip2 存档(例如以 结尾.tar.bz2),请使用以下命令:

            $ tar -jxvf filename.tar.gz

提示:如果您不想记住所有用于提取档案的命令行开关,我建议您获取以下实用程序之一(或两者):dtrx(我的最爱!)或 deco(更受欢迎)。使用这两种实用程序,您只需输入实用程序的名称(dtrx 或 deco)和文件名,它就会完成其余所有工作。这两个实用程序都“知道”如何处理您可能遇到的大多数档案格式,并且它们具有出色的错误处理功能。

从源代码构建时,您可能会遇到两种常见类型的错误:

  1. 当您运行配置脚本(通常名为 config 或 configure)来创建特定于您的设置的 makefile 时,就会发生配置错误。
  2. 当您运行 make 命令(在生成 makefile 之后)并且编译器无法找到所需的某些代码时,就会发生编译器错误。

我们将逐一研究这些问题并讨论如何解决它们。

配置和配置错误

提取源代码存档文件后,您应该在终端中切换到包含提取文件的目录。通常,此目录名称与文件名称相同(不带.tar.gz.tar.bz2扩展名)。但是,有时目录名称只是应用程序的名称,没有任何版本信息。

在源目录中查找README文件和/或INSTALL文件(或具有类似名称的文件)。这些文件通常包含有关如何构建/编译应用程序以及如何安装它的有用信息,包括有关依赖项的信息。“依赖项”只是成功编译所需的其他组件或库的别称。

阅读完READMEand/orINSTALL文件(最好查看该应用程序的任何相关在线文档)后,查找名为config或 的可执行文件(文件上设置了“x”权限) configure。有时文件可能带有扩展名,例如.sh(例如config.sh)。这通常是一个 shell 脚本,它运行一些其他实用程序来确认您有一个“正常”的编译环境。换句话说,它将检查以确保您已安装所需的一切。

提示:如果这是一个基于 Python 的应用程序,您应该找到一个名为的文件,而不是配置文件setup.py。Python 应用程序通常非常容易安装。要以 root 身份安装此应用程序(例如,在 Ubuntu 下将 sudo 放在以下命令前面),请运行以下命令:

    $ python setup.py install

这就是您需要做的全部工作。您可以跳过本教程的其余部分,直接开始使用和享受您的应用程序。

在终端中运行配置脚本。通常,你可以(也应该!)用你的普通用户账户运行配置脚本。

$ ./config

脚本将显示一些消息,让您了解它正在做什么。通常,脚本会指示它是成功还是失败,如果失败,还会显示一些有关失败原因的信息。如果您没有收到任何错误消息,那么通常可以假设一切顺利。

如果您找不到任何看起来像配置脚本的脚本,那么这通常意味着该应用程序非常简单并且与平台无关。这意味着您可以直接跳到下面的构建/编译步骤,因为所提供的脚本Makefile应该可以在任何系统上运行。

一个例子

在本教程中,我将使用名为 Newsbeuter 的基于文本的 RSS 阅读器作为示例,介绍您在构建应用程序时可能遇到的错误类型。对于 Newsbeuter,配置脚本的名称是config.sh。在我的系统上,当我运行 时config.sh,会出现以下错误:

tester@sitlabcpu22:~/download/newsbeuter-1.3$ ./config.sh
Checking for package sqlite3... not found

You need package sqlite3 in order to compile this program.
Please make sure it is installed.

经过一番研究,我发现应用程序确实sqlite3已经安装好了。但是,由于我尝试从源代码构建,因此这是一个提示,config.sh实际上正在寻找的是 的开发库(标头)sqlite3。在 Ubuntu 中,大多数软件包都有一个以 结尾的关联开发对应软件包-dev。(其他平台,如 Fedora,通常使用 的软件包后缀-devel作为开发软件包。)

为了找到适合sqlite3开发包的包,我们可以使用apt-cacheUbuntu 中的实用程序(同样,yumFedora 中的实用程序):

tester@sitlabcpu22:~/download/newsbeuter-1.3$ sudo apt-cache search sqlite

此命令返回的结果列表相当大,因此我们必须做一些侦查工作来确定哪个是合适的软件包。在本例中,合适的软件包是libsqlite3-dev。请注意,有时我们正在寻找的软件包将具有前缀lib,而不仅仅是相同的软件包名称加上-dev。这是因为有时我们只是在寻找可能被许多不同应用程序使用的共享库。要安装libsqlite3-dev,请在终端中运行典型的 apt-get install 命令:

tester@sitlabcpu22:~/download/newsbeuter-1.3$ sudo apt-get install libsqlite3-dev

现在,我们必须config.sh再次运行以确保我们已解决此依赖性问题,并且不再存在任何依赖性问题。(虽然我不会在这里展示,但就 Newsbeuter 而言,我还必须安装该libcurl4-openssl-dev包。)此外,如果您安装开发包(如libsqlite3-dev)并且尚未安装关联的应用程序包(例如sqlite3),则大多数系统将同时自动安装关联的应用程序包。

当配置成功运行时,结果将是它将创建一个或多个 make 文件。这些文件通常有名称Makefile(请记住,在 Unix/Linux 中文件名大小写很重要!)。如果构建包包含子目录,例如src,等等,则每个子目录Makefile也将包含一个 ,。

构建和编译错误

现在,我们已准备好实际编译应用程序。这通常称为构建,这个名称借用自现实世界中构建某物的过程。应用程序的各个“部分”(通常是多个源代码文件)组合在一起形成整个应用程序。make 实用程序管理构建过程并调用其他应用程序(如编译器和链接器)来实际执行工作。在大多数情况下,您只需从运行配置的目录中运行 make(使用您的常规用户帐户)。(在少数情况下,例如编译使用 Qt 库编写的应用程序,您将需要运行另一个“包装器”应用程序,如 qmake。再次强调,请务必查看README和/或INSTALL文档以了解详细信息。)

与上面的配置脚本一样,当您在终端中运行 make(或类似的实用程序)时,它将显示一些有关正在执行的内容以及任何警告和错误的消息。您通常可以忽略警告,因为它们主要针对应用程序的开发人员,并告诉他们有一些标准做法被违反。通常,这些警告不会影响应用程序功能。另一方面,必须处理编译器错误。使用 Newsbeuter,当我运行 make 时,事情进展顺利,但后来我收到一个错误:

tester@sitlabcpu22:~/download/newsbeuter-1.3$ make
...
c++ -ggdb -I/sw/include -I./include -I./stfl -I./filter -I. -I./xmlrss -Wall -Wextra -DLOCALEDIR=\"/usr/local/share/locale\" -o src/configparser.o -c src/configparser.cpp
c++ -ggdb -I/sw/include -I./include -I./stfl -I./filter -I. -I./xmlrss -Wall -Wextra -DLOCALEDIR=\"/usr/local/share/locale\" -o src/colormanager.o -c src/colormanager.cpp
In file included from ./include/pb_view.h:5,
from src/colormanager.cpp:4:
./include/stflpp.h:5:18: error: stfl.h: No such file or directory
In file included from ./include/pb_view.h:5,
from src/colormanager.cpp:4:
./include/stflpp.h:33: error: ISO C++ forbids declaration of \u2018stfl_form\u2019 with no type
./include/stflpp.h:33: error: expected \u2018;\u2019 before \u2018*\u2019 token
./include/stflpp.h:34: error: ISO C++ forbids declaration of \u2018stfl_ipool\u2019 with no type
./include/stflpp.h:34: error: expected \u2018;\u2019 before \u2018*\u2019 token
make: *** [src/colormanager.o] Error 1

一旦遇到第一个错误,make 过程就会停止。处理编译器错误有时是一件棘手的事情。您必须查看错误以找到有关问题的一些线索。通常,问题是缺少一些头文件,这些头文件的扩展名通常是.h.hpp。在上述错误的情况下,很明显(或应该是!)问题是stfl.h找不到头文件。正如此示例所示,您需要查看错误消息的前几行,然后逐步找到问题的根本原因。

在查看了 Newsbeuter 文档(我应该在开始之前就查看了,但这样本教程的这一部分就没有什么意义了!)之后,我发现它需要一个名为 STFL 的第三方库。那么在这种情况下我们该怎么做呢?好吧,我们基本上对所需的库重复这个完全相同的过程:获取库并为其执行配置-构建-安装过程,然后继续构建所需的应用程序。例如,在 STFL 的情况下,我必须安装包libncursesw5-dev才能正确构建它。(通常,在安装另一个所需的应用程序后,没有必要在我们原来的应用程序上重做配置步骤,但这也无妨。)

成功安装 STFL 工具包后,Newsbeuter 的 make 过程成功运行。make 过程通常会从中断处(发生错误的位置)继续。因此,任何已成功编译的文件都不会重新编译。如果要重新编译所有内容,可以运行 make clean all 以删除所有已编译对象,然后再次运行 make。

安装

构建过程成功完成后,您就可以安装应用程序了。在大多数情况下,要将应用程序安装到文件系统的公共区域(例如,/usr/bin/usr/share/bin等),您需要以 root 身份运行安装。安装实际上是整个过程中最简单的步骤。要安装,请在终端中运行:

$ make install

检查此过程的输出是否有任何错误。如果一切顺利,您应该能够在终端中运行命令名称,它将启动。(如果它是 GUI 应用程序,请将 & 附加到命令行末尾,否则您将无法使用终端会话,直到应用程序运行完毕。)

当您从源代码构建应用程序时,它通常不会向 Ubuntu 中的 GUI 菜单添加图标或快捷方式。您需要手动添加。

这就是在 Ubuntu 中从源代码构建和安装应用程序的基本过程,尽管可能有些反复。只要你做过几次,它就会成为你的第二天性!

答案4

“我如何确定要传递给./configure 的参数?”

通常:./configure --help 会告诉您您想要什么。

“我如何知道我安装了哪些库以及哪些版本?”

这取决于系统。一种方法是直接执行,find /|grep libname|less因为通常库文件的文件名中都有版本号。

“如何在不破坏正常系统的情况下安装一个库的多个版本?”

再次强调,这取决于系统和库。sudo make altinstall将为您创建一个版本名称。库文件通常会自行版本化。但请记住;由于版本通常会创建指向“标准化”名称的符号链接,因此这可能会破坏一切。

“如果我在使用软件包管理的系统上从源代码安装东西,最干净的方法是什么?”

在 ./configure 中使用 --prefix 参数并将其放在某处/opt是一种很好的做法。

免责声明:我绝不是专家,但我已经使用 Linux 超过 5 年了从命令行(slackware、CentOS、redhat、ubuntu、其他和 OS X)。

相关内容