|
| 1 | +import importlib |
1 | 2 | import os
|
| 3 | +import pathlib |
2 | 4 | import pytest
|
3 | 5 | import shutil
|
4 | 6 | import subprocess
|
5 | 7 | import sys
|
| 8 | +import textwrap |
6 | 9 | import warnings
|
7 | 10 |
|
8 | 11 | import numpy as np
|
|
39 | 42 | # other fixes in the 0.29 series that are needed even for earlier
|
40 | 43 | # Python versions.
|
41 | 44 | # Note: keep in sync with the one in pyproject.toml
|
42 |
| - required_version = '0.29.30' |
| 45 | + required_version = '0.29.35' |
43 | 46 | if _pep440.parse(cython_version) < _pep440.Version(required_version):
|
44 | 47 | # too old or wrong cython, skip the test
|
45 | 48 | cython = None
|
46 | 49 |
|
47 | 50 |
|
48 |
| -@pytest.mark.skipif(sys.version_info >= (3, 12), |
49 |
| - reason="numpy.distutils not supported anymore") |
50 | 51 | @pytest.mark.skipif(IS_WASM, reason="Can't start subprocess")
|
51 | 52 | @pytest.mark.skipif(cython is None, reason="requires cython")
|
52 | 53 | @pytest.mark.slow
|
53 | 54 | def test_cython(tmp_path):
|
54 |
| - from numpy.distutils.misc_util import exec_mod_from_location |
| 55 | + import glob |
| 56 | + # build the examples in a temporary directory |
55 | 57 | srcdir = os.path.join(os.path.dirname(__file__), '..')
|
56 | 58 | shutil.copytree(srcdir, tmp_path / 'random')
|
57 |
| - # build the examples and "install" them into a temporary directory |
58 | 59 | build_dir = tmp_path / 'random' / '_examples' / 'cython'
|
59 |
| - subprocess.check_call([sys.executable, 'setup.py', 'build', 'install', |
60 |
| - '--prefix', str(tmp_path / 'installdir'), |
61 |
| - '--single-version-externally-managed', |
62 |
| - '--record', str(tmp_path/ 'tmp_install_log.txt'), |
63 |
| - ], |
64 |
| - cwd=str(build_dir), |
65 |
| - ) |
| 60 | + # We don't want a wheel build, so do the steps in a controlled way |
| 61 | + # The meson.build file is not copied as part of the build, so generate it |
| 62 | + with open(build_dir / "meson.build", "wt", encoding="utf-8") as fid: |
| 63 | + fid.write(textwrap.dedent("""\ |
| 64 | + project('random-build-examples', 'c', 'cpp', 'cython') |
| 65 | +
|
| 66 | + # https://mesonbuild.com/Python-module.html |
| 67 | + py_mod = import('python') |
| 68 | + py3 = py_mod.find_installation(pure: false) |
| 69 | + py3_dep = py3.dependency() |
| 70 | +
|
| 71 | + py_mod = import('python') |
| 72 | + py = py_mod.find_installation(pure: false) |
| 73 | + cc = meson.get_compiler('c') |
| 74 | + cy = meson.get_compiler('cython') |
| 75 | +
|
| 76 | + if not cy.version().version_compare('>=0.29.35') |
| 77 | + error('tests requires Cython >= 0.29.35') |
| 78 | + endif |
| 79 | +
|
| 80 | + _numpy_abs = run_command(py3, |
| 81 | + ['-c', 'import os; os.chdir(".."); import numpy; print(os.path.abspath(numpy.get_include() + "../../.."))'], |
| 82 | + check: true |
| 83 | + ).stdout().strip() |
| 84 | +
|
| 85 | + npymath_path = _numpy_abs / 'core' / 'lib' |
| 86 | + npy_include_path = _numpy_abs / 'core' / 'include' |
| 87 | + npyrandom_path = _numpy_abs / 'random' / 'lib' |
| 88 | + npymath_lib = cc.find_library('npymath', dirs: npymath_path) |
| 89 | + npyrandom_lib = cc.find_library('npyrandom', dirs: npyrandom_path) |
| 90 | +
|
| 91 | + py.extension_module( |
| 92 | + 'extending_distributions', |
| 93 | + 'extending_distributions.pyx', |
| 94 | + install: false, |
| 95 | + include_directories: [npy_include_path], |
| 96 | + dependencies: [npyrandom_lib, npymath_lib], |
| 97 | + ) |
| 98 | + py.extension_module( |
| 99 | + 'extending', |
| 100 | + 'extending.pyx', |
| 101 | + install: false, |
| 102 | + include_directories: [npy_include_path], |
| 103 | + dependencies: [npyrandom_lib, npymath_lib], |
| 104 | + ) |
| 105 | + """)) |
| 106 | + target_dir = build_dir / "build" |
| 107 | + os.makedirs(target_dir, exist_ok=True) |
| 108 | + subprocess.check_call(["meson", "setup", str(build_dir)], cwd=target_dir) |
| 109 | + subprocess.check_call(["meson", "compile"], cwd=target_dir) |
| 110 | + |
66 | 111 | # gh-16162: make sure numpy's __init__.pxd was used for cython
|
67 | 112 | # not really part of this test, but it is a convenient place to check
|
68 |
| - with open(build_dir / 'extending.c') as fid: |
| 113 | + |
| 114 | + g = glob.glob(str(target_dir / "*" / "extending.pyx.c")) |
| 115 | + with open(g[0]) as fid: |
69 | 116 | txt_to_find = 'NumPy API declarations from "numpy/__init__'
|
70 | 117 | for i, line in enumerate(fid):
|
71 | 118 | if txt_to_find in line:
|
72 | 119 | break
|
73 | 120 | else:
|
74 | 121 | assert False, ("Could not find '{}' in C file, "
|
75 | 122 | "wrong pxd used".format(txt_to_find))
|
76 |
| - # get the path to the so's |
77 |
| - so1 = so2 = None |
78 |
| - with open(tmp_path /'tmp_install_log.txt') as fid: |
79 |
| - for line in fid: |
80 |
| - if 'extending.' in line: |
81 |
| - so1 = line.strip() |
82 |
| - if 'extending_distributions' in line: |
83 |
| - so2 = line.strip() |
84 |
| - assert so1 is not None |
85 |
| - assert so2 is not None |
86 |
| - # import the so's without adding the directory to sys.path |
87 |
| - exec_mod_from_location('extending', so1) |
88 |
| - extending_distributions = exec_mod_from_location( |
89 |
| - 'extending_distributions', so2) |
| 123 | + # import without adding the directory to sys.path |
| 124 | + so1 = sorted(glob.glob(str(target_dir / "extending.*")))[0] |
| 125 | + so2 = sorted(glob.glob(str(target_dir / "extending_distributions.*")))[0] |
| 126 | + spec1 = importlib.util.spec_from_file_location("extending", so1) |
| 127 | + spec2 = importlib.util.spec_from_file_location("extending_distributions", so2) |
| 128 | + extending = importlib.util.module_from_spec(spec1) |
| 129 | + spec1.loader.exec_module(extending) |
| 130 | + extending_distributions = importlib.util.module_from_spec(spec2) |
| 131 | + spec2.loader.exec_module(extending_distributions) |
90 | 132 | # actually test the cython c-extension
|
91 | 133 | from numpy.random import PCG64
|
92 | 134 | values = extending_distributions.uniforms_ex(PCG64(0), 10, 'd')
|
|
0 commit comments