Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import atexit
import binascii
import collections
import contextlib
import glob
import importlib.util
import inspect
Expand All @@ -30,7 +31,30 @@
import weakref
import zipfile

from . import extra

@contextlib.contextmanager
def _swig_import():
'''
Context manager for importing our SWIG modules `extra` and `mupdf`.

With swig<4.4, module initialisation gives `DeprecationWarning: builtin
type SwigPyPacked has no __module__ attribute` (also for SwigPyObject and
swigvarlink). If warnings are errors (for example `python -W error` or
`pytest -W error`), swig's init code ignores the resulting exception and
then crashes with SEGV. So we ignore these warnings while importing. See
#4392 and https://github.com/swig/swig/issues/3061.
'''
with warnings.catch_warnings():
warnings.filterwarnings(
'ignore',
'builtin type .+ has no __module__ attribute',
DeprecationWarning,
)
yield


with _swig_import():
from . import extra

# Set up g_out_log and g_out_message from environment variables.
#
Expand Down Expand Up @@ -396,10 +420,11 @@ def _warn_layout_once():
# directory for conventional wheel installs, or from separate place in case
# we are using a separately-installed system installation of mupdf.
#
try:
from . import mupdf
except Exception:
import mupdf
with _swig_import():
try:
from . import mupdf
except Exception:
import mupdf
if hasattr(mupdf, 'internal_check_ndebug'):
mupdf.internal_check_ndebug()
mupdf.reinit_singlethreaded()
Expand Down
7 changes: 7 additions & 0 deletions src/extra.i
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ otherwise compilation can fail because free() and malloc() are not declared. */

%init
%{
/* With swig<4.4, if we have not created swig's globals object by the time
we are unloaded at exit, swig creates it then, which gives `DeprecationWarning:
builtin type swigvarlink has no __module__ attribute` and a SEGV if warnings
are errors. So we create it now, while src/__init__.py is ignoring these
warnings. See #4392. */
SWIG_globals();

/* Initialise some globals that require Python functions.

[Prior to 2023-08-18 we initialised these global variables inline,
Expand Down
51 changes: 28 additions & 23 deletions tests/test_general.py
Original file line number Diff line number Diff line change
Expand Up @@ -2037,7 +2037,22 @@ def test_4392():
e3 = subprocess.run(command, shell=1, check=0).returncode
print(f'{e3=}')

print(f'{e1=} {e2=} {e3=}', flush=1)
# With swig<4.4 an unclosed file used to give a SEGV at exit, because
# swig created a new type when unloading `extra`.
command = f'{sys.executable} -Werror -c "import pymupdf; open({path!r})"'
print(f'Running: {command}', flush=1)
e4 = subprocess.run(command, shell=1, check=0).returncode
print(f'{e4=}')

# Check that we don't get swig's `DeprecationWarning: builtin type
# SwigPyPacked has no __module__ attribute` etc.
command = f'{sys.executable} -W default -c "import pymupdf"'
print(f'Running: {command}', flush=1)
cp = subprocess.run(command, shell=1, check=0, capture_output=1, text=1)
print(f'{cp.returncode=}')
print(f'{cp.stderr=}')

print(f'{e1=} {e2=} {e3=} {e4=}', flush=1)

print(f'{pymupdf.swig_version=}', flush=1)
print(f'{pymupdf.swig_version_tuple=}', flush=1)
Expand All @@ -2052,30 +2067,20 @@ def test_4392():
# Exit code 4: pytest command line usage error
# Exit code 5: No tests were collected

# With swig<4.4 we used to get SEGV's here, but now we ignore swig's
# warnings when importing.
assert e1 == 5
if pymupdf.swig_version_tuple >= (4, 4):
if sysconfig.get_config_var('Py_GIL_DISABLED') == 1 and sys._is_gil_enabled():
assert e2 == 4
else:
assert e2 == 5
if sysconfig.get_config_var('Py_GIL_DISABLED') == 1 and sys._is_gil_enabled():
# GIL warning results in failure because of -Werror.
assert e3 == 1
else:
assert e3 == 0
if sysconfig.get_config_var('Py_GIL_DISABLED') == 1 and sys._is_gil_enabled():
# GIL warning results in failure because of -Werror.
assert e2 == 4
assert e3 == 1
assert e4 == 1
else:
# We get SEGV's etc with older swig.
if platform.system() == 'Windows':
assert (e2, e3) == (0xc0000005, 0xc0000005)
elif platform.system() == 'Linux':
# On plain linux we get (139, 139). On manylinux we get (-11,
# -11). On MacOS we get (-11, -11).
assert (e2, e3) == (139, 139) or (e2, e3) == (-11, -11)
elif platform.system() == 'Darwin':
# python3.14t gives (4, -11)?
assert (e2, e3) == (-11, -11) or (e2, e3) == (4, -11)
else:
assert e2 and e3
assert e2 == 5
assert e3 == 0
assert e4 == 0
assert cp.returncode == 0
assert 'has no __module__ attribute' not in cp.stderr


def test_4639():
Expand Down
Loading