Hello again GnuCash developers, Attached is a patch that adds python binding support.
I've been working on this recently with my colleague Jeff Green, your 2007 Google SoC register-rewrite student. We're both members/employees of ParIT Worker Co-operative, (http://parit.ca) in Winnipeg, Canada. Just to let you know, Jeff's work on the register-rewrite over the last few months has been paid for by ParIT, as is my python binding work. I last talked about python bindings almost a year ago: http://lists.gnucash.org/pipermail/gnucash-user/2007-March/019720.html Derek replied: > Any reason not to feed these back to us for trunk? I had reasons, but none of them were any good, and I agreed with the idea: > If having this be part of GnuCash is consistent with the project's > goals, I'd be glad to put in the work needed to merge it in. In fact, there turned out to be many reasons why a separate project was a bad idea: * I was using private (not exported to $prefix/include/gnucash) headers, which required people to download the GnuCash source to build the bindings. Ouch. * There was limited pressure on me to keep the bindings in sync with changes in GnuCash's api. (if these made it here upstream and later got pulled out from bit-rot, my employer would be very unhappy with me) * More effort is involved to keep the two apis in sync, two projects and two source trees to build and work with instead of one. * Less leverage of the GnuCash community I've tried to keep this patch minimal, just enough to open/create and save a file, e.g. all the qof_session_* functions. My original bindings were bigger, they included support for most of the engine and business stuff. This initial minimalism should make the patch easier for you to judge. The attached patch is also small because I'm re-writing these python bindings. There were many things about the old ones that made maintenance not as easy as I would of liked. I figured it was better to submit here right away before that re-write was completed, to allow this community to participate from the beginning. I think the new binding architecture is nice, and your feedback will be appreciated. GnuCash's headers are swig safe (Thank you!), and we can get away with our swig interface file simply containing: %include <qofsession.h> instead of having to list each piece of the api one by one to avoid swig errors. For each set of C api functions that act as a class, we create a python class and add most of the C api functions with pattern matching like so: """ class Session(ClassFromFunctions): _module = gnucash_core_c _new_instance = "qof_session_new" Session.add_methods_with_prefix("qof_session_") """ So with that, you get qof_session_save as Session.save, qof_session_begin as Session.begin, and all the without having to list them. So, you do such a good job of naming your functions in the same class on a consistent basis, that this gets us a huge part of the api for free, again without having to list it. (Thank you again) Further refinements to these functions can be added with decorators. For example, I wanted calls to qof_session_load and qof_session_save to have None as a default argument for the progress bar (so you can just call Session.save() and not monitor progress) - so I created and added a decorator for it: def one_arg_default_none(function): return default_arguments_decorator(function, None, None) Session.decorate_functions(one_arg_default_none, "load", "save") To enable the bindings, you have to use --enable-python-bindings. I would be willing and honoured to further develop these here upstream in a branch, but given that the python bindings are completely optional (and off by default) perhaps you would even find it reasonable to put these in trunk from the start, even before they reach completion and maturity? Additional information in src/optional/python-bindings/README Mark Jenkins, ParIT Worker Co-operative p.s. README.patches says I should use diff -urN instead of svn diff when submitting patches that add new files. (like this one). Because I added my new files with svn add, (and tested the resulting patch against a blank trunk), the patch I generated via svn diff seems to be fine. I understand that after applying it you'll have to svn add the new files, but is this any different with a diff generated patch? bcc some people who have expressed interest in this
Index: src/optional/Makefile.am =================================================================== --- src/optional/Makefile.am (revision 16964) +++ src/optional/Makefile.am (working copy) @@ -1 +1 @@ -SUBDIRS = xsl +SUBDIRS = xsl ${PYTHON_DIR} Index: src/optional/python-bindings/function_class.py =================================================================== --- src/optional/python-bindings/function_class.py (revision 0) +++ src/optional/python-bindings/function_class.py (revision 0) @@ -0,0 +1,166 @@ +# function_class.py -- Library for making python classes from a set +# of functions. +# +# Copyright (C) 2008 ParIT Worker Co-operative <[EMAIL PROTECTED]> +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, contact: +# Free Software Foundation Voice: +1-617-542-5942 +# 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 +# Boston, MA 02110-1301, USA [EMAIL PROTECTED] +# +# @author Mark Jenkins, ParIT Worker Co-operative <[EMAIL PROTECTED]> + +INSTANCE_ARGUMENT = "instance" + +class ClassFromFunctions(object): + """Inherit this class to give yourself a python class that wraps a set of + functions that together consitute the methods of the class. + + The method functions must all have as a first argument an object + holding the instance data. There must also be a function that + returns a new instance of the class, the constructor. + + Your subclass must define + _module - The module where the method functions, including the + constructor can be found + _new_instance - The name of a function that serves as a constructor, + returning the instance data. + + To access the instance data, use the read-only property instance. + + To add some functions from _module as methods, call classmethods like + add_method and add_methods_with_prefix. + """ + def __new__(cls, *args): + # why reimpliment __new__? Because later on we're going to + # use new to avoid creating new instances when existing instances + # already exist with the same __instance value, or equivlent __instance + # values, where this is desirable... + return super(ClassFromFunctions, cls).__new__(cls, *args) + + def __init__(self, *args, **kargs): + """Construct a new instance, using either the function + self._module[self._new_instance] or using existing instance + data. (specified with the keyword argument, instance) + + Pass the arguments that should be passed on to + self._module[self._new_instance] . Any arguments of that + are instances of ClassFromFunctions will be switched with the instance + data. (by calling the .instance property) + """ + if INSTANCE_ARGUMENT in kargs: + self.__instance = kargs[INSTANCE_ARGUMENT] + else: + self.__instance = getattr(self._module, self._new_instance)( + *process_list_convert_to_instance(args) ) + + def get_instance(self): + """Get the instance data. + + You can also call the instance property + """ + return self.__instance + + instance = property(get_instance) + + # CLASS METHODS + + @classmethod + def add_method(cls, function_name, method_name): + """Add the function, method_name to this class as a method named name + """ + def method_function(self, *meth_func_args): + return getattr(self._module, function_name)( + self.instance, + *process_list_convert_to_instance(meth_func_args) ) + + setattr(cls, method_name, method_function) + setattr(method_function, "__name__", method_name) + return method_function + + @classmethod + def add_methods_with_prefix(cls, prefix): + """Add a group of functions with the same prefix + """ + for function_name, function_value, after_prefix in \ + extract_attributes_with_prefix(cls._module, prefix): + cls.add_method(function_name, after_prefix) + + @classmethod + def add_constructor_and_methods_with_prefix(cls, prefix, constructor): + """Add a group of functions with the same prefix, and set the + _new_instance attribute to prefix + constructor + """ + cls.add_methods_with_prefix(prefix) + cls._new_instance = prefix + constructor + + @classmethod + def decorate_functions(cls, decorator, *args): + for function_name in args: + setattr( cls, function_name, + decorator( getattr(cls, function_name) ) ) + +def method_function_returns_instance(method_function, cls): + """A function decorator that is used to decorates method functions that + return instance data, to return instances instead. + + You can't use this decorator with @, because this function has a second + argument. + """ + assert( 'instance' == INSTANCE_ARGUMENT ) + def new_function(*args): + return cls( instance=method_function(*args) ) + + return new_function + +def default_arguments_decorator(function, *args): + """Decorates a function to give it default, positional arguments + + You can't use this decorator with @, because this function has more + than one argument. + """ + def new_function(*function_args): + new_argset = list(function_args) + new_argset.extend( args[ len(function_args): ] ) + return function( *new_argset ) + return new_function + +def return_instance_if_value_has_it(value): + """Return value.instance if value is an instance of ClassFromFunctions, + else return value + """ + if isinstance(value, ClassFromFunctions): + return value.instance + else: + return value + +def process_list_convert_to_instance( value_list ): + """Return a list built from value_list, where if a value is in an instance + of ClassFromFunctions, we put value.instance in the list instead. + + Things that are not instances of ClassFromFunctions are returned to + the new list unchanged. + """ + return [ return_instance_if_value_has_it(value) + for value in value_list ] + +def extract_attributes_with_prefix(obj, prefix): + """Generator that iterates through the attributes of an object and + for any attribute that matches a prefix, this yields + the attribute name, the attribute value, and the text that appears + after the prefix in the name + """ + for attr_name, attr_value in obj.__dict__.iteritems(): + if attr_name.startswith(prefix): + after_prefix = attr_name[ len(prefix): ] + yield attr_name, attr_value, after_prefix Index: src/optional/python-bindings/example_scripts/simple_session.py =================================================================== --- src/optional/python-bindings/example_scripts/simple_session.py (revision 0) +++ src/optional/python-bindings/example_scripts/simple_session.py (revision 0) @@ -0,0 +1,33 @@ +#!/usr/bin/env python + +from gnucash import \ + Session, GnuCashBackendException, \ + ERR_BACKEND_LOCKED, ERR_FILEIO_FILE_NOT_FOUND + +FILE_1 = "/tmp/not_there.xac" +FILE_2 = "/tmp/example_file.xac" + +# open a file that isn't there, detect the error +session = None +try: + session = Session("file:%s" % FILE_1) +except GnuCashBackendException, backend_exception: + assert( ERR_FILEIO_FILE_NOT_FOUND in backend_exception.errors) + + +# create a new file +session = Session("file:%s" % FILE_2, True) +session.save() +session.end() +session.destroy() + +# open the new file, try to open it a second time, detect the lock +session = Session("file:%s" % FILE_2) +try: + session_2 = Session("file:%s" % FILE_2) +except GnuCashBackendException, backend_exception: + assert( ERR_BACKEND_LOCKED in backend_exception.errors ) +session.end() +session.destroy() + + Index: src/optional/python-bindings/glib.i =================================================================== --- src/optional/python-bindings/glib.i (revision 0) +++ src/optional/python-bindings/glib.i (revision 0) @@ -0,0 +1,60 @@ +/* + * glib.i -- SWIG interface file for type translation of glib types + * + * Copyright (C) 2008 ParIT Worker Co-operative <[EMAIL PROTECTED]> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, contact: + * + * Free Software Foundation Voice: +1-617-542-5942 + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 + * Boston, MA 02110-1301, USA [EMAIL PROTECTED] + * + * @author Mark Jenkins, ParIT Worker Co-operative <[EMAIL PROTECTED]> + */ + +%typemap(in) gboolean { + if ($input == Py_True) + $1 = TRUE; + else if ($input == Py_False) + $1 = FALSE; + else + { + PyErr_SetString( + PyExc_ValueError, + "Python object passed to a gboolean argument was not True " + "or False" ); + return NULL; + } +} + +%typemap(out) gboolean { + if ($1 == TRUE) + { + Py_INCREF(Py_True); + $result = Py_True; + } + else if ($1 == FALSE) + { + Py_INCREF(Py_False); + $result = Py_False; + } + else + { + PyErr_SetString( + PyExc_ValueError, + "function returning gboolean returned a value that wasn't " + "TRUE or FALSE."); + return NULL; + } +} Index: src/optional/python-bindings/__init__.py =================================================================== --- src/optional/python-bindings/__init__.py (revision 0) +++ src/optional/python-bindings/__init__.py (revision 0) @@ -0,0 +1,6 @@ +# import all the symbols from gnucash_core, so basic gnucash stuff can be +# loaded with: +# >>> from gnucash import thingy +# instead of +# >>> from gnucash.gnucash_core import thingy +from gnucash_core import * Index: src/optional/python-bindings/gnucash_core.i =================================================================== --- src/optional/python-bindings/gnucash_core.i (revision 0) +++ src/optional/python-bindings/gnucash_core.i (revision 0) @@ -0,0 +1,52 @@ +/* + * gnucash_core.i -- SWIG interface file for the core parts of GnuCash + * + * Copyright (C) 2008 ParIT Worker Co-operative <[EMAIL PROTECTED]> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, contact: + * + * Free Software Foundation Voice: +1-617-542-5942 + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 + * Boston, MA 02110-1301, USA [EMAIL PROTECTED] + * + * @author Mark Jenkins, ParIT Worker Co-operative <[EMAIL PROTECTED]> + * @author Jeff Green, ParIT Worker Co-operative <[EMAIL PROTECTED]> + */ + +%module(package="gnucash") gnucash_core_c + +%{ +#include "config.h" +#include "qofsession.h" +#include <guile/gh.h> +%} + +%include <glib.i> + +%include <qofbackend.h> + +// this function is defined in qofsession.h, but isn't found in the libraries, +// ignoroed because SWIG attempts to link against (to create language bindings) +%ignore qof_session_not_saved; +%include <qofsession.h> + +%init %{ + +g_type_init(); +scm_init_guile(); +gnc_module_load("gnucash/engine", 0); +gnc_module_load("gnucash/business-core-file", 0); + +%} + Index: src/optional/python-bindings/gnucash_core.py =================================================================== --- src/optional/python-bindings/gnucash_core.py (revision 0) +++ src/optional/python-bindings/gnucash_core.py (revision 0) @@ -0,0 +1,119 @@ +# gnucash_core.py -- High level python wrapper classes for the core parts +# of GnuCash +# +# Copyright (C) 2008 ParIT Worker Co-operative <[EMAIL PROTECTED]> +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, contact: +# Free Software Foundation Voice: +1-617-542-5942 +# 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 +# Boston, MA 02110-1301, USA [EMAIL PROTECTED] +# +# @author Mark Jenkins, ParIT Worker Co-operative <[EMAIL PROTECTED]> + +import gnucash_core_c + +from function_class import \ + ClassFromFunctions, extract_attributes_with_prefix, \ + default_arguments_decorator + +class GnuCashCoreClass(ClassFromFunctions): + _module = gnucash_core_c + +class GnuCashBackendException(Exception): + def __init__(self, msg, errors): + Exception.__init__(self, msg) + self.errors = errors + +class Session(GnuCashCoreClass): + def __init__(self, book_uri=None, is_new=False): + """A convienent contructor that allows you to specify a book URI, + begin the session, and load the book. + + This can give you the power of calling + qof_session_new, qof_session_begin, and qof_session_load all in one! + + book_uri can be None to skip the calls to qof_session_begin and + qof_session_load, or it can be a string like "file:/test.xac" + + qof_session_load is only called if is_new is set to False + + is_new is passed to qof_session_begin as the + argument create_if_nonexistent + + This function can raise a GnuCashBackendException. If it does, + you don't need to cleanup and call end() and destroy(), that is handled + for you, and the exception is raised. + """ + GnuCashCoreClass.__init__(self) + if book_uri is not None: + try: + self.begin(book_uri, False, is_new) + if not is_new: + self.load() + except GnuCashBackendException, backend_exception: + self.end() + self.destroy() + raise + + def raise_backend_errors(self, called_function="qof_session function"): + """Raises a GnuCashBackendException if there are outstanding + QOF_BACKEND errors. + + set called_function to name the function that was last called + """ + errors = self.pop_all_errors() + if errors != (): + raise GnuCashBackendException( + "call to %s resulted in the " + "following errors, %s" % (called_function, errors), + errors ) + + def generate_errors(self): + """A generator that yeilds any outstanding QofBackend errors + """ + while self.get_error() is not ERR_BACKEND_NO_ERR: + error = self.pop_error() + yield error + + def pop_all_errors(self): + """Returns any accumulated qof backend errors as a tuple + """ + return tuple( self.generate_errors() ) + + # STATIC METHODS + @staticmethod + def raise_backend_errors_after_call(function): + """A function decorator that results in a call to + raise_backend_errors after execution. + """ + def new_function(self, *args): + return_value = function(self, *args) + self.raise_backend_errors(function.__name__) + return return_value + return new_function + + +Session.add_constructor_and_methods_with_prefix('qof_session_', 'new') + +def one_arg_default_none(function): + return default_arguments_decorator(function, None, None) +Session.decorate_functions(one_arg_default_none, "load", "save") + +Session.decorate_functions( Session.raise_backend_errors_after_call, + "begin", "load", "save", "end") + +# import all of the session backend error codes into this module +this_module_dict = globals() +for error_name, error_value, error_name_after_prefix in \ + extract_attributes_with_prefix(gnucash_core_c, 'ERR_'): + this_module_dict[ error_name ] = error_value Index: src/optional/python-bindings/Makefile.am =================================================================== --- src/optional/python-bindings/Makefile.am (revision 0) +++ src/optional/python-bindings/Makefile.am (revision 0) @@ -0,0 +1,22 @@ +BUILT_SOURCES = gnucash_core.c +SWIG_SOURCES = gnucash_core.i + +pkgpython_PYTHON = __init__.py function_class.py \ +gnucash_core.py gnucash_core_c.py + +pkgpyexec_LTLIBRARIES = _gnucash_core_c.la +_gnucash_core_c_la_SOURCES = $(BUILT_SOURCES) $(SWIG_SOURCES) +_gnucash_core_c_la_CPPFLAGS = $(PYTHON_CPPFLAGS) \ + -I$(top_srcdir)/src $(QOF_CFLAGS) \ + $(GLIB_CFLAGS) $(GUILE_INCS) + +# Suppress all warnings for now, but we really only need to -Wno-implicit +AM_CFLAGS = -w + +_gnucash_core_c_la_LDFLAGS = -avoid-version -module +_gnucash_core_c_la_LIBADD = ${QOF_LIBS} ${GUILE_LIBS} ${GLIB_LIBS} \ + ${top_builddir}/src/gnc-module/libgnc-module.la + +gnucash_core.c : $(SWIG_SOURCES) + $(SWIG) $(SWIG_PYTHON_OPT) -Wall -Werror \ + -I$(top_srcdir)/src $(QOF_CFLAGS) -o $@ $< Index: src/optional/python-bindings/README =================================================================== --- src/optional/python-bindings/README (revision 0) +++ src/optional/python-bindings/README (revision 0) @@ -0,0 +1,22 @@ +use --enable-python-bindings when invoking configure to install + +GnuCash python bindings will be installed into +the standard place for python extensions, on many platforms this will be +$prefix/lib/python$version/site-packages/ + +To use, make sure the PYTHONPATH includes the above directory, and run +python with gnucash-env + +For example: +$ ./configure --enable-python-bindings --prefix=/opt/gnucash/ +$ make +$ su +# make install +$ PYTHONPATH=/opt/gnucash/lib/python2.4/site-packages/ \ +/opt/gnucash/bin/gnucash-env python yourscript.py + +the src/optional/python-bindings/example_scripts directory contains some +example scripts using the python bindings + + +Mark Jenkins <[EMAIL PROTECTED]> Index: macros/ac_python_devel.m4 =================================================================== --- macros/ac_python_devel.m4 (revision 0) +++ macros/ac_python_devel.m4 (revision 0) @@ -0,0 +1,64 @@ +dnl @synopsis AC_PYTHON_DEVEL +dnl +dnl Checks for Python and tries to get the include path to 'Python.h'. +dnl It provides the $(PYTHON_CPPFLAGS) and $(PYTHON_LDFLAGS) output +dnl variable. +dnl +dnl @category InstalledPackages +dnl @author Sebastian Huber <[EMAIL PROTECTED]> +dnl @author Alan W. Irwin <[EMAIL PROTECTED]> +dnl @author Rafael Laboissiere <[EMAIL PROTECTED]> +dnl @author Andrew Collier <[EMAIL PROTECTED]> +dnl @version 2004-07-14 +dnl @license GPLWithACException + +AC_DEFUN([AC_PYTHON_DEVEL],[ + # + # should allow for checking of python version here... + # + AC_REQUIRE([AM_PATH_PYTHON]) + + # Check for Python include path + AC_MSG_CHECKING([for Python include path]) + python_path=`echo $PYTHON | sed "s,/bin.*$,,"` + for i in "$python_path/include/python$PYTHON_VERSION/" "$python_path/include/python/" "$python_path/" ; do + python_path=`find $i -type f -name Python.h -print | sed "1q"` + if test -n "$python_path" ; then + break + fi + done + python_path=`echo $python_path | sed "s,/Python.h$,,"` + AC_MSG_RESULT([$python_path]) + if test -z "$python_path" ; then + AC_MSG_ERROR([cannot find Python include path]) + fi + AC_SUBST([PYTHON_CPPFLAGS],[-I$python_path]) + + # Check for Python library path + AC_MSG_CHECKING([for Python library path]) + python_path=`echo $PYTHON | sed "s,/bin.*$,,"` + for i in "$python_path/lib/python$PYTHON_VERSION/config/" "$python_path/lib/python$PYTHON_VERSION/" "$python_path/lib/python/config/" "$python_path/lib/python/" "$python_path/" ; do + python_path=`find $i -type f -name libpython$PYTHON_VERSION.* -print | sed "1q"` + if test -n "$python_path" ; then + break + fi + done + python_path=`echo $python_path | sed "s,/libpython.*$,,"` + AC_MSG_RESULT([$python_path]) + if test -z "$python_path" ; then + AC_MSG_ERROR([cannot find Python library path]) + fi + AC_SUBST([PYTHON_LDFLAGS],["-L$python_path -lpython$PYTHON_VERSION"]) + # + python_site=`echo $python_path | sed "s/config/site-packages/"` + AC_SUBST([PYTHON_SITE_PKG],[$python_site]) + # + # libraries which must be linked in when embedding + # + AC_MSG_CHECKING(python extra libraries) + PYTHON_EXTRA_LIBS=`$PYTHON -c "import distutils.sysconfig; \ + conf = distutils.sysconfig.get_config_var; \ + print conf('LOCALMODLIBS')+' '+conf('LIBS')" + AC_MSG_RESULT($PYTHON_EXTRA_LIBS)` + AC_SUBST(PYTHON_EXTRA_LIBS) +]) Index: configure.in =================================================================== --- configure.in (revision 16964) +++ configure.in (working copy) @@ -1322,6 +1322,28 @@ fi AC_SUBST(LC_MESSAGES_ENUM) +###-------------------------------------------------------- +### Make Python bindings optional +###-------------------------------------------------------- +enable_python=false + +AC_ARG_ENABLE(python-bindings, + [ --enable-python-bindings enable python bindings], + [case "${enableval}" in + yes) enable_python=true ;; + no) enable_python=false ;; + *) enable_python=true ;; + esac] + ) +if test x${enable_python} = "xtrue" +then + PYTHON_DIR=python-bindings + AM_PATH_PYTHON(2.4) + AC_PYTHON_DEVEL(>= '2.4') + SWIG_PYTHON +fi +AC_SUBST(PYTHON_DIR) + ###------------------------------------------------------------------------- ### Additional compiler warnings (or not) if we're running GCC ###------------------------------------------------------------------------- @@ -1512,6 +1534,7 @@ src/import-export/hbci/schemas/Makefile src/import-export/hbci/test/Makefile src/optional/Makefile + src/optional/python-bindings/Makefile src/optional/xsl/Makefile src/pixmaps/Makefile src/quotes/Makefile @@ -1578,6 +1601,10 @@ components="$components hbci" fi +if test x${PYTHON_DIR} != x; then +components="$components python-bindings" +fi + AC_MSG_RESULT([ Options detected/selected -------------------------
_______________________________________________ gnucash-devel mailing list gnucash-devel@gnucash.org https://lists.gnucash.org/mailman/listinfo/gnucash-devel