如何使用autotools同时构建Python接口和库
我现在有一个用C++写的库,使用GNU autotools来构建,我想给它加一个Python接口。我用SWIG开发了这个接口,但在把Python模块的编译过程和其他部分整合在一起时遇到了一些麻烦。
我查过AM_PATH_PYTHON这个宏,但它似乎没有设置Python.h的包含路径,所以当我编译模块时,出现了很多关于缺少包含文件的错误。有没有办法从AM_PATH_PYTHON中获取Python的包含路径和链接标志呢?
顺便说一下,我觉得用Python的distutils方法(setup.py)可能不行,因为这需要库的位置来链接新的模块。由于库在编译时还没有安装,我得用相对路径(比如../src/lib.so),但这样一来,一旦Python模块安装后就会出问题,因为库会在/usr/lib或/usr/local/lib下。
编辑:
现在它能找到正在编译的.h文件了,但安装后(在正确的位置)Python却无法加载这个模块。代码生成了foo.so,当我“import foo”时出现了这个:
ImportError: dynamic module does not define init function (initfoo)
不过,如果我把它从foo.so重命名为_foo.so,它就能正常加载和运行,只是我得用“import _foo”,这让我觉得不太方便。当我按照SWIG的说明在当前目录生成_foo.so时,“import foo”就能正常工作,所以我不太明白为什么在库安装到站点目录后会出问题。
编辑2:
结果问题出在我忘了把SWIG生成的foo.py文件复制到与_foo.so一起的安装目录里。一旦我做了这个,所有东西就都按预期工作了!现在我只需要想办法写一些automake规则,把文件复制到安装目录里……
4 个回答
在命令行手动构建
如果你不想在你的makefile中添加SWIG的步骤,可以使用以下命令来构建SWIG扩展(前提是你有source_file.cpp和your_extension.i这两个文件来创建你的Python模块):
# creation of your_extension_wrap.cpp
swig -c++ -python -o your_extension_wrap.cpp your_extension.i
# creation of your_extension_wrap.o, source_file.o and your_extension.py
g++ -fPIC -c your_extension_wrap.cpp source_file.cpp -I/usr/include/python2.7
# creation of the library _your_extension.so
g++ -shared your_extension_wrap.o source_file.o -o _your_extension.so
注意:在共享对象的名字前加个下划线是很重要的,否则当你执行import your_extension时,Python找不到它。
将SWIG添加到Autoconf
我写了一个小程序,里面需要一个用C++写的文件(比如说,四舍五入的方法)。
你需要额外的Autoconf宏来启用SWIG支持。正如johanvdw所提到的,使用这两个m4宏:ax_pck_swig和ax_swig_python会更简单。我从Autoconf宏库下载了它们,并把它们放在我项目树的m4子目录中:
trunk
├── configure.ac
├── __init__.py
├── m4
│ ├── ax_pkg_swig.m4
│ ├── ax_swig_python.m4
│ ├── libtool.m4
│ ├── lt~obsolete.m4
│ ├── ltoptions.m4
│ ├── ltsugar.m4
│ └── ltversion.m4
├── Makefile.am
├── rounding_swig
│ ├── compile.txt
│ ├── __init__.py
│ ├── Makefile.am
│ ├── rnd_C.cpp
│ ├── rounding.i
│ ├── rounding_wrap.cpp
└── src
├── cadna_add.py
├── cadna_computedzero.py
├── cadna_convert.py
├── __init__.py
└── Makefile.am
当你把这两个m4宏放在一个子目录时,需要在你的trunk/Makefile.am中添加这一行:
ACLOCAL_AMFLAGS = -I m4
现在,让我们看看trunk/configure.ac:
AC_PREREQ([2.69]) # Check autoconf version
AC_INIT(CADNA_PY, 1.0.0, cadna-team@lip6.fr) # Name of your software
AC_CONFIG_SRCDIR([rounding_swig/rnd_C.cpp]) # Name of the c++ source
AC_CONFIG_MACRO_DIR(m4) # Indicate where are your m4 macro
AC_CONFIG_HEADERS(config.h)
AM_INIT_AUTOMAKE
AC_DISABLE_STATIC #enable shared libraries
# Checks for programs.
AC_PROG_LIBTOOL # check libtool
AC_PROG_CXX # check c++ compiler
AM_PATH_PYTHON(2.3) # check python version
AX_PKG_SWIG(1.3.21) # check swig version
AX_SWIG_ENABLE_CXX # fill some variable usefull later
AX_SWIG_PYTHON # same
# Checks for header files.
AC_CHECK_HEADERS([fenv.h stdlib.h string.h]) # any header needed by your c++ source
# Checks for typedefs, structures, and compiler characteristics.
AC_CHECK_HEADER_STDBOOL
AC_TYPE_SIZE_T
# Checks for library functions.
AC_FUNC_MALLOC
AC_CHECK_FUNCS([fesetround memset strstr])
AC_CONFIG_FILES([
Makefile
src/Makefile
rounding_swig/Makefile
])
LIBPYTHON="python$PYTHON_VERSION" # define the python interpreter
LDFLAGS="$LDFLAGS -l$LIBPYTHON"
AC_OUTPUT
将SWIG添加到Automake
在你的trunk/Makefile.am中,你需要做以下事情:
ACLOCAL_AMFLAGS = -I m4
# Indicate the subdir of c++ file and python file
SUBDIRS = src rounding_swig
# Indicate a list of all the files that are part of the package, but
# are not installed by default and were not specified in any other way
EXTRA_DIST= \
rounding_swig/rounding.i \
rounding_swig/testrounding.py \
rounding_swig/testrounding.cpp
在你的trunk/src/Makefile.am中:
# Python source files that will be install in prefix/lib/name_of_your_python_interpreter/site-packages/name_of_your_project
pkgpython_PYTHON = cadna_add.py cadna_computedzero.py cadna_convert.py
比较难的部分在trunk/rounding_swig/Makefile.am。它会创建一个库文件.l和一个_your_extension.so,并把它放在prefix/lib64/python2.7/site-packages/name_of_the_project/目录下。
# Name of the cpp source file
BUILT_SOURCES = rounding_wrap.cpp
# Name of the swig source file
SWIG_SOURCES = rounding.i
# Python source files that will be install in prefix/lib/name_of_your_python_interpreter/site-packages/name_of_your_project
pkgpython_PYTHON = rounding.py __init__.py
pkgpyexec_LTLIBRARIES = _rounding.la
_rounding_la_SOURCES = rounding_wrap.cpp $(SWIG_SOURCES) rnd_C.cpp
_rounding_la_CPPFLAGS = $(AX_SWIG_PYTHON_CPPFLAGS) -I$(top_srcdir)/rounding_swig -I/usr/include/python@PYTHON_VERSION@ -lpython@PYTHON_VERSION@
_rounding_la_LDFLAGS = -module
rounding_wrap.cpp: $(SWIG_SOURCES)
$(SWIG) $(AX_SWIG_PYTHON_OPT) -I$(top_srcdir)/rounding_swig -I/usr/include/python@PYTHON_VERSION@ -o $@ $<
最后,如果你没有其他5个宏,可以通过输入以下命令来获取:
autoreconf -i
最后,安装你的项目:
libtoolize && aclocal && autoheader && autoconf && automake -a -c
./configure --prefix=<install prefix>
make
make install
PS:这里
这里是我在``configure.ac``文件中调用的一个autoconf宏,用来找到Python的包含目录(PYTHONINC)和Python的安装目录(通过AM_PATH_PYTHON)。
AC_DEFUN([adl_CHECK_PYTHON],
[AM_PATH_PYTHON([2.0])
AC_CACHE_CHECK([for $am_display_PYTHON includes directory],
[adl_cv_python_inc],
[adl_cv_python_inc=`$PYTHON -c "from distutils import sysconfig; print sysconfig.get_python_inc()" 2>/dev/null`])
AC_SUBST([PYTHONINC], [$adl_cv_python_inc])])
然后我的wrap/python/Makefile.am
使用Libtool构建两个Swig模块,方法如下:
SUBDIRS = . cgi-bin ajax tests
AM_CPPFLAGS = -I$(PYTHONINC) -I$(top_srcdir)/src $(BUDDY_CPPFLAGS) \
-DSWIG_TYPE_TABLE=spot
EXTRA_DIST = spot.i buddy.i
python_PYTHON = $(srcdir)/spot.py $(srcdir)/buddy.py
pyexec_LTLIBRARIES = _spot.la _buddy.la
MAINTAINERCLEANFILES = \
$(srcdir)/spot_wrap.cxx $(srcdir)/spot.py \
$(srcdir)/buddy_wrap.cxx $(srcdir)/buddy.py
## spot
_spot_la_SOURCES = $(srcdir)/spot_wrap.cxx $(srcdir)/spot_wrap.h
_spot_la_LDFLAGS = -avoid-version -module
_spot_la_LIBADD = $(top_builddir)/src/libspot.la
$(srcdir)/spot_wrap.cxx: $(srcdir)/spot.i
$(SWIG) -c++ -python -I$(srcdir) -I$(top_srcdir)/src $(srcdir)/spot.i
$(srcdir)/spot.py: $(srcdir)/spot.i
$(MAKE) $(AM_MAKEFLAGS) spot_wrap.cxx
## buddy
_buddy_la_SOURCES = $(srcdir)/buddy_wrap.cxx
_buddy_la_LDFLAGS = -avoid-version -module $(BUDDY_LDFLAGS)
$(srcdir)/buddy_wrap.cxx: $(srcdir)/buddy.i
$(SWIG) -c++ -python $(BUDDY_CPPFLAGS) $(srcdir)/buddy.i
$(srcdir)/buddy.py: $(srcdir)/buddy.i
$(MAKE) $(AM_MAKEFLAGS) buddy_wrap.cxx
上述规则使得Swig的结果被视为源文件(也就是说,它会和Bison生成的解析器一样被打包在tar包里)。这样最终用户就不需要安装Swig了。
当你运行make
时,*.so
文件会被Libtool隐藏在某个.libs/
目录下,但在运行make install
后,它们会被复制到正确的位置。
唯一的小技巧是如何在运行make install
之前,从源代码目录中使用这些模块。例如,在运行make check
时。为此,我生成了一个名为run
的脚本(通过configure
),它在运行任何python
脚本之前设置PYTHONPATH
,我通过这个run
脚本执行所有的测试用例。以下是run.in
的内容,在configure
替换任何值之前:
# Darwin needs some help in figuring out where non-installed libtool
# libraries are (on this platform libtool encodes the expected final
# path of dependent libraries in each library).
modpath='../.libs:@top_builddir@/src/.libs:@top_builddir@/buddy/src/.libs'
# .. is for the *.py files, and ../.libs for the *.so. We used to
# rely on a module called ltihooks.py to teach the import function how
# to load a Libtool library, but it started to cause issues with
# Python 2.6.
pypath='..:../.libs:@srcdir@/..:@srcdir@/../.libs:$PYTHONPATH'
test -z "$1" &&
PYTHONPATH=$pypath DYLD_LIBRARY_PATH=$modpath exec @PYTHON@
case $1 in
*.py)
PYTHONPATH=$pypath DYLD_LIBRARY_PATH=$modpath exec @PYTHON@ "$@";;
*.test)
exec sh -x "$@";;
*)
echo "Unknown extension" >&2
exit 2;;
esac
如果你想在一个真实项目中看到这些操作的实际效果,可以从https://spot.lrde.epita.fr/install.html获取。
要找到包含路径,我会使用 python-config
。关键是要使用与 $PYTHON
中安装的 Python 对应的 python-config
。
AM_PATH_PYTHON
AC_ARG_VAR([PYTHON_INCLUDE], [Include flags for python, bypassing python-config])
AC_ARG_VAR([PYTHON_CONFIG], [Path to python-config])
AS_IF([test -z "$PYTHON_INCLUDE"], [
AS_IF([test -z "$PYTHON_CONFIG"], [
AC_PATH_PROGS([PYTHON_CONFIG],
[python$PYTHON_VERSION-config python-config],
[no],
[`dirname $PYTHON`])
AS_IF([test "$PYTHON_CONFIG" = no], [AC_MSG_ERROR([cannot find python-config for $PYTHON.])])
])
AC_MSG_CHECKING([python include flags])
PYTHON_INCLUDE=`$PYTHON_CONFIG --includes`
AC_MSG_RESULT([$PYTHON_INCLUDE])
])
另一种选择是查看 distutils.sysconfig
模块(这和用 distutils 来构建你的代码没有关系)。你可以运行 python -c "import distutils.sysconfig; help(distutils.sysconfig)"
来查看相关信息。