In a Python/Django based open source project I am working on we use Travis and GH Actions for CI. We support PyPy and hence run our CI tests for PyPy as well. Since a couple of months we were not able to run those PyPy tests successfully anymore, because we kept hitting this error: OSError: Cannot load library libgdal.so.20: /usr/lib/libgdal.so.20: undefined symbol: sqlite3_column_table_name
, occuring if we would run Django's manage.py test
command (traceback at end of post). Some GIS-related features in Django required the GDAL library, which in turns requrires the SQLite3 library. And it seems to requires sqlite3 to be compiled with column meta data enabled.
This was reproducible locally only if the installed sqlite3
library would be compiled without SQLITE_ENABLE_COLUMN_METADATA
. After searching all installed sqlite3
libraries on the CI servers, it became clear that PyPy has its own copy of libsqlite3.so.0
installed, which it clearly prefers over the OS installed version during runtime, even though ldd
would refer to the OS library:
$ ldd -d /usr/lib/libgdal.so | grep sqlite3
libsqlite3.so.0 => /usr/local/lib/libsqlite3.so.0 (0x00007f09d6124000)
I suspect the reason PyPy uses a different library (its own copy) during runtime is its dynamic loader (referenced in the traceback) and where it looks. And I suspect its own copy of libsqlite3.so.0
is created when PyPy is installed by the CI provider.
The only way to work around this that I found is by explicitly preloading the OS level library before I run the PyPy tests:
export LD_PRELOAD=/usr/local/lib/libsqlite3.so.0
This however feels like a hack and I am wondering if there is a better way to do this? Can I make PyPy (or virtualenv) just use OS libraries instead of own copies, or at least have it update its copies?
Traceback for undefined symbol error
:
$ coverage run manage.py test catmaid.tests
Traceback (most recent call last):
File "manage.py", line 11, in <module>
execute_from_command_line(sys.argv)
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line
utility.execute()
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/core/management/__init__.py", line 377, in execute
django.setup()
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/__init__.py", line 24, in setup
apps.populate(settings.INSTALLED_APPS)
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/apps/registry.py", line 114, in populate
app_config.import_models()
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/apps/config.py", line 211, in import_models
self.models_module = import_module(models_module_name)
File "/opt/python/pypy3.6-7.3.1/lib-python/3/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1003, in _gcd_import
File "<frozen importlib._bootstrap>", line 980, in _find_and_load
File "<frozen importlib._bootstrap>", line 964, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 674, in _load_unlocked
File "<builtin>/frozen importlib._bootstrap_external", line 691, in exec_module
File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/contrib/auth/models.py", line 2, in <module>
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/contrib/auth/base_user.py", line 47, in <module>
class AbstractBaseUser(models.Model):
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/db/models/base.py", line 121, in __new__
new_class.add_to_class('_meta', Options(meta, app_label))
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/db/models/base.py", line 325, in add_to_class
value.contribute_to_class(cls, name)
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/db/models/options.py", line 208, in contribute_to_class
self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/db/__init__.py", line 28, in __getattr__
return getattr(connections[DEFAULT_DB_ALIAS], item)
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/db/utils.py", line 207, in __getitem__
backend = load_backend(db['ENGINE'])
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/db/utils.py", line 111, in load_backend
return import_module('%s.base' % backend_name)
File "/opt/python/pypy3.6-7.3.1/lib-python/3/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File "<frozen importlib._bootstrap>", line 1003, in _gcd_import
File "<frozen importlib._bootstrap>", line 980, in _find_and_load
File "<frozen importlib._bootstrap>", line 964, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 674, in _load_unlocked
File "<builtin>/frozen importlib._bootstrap_external", line 691, in exec_module
File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
File "/home/travis/build/[secure]/CATMAID/django/lib/custom_postgresql_psycopg2/base.py", line 33, in <module>
from django.contrib.gis.db.backends.postgis.base import DatabaseWrapper as PostGISDatabaseWrapper
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/contrib/gis/db/backends/postgis/base.py", line 6, in <module>
from .features import DatabaseFeatures
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/contrib/gis/db/backends/postgis/features.py", line 1, in <module>
from django.contrib.gis.db.backends.base.features import BaseSpatialFeatures
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/contrib/gis/db/backends/base/features.py", line 3, in <module>
from django.contrib.gis.db.models import aggregates
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/contrib/gis/db/models/__init__.py", line 3, in <module>
import django.contrib.gis.db.models.functions # NOQA
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/contrib/gis/db/models/functions.py", line 3, in <module>
from django.contrib.gis.db.models.fields import BaseSpatialField, GeometryField
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/contrib/gis/db/models/fields.py", line 3, in <module>
from django.contrib.gis import forms, gdal
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/contrib/gis/forms/__init__.py", line 3, in <module>
from .fields import ( # NOQA
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/contrib/gis/forms/fields.py", line 2, in <module>
from django.contrib.gis.gdal import GDALException
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/contrib/gis/gdal/__init__.py", line 28, in <module>
from django.contrib.gis.gdal.datasource import DataSource
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/contrib/gis/gdal/datasource.py", line 39, in <module>
from django.contrib.gis.gdal.driver import Driver
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/contrib/gis/gdal/driver.py", line 5, in <module>
from django.contrib.gis.gdal.prototypes import ds as vcapi, raster as rcapi
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/contrib/gis/gdal/prototypes/ds.py", line 9, in <module>
from django.contrib.gis.gdal.libgdal import GDAL_VERSION, lgdal
File "/home/travis/virtualenv/pypy3.6-7.3.1/site-packages/django/contrib/gis/gdal/libgdal.py", line 46, in <module>
lgdal = CDLL(lib_path)
File "/opt/python/pypy3.6-7.3.1/lib-python/3/ctypes/__init__.py", line 350, in __init__
pypy_dll = _ffi.CDLL(name, mode)
OSError: Cannot load library libgdal.so.20: /usr/lib/libgdal.so.20: undefined symbol: sqlite3_column_table_name