From 441be45096c15504a96a7ad6346953c0818b7afa Mon Sep 17 00:00:00 2001 From: Julian Smith Date: Thu, 6 Nov 2025 00:52:35 +0000 Subject: [PATCH 1/4] pipcl.py: swig_get(): also download/build/install bison if required. --- pipcl.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/pipcl.py b/pipcl.py index e1cac6979..943b8393f 100644 --- a/pipcl.py +++ b/pipcl.py @@ -3348,6 +3348,48 @@ def swig_get(swig, quick, swig_local='pipcl-swig-git'): if quick and os.path.isfile(swig_binary): log1(f'{quick=} and {swig_binary=} already exists, so not downloading/building.') else: + # Building swig requires bison>=3.5. + bison_ok = 0 + e, text = run(f'bison --version', capture=1, check=0) + if not e: + log(textwrap.indent(text, ' ')) + m = re.search('bison (GNU Bison) ([0-9]+)[.]([0-9]+)', text) + if m: + assert m, f'Unexpected output from `bison --version`: {text!r}' + version_tuple = int(m.group(1)), int(m.group2()) + if version_tuple >= (3, 5): + bison_ok = 1 + if not bison_ok: + if 0: + # Use git checkout. Fails to find scan-code.c. Presumably + # something wrong with ./bootstrap? + log(f'Cloning/fetching/build/installing bison.') + bison_git = git_get( + 'pipcl-bison-git', + remote='https://git.savannah.gnu.org/git/bison.git', + #branch='master', + tag='v3.5.4', + submodules=0, # recursive update fails. + ) + run(f'cd {bison_git} && git submodule update --init', prefix='bison git submodule update --init: ') + run(f'cd {bison_git} && ./bootstrap', prefix='bison bootstrap: ') + run(f'cd {bison_git} && ./configure', prefix='bison configure: ') + run(f'cd {bison_git} && make', prefix='bison make: ') + run(f'cd {bison_git} && sudo make install', prefix='bison make install: ') + else: + bison_version = 'bison-3.5.4' + if not os.path.exists(f'{bison_version}.tar.gz'): + run( + f'wget -O {bison_version}.tar.gz-0 http://www.mirrorservice.org/sites/ftp.gnu.org/gnu/bison/{bison_version}.tar.gz', + prefix='bison wget: ', + ) + os.rename(f'{bison_version}.tar.gz-0', f'{bison_version}.tar.gz') + if not os.path.exists(f'{bison_version}'): + run(f'tar -xzf {bison_version}.tar.gz', prefix='bison extract: ') + run(f'cd {bison_version} && ./configure', prefix='bison configure: ') + run(f'cd {bison_version} && make', prefix='bison make: ') + run(f'cd {bison_version} && sudo make install', prefix='bison make install: ') + # Clone swig. swig_env_extra = None swig_local = git_get( @@ -3373,6 +3415,7 @@ def swig_get(swig, quick, swig_local='pipcl-swig-git'): macos_add_brew_path('bison', swig_env_extra) run(f'which bison') run(f'which bison', env_extra=swig_env_extra) + # Build swig. run(f'cd {swig_local} && ./autogen.sh', env_extra=swig_env_extra) run(f'cd {swig_local} && ./configure --prefix={swig_local}/install-dir', env_extra=swig_env_extra) From 5eb8a509d8e9fed022b2f1f08d8721eca2fe9d74 Mon Sep 17 00:00:00 2001 From: Julian Smith Date: Tue, 18 Nov 2025 22:45:11 +0000 Subject: [PATCH 2/4] pipcl.py: swig_get(): fix for macos-graal. Include brew's bison when checking bison version; avoids the need to build bison on graal, which currently fails. --- pipcl.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/pipcl.py b/pipcl.py index 943b8393f..cfc834d06 100644 --- a/pipcl.py +++ b/pipcl.py @@ -3348,9 +3348,27 @@ def swig_get(swig, quick, swig_local='pipcl-swig-git'): if quick and os.path.isfile(swig_binary): log1(f'{quick=} and {swig_binary=} already exists, so not downloading/building.') else: + if darwin(): + run(f'brew install automake') + run(f'brew install pcre2') + run(f'brew install bison') + # Default bison doesn't work, and Brew's bison is not added to $PATH. + # + # > bison is keg-only, which means it was not symlinked into /opt/homebrew, + # > because macOS already provides this software and installing another version in + # > parallel can cause all kinds of trouble. + # > + # > If you need to have bison first in your PATH, run: + # > echo 'export PATH="/opt/homebrew/opt/bison/bin:$PATH"' >> ~/.zshrc + # + swig_env_extra = dict() + macos_add_brew_path('bison', swig_env_extra) + run(f'which bison') + run(f'which bison', env_extra=swig_env_extra) + # Building swig requires bison>=3.5. bison_ok = 0 - e, text = run(f'bison --version', capture=1, check=0) + e, text = run(f'bison --version', capture=1, check=0, env_extra=swig_env_extra) if not e: log(textwrap.indent(text, ' ')) m = re.search('bison (GNU Bison) ([0-9]+)[.]([0-9]+)', text) @@ -3398,24 +3416,6 @@ def swig_get(swig, quick, swig_local='pipcl-swig-git'): remote='https://github.com/swig/swig.git', branch='master', ) - if darwin(): - run(f'brew install automake') - run(f'brew install pcre2') - run(f'brew install bison') - # Default bison doesn't work, and Brew's bison is not added to $PATH. - # - # > bison is keg-only, which means it was not symlinked into /opt/homebrew, - # > because macOS already provides this software and installing another version in - # > parallel can cause all kinds of trouble. - # > - # > If you need to have bison first in your PATH, run: - # > echo 'export PATH="/opt/homebrew/opt/bison/bin:$PATH"' >> ~/.zshrc - # - swig_env_extra = dict() - macos_add_brew_path('bison', swig_env_extra) - run(f'which bison') - run(f'which bison', env_extra=swig_env_extra) - # Build swig. run(f'cd {swig_local} && ./autogen.sh', env_extra=swig_env_extra) run(f'cd {swig_local} && ./configure --prefix={swig_local}/install-dir', env_extra=swig_env_extra) From c81d6d2e95125b1dce9c8282ded3462bffcadd2f Mon Sep 17 00:00:00 2001 From: Julian Smith Date: Sun, 18 Jan 2026 02:00:44 +0000 Subject: [PATCH 3/4] tests/test_typing.py: new, check that mypy finds py.typed file in pymupdf install. --- tests/test_typing.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/test_typing.py diff --git a/tests/test_typing.py b/tests/test_typing.py new file mode 100644 index 000000000..cab4450de --- /dev/null +++ b/tests/test_typing.py @@ -0,0 +1,33 @@ +import os +import subprocess + +import pymupdf + + +def run(command, check=1): + print(f'Running: {command}') + subprocess.run(command, shell=1, check=check) + +def test_py_typed(): + print(f'test_py_typed(): {pymupdf.__path__=}') + run('pip uninstall -y mypy') + run('pip install mypy') + root = os.path.abspath(f'{__file__}/../..') + + # Run mypy on this .py file; it will fail at `import pymypdf` if the + # pymupdf install does not have a py.typed file. + # + # This doesn't actually check pymupdf's typing. It looks like + # we can do that with `mypy -m pymupdf`, but as of 2026-1-18 this + # gives many errors such as: + # + # ...site-packages/pymupdf/__init__.py:15346: error: point_like? has no attribute "y" [attr-defined] + # + # It's important to use `--no-incremental`, otherwise if one has + # experimented with `mypy -m pymupdf`, this test will get the + # same failures, via `.mypy_cache/` directories. + # + # We run in sub-directory to avoid spurious mypy errors + # if there is a local mupdf/ directory. + # + run(f'cd {root}/tests && mypy --no-incremental {os.path.abspath(__file__)}') From e06127d30077475f1e7054883aa670805157605c Mon Sep 17 00:00:00 2001 From: Julian Smith Date: Sun, 18 Jan 2026 01:36:23 +0000 Subject: [PATCH 4/4] setup.py: add py.typed file to wheels and installs. See PEP-561. Also removed recently-added src/py.typed, as it did not appear in wheels/installs and is not required. --- setup.py | 1 + src/py.typed | 0 2 files changed, 1 insertion(+) delete mode 100644 src/py.typed diff --git a/setup.py b/setup.py index ea7a7a395..e6b6f43e2 100755 --- a/setup.py +++ b/setup.py @@ -665,6 +665,7 @@ def add(flavour, from_, to_): add('p', f'{g_root}/src/_wxcolors.py', to_dir) add('p', f'{g_root}/src/_apply_pages.py', to_dir) add('p', f'{g_root}/src/build/extra.py', to_dir) + add('p', b'', f'{to_dir}/py.typed') if path_so_leaf: add('p', f'{g_root}/src/build/{path_so_leaf}', to_dir) diff --git a/src/py.typed b/src/py.typed deleted file mode 100644 index e69de29bb..000000000