构建Python模块并链接到MacOSX框架
我正在尝试在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 个回答
我不太确定你想要做什么以及你期望的结果是什么,但也许这些信息能帮到你。因为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 /
来构建扩展模块的。
一般来说,为了让一切正常工作,你需要确保:
解释器、所有C语言扩展模块以及它们链接的所有外部框架和/或共享库之间至少有一个
arch
是相同的。解释器是在那个(或这些)共同的架构中执行的。
在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位模式(如果可用)。
所以,根据你想要做的事情,如果我理解得没错,你可能可以通过以下方式解决这个问题:
重新构建你的框架,包含
-arch x86_64
使用苹果提供的Python(
/usr/bin/python
)以32位模式运行,或者使用python.org的2.6.5版本以32位模式重新安装MacPorts的Python(未经测试!):
sudo port selfupdate sudo port clean python26 sudo port install python26 +universal universal_archs=i386
虽然事情已经过去很久,但我自己也有同样的问题,于是我稍微查了一下,发现了这个:
/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
这个方法效果很好。
这段话跟未定义的动态查找没关系,主要是和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
来安装了。