Skip to content

Commit 8718806

Browse files
scripts/test.py: added --cibw-test-project.
Creates minimal project useful when testing cibw with pyodide.
1 parent 60fd7f4 commit 8718806

1 file changed

Lines changed: 176 additions & 15 deletions

File tree

scripts/test.py

Lines changed: 176 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
Examples:
66
7-
./PyMuPDF/scripts/test.py --m mupdf build test
7+
./PyMuPDF/scripts/test.py -m mupdf build test
88
Build and test with pre-existing local mupdf/ checkout.
99
1010
./PyMuPDF/scripts/test.py build test
@@ -13,10 +13,13 @@
1313
./PyMuPDF/scripts/test.py -m 'git:https://git.ghostscript.com/mupdf.git' build test
1414
Build and test with internal checkout of MuPDF master.
1515
16-
./PyMuPDF/scripts/test.py -m 'git:--branch 1.26.x https://github.com/ArtifexSoftware/mupdf.git' build test
16+
./PyMuPDF/scripts/test.py -m ':1.26.x' build test
1717
Build and test using internal checkout of mupdf 1.26.x branch from
1818
Github.
1919
20+
./PyMuPDF/scripts/test.py install test -i 1.26.3 -k test_2596
21+
Install pymupdf-1.26.3 from pupi.org and test only test_2596.
22+
2023
Usage:
2124
2225
* Command line arguments are called parameters if they start with `-`,
@@ -31,7 +34,7 @@
3134
Other:
3235
3336
* If we are not already running inside a Python venv, we automatically create a
34-
venv and re-run ourselves inside it.
37+
venv and re-run ourselves inside it (also see the -v option).
3538
* Build/wheel/install commands always install into the venv.
3639
* Tests use whatever PyMuPDF/MuPDF is currently installed in the venv.
3740
* We run tests with pytest.
@@ -55,6 +58,7 @@
5558
`setup.py`.]
5659
5760
--build-flavour <build_flavour>
61+
[Obsolete.]
5862
Combination of 'p', 'b', 'd'. See ../setup.py's description of
5963
PYMUPDF_SETUP_FLAVOUR. Default is 'pbd', i.e. self-contained PyMuPDF
6064
wheels including MuPDF build-time files.
@@ -71,10 +75,11 @@
7175
--cibw-name <cibw_name>
7276
Name to use when installing cibuildwheel, e.g.:
7377
--cibw-name cibuildwheel==3.0.0b1
78+
--cibw-name git+https://github.com/pypa/cibuildwheel
7479
Default is `cibuildwheel`, i.e. the current release.
7580
7681
--cibw-pyodide 0|1
77-
Experimental, make `cibuild` command build a pyodide wheel.
82+
Experimental, make `cibw` command build a pyodide wheel.
7883
2025-05-27: this fails when building mupdf C API - `ld -r -b binary
7984
...` fails with:
8085
emcc: error: binary: No such file or directory ("binary" was expected to be an input file, based on the commandline arguments provided)
@@ -90,6 +95,47 @@
9095
--cibw-release-2
9196
Set up so that `cibw` builds only linux-aarch64 wheel.
9297
98+
--cibw-test-project 0|1
99+
If 1, command `cibw` will use a minimal test project instead of the
100+
PyMuPDF directory itself.
101+
102+
The test project uses setjmp/longjmp and C++ throw/catch.
103+
104+
The test checks for current behaviour, so with `--cibw-pyodide 1` it
105+
succeeds if the cibw command fails with the expected error message.
106+
107+
2025-08-22:
108+
Builds ok on Linux.
109+
110+
Fails at runtime with --cibw-pyodide 1:
111+
112+
With compile/link flags ``:
113+
(+45.0s): remote.py:233:main: jules-devuan: Pyodide has suffered a fatal error. Please report this to the Pyodide maintainers.
114+
(+45.1s): remote.py:233:main: jules-devuan: Stack (most recent call first):
115+
(+45.1s): remote.py:233:main: jules-devuan: File "/tmp/cibw-run-h_pfo0wf/cp312-pyodide_wasm32/venv-test/lib/python3.12/site-packages/foo/__init__.py", line 63 in bar
116+
(+45.1s): remote.py:233:main: jules-devuan: File "<string>The cause of the fatal error was:
117+
(+45.1s): remote.py:233:main: jules-devuan: CppException std::runtime_error: deliberate exception
118+
(+45.1s): remote.py:233:main: jules-devuan: at convertCppException (/home/jules/.cache/cibuildwheel/pyodide-build-0.30.7/0.27.7/xbuildenv/pyodide-root/dist/pyodide.asm.js:10:48959)
119+
(+45.1s): remote.py:233:main: jules-devuan: at API.fatal_error (/home/jules/.cache/cibuildwheel/pyodide-build-0.30.7/0.27.7/xbuildenv/pyodide-root/dist/pyodide.asm.js:10:49253)
120+
(+45.1s): remote.py:233:main: jules-devuan: at main (file:///home/jules/.cache/cibuildwheel/pyodide-build-0.30.7/0.27.7/xbuildenv/pyodide-root/dist/python_cli_entry.mjs:149:13) {
121+
(+45.1s): remote.py:233:main: jules-devuan: ty: 'std::runtime_error',
122+
(+45.1s): remote.py:233:main: jules-devuan: pyodide_fatal_error: true
123+
(+45.1s): remote.py:233:main: jules-devuan: }
124+
(+45.1s): remote.py:233:main: jules-devuan: ", line 1 in <module>
125+
(+45.1s): remote.py:233:main: jules-devuan: CppException std::runtime_error: deliberate exception
126+
(+45.1s): remote.py:233:main: jules-devuan: at convertCppException (/home/jules/.cache/cibuildwheel/pyodide-build-0.30.7/0.27.7/xbuildenv/pyodide-root/dist/pyodide.asm.js:10:48959)
127+
(+45.1s): remote.py:233:main: jules-devuan: at API.fatal_error (/home/jules/.cache/cibuildwheel/pyodide-build-0.30.7/0.27.7/xbuildenv/pyodide-root/dist/pyodide.asm.js:10:49253)
128+
(+45.1s): remote.py:233:main: jules-devuan: at main (file:///home/jules/.cache/cibuildwheel/pyodide-build-0.30.7/0.27.7/xbuildenv/pyodide-root/dist/python_cli_entry.mjs:149:13) {
129+
(+45.1s): remote.py:233:main: jules-devuan: ty: 'std::runtime_error',
130+
(+45.1s): remote.py:233:main: jules-devuan: pyodide_fatal_error: true
131+
(+45.1s): remote.py:233:main: jules-devuan: }
132+
133+
With compile/link flags `-fwasm-exceptions`:
134+
[LinkError: WebAssembly.instantiate(): Import #60 module="env" function="__c_longjmp": tag import requires a WebAssembly.Tag]
135+
136+
With compile/link flags `-fwasm-exceptions -sSUPPORT_LONGJMP=wasm`:
137+
[LinkError: WebAssembly.instantiate(): Import #60 module="env" function="__c_longjmp": tag import requires a WebAssembly.Tag]
138+
93139
-d
94140
Equivalent to `-b debug`.
95141
@@ -341,6 +387,7 @@ def main(argv):
341387
cibw_name = None
342388
cibw_pyodide = None
343389
cibw_pyodide_version = None
390+
cibw_test_project = None
344391
commands = list()
345392
env_extra = dict()
346393
graal = False
@@ -424,7 +471,10 @@ def main(argv):
424471
cibw_name = next(args)
425472

426473
elif arg == '--cibw-pyodide':
427-
cibw_pyodide = next(args)
474+
cibw_pyodide = int(next(args))
475+
476+
elif arg == '--cibw-test-project':
477+
cibw_test_project = int(next(args))
428478

429479
elif arg == '-d':
430480
env_extra['PYMUPDF_SETUP_MUPDF_BUILD_TYPE'] = 'debug'
@@ -622,19 +672,13 @@ def main(argv):
622672

623673
elif command == 'cibw':
624674
# Build wheel(s) with cibuildwheel.
625-
if cibw_pyodide and env_extra.get('CIBW_BUILD') is None:
626-
assert 0, f'Need a Python version for Pyodide.'
627-
CIBW_BUILD = 'cp312*'
628-
env_extra['CIBW_BUILD'] = CIBW_BUILD
629-
log(f'Defaulting to {CIBW_BUILD=} for Pyodide.')
630-
#if cibw_pyodide_version == None:
631-
# cibw_pyodide_version = '0.28.0'
632675
cibuildwheel(
633676
env_extra,
634677
cibw_name or 'cibuildwheel',
635678
cibw_pyodide,
636679
cibw_pyodide_version,
637680
cibw_sdist,
681+
cibw_test_project,
638682
)
639683

640684
elif command == 'install':
@@ -775,7 +819,14 @@ def build(
775819
run(f'pip install{build_isolation_text} -v --force-reinstall {pymupdf_dir_abs}', env_extra=env_extra)
776820

777821

778-
def cibuildwheel(env_extra, cibw_name, cibw_pyodide, cibw_pyodide_version, cibw_sdist):
822+
def cibuildwheel(
823+
env_extra,
824+
cibw_name,
825+
cibw_pyodide,
826+
cibw_pyodide_version,
827+
cibw_sdist,
828+
cibw_test_project,
829+
):
779830

780831
if cibw_sdist and platform.system() == 'Linux':
781832
log(f'Building sdist.')
@@ -823,14 +874,17 @@ def cibuildwheel(env_extra, cibw_name, cibw_pyodide, cibw_pyodide_version, cibw_
823874
CIBW_BUILD = env_extra.get('CIBW_BUILD')
824875
log(f'{CIBW_BUILD=}')
825876
if CIBW_BUILD is None:
826-
if os.environ.get('GITHUB_ACTIONS') == 'true':
877+
if cibw_pyodide:
878+
CIBW_BUILD = 'cp312*'
879+
elif os.environ.get('GITHUB_ACTIONS') == 'true':
827880
# Build/test all supported Python versions.
828881
CIBW_BUILD = 'cp39* cp310* cp311* cp312* cp313*'
829882
else:
830883
# Build/test current Python only.
831884
v = platform.python_version_tuple()[:2]
832885
log(f'{v=}')
833886
CIBW_BUILD = f'cp{"".join(v)}*'
887+
log(f'Defaulting to {CIBW_BUILD=}.')
834888

835889
cibw_pyodide_args = ''
836890
if cibw_pyodide:
@@ -847,7 +901,11 @@ def cibuildwheel(env_extra, cibw_name, cibw_pyodide, cibw_pyodide_version, cibw_
847901
# docker. Note that this will miss any settings in the original
848902
# environment.
849903
env_extra['CIBW_ENVIRONMENT_PASS_LINUX'] = ' '.join(sorted(env_extra.keys()))
850-
904+
905+
if cibw_test_project:
906+
cibw_do_test_project(env_extra, CIBW_BUILD, cibw_pyodide, cibw_pyodide_args)
907+
return
908+
851909
# Build for lowest (assumed first) Python version.
852910
#
853911
CIBW_BUILD_0 = CIBW_BUILD.split()[0]
@@ -862,6 +920,109 @@ def cibuildwheel(env_extra, cibw_name, cibw_pyodide, cibw_pyodide_version, cibw_
862920
env_extra['CIBW_BUILD'] = CIBW_BUILD
863921
run(f'cd {pymupdf_dir} && cibuildwheel{cibw_pyodide_args}', env_extra=env_extra)
864922
run(f'ls -ld {pymupdf_dir}/wheelhouse/*')
923+
924+
925+
def cibw_do_test_project(env_extra, CIBW_BUILD, cibw_pyodide, cibw_pyodide_args):
926+
testdir = f'{pymupdf_dir_abs}/cibw_test'
927+
shutil.rmtree(testdir, ignore_errors=1)
928+
os.mkdir(testdir)
929+
with open(f'{testdir}/setup.py', 'w') as f:
930+
f.write(textwrap.dedent(f'''
931+
import shutil
932+
import sys
933+
import pipcl
934+
935+
def build():
936+
so_leaf = pipcl.build_extension(
937+
name = 'foo',
938+
path_i = 'foo.i',
939+
outdir = 'build',
940+
compiler_extra = {'-fwasm-exceptions -sSUPPORT_LONGJMP=wasm' if cibw_pyodide else ''!r},
941+
linker_extra = {'-fwasm-exceptions -sSUPPORT_LONGJMP=wasm' if cibw_pyodide else ''!r},
942+
)
943+
return [
944+
('build/foo.py', 'foo/__init__.py'),
945+
(f'build/{{so_leaf}}', f'foo/'),
946+
]
947+
948+
p = pipcl.Package(
949+
name = 'foo',
950+
version = '1.2.3',
951+
fn_build = build,
952+
)
953+
954+
build_wheel = p.build_wheel
955+
build_sdist = p.build_sdist
956+
# Handle old-style setup.py command-line usage:
957+
if __name__ == '__main__':
958+
p.handle_argv(sys.argv)
959+
'''))
960+
with open(f'{testdir}/foo.i', 'w') as f:
961+
f.write(textwrap.dedent('''
962+
%{
963+
#include <setjmp.h>
964+
#include <stdio.h>
965+
#include <string.h>
966+
#include <stdexcept>
967+
968+
static sigjmp_buf jmpbuf;
969+
static int bar0(const char* text)
970+
{
971+
printf("bar(): text: %s\\\\n", text);
972+
int len = (int) strlen(text);
973+
printf("bar(): len=%i\\\\n", len);
974+
//printf("calling longjmp().\\n");
975+
//longjmp(jmpbuf, 1);
976+
fflush(stdout);
977+
return len;
978+
}
979+
int bar1(const char* text)
980+
{
981+
int ret = 0;
982+
/*if (setjmp(jmpbuf) == 0)
983+
{
984+
ret = bar0(text);
985+
}
986+
else
987+
{
988+
printf("setjmp() returned non-zero.\\n");
989+
throw std::runtime_error("deliberate exception");
990+
}*/
991+
throw std::runtime_error("deliberate exception");
992+
return ret;
993+
}
994+
int bar(const char* text)
995+
{
996+
int ret = 0;
997+
try
998+
{
999+
ret = bar1(text);
1000+
}
1001+
catch(std::exception& e)
1002+
{
1003+
printf("Received exception: %s\\n", e.what());
1004+
}
1005+
return ret;
1006+
}
1007+
%}
1008+
int bar(const char* text);
1009+
'''))
1010+
shutil.copy2(f'{pymupdf_dir_abs}/pipcl.py', f'{testdir}/pipcl.py')
1011+
shutil.copy2(f'{pymupdf_dir_abs}/wdev.py', f'{testdir}/wdev.py')
1012+
1013+
env_extra['CIBW_BUILD'] = CIBW_BUILD
1014+
env_extra['CIBW_TEST_COMMAND'] = f'python -c "import foo; foo.bar(\\"some text\\")"'
1015+
1016+
if cibw_pyodide:
1017+
# Expect failure.
1018+
e, text = run(f'cd {testdir} && cibuildwheel{cibw_pyodide_args}', env_extra=env_extra, capture=1, check=0)
1019+
log(f'{e=}')
1020+
log(f'text is:\n{textwrap.indent(text, " ")}')
1021+
assert e
1022+
assert 'module="env" function="__c_longjmp": tag import requires a WebAssembly.Tag' in text
1023+
else:
1024+
run(f'cd {testdir} && cibuildwheel{cibw_pyodide_args}', env_extra=env_extra)
1025+
run(f'ls -ld {testdir}/wheelhouse/*')
8651026

8661027

8671028
def build_pyodide_wheel(pyodide_build_version=None):

0 commit comments

Comments
 (0)