Mercurial > hgrepos > Python2 > PyMuPDF
changeset 41:71bcc18e306f
MERGE: New upstream PyMuPDF v1.26.5 including MuPDF v1.26.10
BUGS: Needs some additional changes yet.
Not yet tested.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Sat, 11 Oct 2025 15:24:40 +0200 |
| parents | 8934ac156ef5 (diff) aa33339d6b8a (current diff) |
| children | 4621bd954a09 |
| files | mupdf-source/scripts/jlib.py mupdf-source/scripts/wrap/__main__.py pipcl.py setup.py src/__init__.py |
| diffstat | 13 files changed, 324 insertions(+), 124 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Sat Oct 11 15:24:40 2025 +0200 @@ -0,0 +1,5 @@ +syntax: regexp + +(^|/)_venv.* +^_.*\.(log|txt)$ +^_tmp/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgtags Sat Oct 11 15:24:40 2025 +0200 @@ -0,0 +1,2 @@ +f76e6575dca9fab27786e07528fba717cc4bcd00 v1.26.4+1 +14b91574d44af9549d6db8f43c103eedbca1849a v1.26.4+2
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile.freebsd Sat Oct 11 15:24:40 2025 +0200 @@ -0,0 +1,118 @@ +# -*- mode: makefile; coding: utf-8 -*- +# +# Needs GNU make (aka gmake) and binutils/ar or LLVM/ar +# (instead of FreeBSD /usr/bin/ar). +# +# Building against an installed mupdf package fails because the libmupdfcpp.so +# is missing. +# +# Prepare: +# +# - Create a Python venv and activate it +# +# - Install a LLVM package that has the same version as the libclang +# Python package that will be used/installed +# (e.g. pkg install llvm18 because of libclang>=18,<19) +# +# - In the "bin"-directory of the venv symlink "make" to /usr/local/bin/gmake +# and "ar" to /usr/local/llvm18/bin/llvm-ar +# +# * Makefiles have GNU syntax +# * ar is called with @-response-files -- which are a GNU feature (also +# implemented by LLVM's ar) +# +# - At first to not build with tesseract (OCR) +# +# export PYMUPDF_SETUP_MUPDF_TESSERACT=0 +# + +.PHONY: all wheel sdist populate-venv check + +.SILENT: check + +THIS_MAKEFILE_JUSTNAME:= $(firstword $(MAKEFILE_LIST)) +THIS_MAKEFILE_DIR:= $(abspath $(dir $(THIS_MAKEFILE_JUSTNAME))) + +PYMUPDF_SETUP_MUPDF_BUILD?= $(THIS_MAKEFILE_DIR)/mupdf-source +# It would compile with tesseract because its sources are vendored by MuPDF +PYMUPDF_SETUP_MUPDF_TESSERACT?= 0 +# +# We use clang instead of libclang: On FreeBSD you have to install +# llvm to get a libclang.so installed; there is no libclang package +# with a bundled libclang.so. Additionally, libclang currently is not +# up-to-date with respect to clang versions >= 19. +# +PYMUPDF_SETUP_LIBCLANG?= clang>=18,<19 +LIBCLANG_LIBRARY_PATH?= $(CLANG_DIR)/lib +CLANG_DIR?= /usr/local/llvm18 + +TEST= /bin/test +LOCALBASE?= /usr/local +SYMLINK?= /bin/ln -s +PYTHON?= python3 +PYTHON_PREFIXES!= $(PYTHON) -c 'import sys; print(sys.prefix); print(sys.base_prefix)' + +# +# Setting these does not work for some parts built by sub-makes. +# Symlink in the venv instead. +# +#CC= $(CLANG_DIR)/bin/clang +#CXX= $(CLANG_DIR)/bin/clang++ + +# Define _FORTIFY_SOURCE=$(FORTIFY) (if != 0, default 0) +FORTIFY?= 3 +# +# If != 0 (default 1): +# -fno-delete-null-pointer-checks +# +# Should always be done when fortifying: +# https://github.com/ossf/wg-best-practices-os-developers/issues/659 +# +# -Werror=implicit-function-declaration +# -fstack-clash-protection +# -fstack-protector-strong +# +EXTRA_CHECKS?= 1 + + +all: sdist wheel + + +wheel: check + $(TEST) -e $(firstword $(PYTHON_PREFIXES))/bin/make || $(SYMLINK) $(LOCALBASE)/bin/gmake $(firstword $(PYTHON_PREFIXES))/bin/make + $(TEST) -e $(firstword $(PYTHON_PREFIXES))/bin/ar || $(SYMLINK) $(CLANG_DIR)/bin/llvm-ar $(firstword $(PYTHON_PREFIXES))/bin/ar + $(TEST) -e $(firstword $(PYTHON_PREFIXES))/bin/cc || $(SYMLINK) $(CLANG_DIR)/bin/clang $(firstword $(PYTHON_PREFIXES))/bin/cc + $(TEST) -e $(firstword $(PYTHON_PREFIXES))/bin/c++ || $(SYMLINK) $(CLANG_DIR)/bin/clang++ $(firstword $(PYTHON_PREFIXES))/bin/c++ + $(TEST) -e $(firstword $(PYTHON_PREFIXES))/bin/ld || $(SYMLINK) $(CLANG_DIR)/bin/ld.lld $(firstword $(PYTHON_PREFIXES))/bin/ld + FORTIFY=$(FORTIFY) EXTRA_CHECKS=$(EXTRA_CHECKS) PIPCL_VERBOSE=2 LIBCLANG_LIBRARY_PATH=$(LIBCLANG_LIBRARY_PATH) PYMUPDF_SETUP_MUPDF_BUILD=$(PYMUPDF_SETUP_MUPDF_BUILD) PYMUPDF_SETUP_MUPDF_TESSERACT=$(PYMUPDF_SETUP_MUPDF_TESSERACT) PYMUPDF_SETUP_LIBCLANG='$(PYMUPDF_SETUP_LIBCLANG)' $(PYTHON) -m build --wheel --verbose --no-isolation + + +sdist: check + PIPCL_VERBOSE=2 LIBCLANG_LIBRARY_PATH=$(LIBCLANG_LIBRARY_PATH) PYMUPDF_SETUP_MUPDF_BUILD=$(PYMUPDF_SETUP_MUPDF_BUILD) PYMUPDF_SETUP_MUPDF_TESSERACT=$(PYMUPDF_SETUP_MUPDF_TESSERACT) PYMUPDF_SETUP_LIBCLANG='$(PYMUPDF_SETUP_LIBCLANG)' $(PYTHON) -m build --sdist --verbose --no-isolation + + +populate-venv: +ifneq ($(firstword $(PYTHON_PREFIXES)),$(lastword $(PYTHON_PREFIXES))) + $(PYTHON) -m pip install -U -r requirements-build.txt +else + $(error Not in a Python virtual environment) +endif + + +check: +ifneq ($(firstword $(PYTHON_PREFIXES)),$(lastword $(PYTHON_PREFIXES))) + $(PYTHON) -m pip freeze | grep -E '^\s*setuptools-scm==' >/dev/null + $(PYTHON) -m pip freeze | grep -E '^\s*build==' >/dev/null + $(PYTHON) -m pip freeze | grep -E '^\s*swig==' >/dev/null + $(PYTHON) -m pip freeze | grep -E '^\s*clang==18\.' >/dev/null + # This covers the "clang" and "libclang" package + $(PYTHON) -c 'import clang.cindex' >/dev/null + $(TEST) -x $(LOCALBASE)/bin/gmake + $(TEST) -x $(LOCALBASE)/bin/cmake + $(TEST) -x $(CLANG_DIR)/bin/llvm-ar + $(TEST) -x $(CLANG_DIR)/bin/clang + $(TEST) -x $(CLANG_DIR)/bin/clang++ + $(TEST) -x $(CLANG_DIR)/bin/ld.lld +else + $(error Not in a Python virtual environment) +endif
--- a/PKG-INFO Sat Oct 11 11:31:38 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ -Metadata-Version: 2.1 -Name: PyMuPDF -Version: 1.26.5 -Summary: A high performance Python library for data extraction, analysis, conversion & manipulation of PDF (and other) documents. -Description-Content-Type: text/markdown -Author: Artifex -Author-email: support@artifex.com -License: Dual Licensed - GNU AFFERO GPL 3.0 or Artifex Commercial License -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Developers -Classifier: Intended Audience :: Information Technology -Classifier: Operating System :: MacOS -Classifier: Operating System :: Microsoft :: Windows -Classifier: Operating System :: POSIX :: Linux -Classifier: Programming Language :: C -Classifier: Programming Language :: C++ -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Topic :: Utilities -Classifier: Topic :: Multimedia :: Graphics -Classifier: Topic :: Software Development :: Libraries -Requires-Python: >=3.9 -Project-URL: Documentation, https://pymupdf.readthedocs.io/ -Project-URL: Source, https://github.com/pymupdf/pymupdf -Project-URL: Tracker, https://github.com/pymupdf/PyMuPDF/issues -Project-URL: Changelog, https://pymupdf.readthedocs.io/en/latest/changes.html - -# PyMuPDF - -**PyMuPDF** is a high performance **Python** library for data extraction, analysis, conversion & manipulation of [PDF (and other) documents](https://pymupdf.readthedocs.io/en/latest/the-basics.html#supported-file-types). - -# Community -Join us on **Discord** here: [#pymupdf](https://discord.gg/TSpYGBW4eq) - - -# Installation - -**PyMuPDF** requires **Python 3.9 or later**, install using **pip** with: - -`pip install PyMuPDF` - -There are **no mandatory** external dependencies. However, some [optional features](#pymupdf-optional-features) become available only if additional packages are installed. - -You can also try without installing by visiting [PyMuPDF.io](https://pymupdf.io/#examples). - - -# Usage - -Basic usage is as follows: - -```python -import pymupdf # imports the pymupdf library -doc = pymupdf.open("example.pdf") # open a document -for page in doc: # iterate the document pages - text = page.get_text() # get plain text encoded as UTF-8 - -``` - - -# Documentation - -Full documentation can be found on [pymupdf.readthedocs.io](https://pymupdf.readthedocs.io). - - - -# <a id="pymupdf-optional-features"></a>Optional Features - -* [fontTools](https://pypi.org/project/fonttools/) for creating font subsets. -* [pymupdf-fonts](https://pypi.org/project/pymupdf-fonts/) contains some nice fonts for your text output. -* [Tesseract-OCR](https://github.com/tesseract-ocr/tesseract) for optical character recognition in images and document pages. - - - -# About - -**PyMuPDF** adds **Python** bindings and abstractions to [MuPDF](https://mupdf.com/), a lightweight **PDF**, **XPS**, and **eBook** viewer, renderer, and toolkit. Both **PyMuPDF** and **MuPDF** are maintained and developed by [Artifex Software, Inc](https://artifex.com). - -**PyMuPDF** was originally written by [Jorj X. McKie](mailto:jorj.x.mckie@outlook.de). - - -# License and Copyright - -**PyMuPDF** is available under [open-source AGPL](https://www.gnu.org/licenses/agpl-3.0.html) and commercial license agreements. If you determine you cannot meet the requirements of the **AGPL**, please contact [Artifex](https://artifex.com/contact/pymupdf-inquiry.php) for more information regarding a commercial license.
--- a/mupdf-source/Makerules Sat Oct 11 11:31:38 2025 +0200 +++ b/mupdf-source/Makerules Sat Oct 11 15:24:40 2025 +0200 @@ -105,6 +105,15 @@ CFLAGS += -ffunction-sections -fdata-sections endif +ifneq ($(EXTRA_CHECKS),) + ifneq ($(EXTRA_CHECKS),0) + CFLAGS += -fno-delete-null-pointer-checks + CFLAGS += -Werror=implicit-function-declaration + CFLAGS += -fstack-clash-protection + CFLAGS += -fstack-protector-strong + endif +endif + ifeq ($(OS),Darwin) LDREMOVEUNREACH := -Wl,-dead_strip SO := dylib @@ -139,6 +148,8 @@ LDFLAGS += -sTOTAL_MEMORY=48MB else ifeq ($(OS),Linux) LIB_LDFLAGS = -shared -Wl,-soname,$(notdir $@) + else ifeq ($(OS),FreeBSD) + LIB_LDFLAGS = -shared -Wl,-soname,$(notdir $@) else LIB_LDFLAGS = -shared endif @@ -153,12 +164,21 @@ else ifeq ($(build),release) CFLAGS += -pipe -O2 -DNDEBUG LDFLAGS += $(LDREMOVEUNREACH) -Wl,-s + ifneq ($(OS),Darwin) + LDFLAGS += -Wl,-z,relro,-z,now + endif else ifeq ($(build),small) CFLAGS += -pipe -Os -DNDEBUG LDFLAGS += $(LDREMOVEUNREACH) -Wl,-s + ifneq ($(OS),Darwin) + LDFLAGS += -Wl,-z,relro,-z,now + endif else ifeq ($(build),valgrind) CFLAGS += -pipe -O2 -DNDEBUG -DPACIFY_VALGRIND LDFLAGS += $(LDREMOVEUNREACH) -Wl,-s + ifneq ($(OS),Darwin) + LDFLAGS += -Wl,-z,relro,-z,now + endif else ifeq ($(build),sanitize) CFLAGS += -pipe -g $(SANITIZE_FLAGS) LDFLAGS += -g $(SANITIZE_FLAGS) @@ -174,6 +194,9 @@ else ifeq ($(build),native) CFLAGS += -pipe -O2 -DNDEBUG -march=native LDFLAGS += $(LDREMOVEUNREACH) -Wl,-s + ifneq ($(OS),Darwin) + LDFLAGS += -Wl,-z,relro,-z,now + endif else ifeq ($(build),memento) CFLAGS += -pipe -g -DMEMENTO -DMEMENTO_MUPDF_HACKS LDFLAGS += -g -rdynamic
--- a/mupdf-source/scripts/jlib.py Sat Oct 11 11:31:38 2025 +0200 +++ b/mupdf-source/scripts/jlib.py Sat Oct 11 15:24:40 2025 +0200 @@ -2306,5 +2306,11 @@ ret += " -Wl,-rpath,'$ORIGIN'" else: ret += " -Wl,-rpath,'$ORIGIN',-z,origin" + if not darwin and (platform.system() != 'Windows'): + # *BSD and Linux + # Full RELRO + ret += ' -Wl,-z,relro,-z,now' + # Strip + ret += ' -Wl,-s' #log('{sos=} {ld_origin=} {ret=}') return ret.strip()
--- a/mupdf-source/scripts/pipcl.py Sat Oct 11 11:31:38 2025 +0200 +++ b/mupdf-source/scripts/pipcl.py Sat Oct 11 15:24:40 2025 +0200 @@ -1772,9 +1772,14 @@ This function can be useful for the `fn_sdist()` callback. ''' - command = 'cd ' + directory + ' && git ls-files' - if submodules: - command += ' --recurse-submodules' + if os.path.isdir(os.path.join(directory, ".hg")): + command = 'cd ' + directory + ' && hg files' + if submodules: + command += ' --subrepos' + else: + command = 'cd ' + directory + ' && git ls-files' + if submodules: + command += ' --recurse-submodules' log1(f'Running {command=}') text = subprocess.check_output( command, shell=True) ret = [] @@ -1860,6 +1865,10 @@ def openbsd(): return platform.system() == 'OpenBSD' +def freebsd(): + return platform.system() == 'FreeBSD' + + class PythonFlags: ''' Compile/link flags for the current python, for example the include path @@ -2309,7 +2318,7 @@ return `path`. Useful if Linux shared libraries have been created with `-Wl,-soname,...`, where we need to embed the versioned library. ''' - if linux() and os.path.islink(path): + if (linux() or freebsd()) and os.path.islink(path): path2 = os.path.realpath(path) if subprocess.run(f'objdump -p {path2}|grep SONAME', shell=1, check=0).returncode == 0: return path2
--- a/mupdf-source/scripts/wrap/__main__.py Sat Oct 11 11:31:38 2025 +0200 +++ b/mupdf-source/scripts/wrap/__main__.py Sat Oct 11 15:24:40 2025 +0200 @@ -1537,6 +1537,11 @@ dir_so_flags = os.path.basename( build_dirs.dir_so).split( '-') cflags = os.environ.get('XCXXFLAGS', '') + if os.environ.get('EXTRA_CHECKS', '1') != '0': + cflags += ' -fno-delete-null-pointer-checks' + cflags += ' -Werror=implicit-function-declaration' + cflags += ' -fstack-clash-protection' + cflags += ' -fstack-protector-strong' windows_build_type = build_dirs.windows_build_type() so_version = get_so_version( build_dirs) @@ -1670,7 +1675,7 @@ elif 'shared' in dir_so_flags: link_soname_arg = '' - if state.state_.linux and so_version: + if (state.state_.linux or state.state_.freebsd) and so_version: link_soname_arg = f'-Wl,-soname,{os.path.basename(libmupdfcpp)}' command = ( textwrap.dedent( f''' @@ -1694,7 +1699,7 @@ ) if command_was_run: macos_patch( libmupdfcpp, f'{build_dirs.dir_so}/libmupdf.dylib{so_version}') - if so_version and state.state_.linux: + if so_version and (state.state_.linux or state.state_.freebsd): jlib.system(f'ln -sf libmupdfcpp.so{so_version} {build_dirs.dir_so}/libmupdfcpp.so') elif 'fpic' in dir_so_flags:
--- a/mupdf-source/scripts/wrap/state.py Sat Oct 11 11:31:38 2025 +0200 +++ b/mupdf-source/scripts/wrap/state.py Sat Oct 11 15:24:40 2025 +0200 @@ -21,6 +21,12 @@ f'or (OpenBSD only) `pkg_add py3-llvm.`\n' ) clang = None +else: + if os.environ.get('LIBCLANG_LIBRARY_FILE', None): + clang.cindex.Config.set_library_file(os.environ['LIBCLANG_LIBRARY_FILE']) + elif os.environ.get('LIBCLANG_LIBRARY_PATH', None): + clang.cindex.Config.set_library_path(os.environ['LIBCLANG_LIBRARY_PATH']) + omit_fns = [ 'fz_open_file_w', @@ -69,6 +75,7 @@ self.windows = (self.os_name == 'Windows' or self.os_name.startswith('CYGWIN')) self.cygwin = self.os_name.startswith('CYGWIN') self.openbsd = self.os_name == 'OpenBSD' + self.freebsd = self.os_name == 'FreeBSD' self.linux = self.os_name == 'Linux' self.macos = self.os_name == 'Darwin' self.pyodide = os.environ.get('OS') == 'pyodide'
--- a/pipcl.py Sat Oct 11 11:31:38 2025 +0200 +++ b/pipcl.py Sat Oct 11 15:24:40 2025 +0200 @@ -743,7 +743,7 @@ # Add the files returned by fn_build(). # for item in items: - from_, (to_abs, to_rel) = self._fromto(item) + from_, (to_abs, to_rel, _) = self._fromto(item) add(from_, to_rel) # Add <name>-<version>.dist-info/WHEEL. @@ -841,10 +841,24 @@ textb = text.encode('utf8') return add(textb, name) + def add_symlink(from_, name, linkname=None): + check_name(name) + assert isinstance(from_, str) + assert isinstance(name, str) + assert name + assert os.path.islink(from_) + if linkname is None: + linkname = os.readlink(from_) + log2(f'Adding symlink: {os.path.relpath(from_)} => {name} --> {linkname}') + ti = tar.gettarinfo(from_, name) + tar.addfile(ti) + found_pyproject_toml = False for item in items: - from_, (to_abs, to_rel) = self._fromto(item) - if isinstance(from_, bytes): + from_, (to_abs, to_rel, to_symlink) = self._fromto(item, resolve_symlinks=False) + if to_symlink: + add_symlink(from_, to_rel, to_symlink) + elif isinstance(from_, bytes): add(from_, to_rel) else: if from_.startswith(f'{os.path.abspath(sdist_directory)}/'): @@ -1105,7 +1119,7 @@ record.add_content(content, to_rel) for item in items: - from_, (to_abs, to_rel) = self._fromto(item) + from_, (to_abs, to_rel, _) = self._fromto(item) log0(f'{from_=} {to_abs=} {to_rel=}') to_abs2 = f'{root2}/{to_rel}' add_file( from_, to_abs2, to_rel) @@ -1500,7 +1514,7 @@ ret += '\n' return ret - def _path_relative_to_root(self, path, assert_within_root=True): + def _path_relative_to_root(self, path, assert_within_root=True, resolve_symlinks=True): ''' Returns `(path_abs, path_rel)`, where `path_abs` is absolute path and `path_rel` is relative to `self.root`. @@ -1516,14 +1530,21 @@ p = path else: p = os.path.join(self.root, path) + path_abs = p + # always resolve symlinks at first p = os.path.realpath(os.path.abspath(p)) if assert_within_root: assert p.startswith(self.root+os.sep) or p == self.root, \ f'Path not within root={self.root+os.sep!r}: {path=} {p=}' p_rel = os.path.relpath(p, self.root) - return p, p_rel - - def _fromto(self, p): + if resolve_symlinks or not os.path.islink(path_abs): + return p, p_rel, None + else: + assert os.path.islink(path_abs) + p_rel = os.path.relpath(path_abs, self.root) + return path_abs, p_rel, os.readlink(path_abs) + + def _fromto(self, p, resolve_symlinks=True): ''' Returns `(from_, (to_abs, to_rel))`. @@ -1567,8 +1588,8 @@ if to_.startswith( prefix): to_ = f'{_normalise2(self.name)}-{self.version}.data/{to_[ len(prefix):]}' if isinstance(from_, str): - from_, _ = self._path_relative_to_root( from_, assert_within_root=False) - to_ = self._path_relative_to_root(to_) + from_, _, _ = self._path_relative_to_root( from_, assert_within_root=False, resolve_symlinks=resolve_symlinks) + to_ = self._path_relative_to_root(to_, resolve_symlinks=resolve_symlinks) assert isinstance(from_, (str, bytes)) log2(f'returning {from_=} {to_=}') return from_, to_ @@ -1890,6 +1911,11 @@ {' '.join(path_os)} {linker_extra} ''' + if os.environ.get('EXTRA_CHECKS', '1') != '0': + general_flags += ' -fno-delete-null-pointer-checks' + general_flags += ' -Werror=implicit-function-declaration' + general_flags += ' -fstack-clash-protection' + general_flags += ' -fstack-protector-strong' elif pyodide(): command2 = f''' {linker_command} @@ -2096,9 +2122,14 @@ This function can be useful for the `fn_sdist()` callback. ''' - command = 'cd ' + directory + ' && git ls-files' - if submodules: - command += ' --recurse-submodules' + if os.path.isdir(os.path.join(directory, ".hg")): + command = 'cd ' + directory + ' && hg files' + if submodules: + command += ' --subrepos' + else: + command = 'cd ' + directory + ' && git ls-files' + if submodules: + command += ' --recurse-submodules' log1(f'Running {command=}') text = subprocess.check_output( command, shell=True) ret = [] @@ -2416,6 +2447,9 @@ def openbsd(): return platform.system() == 'OpenBSD' +def freebsd(): + return platform.system() == 'FreeBSD' + def show_system(): ''' @@ -2899,7 +2933,7 @@ def _assert_version_pep_440(version): assert re.match( - r'^([1-9][0-9]*!)?(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))*((a|b|rc)(0|[1-9][0-9]*))?(\.post(0|[1-9][0-9]*))?(\.dev(0|[1-9][0-9]*))?$', + r'^([1-9][0-9]*!)?(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))*((a|b|rc)(0|[1-9][0-9]*))?(\.post(0|[1-9][0-9]*))?(\.dev(0|[1-9][0-9]*))?(?:\+([a-z0-9]+(?:[-_\.][a-z0-9]+)*))?$', version, ), \ f'Bad version: {version!r}.' @@ -3013,7 +3047,7 @@ return `path`. Useful if Linux shared libraries have been created with `-Wl,-soname,...`, where we need to embed the versioned library. ''' - if linux() and os.path.islink(path): + if (linux() or freebsd()) and os.path.islink(path): path2 = os.path.realpath(path) if subprocess.run(f'objdump -p {path2}|grep SONAME', shell=1, check=0).returncode == 0: return path2
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/requirements-build.txt Sat Oct 11 15:24:40 2025 +0200 @@ -0,0 +1,12 @@ +# Build in a non-isolated venv +# +# cmake is required as a system package: could not build it as Python package +# on the fly. +# +# gmake is to be used! +# +setuptools +setuptools-scm +build +swig +clang>=18,<19
--- a/setup.py Sat Oct 11 11:31:38 2025 +0200 +++ b/setup.py Sat Oct 11 15:24:40 2025 +0200 @@ -367,27 +367,63 @@ Returns `(sha, comment, diff, branch)`, all items are str or None if not available. + Also handles Mercurial repository information. + directory: Root of git checkout. ''' sha, comment, diff, branch = '', '', '', '' - cp = subprocess.run( - f'cd {directory} && (PAGER= git show --pretty=oneline|head -n 1 && git diff)', - capture_output=1, - shell=1, - text=1, - ) - if cp.returncode == 0: - sha, _ = cp.stdout.split(' ', 1) - comment, diff = _.split('\n', 1) - cp = subprocess.run( - f'cd {directory} && git rev-parse --abbrev-ref HEAD', - capture_output=1, - shell=1, - text=1, - ) - if cp.returncode == 0: - branch = cp.stdout.strip() + if os.path.isdir(os.path.join(directory, '.hg')): + cp = subprocess.run( + f'hg -R {directory} id --id', + capture_output=1, + shell=1, + text=1, + ) + if cp.returncode == 0: + sha = cp.stdout.strip() + cp = subprocess.run( + f'hg -R {directory} diff --git', + capture_output=1, + shell=1, + text=1, + ) + if cp.returncode == 0: + diff = cp.stdout + cp = subprocess.run( + f'hg -R {directory} log --rev . --template "{{branch}}"', + capture_output=1, + shell=1, + text=1, + ) + if cp.returncode == 0: + branch = cp.stdout.strip() + fp = subprocess.run( + f'hg -R {directory} log --rev . --template "{{desc|firstline}}"', + capture_output=1, + shell=1, + text=1, + ) + if cp.returncode == 0: + comment = cp.stdout.strip() + else: + cp = subprocess.run( + f'cd {directory} && (PAGER= git show --pretty=oneline|head -n 1 && git diff)', + capture_output=1, + shell=1, + text=1, + ) + if cp.returncode == 0: + sha, _ = cp.stdout.split(' ', 1) + comment, diff = _.split('\n', 1) + cp = subprocess.run( + f'cd {directory} && git rev-parse --abbrev-ref HEAD', + capture_output=1, + shell=1, + text=1, + ) + if cp.returncode == 0: + branch = cp.stdout.strip() log(f'git_info(): directory={directory!r} returning branch={branch!r} sha={sha!r} comment={comment!r}') return sha, comment, diff, branch @@ -740,7 +776,11 @@ version_p_tuple = tuple(int_or_0(i) for i in version_p.split('.')) log(f'{swig_version=}') text = '' - text += f'mupdf_location = {mupdf_location!r}\n' + if os.path.isdir(mupdf_location): + mupdf_location = mupdf_location.rstrip('/') + text += 'mupdf_location = ' + repr(os.path.basename(mupdf_location)) + '\n' + else: + text += f'mupdf_location = {mupdf_location!r}\n' text += f'pymupdf_version = {version_p!r}\n' text += f'pymupdf_version_tuple = {version_p_tuple!r}\n' text += f'pymupdf_git_sha = {sha!r}\n' @@ -918,6 +958,10 @@ log( f'Setting XCFLAGS and XCXXFLAGS to predefine TOFU_CJK_EXT.') env_add(env, 'XCFLAGS', '-DTOFU_CJK_EXT') env_add(env, 'XCXXFLAGS', '-DTOFU_CJK_EXT') + fortify = os.environ.get('FORTIFY', '0') + if fortify != '0': + env_add(env, 'XCFLAGS', f'-D_FORTIFY_SOURCE={fortify}') + env_add(env, 'XCXXFLAGS', f'-D_FORTIFY_SOURCE={fortify}') if openbsd or freebsd: env_add(env, 'CXX', 'c++', ' ') @@ -1143,6 +1187,9 @@ debug = 'debug' in mupdf_build_dir_flags r_extra = '' defines = list() + fortify = os.environ.get('FORTIFY', '0') + if fortify != '0': + defines.append(f'_FORTIFY_SOURCE={fortify}') if windows: defines.append('FZ_DLL_CLIENT') wp = pipcl.wdev.WindowsPython() @@ -1202,6 +1249,14 @@ if cxxflags: compiler_extra += f' {cxxflags}' + + if not darwin and (platform.system() != 'Windows'): + # *BSD and Linux + # Full RELRO + linker_extra += ' -Wl,-z,relro,-z,now' + # Strip + linker_extra += ' -Wl,-s' + return compiler_extra, linker_extra, includes, defines, optimise, debug, libpaths, libs, libraries, @@ -1254,6 +1309,7 @@ 'Operating System :: MacOS', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX :: Linux', + 'Operating System :: POSIX :: BSD :: FreeBSD', 'Programming Language :: C', 'Programming Language :: C++', 'Programming Language :: Python :: 3 :: Only', @@ -1267,7 +1323,7 @@ # # PyMuPDF version. -version_p = '1.26.5' +version_p = '1.26.5+XXXFIXME0' version_mupdf = '1.26.10' @@ -1347,6 +1403,12 @@ # We can't pip install pytest on pyodide, so specify it here. requires_dist.append('pytest') + # + # We need packaging because of extended version parsing (including local + # version specifiers. + # + requires_dist.append('packaging') + p = pipcl.Package( name, version,
--- a/src/__init__.py Sat Oct 11 11:31:38 2025 +0200 +++ b/src/__init__.py Sat Oct 11 15:24:40 2025 +0200 @@ -394,7 +394,7 @@ # Versions as tuples; useful when comparing versions. # -mupdf_version_tuple = tuple( [_int_rc(i) for i in mupdf_version.split('.')]) +mupdf_version_tuple = packaging.version.Version(mupdf_version).release assert mupdf_version_tuple == (mupdf.FZ_VERSION_MAJOR, mupdf.FZ_VERSION_MINOR, mupdf.FZ_VERSION_PATCH), \ f'Inconsistent MuPDF version numbers: {mupdf_version_tuple=} != {(mupdf.FZ_VERSION_MAJOR, mupdf.FZ_VERSION_MINOR, mupdf.FZ_VERSION_PATCH)=}'
