Context

After fixing the problem discussed in part 1. Another bug was hit.

$ python3
>>> import bluetooth
ImportError: /usr/lib/python3.6/site-packages/bluetooth/_bluetooth.so: undefined symbol: PyString_FromStringAndSize

After googling that specific error i was made aware that PyString_FromStringAndSize is not available on python3.

But why then ? Why did it get included in the python3 build ? Let's take a look at the source.

$ xbps-src -I extract python-bluez
$ cd masterdir/builddir/pybluez-0.22
$ rg --no-ignore PyString_FromStringAndSize 
bluez/btmodule.c
664:        PyObject *buf = PyString_FromStringAndSize((char *)NULL, buflen);
1073:	buf = PyString_FromStringAndSize((char *) 0, len);
1123:	buf = PyString_FromStringAndSize((char *) 0, len);
1949:    return PyString_FromStringAndSize(rparam, req.rlen);
2124:    return PyString_FromStringAndSize(param, len); \
2153:    return PyString_FromStringAndSize(param, len); \

port3/port3.h
5:    #define PyString_FromStringAndSize PyBytes_FromStringAndSize

osx/_osxbt.c
190:    buf = PyString_FromStringAndSize((char*)0, datalen);
456:            rawrecord = PyString_FromStringAndSize( qs->lpBlob->pBlobData,

msbt/_msbt.c
292:    buf = PyString_FromStringAndSize((char*)0, datalen);
646:            rawrecord = PyString_FromStringAndSize( qs->lpBlob->pBlobData,

Interesting results, specially that port/port3.h, let's take a closer look

$ cat port3/port3.h
#if PY_MAJOR_VERSION >= 3
    #define PyInt_FromLong PyLong_FromLong
    #define PyString_FromString PyUnicode_FromString
    #define PyString_FromStringAndSize PyBytes_FromStringAndSize
    #define _PyString_Resize _PyBytes_Resize
    #define PyString_AS_STRING PyBytes_AS_STRING
    #define PyInt_AsLong PyLong_AsLong
    #define PyString_AsString PyBytes_AsString
    #define PyInt_Check PyLong_Check
    #define PyInt_AS_LONG PyLong_AS_LONG
    #define BYTES_FORMAT_CHR "y#"
#else
    #ifndef Py_TYPE
        #define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)
    #endif
    #define BYTES_FORMAT_CHR "s#"
#endif

So for some reason, PY_MAJOR_VERSION is not set correctly. After some googling it was revealed that PY_MAJOR_VERSION is defined by python headers, more specifically the patchlevel.h header.

$ rg --no-ignore PY_MAJOR_VERSION /usr/include/python3.6m
/usr/include/python3.6m/patchlevel.h
19:#define PY_MAJOR_VERSION	3
31:#define PY_VERSION_HEX ((PY_MAJOR_VERSION << 24) | \

Problem

Well, let's take a look at our include directives, maybe the correct one isn't being included. Let's see the build logs.

aarch64-linux-gnu-gcc -pthread -fstack-clash-protection -D_FORTIFY_SOURCE=2 -O2 -pipe -march=armv8-a -I/usr/aarch64-linux-gnu/usr/include -I/usr/aarch64-linux-gnu/include/python2.7 -I/usr/aarch64-linux-gnu/usr/include -I/usr/aarch64-linux-gnu/include/python3.6m -I/usr/aarch64-linux-gnu/usr/include -Wl,-z,relro -Wl,-z,now -Wl,--as-needed -L/usr/aarch64-linux-gnu/usr/lib -L/usr/aarch64-linux-gnu/lib/python2.7 -L/usr/aarch64-linux-gnu/usr/lib -L/usr/aarch64-linux-gnu/lib/python3.6 -L/usr/aarch64-linux-gnu/usr/lib -DNDEBUG -g -fwrapv -O3 -Wall -fstack-clash-protection -D_FORTIFY_SOURCE=2 -O2 -pipe -march=armv8-a -I/usr/aarch64-linux-gnu/usr/include -I/usr/aarch64-linux-gnu/include/python2.7 -I/usr/aarch64-linux-gnu/usr/include -I/usr/aarch64-linux-gnu/include/python3.6m -I/usr/aarch64-linux-gnu/usr/include -fPIC -I./port3 -I/usr/include/python3.6m -c bluez/btsdp.c -o build-3.6/temp.linux-x86_64-3.6/bluez/btsdp.o

This has too much content, lets remove everything but the include directives, remove duplicates and break one per line to better examine them.

aarch64-linux-gnu-gcc
-I/usr/aarch64-linux-gnu/usr/include
-I/usr/aarch64-linux-gnu/include/python2.7
-I/usr/aarch64-linux-gnu/include/python3.6m
-I./port3

Much clearer, and it is now visible why the wrong header was included. The python2 headers are being included before the python3 ones, PY_MAJOR_VERSION is always being set to 2.

To find out why includes meant for python2 are getting inserted into the python3 build i turned to the build style.

For those uninitiated, build styles are snippets in shell that define functions and environment variables for dealing with specific build systems, like cmake and meson and specific languages like go and python.

In the case of python-bluez the build style being used is python-module, which builds the package for python2 and python3 in separate build directories.

Let's then take a peek into common/build-style/python-module.

do_build() {
    : ${python_versions:="2.7 $py3_ver"}
    local pyver= pysufx=

    for pyver in $python_versions; do
        if [ -n "$CROSS_BUILD" ]; then
            PYPREFIX="$XBPS_CROSS_BASE"
            if [ "$pyver" != "2.7" ]; then
                pysufx=m
            fi
            CFLAGS+=" -I${XBPS_CROSS_BASE}/include/python${pyver}${pysufx} -I${XBPS_CROSS_BASE}/usr/include"
            LDFLAGS+=" -L${XBPS_CROSS_BASE}/lib/python${pyver} -L${XBPS_CROSS_BASE}/usr/lib"
            CC="${XBPS_CROSS_TRIPLET}-gcc -pthread $CFLAGS $LDFLAGS"
            LDSHARED="${CC} -shared $LDFLAGS"
            env CC="$CC" LDSHARED="$LDSHARED" \
                PYPREFIX="$PYPREFIX" CFLAGS="$CFLAGS" \
                LDFLAGS="$LDFLAGS" python${pyver} setup.py \
                    build --build-base=build-${pyver} ${make_build_args}
        else
            python${pyver} setup.py build --build-base=build-${pyver} ${make_build_args}
        fi
    done
}

Noticed it ?, CFLAGS and LDFLAGS are being appended. But they are global variables and will not reset between each loop. That means that after building for python2 it will have a polluted environment for building for python3!

Solution

This should be an easy fix... Just store the CFLAGS and LDFLAGS at the start of the function as another variable and reset between each loop!

diff --git a/common/build-style/python-module.sh b/common/build-style/python-module.sh
index 57ec8c73831..d3c93de38ca 100644
--- a/common/build-style/python-module.sh
+++ b/common/build-style/python-module.sh
@@ -4,10 +4,13 @@
 
 do_build() {
    : ${python_versions:="2.7 $py3_ver"}
-   local pyver= pysufx=
+   local pyver= pysufx= tmp_cflags="$CFLAGS" tmp_ldflags="$LDFLAGS"
 
    for pyver in $python_versions; do
        if [ -n "$CROSS_BUILD" ]; then
+           CFLAGS="$tmp_cflags"
+           LDFLAGS="$tmp_ldflags"
+
            PYPREFIX="$XBPS_CROSS_BASE"
            if [ "$pyver" != "2.7" ]; then
                pysufx=m

And done, let's build it again and take a look at the build logs and perform the same cleanup done as the first time for readability.

aarch64-linux-gnu-gcc
-I/usr/aarch64-linux-gnu/usr/include
-I/usr/aarch64-linux-gnu/include/python3.6m
-I./port3

And let's run the module again and see what happens

$ python3
>>> import bluetooth
>>>

Success! Another bug squashed! With this the path to having python-gobject available to cross arches almost complete! Onwards to making gobject-introspection cross!