Available Languages: | Deutsch | English | Français | 日本語 (Nihongo) | 中文 (简) (Simplified Chinese) |

移植 Unix 软件到 Darwin 和 Mac OS X 上

本文档包含如何移植 Unix 软件到 Darwin 和 Mac OS X 平台上的提示。 这里的信息适用于 Mac OS X 10.0.x 和 Darwin 1.3.x。 这两种操作系统我们都用 Darwin 来指代,因为 Mac OS X 实际上只是 Darwin 的一个超集。

Contents

1 基本知识

1.1 Darwin 的来历

Darwin 是从 NeXTStep / OpenStep 演化而来的类 Unix 操作系统。 据资料上它最初从 4.4BSD Lite 分支出来。 BSD 的继承关系在它上面仍然表现出来,事实上,最近 Darwin 仍然使用 FreeBSD 和 NetBSD 的代码来进行改进。

Darwin 的核心基于 Mach 3.0,BSD 和专利的面向对象驱动层 IOKit 的结合。虽然 Mach 最初是一个微内核的设计,但在它之上的 BSD 核心则是全功能形式的,由于现在两者如此紧密地缠绕在一起,所以应该把它们看成一个整体单一的全功能内核。

随 Darwin 提供的用户级工具主要是 BSD 家族的,而不是 Linux 所提供的 GNU 家族。 不过苹果在这方面并不象其它 BSD 版本那么严格,而只是以一种折衷的态度来对待。 例如,苹果同时提供 BSD make 和 GNU make,默认安装的是 GNU make。

1.2 编译器和工具

简单地说:使用的编译器是从 gcc 衍生物,但安装为 cc;你可能需要修改 Makefiles。 多数软件包都不能构建共享库。 如果你获得有关宏的编译错误,使用 -no-cpp-precomp 选项。

详细地说:Mac OS X 开发工具里面的编译器工具组合是一个怪兽。 编译器是基于 gcc 2.95.2 套件的,但做了些修改以支持 Objective C 语言和一些 Darwin 特殊东西。 预编译器 (cpp) 有两个版本。 一个是从 gcc 2.95.2 来的标准的预编译器,另外一个是苹果编写的专门的预编译器,它支持对头文件的预编译。 后者是默认使用的版本,因为它比较快一些。 不过,一些代码不能用苹果的预编译器编译,所以你需要使用 -no-cpp-precomp 选项来转用标准的预编译器。 (注意:我以前推荐 -traditional-cpp 选项。 不过,在 GCC 3 中,它的含义发生了一些轻微的改变,使得对多数使用它的软件包都不可用了。 -no-cpp-precomp 则对于当前的开发工具和将来基于 GCC 3 的编译器都会有相同的我们希望的效果。)

汇编器是基于 gas 1.38 的。连接器不是基于 GNU 工具的。 这在构建共享库的时候会有问题,因为 GNU libtool 和它产生的 configure 脚本不知道如何处理苹果的连接器。

1.3 主机类型

简单地说:如果配置过程出现 "Can't determine host type" 错误,从 /usr/share/libtool (在 10.2 之前的版本是 /usr/libexec) 拷贝 config.guess 和 config.sub 文件到当前目录。

详细地说:GNU 世界使用一个规范的格式来描述系统类型。 它由三部分组成:cpu 类型,制造商和操作系统。 有些时候会加上第四部分-这时第三部分表示内核,而第四部分表示操作系统。 所有部分都使用小写字母,并以破折号连接。 例如:i586-pc-linux-gnuhppa1.1-hp-hpux10.20sparc-sun-solaris2.6。 对应于 Mac OS X 10.0 的主机类型是 powerpc-apple-darwin1.3。 Versions of Mac OS X 10.2 bring various powerpc-apple-darwin6.x.0 and 10.3 gives powerpc-apple-darwin7.x.0, where "x" depends on the exact OS version.

许多使用 autoconf 的软件包需要知道它们所编译于的系统平台。(附注:为了支持交叉编译和移植,实际上会有三种类型-主机类型,构建类型,目标类型。通常来说,它们是相同的)。 你可以把主机类型作为参数传递给 configure 脚本,或让它自己猜测。

配置脚本使用两个配套的脚本来检测主机类型。config.guess 尝试猜测主机类型,config.sub 用于验证和规范主机类型。 这些脚本是作为独立的实体进行维护的,但它们会被包括在每个使用它们的软件包中。 直到最近,这些脚本才能够认识 Darwin 或 Mac OS X。 如果你有一个不认识 Darwin 的软件包,你需要替换包括在软件包里面的 config.guess 和 config.sub 脚本。 幸运的是,苹果放了一个可用的版本在 /usr/share/libtool (对 10.2 之前的版本是 /usr/libexec) 中,所以你只需要从那里拷贝就可以了。

If you are constructing a Fink package, you can use the UpdateConfigGuess and/or UpdateConfigGuessInDirs fields in your .info package description file to do this update automatically.

1.4 函数库

简单地说:你可以从 Makefile 中删除 -lm,这不会造成问题,但你不需要这样做。

详细地说:Mac OS X 没有单独的 libc,libm,libcurses,libpthread 等函数库。 它们都是系统函数库 libSystem 的一部分(在更早的版本中,它们实际上是系统框架(framework))。 不过,苹果在 /usr/lib 中放置了合适的符号连接,所以连接到 -lm 也是可以的。 唯一的例外是 -lutil。在其它系统中,libutil 包含与伪终端和登录记帐有关的函数。 这些函数在 libSystem 中,但没有提供 libutil.dylib 的符号连接。

1.5 其它信息来源

关于移植的另外一个信息来源是一个在 MetaPkg Wiki 的 Wiki 站点。

你可以阅读苹果的技术文摘 TN2071:《移植 Unix 命令行工具到 Mac OS X》。

2 共享代码

2.1 共享库和可加载模块对比

Mach-O 的一个令人吃惊的特性是对共享函数库和可加载模块的严格区分。 在 ELF 系统上,两者是相同的;任何的共享代码都可以作为函数库或作为动态加载模块。 Use otool -hv some_file to see the filetype of some_file.

Mach-O 共享函数库具有 MH_DYLIB 文件类型,扩展名为 .dylib。 它们可以通过常用的静态连接标志进行连接,,比如 -lfoo 会连接 libfoo.dylib。 不过,它们不能以模块形式加载。 (附注:共享库可以通过一个 API 动态装载。 不过,那个 API 和一般用于束的 API 并不相同;语法上的差别也使它不能作为 dlopen() 的模拟。 最值得注意的是,共享库不能被卸载。)

按 Mach-O 的术语,可加载模块称为束("bundles")。 它们的文件类型为 MH_BUNDLE。 由于没有其它的组件会使用它,它们可以有任意的扩展名。 苹果推荐使用 .bundle 扩展名,但多数移植的软件会因为兼容性的原因而使用 .so 的扩展名。 束可以通过 dyld API 动态加载和卸载,在这些 API 之上有一个封装来模拟 dlopen()。 没有办法把束当作共享库来进行连接。 不过,可以把一个束连接到一个真正的共享库这样在束加载的时候,共享库也会被自动加载。

2.2 版本编号

在 ELF 系统中,版本号通常会添加到共享库的文件名后面,例如:libqt.so.2.3.0。 在 Darwin 中,版本号被放在函数库名和扩展名之间,例如:libqt.2.3.0.dylib。 注意,这样使得你可以在连接的时候指定所要连接的版本,比方说对上面的例子,可以使用:-lqt.2.3.0

当创建一个共享库时,你可以指定一个在运行时搜索函数库所用的名字。 通常都会这样去做,而使得可以同时安装一个函数库的不同主版本。 在 ELF 系统上,这称为 soname。 区别是在 Darwin 上你可以(而且是必须)指定包括文件名的完整路径。 这消除了 "rpath" 选项以及 ldconfig/ld.so.cache 系统的需要。 要使用一个还没有安装的函数库,你可以设置 DYLD_LIBRARY_PATH 环境变量;查阅 dyld 的手册页获取更多细节。

Mach-O 的格式还提供了真正的次要版本号的检查,在 ELF 系统上这点不是很清楚。 每个 Mach-O 函数库有两个版本号:"当前" 版本号和 "兼容" 版本号。 这两个版本号都有三组以句点分隔的数字组成,例如:1.4.2。 第一个数字必需为非零值。 第二和第三个数字可以不提供,这时缺省为零值。 没有指定版本号的时候,默认为 0.0.0,这是一种通配值。

The "current" version is for informational purposes only. The "compatibility" version is used for checking as follows. When an executable is linked, the version information from the library is copied into the executable. When that executable is run, the stored version information is checked against the version information in the library that is loaded. dyld generates a run-time error and terminates the program unless the loaded library version is equal to or newer than the one used during linking.

2.3 编译器标志

在 Darwin 上默认会生成"位置无关代码"(PIC)。 事实上,PowerPC 的代码在设计上就是位置无关的,所以这不会产生性能的损失。 因此,在编译共享库或模块的时候,你不需要指定 PIC 选项。 不过,连接器不允许在共享库中使用 "common" 标志,所以你需要使用 -fno-common 编译器选项。

2.4 构建一个共享库

要构建一个共享库,你要使用 -dynamiclib 选项调用编译器。 这可以通过一个完整的例子来演示。 我们要编译一个名为 libfoo 的库,它由 source.c 和 code.c 两个源程序文件组成。 版本号是 2.4.5,2 是主版本号(发生了不兼容的 API 改变),4 是次要版本号(后向兼容的 API 改变),5 是修正错误的修订版计数(有些人把这称为 "teeny" 修订版,它包含完全兼容的改变)。 函数库不依赖于其它共享库,会被安装在 /usr/local/lib

cc -fno-common -c source.c
cc -fno-common -c code.c
cc -dynamiclib -install_name /usr/local/lib/libfoo.2.dylib \
 -compatibility_version 2.4 -current_version 2.4.5 \
 -o libfoo.2.4.5.dylib source.o code.o
rm -f libfoo.2.dylib libfoo.dylib
ln -s libfoo.2.4.5.dylib libfoo.2.dylib
ln -s libfoo.2.4.5.dylib libfoo.dylib

注意版本号的各个部分所使用的地方。 When linking against this library, one would normally use the -lfoo flag, which accesses the libfoo.dylib symlink. Regardless of which symlink or file is specified, though, it is the install_name that is written into one's program. That means one can delete the "higher" (less version-specific) symlink libfoo.dylib after compiling. During a minor-revision library upgrade, one just changes the target of the libfoo.2.dylib symlink that is used by the runtime dynamic linker.

2.5 构建一个模块

要构建一个可加载模块,你用 -bundle 参数调用编译器。 如果模块会使用主程序中的标识符,你需要指定 -undefined suppress 来允许未定义标识符, 同时也配合使用 -flat_namespace 来满足 Mac OS X 10.1 中的新连接器。 一个详细的例子是:

cc -fno-common -c source.c
cc -fno-common -c code.c
cc -bundle -flat_namespace -undefined suppress \
 -o mymodule.so source.o code.o

注意这里没有使用版本号码。 理论上来说,可以使用版本号码,但从实用的角度来说,这样做是没有意义的。 另外注意的是,对束没有命名限制。 一些软件包喜欢使用 "lib",因为在其它系统会有这个要求。这样做不会有问题 since a program would use the full filename when loading a module.

3 GNU libtool

要构建共享库的 GNU 软件包使用 GNU libtool 来在构建和安装过程中隐藏那些平台相关的过程。

3.1 有关情况

宏观地说,可以找到 libtool 的四个分支:

作为结论,libtool 1.3.x 以及使用它的软件包(它们是使用 libtool 的软件包的主流)需要进行补丁后在可以在 Darwin 上构建共享库。 Apple 在 Mac OS X 中包括了一个修正的版本,但在多数情况下不能正常工作。Christoph Pfisterer 通过把正确的路径和完整的版本编号固定编程的方法改进了那个补丁。 从 1.4 开始,这个修改被加进了上游的 libtool 发型版中。 Fink 团队的成员仍然不对 libtool 进行改进,并把他们的结果反馈会 libtool 的维护者。 版本编号方案对于所有 libtool 版本都是兼容的。

附注: 所有 libtool 版本所包括的 libltdl 库只有在安装了 dlcompat 的 Darwin 才可以使用。 This is included with OS X starting with 10.3. For previous versions, one can install the fink "dlcompat" family of packages.

3.2 1.3.5 补丁

如果你自己构建 libtool 1.3.5,你需要应用这个补丁 [updated 2002-06-09] 到 libtool 1.3.5 的源程序,然后删除 ltconfigltmain.sh 两个文件(它们会在你运行 configure 和 make 的时候从合适的 .in 文件中重建)。 在 Fink 的 libtool 1.3.5 软件包中,这个过程是自动的。

但那仅是全部工作的一半 ─ 每个软件包是通过它自己附带的 ltconfig 和 ltmain.sh 来使用 libtool。 所以如果你需要构建共享库的时候,你要把每个软件包里面的这两个文件替换掉。 注意,你要在运行配置脚本之前做这个事情。 方便起见,你可以在这里获取这两个文件: ltconfig (98K) 和 ltmain.sh (110K) [both updated 2002-06-09]

3.3 修正 1.4.x

现在广泛使用的 libtool 1.4.x 至少有三个不同版本(1.4.1,1.4.2,和更新的开发版本),它们在 Darwin 上都有一些问题。不过在它们上面如何修正的方法却是不同的。Fink 所提供的 "libtool14" 软件包已经包括了所有需要的修正。 不过,你仍然需要手工修正受影响的文件包的 ltconfigltmain.sh 文件来使它可以正常工作。

  1. flat_namespace 问题: 这个问题只发生在 Mac OS X10.1 和更新的版本上使用 libtool 的时候。 原因是 libtool 尝试使用 -undefined suppress 来允许未定义的标识符,但没有同时使用 -flat_namespace 选项。 从 10.1 开始,这不再可以工作了。典型的补丁是这样的:
    diff -Naur gdk-pixbuf-0.16.0.old/configure gdk-pixbuf-0.16.0.new/configure
    --- gdk-pixbuf-0.16.0.old/configure	Wed Jan 23 10:11:48 2002
    +++ gdk-pixbuf-0.16.0.new/configure	Thu Jan 31 03:19:54 2002
    @@ -3334,7 +3334,7 @@
         ;;
     
       darwin* | rhapsody*)
    -    allow_undefined_flag='-undefined suppress'
    +    allow_undefined_flag='-flat_namespace -undefined suppress'
         # FIXME: Relying on posixy $() will cause problems for
         #        cross-compilation, but unfortunately the echo tests do not
         #        yet detect zsh echo's removal of \ escapes.
    
  2. 可加载模块问题: 这个问题是由于 zsh (在 10.0 和 10.1 上是默认的 shell;starting in 10.2 bash is the default) 的非标准行为引起的。 Zsh 的非标准引号处理方式使得可加载模块不能正确地构建,最后它们被构建程共享库(不象 Linux,在 Darwin 上这是完全不同的东西)。一个典型的修正办法是(你不能完全不经修改地照搬):
    diff -Naur gnome-core-1.4.0.6.old/configure gnome-core-1.4.0.6.new/configure
    --- gnome-core-1.4.0.6.old/configure	Sun Jan 27 08:19:48 2002
    +++ gnome-core-1.4.0.6.new/configure	Fri Feb  8 01:10:21 2002
    @@ -4020,7 +4020,7 @@
         # FIXME: Relying on posixy $() will cause problems for
         #        cross-compilation, but unfortunately the echo tests do not
         #        yet detect zsh echo's removal of \ escapes.
    -    archive_cmds='$nonopt $(test "x$module" = xyes && echo -bundle || echo -dynamiclib) ...'
    +    archive_cmds='$nonopt $(test x$module = xyes && echo -bundle || echo -dynamiclib) ...'
         # We need to add '_' to the symbols in $export_symbols first
         #archive_expsym_cmds="$archive_cmds"' && strip -s $export_symbols'
         hardcode_direct=yes
    

    这个问题在 1.4.2 后的某个 libtool 版本修正了。

  3. 简易库问题: 在一些情况下,libtool 不能连接简易库,而给出 "multiple definitions" 错误。 这是有 libtool 里面的一些更根本的问题导致的。 有一个治标不治本,不过在实践中很有效的办法来解决(感谢 Dave Vasilevsky):
    --- ltmain.sh.old       2002-04-27 00:01:23.000000000 -0400
    +++ ltmain.sh   2002-04-27 00:01:45.000000000 -0400
    @@ -2894,7 +2894,18 @@
            if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then
              eval cmds=\"$archive_expsym_cmds\"
            else
    +         save_deplibs="$deplibs"
    +         for conv in $convenience; do
    +       tmp_deplibs=
    +       for test_deplib in $deplibs; do
    +         if test "$test_deplib" != "$conv"; then
    +           tmp_deplibs="$tmp_deplibs $test_deplib"
    +         fi
    +       done
    +       deplibs="$tmp_deplibs"
    +         done
              eval cmds=\"$archive_cmds\"
    +         deplibs="$save_deplibs"
            fi
            save_ifs="$IFS"; IFS='~'
            for cmd in $cmds; do
    
  4. DESTDIR 问题: 有些设置了 DESTDIR 并使用 libtool 1.4.2 的软件包会在重新连接的时候有问题。 这个问题在这些邮件消息里面被讨论:

    http://mail.gnu.org/archive/html/libtool/2002-04/msg00019.html

    http://mail.gnu.org/archive/html/libtool/2002-04/msg00021.html

    http://mail.gnu.org/archive/html/libtool/2002-04/msg00025.html,

    对这个问题的补丁在这里被讨论:

    http://mail.gnu.org/archive/html/libtool/2002-04/msg00043.html.

3.4 更多注解

关于 libtool 本身以及它的进展的更多信息,查看libtool 首页

附注: 苹果的开发工具包中也包括一个叫做 libtool 的程序,用于编译器构建共享库。 不过,这和 GNU libtool 一点关系也没有。 苹果提供的 GNU libtool 则被安装为 glibtool。 这可以通过 --program-transform-name=s/libtool/glibtool/ 来配置 GNU libtool 来解决。

4 为 10.2 做准备

4.1 bash shell

Fink 使得从 OS X 10.0 到 OS X 10.1 的转换很容易, 这得益于在转换到来之前就已经做好了计划。 我们计划在下一次转换到来之前也做好准备,但现在还不是太多的细节。

我们直到 OS X 10.2 将使用 bash 而不是 zsh 来提供 /bin/sh 的功能。这至少对 fink 有三点联系。

4.2 gcc3 编译器

Mac OS X 10.2 使用 gcc3 编译器。

一些使用可加载模块及 libtool 的软件包会因为一个 install_name 错误而失败,因为 libtool 甚至会和 -bundle 标志一起传递 -install_name 标志(即使这不是严格必需的)。 这种情况在 gcc2 编译器下是可接受的,那对 gcc3 就不行了。修正方法可以在这里找到。 注意,如果你的软件包使用 libtool-1.3.5 (例如,如果你使用 UpdateLibtool: True),你不需要这个补丁。 因为它已经被包括到 fink 的 ltconfig 文件的修订版本中了(在 fink 的预发布版本中提供)。

另外一个关于 gcc3 编译器的问题是 gcc2 和 gcc3 之间对 C++ ABI 的不兼容性。 在实践中,这意味着用 gcc3 编译的 C++ 程序不能链接到用 gcc2 编译的库。

5 为 10.3 做准备

5.1 Perl

In OS X 10.2, /usr/bin/perl was perl 5.6.0 and the architecture string was "darwin". In OS X 10.3, /usr/bin/perl was upgraded to perl 5.8.1 and the architecture string was changed to "darwin-thread-multi-2level". These changes probably do not affect ordinary uses of the perl executable for package creation since each perl executable knows where to find its own modules. Maintainers of perl-module ("-pm") packages who follow the current Perl Modules packaging policy and are careful to follow the CompileScript and InstallScript documentation will already have things set up correctly.

5.2 New symbol definitions

Starting in Mac OS X 10.3, there is now always a complete definition for the socklen_t type. To get this typedef defined, you may need to add to your program:

#include <sys/types.h>
#include <sys/socket.h>
  

5.3 New builtin system libraries

Mac OS X 10.3 includes several libraries that were not in previous system releases, and so were provided as fink packages:

FieldValue
libpoll

The files /usr/lib/libpoll.dylib and /usr/include/poll.h are now included, however the OS X-supplied implementation of this library is not complete. If it is sufficient for your purposes, you can remove dependencies on the Fink "libpoll" and "libpoll-shlibs" packages. The library code is actually incorporated into libSystem, which is automatically linked. That means that -lpoll is not necessary if you wish to use the OS X implementation. Be aware that OS X supplies a libpoll.dylib, so -lpoll may give different results depending on whether you have the Fink "libpoll" package installed or not.

libdl

The files /usr/lib/libdl.dylib and /usr/include/dlfcn.h are now included, so you should not need dependencies on the Fink "dlcompat", "dlcompat-dev", and "dlcompat-shlibs" packages. The library code is actually incorportated into libSystem, which is automatically linked. That means that -ldl is not necessary (but has no effect if used).

GNU getopt

This library, including the getopt_long() function, has been incorportated into libSystem and /usr/include/getopt.h, so you may not need to use the Fink "libgnugetopt" and "libgnugetopt-shlibs" packages. Because libSystem is automatically linked and /usr/include is automatically searched for headers, you could remove any -lgnugetopt and -I/sw/include/gnugetopt flags that were manually added in order to access Fink's "libgnugetopt".

When migrating a package to OS X 10.3, try to remove these deprecated dependencies, as those packages may be removed from these newer package trees in the future. This means you will need a separate package description file for each tree. As always, the Revision must be increased when making changes to a package. In this manner, a user who upgrades from OS X 10.2 to 10.3 will see 10.3-specific packages as "newer" than his existing 10.2 ones. By convention, the Revision should be increased by 10 when changes are made for migration to a higher tree inn order to leave space for the lower-tree package to be updated in the future.

When testing a migrated package, make sure to uninstall the packages whose BuildDepends you removed. Otherwise the compiler may still link the Fink-supplied libraries.

6 为 10.4 做准备

6.1 Perl

/usr/bin/perl is now perl 5.8.6; the architecture string is still "darwin-thread-multi-2level".

6.2 New symbol definitions

Mac OS X 10.4 has changed the types of many symbols. If you previously set a type explicitly, for example, defining socklen_t as int, that definition may no longer be correct.

6.3 New builtin system libraries

The poll() function in Mac OS X 10.3 was actually an emulation implemented using select(). In Mac OS X 10.4, poll() is a real function implemented in the kernel, however it is broken when used with sockets. Consider ignoring the system's poll() completely. Fink's glib2 package has been patched to use a fully functional emulation, so it is safe to use that library's implementation of poll-like functions.


Copyright Notice

Copyright (c) 2001 Christoph Pfisterer, Copyright (c) 2001-2015 The Fink Project. You may distribute this document in print for private purposes, provided the document and this copyright notice remain complete and unmodified. Any commercial reproduction and any online publication requires the explicit consent of the author.


Generated from $Fink: porting.zh.xml,v 1.9 2014/10/25 09:21:47 gecko2 Exp $