From e51dfdf76efc118608432d0e1401861c41c5414e Mon Sep 17 00:00:00 2001 From: Julian Smith Date: Thu, 2 Jul 2026 16:13:48 +0100 Subject: [PATCH] Removed support for building separate pymupdfb and pymupdfd wheels. We used to build separate wheels: pymupdf: pymupdf code without mupdf libraries. pymupdfb: mupdf libraries. pymupdfd: mupdf headers and windows .d library stubs. This was to reduce the overall size of our wheels on pypi.org when we had a separate wheel for each supported python version - it meant that each of these wheels could share the same (large) pymupdfb wheel. But for a while now we've been using py_limited_api to produce a single pymupdf wheel for each OS that works on all python versions. So we don't need the (fairly copmlex) support for building pymupdfb etc, and it's unlikely that we will need it in the future either. --- READMEb.md | 7 -- READMEd.md | 4 - setup.py | 332 +++++++++++++++-------------------------------------- 3 files changed, 94 insertions(+), 249 deletions(-) delete mode 100644 READMEb.md delete mode 100644 READMEd.md diff --git a/READMEb.md b/READMEb.md deleted file mode 100644 index 3e1900038..000000000 --- a/READMEb.md +++ /dev/null @@ -1,7 +0,0 @@ -# PyMuPDFb - -This wheel contains [MuPDF](https://mupdf.readthedocs.io/) shared libraries for -use by [PyMuPDF](https://pymupdf.readthedocs.io/). - -This wheel is shared by PyMuPDF wheels that are specific to different Python -versions, significantly reducing the total size of a release. diff --git a/READMEd.md b/READMEd.md deleted file mode 100644 index e37c5c0ea..000000000 --- a/READMEd.md +++ /dev/null @@ -1,4 +0,0 @@ -# PyMuPDFd - -This wheel contains [MuPDF](https://mupdf.readthedocs.io/) build-time files -that were used to build [PyMuPDF](https://pymupdf.readthedocs.io/). diff --git a/setup.py b/setup.py index 6d33310de..534b51b0a 100755 --- a/setup.py +++ b/setup.py @@ -49,39 +49,6 @@ PYMUPDF_SETUP_DUMMY If 1, we build dummy sdist and wheel with no files. - PYMUPDF_SETUP_FLAVOUR - Control building of separate wheels for PyMuPDF. - - Must be unset or a combination of 'p', 'b' and 'd'. - - Default is 'pbd'. - - 'p': - Generated wheel contains PyMuPDF code. - 'b': - Generated wheel contains MuPDF libraries; these are independent of - the Python version. - 'd': - Generated wheel contains includes and libraries for MuPDF. - - If 'p' is included, the generated wheel is called PyMuPDF. - Otherwise if 'b' is included the generated wheel is called PyMuPDFb. - Otherwise if 'd' is included the generated wheel is called PyMuPDFd. - - For example: - - 'pb': a `PyMuPDF` wheel with PyMuPDF runtime files and MuPDF - runtime shared libraries. - - 'b': a `PyMuPDFb` wheel containing MuPDF runtime shared libraries. - - 'pbd' a `PyMuPDF` wheel with PyMuPDF runtime files and MuPDF - runtime shared libraries, plus MuPDF build-time files (includes, - *.lib files on Windows). - - 'd': a `PyMuPDFd` wheel containing MuPDF build-time files - (includes, *.lib files on Windows). - PYMUPDF_SETUP_LIBCLANG For internal testing. @@ -227,9 +194,6 @@ g_root = os.path.abspath( f'{__file__}/..') -# Name of file that identifies that we are in a PyMuPDF sdist. -g_pymupdfb_sdist_marker = 'pymupdfb_sdist' - python_version_tuple = tuple(int(x) for x in platform.python_version_tuple()[:2]) PYMUPDF_SETUP_PY_LIMITED_API = os.environ.get('PYMUPDF_SETUP_PY_LIMITED_API') @@ -612,113 +576,96 @@ def build(): PYMUPDF_SETUP_FAKE_NOGIL, ) else: - if 'p' not in PYMUPDF_SETUP_FLAVOUR and 'b' not in PYMUPDF_SETUP_FLAVOUR: - # We only need MuPDF headers, so no point building MuPDF. - log(f'Not building MuPDF because not Windows and {PYMUPDF_SETUP_FLAVOUR=}.') - mupdf_build_dir = None - else: - mupdf_build_dir = build_mupdf_unix( - mupdf_local, - build_type, - overwrite_config, - g_py_limited_api, - PYMUPDF_SETUP_MUPDF_REFCHECK_IF, - PYMUPDF_SETUP_MUPDF_TRACE_IF, - PYMUPDF_SETUP_SWIG, - PYMUPDF_SETUP_FAKE_NOGIL, - ) - log( f'build(): mupdf_build_dir={mupdf_build_dir!r}') - - # Build `extra` module. - # - if 'p' in PYMUPDF_SETUP_FLAVOUR: - path_so_leaf = _build_extension( + mupdf_build_dir = build_mupdf_unix( mupdf_local, - mupdf_build_dir, build_type, + overwrite_config, g_py_limited_api, + PYMUPDF_SETUP_MUPDF_REFCHECK_IF, + PYMUPDF_SETUP_MUPDF_TRACE_IF, + PYMUPDF_SETUP_SWIG, + PYMUPDF_SETUP_FAKE_NOGIL, ) - else: - log(f'Not building extension.') - path_so_leaf = None + log( f'build(): mupdf_build_dir={mupdf_build_dir!r}') - # Generate list of (from, to) items to return to pipcl. What we add depends - # on PYMUPDF_SETUP_FLAVOUR. + # Build `extra` module. # + path_so_leaf = _build_extension( + mupdf_local, + mupdf_build_dir, + build_type, + g_py_limited_api, + ) + + # Generate list of (from, to) items to return to pipcl. ret = list() - def add(flavour, from_, to_): - assert flavour in 'pbd' - if flavour in PYMUPDF_SETUP_FLAVOUR: - ret.append((from_, to_)) to_dir = 'pymupdf/' to_dir_d = f'{to_dir}/mupdf-devel' # Add implementation files. - add('p', f'{g_root}/src/__init__.py', to_dir) - add('p', f'{g_root}/src/__main__.py', to_dir) - add('p', f'{g_root}/src/pymupdf.py', to_dir) - add('p', f'{g_root}/src/table.py', to_dir) - add('p', f'{g_root}/src/utils.py', to_dir) - 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) + ret.append( (f'{g_root}/src/__init__.py', to_dir) ) + ret.append( (f'{g_root}/src/__main__.py', to_dir) ) + ret.append( (f'{g_root}/src/pymupdf.py', to_dir) ) + ret.append( (f'{g_root}/src/table.py', to_dir) ) + ret.append( (f'{g_root}/src/utils.py', to_dir) ) + ret.append( (f'{g_root}/src/_wxcolors.py', to_dir) ) + ret.append( (f'{g_root}/src/_apply_pages.py', to_dir) ) + ret.append( (f'{g_root}/src/build/extra.py', to_dir) ) + ret.append( (b'', f'{to_dir}/py.typed') ) + ret.append( (f'{g_root}/src/build/{path_so_leaf}', to_dir) ) # Add support for `fitz` backwards compatibility. - add('p', f'{g_root}/src/fitz___init__.py', 'fitz/__init__.py') - add('p', f'{g_root}/src/fitz_table.py', 'fitz/table.py') - add('p', f'{g_root}/src/fitz_utils.py', 'fitz/utils.py') + ret.append( (f'{g_root}/src/fitz___init__.py', 'fitz/__init__.py') ) + ret.append( (f'{g_root}/src/fitz_table.py', 'fitz/table.py') ) + ret.append( (f'{g_root}/src/fitz_utils.py', 'fitz/utils.py') ) if mupdf_local: # Add MuPDF Python API. - add('p', f'{mupdf_build_dir}/mupdf.py', to_dir) + ret.append( (f'{mupdf_build_dir}/mupdf.py', to_dir) ) # Add MuPDF shared libraries. if windows: wp = pipcl.wdev.WindowsPython() - add('p', f'{mupdf_build_dir}/_mupdf.pyd', to_dir) - add('b', f'{mupdf_build_dir}/mupdfcpp{wp.cpu.windows_suffix}.dll', to_dir) + ret.append( (f'{mupdf_build_dir}/_mupdf.pyd', to_dir) ) + ret.append( (f'{mupdf_build_dir}/mupdfcpp{wp.cpu.windows_suffix}.dll', to_dir) ) # Add Windows .lib files. mupdf_build_dir2 = _windows_lib_directory(mupdf_local, build_type) - add('d', f'{mupdf_build_dir2}/mupdfcpp{wp.cpu.windows_suffix}.lib', f'{to_dir_d}/lib/') + ret.append( (f'{mupdf_build_dir2}/mupdfcpp{wp.cpu.windows_suffix}.lib', f'{to_dir_d}/lib/') ) # MuPDF-1.25+ language bindings build also builds libmuthreads. - add('d', f'{mupdf_build_dir2}/libmuthreads.lib', f'{to_dir_d}/lib/') + ret.append( (f'{mupdf_build_dir2}/libmuthreads.lib', f'{to_dir_d}/lib/') ) elif darwin: - add('p', f'{mupdf_build_dir}/_mupdf.so', to_dir) - add('b', f'{mupdf_build_dir}/libmupdfcpp.so', to_dir) - add('b', f'{mupdf_build_dir}/libmupdf.dylib', to_dir) - add('d', f'{mupdf_build_dir}/libmupdf-threads.a', f'{to_dir_d}/lib/') + ret.append( (f'{mupdf_build_dir}/_mupdf.so', to_dir) ) + ret.append( (f'{mupdf_build_dir}/libmupdfcpp.so', to_dir) ) + ret.append( (f'{mupdf_build_dir}/libmupdf.dylib', to_dir) ) + ret.append( (f'{mupdf_build_dir}/libmupdf-threads.a', f'{to_dir_d}/lib/') ) elif pyodide: - add('p', f'{mupdf_build_dir}/_mupdf.so', to_dir) - add('b', f'{mupdf_build_dir}/libmupdfcpp.so', to_dir) - add('b', f'{mupdf_build_dir}/libmupdf.so', to_dir) + ret.append( (f'{mupdf_build_dir}/_mupdf.so', to_dir) ) + ret.append( (f'{mupdf_build_dir}/libmupdfcpp.so', to_dir) ) + ret.append( (f'{mupdf_build_dir}/libmupdf.so', to_dir) ) else: - add('p', f'{mupdf_build_dir}/_mupdf.so', to_dir) - add('b', pipcl.get_soname(f'{mupdf_build_dir}/libmupdfcpp.so'), to_dir) - add('b', pipcl.get_soname(f'{mupdf_build_dir}/libmupdf.so'), to_dir) - add('d', f'{mupdf_build_dir}/libmupdf-threads.a', f'{to_dir_d}/lib/') - - if 'd' in PYMUPDF_SETUP_FLAVOUR: - # Add MuPDF C and C++ headers to `ret_d`. Would prefer to use - # pipcl.git_items() but hard-coded mupdf tree is not a git - # checkout. - # - for root in ( - f'{mupdf_local}/include', - f'{mupdf_local}/platform/c++/include', - ): - for dirpath, dirnames, filenames in os.walk(root): - for filename in filenames: - if not filename.endswith('.h'): - continue - header_abs = os.path.join(dirpath, filename) - assert header_abs.startswith(root) - header_rel = header_abs[len(root)+1:] - add('d', f'{header_abs}', f'{to_dir_d}/include/{header_rel}') + ret.append( (f'{mupdf_build_dir}/_mupdf.so', to_dir) ) + ret.append( (pipcl.get_soname(f'{mupdf_build_dir}/libmupdfcpp.so'), to_dir) ) + ret.append( (pipcl.get_soname(f'{mupdf_build_dir}/libmupdf.so'), to_dir) ) + ret.append( (f'{mupdf_build_dir}/libmupdf-threads.a', f'{to_dir_d}/lib/') ) + + # Add MuPDF C and C++ headers to `mupdf-devel/`. Would prefer to use + # pipcl.git_items() but hard-coded mupdf tree is not a git + # checkout. + # + for root in ( + f'{mupdf_local}/include', + f'{mupdf_local}/platform/c++/include', + ): + for dirpath, dirnames, filenames in os.walk(root): + for filename in filenames: + if not filename.endswith('.h'): + continue + header_abs = os.path.join(dirpath, filename) + assert header_abs.startswith(root) + header_rel = header_abs[len(root)+1:] + ret.append( (f'{header_abs}', f'{to_dir_d}/include/{header_rel}') ) # Add a .py file containing build-time information - location of MuPDF, # pymupdf git info, swig version etc. @@ -746,15 +693,10 @@ def int_or_0(text): text += f'swig_version_tuple = {swig_version_tuple!r}\n' text += f'fake_no_gil = {PYMUPDF_SETUP_FAKE_NOGIL=="1"!r}\n' log(f'_build.py is:\n{textwrap.indent(text, " ")}') - add('p', text.encode(), f'{to_dir}/_build.py') + ret.append( (text.encode(), f'{to_dir}/_build.py') ) # Add single README file. - if 'p' in PYMUPDF_SETUP_FLAVOUR: - add('p', f'{g_root}/README.md', '$dist-info/README.md') - elif 'b' in PYMUPDF_SETUP_FLAVOUR: - add('b', f'{g_root}/READMEb.md', '$dist-info/README.md') - elif 'd' in PYMUPDF_SETUP_FLAVOUR: - add('d', f'{g_root}/READMEd.md', '$dist-info/README.md') + ret.append( (f'{g_root}/README.md', '$dist-info/README.md') ) return ret @@ -1258,23 +1200,6 @@ def sdist(): if PYMUPDF_SETUP_DUMMY == '1': return ret - if PYMUPDF_SETUP_FLAVOUR == 'b': - # Create a minimal sdist that will build/install a dummy PyMuPDFb. - for p in ( - 'setup.py', - 'pipcl.py', - 'wdev.py', - 'pyproject.toml', - ): - ret.append(p) - ret.append( - ( - b'This file indicates that we are a PyMuPDFb sdist and should build/install a dummy PyMuPDFb package.\n', - g_pymupdfb_sdist_marker, - ) - ) - return ret - for p in pipcl.git_items( g_root): if p.startswith( ( @@ -1295,114 +1220,42 @@ def sdist(): return ret -classifier = [ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Intended Audience :: Information Technology', - 'Operating System :: MacOS', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX :: Linux', - 'Programming Language :: C', - 'Programming Language :: C++', - 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: Implementation :: CPython', - 'Topic :: Utilities', - 'Topic :: Multimedia :: Graphics', - 'Topic :: Software Development :: Libraries', - ] - -# We generate different wheels depending on PYMUPDF_SETUP_FLAVOUR. -# - # PyMuPDF version. version_p = '1.28.0' version_mupdf = '1.28.0' -# PyMuPDFb version. This is the PyMuPDF version whose PyMuPDFb wheels we will -# (re)use if generating separate PyMuPDFb wheels. Though as of PyMuPDF-1.24.11 -# (2024-10-03) we no longer use PyMuPDFb wheels so this is actually unused. -# -version_b = '1.26.3' - -if os.path.exists(f'{g_root}/{g_pymupdfb_sdist_marker}'): - - # We are in a PyMuPDFb sdist. We specify a dummy package so that pip builds - # from sdists work - pip's build using PyMuPDF's sdist will already create - # the required binaries, but pip will still see `requires_dist` set to - # 'PyMuPDFb', so will also download and build PyMuPDFb's sdist. - # - log(f'Specifying dummy PyMuPDFb wheel.') - - def get_requires_for_build_wheel(config_settings=None): - return list() - - p = pipcl.Package( - 'pymupdfb', - version_b, - summary = 'Dummy PyMuPDFb wheel', - description = '', - author = 'Artifex', - author_email = 'support@artifex.com', - license = 'GNU AFFERO GPL 3.0', - tag_python = 'py3', - ) - -else: +if 1: # A normal PyMuPDF package. - with open( f'{g_root}/README.md', encoding='utf-8') as f: - readme_p = f.read() - - with open( f'{g_root}/READMEb.md', encoding='utf-8') as f: - readme_b = f.read() - - with open( f'{g_root}/READMEd.md', encoding='utf-8') as f: - readme_d = f.read() - - tag_python = None requires_dist = list() - entry_points = None - - if 'p' in PYMUPDF_SETUP_FLAVOUR: - version = version_p - name = 'pymupdf' - readme = readme_p - summary = 'A high performance Python library for data extraction, analysis, conversion & manipulation of PDF (and other) documents.' - if 'b' not in PYMUPDF_SETUP_FLAVOUR: - requires_dist.append(f'pymupdfb =={version_b}') - # Create a `pymupdf` command. - entry_points = textwrap.dedent(''' - [console_scripts] - pymupdf = pymupdf.__main__:main - ''') - elif 'b' in PYMUPDF_SETUP_FLAVOUR: - version = version_b - name = 'pymupdfb' - readme = readme_b - summary = 'MuPDF shared libraries for pymupdf.' - tag_python = 'py3' - elif 'd' in PYMUPDF_SETUP_FLAVOUR: - version = version_b - name = 'pymupdfd' - readme = readme_d - summary = 'MuPDF build-time files for pymupdf.' - tag_python = 'py3' - else: - assert 0, f'Unrecognised {PYMUPDF_SETUP_FLAVOUR=}.' - if os.environ.get('PYODIDE_ROOT'): # We can't pip install pytest on pyodide, so specify it here. requires_dist.append('pytest') requires_dist.append('pipcl') p = pipcl.Package( - name, - version, - summary = summary, - description = readme, + 'pymupdf', + version_p, + summary = 'A high performance Python library for data extraction, analysis, conversion & manipulation of PDF (and other) documents.', + description = 'README.md', description_content_type = 'text/markdown', - classifier = classifier, + classifier = [ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'Operating System :: MacOS', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: C', + 'Programming Language :: C++', + 'Programming Language :: Python :: 3 :: Only', + 'Programming Language :: Python :: Implementation :: CPython', + 'Topic :: Utilities', + 'Topic :: Multimedia :: Graphics', + 'Topic :: Software Development :: Libraries', + ], + author = 'Artifex', author_email = 'support@artifex.com', requires_dist = requires_dist, @@ -1414,16 +1267,19 @@ def get_requires_for_build_wheel(config_settings=None): ('Tracker, https://github.com/pymupdf/PyMuPDF/issues'), ('Changelog, https://pymupdf.readthedocs.io/en/latest/changes.html'), ], - - entry_points = entry_points, - + + # We create a `pymupdf` command. + entry_points = textwrap.dedent(''' + [console_scripts] + pymupdf = pymupdf.__main__:main + '''), + fn_build=build, fn_clean=clean, fn_sdist=sdist, - - tag_python=tag_python, + py_limited_api=g_py_limited_api, - + # 30MB: 9 ZIP_DEFLATED # 28MB: 9 ZIP_BZIP2 # 23MB: 9 ZIP_LZMA