Mercurial > hgrepos > Python2 > PyMuPDF
comparison scripts/gh_release.py @ 1:1d09e1dec1d9 upstream
ADD: PyMuPDF v1.26.4: the original sdist.
It does not yet contain MuPDF. This normally will be downloaded when
building PyMuPDF.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 15 Sep 2025 11:37:51 +0200 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 1:1d09e1dec1d9 |
|---|---|
| 1 #! /usr/bin/env python3 | |
| 2 | |
| 3 ''' | |
| 4 Build+test script for PyMuPDF using cibuildwheel. Mostly for use with github | |
| 5 builds. | |
| 6 | |
| 7 We run cibuild manually, in order to build and test PyMuPDF wheels. | |
| 8 | |
| 9 As of 2024-10-08 we also support the old two wheel flavours that make up | |
| 10 PyMuPDF: | |
| 11 | |
| 12 PyMuPDFb | |
| 13 Not specific to particular versions of Python. Contains shared | |
| 14 libraries for the MuPDF C and C++ bindings. | |
| 15 PyMuPDF | |
| 16 Specific to particular versions of Python. Contains the rest of | |
| 17 the PyMuPDF implementation. | |
| 18 | |
| 19 Args: | |
| 20 build | |
| 21 Build using cibuildwheel. | |
| 22 build-devel | |
| 23 Build using cibuild with `--platform` set. | |
| 24 pip_install <prefix> | |
| 25 For internal use. Runs `pip install <prefix>-*<platform_tag>.whl`, | |
| 26 where `platform_tag` will be things like 'win32', 'win_amd64', | |
| 27 'x86_64`, depending on the python we are running on. | |
| 28 venv | |
| 29 Run with remaining args inside a venv. | |
| 30 test | |
| 31 Internal. | |
| 32 | |
| 33 We also look at specific items in the environment. This allows use with Github | |
| 34 action inputs, which can't be easily translated into command-line arguments. | |
| 35 | |
| 36 inputs_flavours | |
| 37 If '0' or unset, build complete PyMuPDF wheels. | |
| 38 If '1', build separate PyMuPDF and PyMuPDFb wheels. | |
| 39 inputs_sdist | |
| 40 inputs_skeleton | |
| 41 Build minimal wheel; for testing only. | |
| 42 inputs_wheels_cps: | |
| 43 Python versions to build for. E.g. 'cp39* cp313*'. | |
| 44 inputs_wheels_default | |
| 45 Default value for other inputs_wheels_* if unset. | |
| 46 inputs_wheels_linux_aarch64 | |
| 47 inputs_wheels_linux_auto | |
| 48 inputs_wheels_linux_pyodide | |
| 49 inputs_wheels_macos_arm64 | |
| 50 inputs_wheels_macos_auto | |
| 51 inputs_wheels_windows_auto | |
| 52 If '1' we build the relevant wheels. | |
| 53 inputs_PYMUPDF_SETUP_MUPDF_BUILD | |
| 54 Used to directly set PYMUPDF_SETUP_MUPDF_BUILD. | |
| 55 E.g. 'git:--recursive --depth 1 --shallow-submodules --branch master https://github.com/ArtifexSoftware/mupdf.git' | |
| 56 inputs_PYMUPDF_SETUP_MUPDF_BUILD_TYPE | |
| 57 Used to directly set PYMUPDF_SETUP_MUPDF_BUILD_TYPE. Note that as of | |
| 58 2024-09-10 .github/workflows/build_wheels.yml does not set this. | |
| 59 PYMUPDF_SETUP_PY_LIMITED_API | |
| 60 If not '0' we build a single wheel for all python versions using the | |
| 61 Python Limited API. | |
| 62 | |
| 63 Building for Pyodide | |
| 64 | |
| 65 If `inputs_wheels_linux_pyodide` is true and we are on Linux, we build a | |
| 66 Pyodide wheel, using scripts/test.py. | |
| 67 | |
| 68 Set up for use outside Github | |
| 69 | |
| 70 sudo apt install docker.io | |
| 71 sudo usermod -aG docker $USER | |
| 72 | |
| 73 Example usage: | |
| 74 | |
| 75 PYMUPDF_SETUP_MUPDF_BUILD=../mupdf py -3.9-32 PyMuPDF/scripts/gh_release.py venv build-devel | |
| 76 ''' | |
| 77 | |
| 78 import glob | |
| 79 import inspect | |
| 80 import os | |
| 81 import platform | |
| 82 import re | |
| 83 import shlex | |
| 84 import subprocess | |
| 85 import sys | |
| 86 import textwrap | |
| 87 | |
| 88 import test as test_py | |
| 89 | |
| 90 pymupdf_dir = os.path.abspath( f'{__file__}/../..') | |
| 91 | |
| 92 sys.path.insert(0, pymupdf_dir) | |
| 93 import pipcl | |
| 94 del sys.path[0] | |
| 95 | |
| 96 log = pipcl.log0 | |
| 97 run = pipcl.run | |
| 98 | |
| 99 | |
| 100 def main(): | |
| 101 | |
| 102 log( '### main():') | |
| 103 log(f'{platform.platform()=}') | |
| 104 log(f'{platform.python_version()=}') | |
| 105 log(f'{platform.architecture()=}') | |
| 106 log(f'{platform.machine()=}') | |
| 107 log(f'{platform.processor()=}') | |
| 108 log(f'{platform.release()=}') | |
| 109 log(f'{platform.system()=}') | |
| 110 log(f'{platform.version()=}') | |
| 111 log(f'{platform.uname()=}') | |
| 112 log(f'{sys.executable=}') | |
| 113 log(f'{sys.maxsize=}') | |
| 114 log(f'sys.argv ({len(sys.argv)}):') | |
| 115 for i, arg in enumerate(sys.argv): | |
| 116 log(f' {i}: {arg!r}') | |
| 117 log(f'os.environ ({len(os.environ)}):') | |
| 118 for k in sorted( os.environ.keys()): | |
| 119 v = os.environ[ k] | |
| 120 log( f' {k}: {v!r}') | |
| 121 | |
| 122 if test_py.github_workflow_unimportant(): | |
| 123 return | |
| 124 | |
| 125 valgrind = False | |
| 126 if len( sys.argv) == 1: | |
| 127 args = iter( ['build']) | |
| 128 else: | |
| 129 args = iter( sys.argv[1:]) | |
| 130 while 1: | |
| 131 try: | |
| 132 arg = next(args) | |
| 133 except StopIteration: | |
| 134 break | |
| 135 if arg == 'build': | |
| 136 build(valgrind=valgrind) | |
| 137 elif arg == 'build-devel': | |
| 138 if platform.system() == 'Linux': | |
| 139 p = 'linux' | |
| 140 elif platform.system() == 'Windows': | |
| 141 p = 'windows' | |
| 142 elif platform.system() == 'Darwin': | |
| 143 p = 'macos' | |
| 144 else: | |
| 145 assert 0, f'Unrecognised {platform.system()=}' | |
| 146 build(platform_=p) | |
| 147 elif arg == 'pip_install': | |
| 148 prefix = next(args) | |
| 149 d = os.path.dirname(prefix) | |
| 150 log( f'{prefix=}') | |
| 151 log( f'{d=}') | |
| 152 for leaf in os.listdir(d): | |
| 153 log( f' {d}/{leaf}') | |
| 154 pattern = f'{prefix}-*{platform_tag()}.whl' | |
| 155 paths = glob.glob( pattern) | |
| 156 log( f'{pattern=} {paths=}') | |
| 157 # Follow pipcl.py and look at AUDITWHEEL_PLAT. This allows us to | |
| 158 # cope if building for both musl and normal linux. | |
| 159 awp = os.environ.get('AUDITWHEEL_PLAT') | |
| 160 if awp: | |
| 161 paths = [i for i in paths if awp in i] | |
| 162 log(f'After selecting AUDITWHEEL_PLAT={awp!r}, {paths=}.') | |
| 163 paths = ' '.join( paths) | |
| 164 run( f'pip install {paths}') | |
| 165 elif arg == 'venv': | |
| 166 command = ['python', sys.argv[0]] | |
| 167 for arg in args: | |
| 168 command.append( arg) | |
| 169 venv( command, packages = 'cibuildwheel') | |
| 170 elif arg == 'test': | |
| 171 project = next(args) | |
| 172 package = next(args) | |
| 173 test( project, package, valgrind=valgrind) | |
| 174 elif arg == '--valgrind': | |
| 175 valgrind = int(next(args)) | |
| 176 else: | |
| 177 assert 0, f'Unrecognised {arg=}' | |
| 178 | |
| 179 | |
| 180 def build( platform_=None, valgrind=False): | |
| 181 log( '### build():') | |
| 182 | |
| 183 platform_arg = f' --platform {platform_}' if platform_ else '' | |
| 184 | |
| 185 # Parameters are in os.environ, as that seems to be the only way that | |
| 186 # Github workflow .yml files can encode them. | |
| 187 # | |
| 188 def get_bool(name, default=0): | |
| 189 v = os.environ.get(name) | |
| 190 if v in ('1', 'true'): | |
| 191 return 1 | |
| 192 elif v in ('0', 'false'): | |
| 193 return 0 | |
| 194 elif v is None: | |
| 195 return default | |
| 196 else: | |
| 197 assert 0, f'Bad environ {name=} {v=}' | |
| 198 inputs_flavours = get_bool('inputs_flavours', 1) | |
| 199 inputs_sdist = get_bool('inputs_sdist') | |
| 200 inputs_skeleton = os.environ.get('inputs_skeleton') | |
| 201 inputs_wheels_default = get_bool('inputs_wheels_default', 1) | |
| 202 inputs_wheels_linux_aarch64 = get_bool('inputs_wheels_linux_aarch64', inputs_wheels_default) | |
| 203 inputs_wheels_linux_auto = get_bool('inputs_wheels_linux_auto', inputs_wheels_default) | |
| 204 inputs_wheels_linux_pyodide = get_bool('inputs_wheels_linux_pyodide', 0) | |
| 205 inputs_wheels_macos_arm64 = get_bool('inputs_wheels_macos_arm64', 0) | |
| 206 inputs_wheels_macos_auto = get_bool('inputs_wheels_macos_auto', inputs_wheels_default) | |
| 207 inputs_wheels_windows_auto = get_bool('inputs_wheels_windows_auto', inputs_wheels_default) | |
| 208 inputs_wheels_cps = os.environ.get('inputs_wheels_cps') | |
| 209 inputs_PYMUPDF_SETUP_MUPDF_BUILD = os.environ.get('inputs_PYMUPDF_SETUP_MUPDF_BUILD') | |
| 210 inputs_PYMUPDF_SETUP_MUPDF_BUILD_TYPE = os.environ.get('inputs_PYMUPDF_SETUP_MUPDF_BUILD_TYPE') | |
| 211 | |
| 212 PYMUPDF_SETUP_PY_LIMITED_API = os.environ.get('PYMUPDF_SETUP_PY_LIMITED_API') | |
| 213 | |
| 214 log( f'{inputs_flavours=}') | |
| 215 log( f'{inputs_sdist=}') | |
| 216 log( f'{inputs_skeleton=}') | |
| 217 log( f'{inputs_wheels_default=}') | |
| 218 log( f'{inputs_wheels_linux_aarch64=}') | |
| 219 log( f'{inputs_wheels_linux_auto=}') | |
| 220 log( f'{inputs_wheels_linux_pyodide=}') | |
| 221 log( f'{inputs_wheels_macos_arm64=}') | |
| 222 log( f'{inputs_wheels_macos_auto=}') | |
| 223 log( f'{inputs_wheels_windows_auto=}') | |
| 224 log( f'{inputs_wheels_cps=}') | |
| 225 log( f'{inputs_PYMUPDF_SETUP_MUPDF_BUILD=}') | |
| 226 log( f'{inputs_PYMUPDF_SETUP_MUPDF_BUILD_TYPE=}') | |
| 227 log( f'{PYMUPDF_SETUP_PY_LIMITED_API=}') | |
| 228 | |
| 229 # Build Pyodide wheel if specified. | |
| 230 # | |
| 231 if platform.system() == 'Linux' and inputs_wheels_linux_pyodide: | |
| 232 # Pyodide wheels are built by running scripts/test.py, not | |
| 233 # cibuildwheel. | |
| 234 command = f'{sys.executable} scripts/test.py -P 1' | |
| 235 if inputs_PYMUPDF_SETUP_MUPDF_BUILD: | |
| 236 command += f' -m {shlex.quote(inputs_PYMUPDF_SETUP_MUPDF_BUILD)}' | |
| 237 command += ' pyodide_wheel' | |
| 238 run(command) | |
| 239 | |
| 240 # Build sdist(s). | |
| 241 # | |
| 242 if inputs_sdist: | |
| 243 if pymupdf_dir != os.path.abspath( os.getcwd()): | |
| 244 log( f'Changing dir to {pymupdf_dir=}') | |
| 245 os.chdir( pymupdf_dir) | |
| 246 # Create PyMuPDF sdist. | |
| 247 run(f'{sys.executable} setup.py sdist') | |
| 248 assert glob.glob('dist/pymupdf-*.tar.gz') | |
| 249 if inputs_flavours: | |
| 250 # Create PyMuPDFb sdist. | |
| 251 run( | |
| 252 f'{sys.executable} setup.py sdist', | |
| 253 env_extra=dict(PYMUPDF_SETUP_FLAVOUR='b'), | |
| 254 ) | |
| 255 assert glob.glob('dist/pymupdfb-*.tar.gz') | |
| 256 | |
| 257 # Build wheels. | |
| 258 # | |
| 259 if (0 | |
| 260 or inputs_wheels_linux_aarch64 | |
| 261 or inputs_wheels_linux_auto | |
| 262 or inputs_wheels_macos_arm64 | |
| 263 or inputs_wheels_macos_auto | |
| 264 or inputs_wheels_windows_auto | |
| 265 ): | |
| 266 env_extra = dict() | |
| 267 | |
| 268 def set_if_unset(name, value): | |
| 269 v = os.environ.get(name) | |
| 270 if v is None: | |
| 271 log( f'Setting environment {name=} to {value=}') | |
| 272 env_extra[ name] = value | |
| 273 else: | |
| 274 log( f'Not changing {name}={v!r} to {value!r}') | |
| 275 set_if_unset( 'CIBW_BUILD_VERBOSITY', '1') | |
| 276 # We exclude pp* because of `fitz_wrap.obj : error LNK2001: unresolved | |
| 277 # external symbol PyUnicode_DecodeRawUnicodeEscape`. | |
| 278 # 2024-06-05: musllinux on aarch64 fails because libclang cannot find | |
| 279 # libclang.so. | |
| 280 # | |
| 281 # Note that we had to disable cp313-win32 when 3.13 was experimental | |
| 282 # because there was no 64-bit Python-3.13 available via `py | |
| 283 # -3.13`. (Win32 builds need to use win64 Python because win32 | |
| 284 # libclang is broken.) | |
| 285 # | |
| 286 set_if_unset( 'CIBW_SKIP', 'pp* *i686 cp36* cp37* *musllinux*aarch64*') | |
| 287 | |
| 288 def make_string(*items): | |
| 289 ret = list() | |
| 290 for item in items: | |
| 291 if item: | |
| 292 ret.append(item) | |
| 293 return ' '.join(ret) | |
| 294 | |
| 295 cps = inputs_wheels_cps if inputs_wheels_cps else 'cp39* cp310* cp311* cp312* cp313*' | |
| 296 set_if_unset( 'CIBW_BUILD', cps) | |
| 297 for cp in cps.split(): | |
| 298 m = re.match('cp([0-9]+)[*]', cp) | |
| 299 assert m, f'{cps=} {cp=}' | |
| 300 v = int(m.group(1)) | |
| 301 if v == 314: | |
| 302 # Need to set CIBW_PRERELEASE_PYTHONS, otherwise cibuildwheel | |
| 303 # will refuse. | |
| 304 log(f'Setting CIBW_PRERELEASE_PYTHONS for Python version {cp=}.') | |
| 305 set_if_unset( 'CIBW_PRERELEASE_PYTHONS', '1') | |
| 306 | |
| 307 if platform.system() == 'Linux': | |
| 308 set_if_unset( | |
| 309 'CIBW_ARCHS_LINUX', | |
| 310 make_string( | |
| 311 'auto64' * inputs_wheels_linux_auto, | |
| 312 'aarch64' * inputs_wheels_linux_aarch64, | |
| 313 ), | |
| 314 ) | |
| 315 if env_extra.get('CIBW_ARCHS_LINUX') == '': | |
| 316 log(f'Not running cibuildwheel because CIBW_ARCHS_LINUX is empty string.') | |
| 317 return | |
| 318 | |
| 319 if platform.system() == 'Windows': | |
| 320 set_if_unset( | |
| 321 'CIBW_ARCHS_WINDOWS', | |
| 322 make_string( | |
| 323 'auto' * inputs_wheels_windows_auto, | |
| 324 ), | |
| 325 ) | |
| 326 if env_extra.get('CIBW_ARCHS_WINDOWS') == '': | |
| 327 log(f'Not running cibuildwheel because CIBW_ARCHS_WINDOWS is empty string.') | |
| 328 return | |
| 329 | |
| 330 if platform.system() == 'Darwin': | |
| 331 set_if_unset( | |
| 332 'CIBW_ARCHS_MACOS', | |
| 333 make_string( | |
| 334 'auto' * inputs_wheels_macos_auto, | |
| 335 'arm64' * inputs_wheels_macos_arm64, | |
| 336 ), | |
| 337 ) | |
| 338 if env_extra.get('CIBW_ARCHS_MACOS') == '': | |
| 339 log(f'Not running cibuildwheel because CIBW_ARCHS_MACOS is empty string.') | |
| 340 return | |
| 341 | |
| 342 def env_pass(name): | |
| 343 ''' | |
| 344 Adds `name` to CIBW_ENVIRONMENT_PASS_LINUX if required to be available | |
| 345 when building wheel with cibuildwheel. | |
| 346 ''' | |
| 347 if platform.system() == 'Linux': | |
| 348 v = env_extra.get('CIBW_ENVIRONMENT_PASS_LINUX', '') | |
| 349 if v: | |
| 350 v += ' ' | |
| 351 v += name | |
| 352 env_extra['CIBW_ENVIRONMENT_PASS_LINUX'] = v | |
| 353 | |
| 354 def env_set(name, value, pass_=False): | |
| 355 assert isinstance( value, str) | |
| 356 if not name.startswith('CIBW'): | |
| 357 assert pass_, f'Non-CIBW* name requires `pass_` to be true. {name=} {value=}.' | |
| 358 env_extra[ name] = value | |
| 359 if pass_: | |
| 360 env_pass(name) | |
| 361 | |
| 362 env_pass('PYMUPDF_SETUP_PY_LIMITED_API') | |
| 363 | |
| 364 if os.environ.get('PYMUPDF_SETUP_LIBCLANG'): | |
| 365 env_pass('PYMUPDF_SETUP_LIBCLANG') | |
| 366 | |
| 367 if inputs_skeleton: | |
| 368 env_set('PYMUPDF_SETUP_SKELETON', inputs_skeleton, pass_=1) | |
| 369 | |
| 370 if inputs_PYMUPDF_SETUP_MUPDF_BUILD not in ('-', None): | |
| 371 log(f'Setting PYMUPDF_SETUP_MUPDF_BUILD to {inputs_PYMUPDF_SETUP_MUPDF_BUILD!r}.') | |
| 372 env_set('PYMUPDF_SETUP_MUPDF_BUILD', inputs_PYMUPDF_SETUP_MUPDF_BUILD, pass_=True) | |
| 373 env_set('PYMUPDF_SETUP_MUPDF_TGZ', '', pass_=True) # Don't put mupdf in sdist. | |
| 374 | |
| 375 if inputs_PYMUPDF_SETUP_MUPDF_BUILD_TYPE not in ('-', None): | |
| 376 log(f'Setting PYMUPDF_SETUP_MUPDF_BUILD_TYPE to {inputs_PYMUPDF_SETUP_MUPDF_BUILD_TYPE!r}.') | |
| 377 env_set('PYMUPDF_SETUP_MUPDF_BUILD_TYPE', inputs_PYMUPDF_SETUP_MUPDF_BUILD_TYPE, pass_=True) | |
| 378 | |
| 379 def set_cibuild_test(): | |
| 380 log( f'set_cibuild_test(): {inputs_skeleton=}') | |
| 381 valgrind_text = '' | |
| 382 if valgrind: | |
| 383 valgrind_text = ' --valgrind 1' | |
| 384 env_set('CIBW_TEST_COMMAND', f'python {{project}}/scripts/gh_release.py{valgrind_text} test {{project}} {{package}}') | |
| 385 | |
| 386 if pymupdf_dir != os.path.abspath( os.getcwd()): | |
| 387 log( f'Changing dir to {pymupdf_dir=}') | |
| 388 os.chdir( pymupdf_dir) | |
| 389 | |
| 390 run('pip install cibuildwheel') | |
| 391 | |
| 392 # We include MuPDF build-time files. | |
| 393 flavour_d = True | |
| 394 | |
| 395 if PYMUPDF_SETUP_PY_LIMITED_API != '0': | |
| 396 # Build one wheel with oldest python, then fake build with other python | |
| 397 # versions so we test everything. | |
| 398 log(f'{PYMUPDF_SETUP_PY_LIMITED_API=}') | |
| 399 env_pass('PYMUPDF_SETUP_PY_LIMITED_API') | |
| 400 CIBW_BUILD_old = env_extra.get('CIBW_BUILD') | |
| 401 assert CIBW_BUILD_old is not None | |
| 402 cp = cps.split()[0] | |
| 403 env_set('CIBW_BUILD', cp) | |
| 404 log(f'Building single wheel.') | |
| 405 run( f'cibuildwheel{platform_arg}', env_extra=env_extra) | |
| 406 | |
| 407 # Fake-build with all python versions, using the wheel we have | |
| 408 # just created. This works by setting PYMUPDF_SETUP_URL_WHEEL | |
| 409 # which makes PyMuPDF's setup.py copy an existing wheel instead | |
| 410 # of building a wheel itself; it also copes with existing | |
| 411 # wheels having extra platform tags (from cibuildwheel's use of | |
| 412 # auditwheel). | |
| 413 # | |
| 414 env_set('PYMUPDF_SETUP_URL_WHEEL', f'file://wheelhouse/', pass_=True) | |
| 415 | |
| 416 set_cibuild_test() | |
| 417 env_set('CIBW_BUILD', CIBW_BUILD_old) | |
| 418 | |
| 419 # Disable cibuildwheels use of auditwheel. The wheel was repaired | |
| 420 # when it was created above so we don't need to do so again. This | |
| 421 # also avoids problems with musl wheels on a Linux glibc host where | |
| 422 # auditwheel fails with: `ValueError: Cannot repair wheel, because | |
| 423 # required library "libgcc_s-a3a07607.so.1" could not be located`. | |
| 424 # | |
| 425 env_set('CIBW_REPAIR_WHEEL_COMMAND', '') | |
| 426 | |
| 427 if platform.system() == 'Linux' and env_extra.get('CIBW_ARCHS_LINUX') == 'aarch64': | |
| 428 log(f'Testing all Python versions on linux-aarch64 is too slow and is killed by github after 6h.') | |
| 429 log(f'Testing on restricted python versions using wheels in wheelhouse/.') | |
| 430 # Testing only on first and last python versions. | |
| 431 cp1 = cps.split()[0] | |
| 432 cp2 = cps.split()[-1] | |
| 433 cp = cp1 if cp1 == cp2 else f'{cp1} {cp2}' | |
| 434 env_set('CIBW_BUILD', cp) | |
| 435 else: | |
| 436 log(f'Testing on all python versions using wheels in wheelhouse/.') | |
| 437 run( f'cibuildwheel{platform_arg}', env_extra=env_extra) | |
| 438 | |
| 439 elif inputs_flavours: | |
| 440 # Build and test PyMuPDF and PyMuPDFb wheels. | |
| 441 # | |
| 442 | |
| 443 # First build PyMuPDFb wheel. cibuildwheel will build a single wheel | |
| 444 # here, which will work with any python version on current OS. | |
| 445 # | |
| 446 flavour = 'b' | |
| 447 if flavour_d: | |
| 448 # Include MuPDF build-time files. | |
| 449 flavour += 'd' | |
| 450 env_set( 'PYMUPDF_SETUP_FLAVOUR', flavour, pass_=1) | |
| 451 run( f'cibuildwheel{platform_arg}', env_extra=env_extra) | |
| 452 run( 'echo after {flavour=}') | |
| 453 run( 'ls -l wheelhouse') | |
| 454 | |
| 455 # Now set environment to build PyMuPDF wheels. cibuildwheel will build | |
| 456 # one for each Python version. | |
| 457 # | |
| 458 | |
| 459 # Tell cibuildwheel not to use `auditwheel`, because it cannot cope | |
| 460 # with us deliberately putting required libraries into a different | |
| 461 # wheel. | |
| 462 # | |
| 463 # Also, `auditwheel addtag` says `No tags to be added` and terminates | |
| 464 # with non-zero. See: https://github.com/pypa/auditwheel/issues/439. | |
| 465 # | |
| 466 env_set('CIBW_REPAIR_WHEEL_COMMAND_LINUX', '') | |
| 467 env_set('CIBW_REPAIR_WHEEL_COMMAND_MACOS', '') | |
| 468 | |
| 469 # We tell cibuildwheel to test these wheels, but also set | |
| 470 # CIBW_BEFORE_TEST to make it first run ourselves with the | |
| 471 # `pip_install` arg to install the PyMuPDFb wheel. Otherwise | |
| 472 # installation of PyMuPDF would fail because it lists the | |
| 473 # PyMuPDFb wheel as a prerequisite. We need to use `pip_install` | |
| 474 # because wildcards do not work on Windows, and we want to be | |
| 475 # careful to avoid incompatible wheels, e.g. 32 vs 64-bit wheels | |
| 476 # coexist during Windows builds. | |
| 477 # | |
| 478 env_set('CIBW_BEFORE_TEST', f'python scripts/gh_release.py pip_install wheelhouse/pymupdfb') | |
| 479 | |
| 480 set_cibuild_test() | |
| 481 | |
| 482 # Build main PyMuPDF wheel. | |
| 483 flavour = 'p' | |
| 484 env_set( 'PYMUPDF_SETUP_FLAVOUR', flavour, pass_=1) | |
| 485 run( f'cibuildwheel{platform_arg}', env_extra=env_extra) | |
| 486 | |
| 487 else: | |
| 488 # Build and test wheels which contain everything. | |
| 489 # | |
| 490 flavour = 'pb' | |
| 491 if flavour_d: | |
| 492 flavour += 'd' | |
| 493 set_cibuild_test() | |
| 494 env_set( 'PYMUPDF_SETUP_FLAVOUR', flavour, pass_=1) | |
| 495 | |
| 496 run( f'cibuildwheel{platform_arg}', env_extra=env_extra) | |
| 497 | |
| 498 run( 'ls -lt wheelhouse') | |
| 499 | |
| 500 | |
| 501 def cpu_bits(): | |
| 502 return 32 if sys.maxsize == 2**31 - 1 else 64 | |
| 503 | |
| 504 | |
| 505 # Name of venv used by `venv()`. | |
| 506 # | |
| 507 venv_name = f'venv-pymupdf-{platform.python_version()}-{cpu_bits()}' | |
| 508 | |
| 509 def venv( command=None, packages=None, quick=False, system_site_packages=False): | |
| 510 ''' | |
| 511 Runs remaining args, or the specified command if present, in a venv. | |
| 512 | |
| 513 command: | |
| 514 Command as string or list of args. Should usually start with 'python' | |
| 515 to run the venv's python. | |
| 516 packages: | |
| 517 List of packages (or comma-separated string) to install. | |
| 518 quick: | |
| 519 If true and venv directory already exists, we don't recreate venv or | |
| 520 install Python packages in it. | |
| 521 ''' | |
| 522 command2 = '' | |
| 523 if platform.system() == 'OpenBSD': | |
| 524 # libclang not available from pypi.org, but system py3-llvm package | |
| 525 # works. `pip install` should be run with --no-build-isolation and | |
| 526 # explicit `pip install swig psutil`. | |
| 527 system_site_packages = True | |
| 528 #ssp = ' --system-site-packages' | |
| 529 log(f'OpenBSD: libclang not available from pypi.org.') | |
| 530 log(f'OpenBSD: system package `py3-llvm` must be installed.') | |
| 531 log(f'OpenBSD: creating venv with --system-site-packages.') | |
| 532 log(f'OpenBSD: `pip install .../PyMuPDF` must be preceded by install of swig etc.') | |
| 533 ssp = ' --system-site-packages' if system_site_packages else '' | |
| 534 if quick and os.path.isdir(venv_name): | |
| 535 log(f'{quick=}: Not creating venv because directory already exists: {venv_name}') | |
| 536 command2 += 'true' | |
| 537 else: | |
| 538 quick = False | |
| 539 command2 += f'{sys.executable} -m venv{ssp} {venv_name}' | |
| 540 if platform.system() == 'Windows': | |
| 541 command2 += f' && {venv_name}\\Scripts\\activate' | |
| 542 else: | |
| 543 command2 += f' && . {venv_name}/bin/activate' | |
| 544 if quick: | |
| 545 log(f'{quick=}: Not upgrading pip or installing packages.') | |
| 546 else: | |
| 547 command2 += ' && python -m pip install --upgrade pip' | |
| 548 if packages: | |
| 549 if isinstance(packages, str): | |
| 550 packages = packages.split(',') | |
| 551 command2 += ' && pip install ' + ' '.join(packages) | |
| 552 command2 += ' &&' | |
| 553 if isinstance( command, str): | |
| 554 command2 += ' ' + command | |
| 555 else: | |
| 556 for arg in command: | |
| 557 command2 += ' ' + shlex.quote(arg) | |
| 558 | |
| 559 run( command2) | |
| 560 | |
| 561 | |
| 562 def test( project, package, valgrind): | |
| 563 | |
| 564 run(f'pip install {test_packages}') | |
| 565 if valgrind: | |
| 566 log('Installing valgrind.') | |
| 567 run(f'sudo apt update') | |
| 568 run(f'sudo apt install valgrind') | |
| 569 run(f'valgrind --version') | |
| 570 | |
| 571 log('Running PyMuPDF tests under valgrind.') | |
| 572 # We ignore memory leaks. | |
| 573 run( | |
| 574 f'{sys.executable} {project}/tests/run_compound.py' | |
| 575 f' valgrind --suppressions={project}/valgrind.supp --error-exitcode=100 --errors-for-leak-kinds=none --fullpath-after=' | |
| 576 f' pytest {project}/tests' | |
| 577 , | |
| 578 env_extra=dict( | |
| 579 PYTHONMALLOC='malloc', | |
| 580 PYMUPDF_RUNNING_ON_VALGRIND='1', | |
| 581 ), | |
| 582 ) | |
| 583 else: | |
| 584 run(f'{sys.executable} {project}/tests/run_compound.py pytest {project}/tests') | |
| 585 | |
| 586 | |
| 587 if platform.system() == 'Windows': | |
| 588 def relpath(path, start=None): | |
| 589 try: | |
| 590 return os.path.relpath(path, start) | |
| 591 except ValueError: | |
| 592 # os.path.relpath() fails if trying to change drives. | |
| 593 return os.path.abspath(path) | |
| 594 else: | |
| 595 def relpath(path, start=None): | |
| 596 return os.path.relpath(path, start) | |
| 597 | |
| 598 | |
| 599 def platform_tag(): | |
| 600 bits = cpu_bits() | |
| 601 if platform.system() == 'Windows': | |
| 602 return 'win32' if bits==32 else 'win_amd64' | |
| 603 elif platform.system() in ('Linux', 'Darwin'): | |
| 604 assert bits == 64 | |
| 605 return platform.machine() | |
| 606 #return 'x86_64' | |
| 607 else: | |
| 608 assert 0, f'Unrecognised: {platform.system()=}' | |
| 609 | |
| 610 | |
| 611 test_packages = 'pytest fontTools pymupdf-fonts flake8 pylint codespell' | |
| 612 if platform.system() == 'Windows' and cpu_bits() == 32: | |
| 613 # No pillow wheel available, and doesn't build easily. | |
| 614 pass | |
| 615 else: | |
| 616 test_packages += ' pillow' | |
| 617 if platform.system().startswith('MSYS_NT-'): | |
| 618 # psutil not available on msys2. | |
| 619 pass | |
| 620 else: | |
| 621 test_packages += ' psutil' | |
| 622 | |
| 623 | |
| 624 if __name__ == '__main__': | |
| 625 main() |
