构建Python模块并链接到MacOSX框架

16 投票
5 回答
6780 浏览
提问于 2025-04-15 21:17

我正在尝试在MacOSX 10.6上构建一个Python扩展,并且想要将它链接到几个框架(只支持i386架构)。我创建了一个setup.py文件,使用了distutils和Extension对象。

为了链接我的框架,我的LDFLAGS环境变量应该像这样:

LDFLAGS = -lc -arch i386 -framework fwk1 -framework fwk2

因为在Extension模块的文档中没有找到'framework'这个关键词,所以我使用了extra_link_args这个关键词。

Extension('test',
define_macros = [('MAJOR_VERSION', '1'), ,('MINOR_VERSION', '0')],
include_dirs = ['/usr/local/include', 'include/', 'include/vitale'],
extra_link_args = ['-arch i386',
                   '-framework fwk1',
                   '-framework fwk2'],
sources = "testmodule.cpp",
language = 'c++' )

一切都编译和链接得很好。如果我从extra_link_args中移除-framework这一行,链接器就会失败,这也是我预期的。以下是通过运行python setup.py build生成的最后两行:

/usr/bin/g++-4.2 -arch x86_64 -arch i386 -isysroot /
-L/opt/local/lib -arch x86_64 -arch i386 -bundle
-undefined dynamic_lookup build/temp.macosx-10.6-intel-2.6/testmodule.o
-o build/lib.macosx-10.6-intel-2.6/test.so
-arch i386 -framework fwk1 -framework fwk2

不幸的是,我刚生成的.so文件无法找到这个框架提供的几个符号。我尝试用otool检查链接的框架,但没有一个出现。

$ otool -L test.so
test.so:
    /usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.9.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.0.1)

这是在一个测试二进制文件上运行otool的输出,这个二进制文件是用g++和ldd以及我在帖子开头描述的LDFLAGS制作的。在这个例子中,-framework确实起作用了。

$ otool -L vitaosx 
vitaosx:
    /Library/Frameworks/fwk1.framework/Versions/A/fwk1 (compatibility version 1.0.0, current version 1.0.0)
    /Library/Frameworks/fwk2.framework/Versions/A/fwk2 (compatibility version 1.0.0, current version 1.0.0)
    /usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.9.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.0.1)

这个问题可能和链接步骤中的"-undefined dynamic_lookup"标志有关吗?我对在Google上找到的几行文档有点困惑。

谢谢,

5 个回答

3

我不太确定你想要做什么以及你期望的结果是什么,但也许这些信息能帮到你。因为C语言扩展模块通常是在Python解释器的执行环境中运行,所以这些扩展模块必须与解释器兼容。在OS X系统上,Python和distutils会费一些劲,确保C语言扩展模块使用的SDK(-sysroot)、MACOSX_DEPLOYMENT_TARGET值和-arch值与Python解释器最初构建时使用的一样。因此,如果你在10.6上使用的是苹果提供的Python,distutils会提供-arch i386 -arch ppc -arch x86_64,这三种架构就是它构建时用的。如果你使用的是当前的python.org OS X安装包(在10.6、10.5或10.4上),它会使用:

gcc-4.0 -arch ppc -arch i386 -isysroot /Developer/SDKs/MacOSX10.4u.sdk

根据你提供的片段,我猜测你在使用MacPorts安装的通用Python,默认情况下,它是用-arch x86_64 -arch i386 -isysroot /来构建扩展模块的。

一般来说,为了让一切正常工作,你需要确保:

  1. 解释器、所有C语言扩展模块以及它们链接的所有外部框架和/或共享库之间至少有一个arch是相同的。

  2. 解释器是在那个(或这些)共同的架构中执行的。

在OS X 10.6上,最后一步并不像应该的那样简单,这取决于你使用的是哪个Python。例如,苹果提供的Python 2.6有一个修改,强制执行32位模式(具体细节可以查看苹果的man python):

export VERSIONER_PYTHON_PREFER_32_BIT=yes

如果你自己构建一个32/64位的通用Python,2.6.5中有一些修复可以让你在运行时选择。不幸的是,MacPorts构建Python的方式绕过了这些修复,所以在10.6上,似乎没有简单的方法可以强制MacPorts的python2.6以32位模式运行。由于一些复杂的原因,即使你使用/usr/bin/arch -i386,它也总是会优先选择64位模式(如果可用)。

所以,根据你想要做的事情,如果我理解得没错,你可能可以通过以下方式解决这个问题:

  1. 重新构建你的框架,包含-arch x86_64

  2. 使用苹果提供的Python(/usr/bin/python)以32位模式运行,或者使用python.org的2.6.5版本

  3. 以32位模式重新安装MacPorts的Python(未经测试!):

    sudo port selfupdate
    sudo port clean python26
    sudo port install python26 +universal universal_archs=i386
    
4

虽然事情已经过去很久,但我自己也有同样的问题,于是我稍微查了一下,发现了这个:

/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/distutils/sysconfig.py

   if 'ARCHFLAGS' in os.environ:
                archflags = os.environ['ARCHFLAGS']
            else:
                archflags = '-arch i386 -arch ppc -arch x86_64'
            _config_vars['ARCHFLAGS'] = archflags
            if archflags.strip() != '':
                _config_vars['CFLAGS'] = _config_vars['CFLAGS'] + ' ' + archflags
                _config_vars['LDFLAGS'] = _config_vars['LDFLAGS'] + ' ' + archflags

我从不同的角度来看这个问题——在10.6版本中,distutils正在尝试构建C扩展,但因为10.6的SDK里没有PPC部分而出现了问题。

不过,

 export ARCHFLAGS="-arch i386 -arch x86_64"
 python setup.py build

这个方法效果很好。

13

这段话跟未定义的动态查找没关系,主要是和distutils有关。它把额外的链接标志加到了构建Python时选择的链接标志后面。其实应该把它放在前面,因为在命令行中,-framework的列表必须在使用它们的对象之前(据我所知,这是因为gcc在链接时是这样处理符号的)。我个人用的一个快速解决办法是这样构建:

    LDFLAGS="-framework Carbon" python setup.py build_ext --inplace

或者你需要的其他框架。LDFLAGS会被放在distutils自己标志的前面。需要注意的是,你的包将无法通过pip install来安装。一个真正的解决办法只能来自distutils——在我看来,他们应该像支持libraries一样支持frameworks

另外,你也可以在你的setup.py中添加:

import os
os.environ['LDFLAGS'] = '-framework Carbon'

这样你的包就可以通过pip install来安装了。

撰写回答