Skip to content

Commit 1f46d11

Browse files
committed
Merge branch 'topic/default/pixi-lock' into 'branch/default'
pixi lock Pythran 0.18 See merge request fluiddyn/transonic!149
2 parents 18a95ac + 6d80cf6 commit 1f46d11

5 files changed

Lines changed: 1896 additions & 3069 deletions

File tree

.github/dist.py

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
'''
2+
This modules contains a distutils extension mechanism for Pythran
3+
* PythranExtension: is used as distutils's Extension
4+
'''
5+
6+
import pythran.config as cfg
7+
from pythran.tables import blas_requires
8+
from pythran.utils import cxxid
9+
10+
from collections import defaultdict
11+
try:
12+
from collections.abc import Iterable
13+
except ImportError:
14+
from collections import Iterable
15+
import os.path
16+
import os
17+
import re
18+
import sys
19+
20+
21+
MODIFIED = True
22+
23+
if sys.version_info >= (3, 10):
24+
from setuptools.command.build_ext import build_ext as LegacyBuildExt
25+
from setuptools.extension import Extension
26+
else:
27+
try:
28+
from distutils.command.build_ext import build_ext as LegacyBuildExt
29+
except ImportError:
30+
from setuptools.command.build_ext import build_ext as LegacyBuildExt
31+
32+
try:
33+
# `numpy.distutils` is deprecated, and won't be present on Python >=3.12
34+
# If it is installed, we need to use it though, so try-import it:
35+
from numpy.distutils.extension import Extension
36+
except ImportError:
37+
from setuptools.extension import Extension
38+
39+
40+
41+
class PythranBuildExtMixIn(object):
42+
"""Subclass of `distutils.command.build_ext.build_ext` which is required to
43+
build `PythranExtension` with the configured C++ compiler. It may also be
44+
subclassed if you want to combine with another build_ext class (NumPy,
45+
Cython implementations).
46+
47+
"""
48+
49+
def build_extension(self, ext):
50+
StringTypes = str,
51+
52+
def get_value(obj, key):
53+
var = getattr(obj, key)
54+
if isinstance(var, Iterable) and not isinstance(var, StringTypes):
55+
return var[0]
56+
else:
57+
return var
58+
59+
def set_value(obj, key, value):
60+
var = getattr(obj, key)
61+
if isinstance(var, Iterable) and not isinstance(var, StringTypes):
62+
var[0] = value
63+
else:
64+
setattr(obj, key, value)
65+
66+
prev = {
67+
# linux-like
68+
'preprocessor': None,
69+
'compiler_cxx': None,
70+
'compiler_so': None,
71+
'compiler_so_cxx': None,
72+
'compiler': None,
73+
'linker_exe': None,
74+
'linker_so': None,
75+
# Windows-like
76+
'cc': None,
77+
}
78+
# Backup compiler settings
79+
for key in list(prev.keys()):
80+
if hasattr(self.compiler, key):
81+
prev[key] = get_value(self.compiler, key)
82+
else:
83+
del prev[key]
84+
# try hard to modify the compiler
85+
if getattr(ext, 'cxx', None) is not None:
86+
for comp in prev:
87+
if hasattr(self.compiler, comp):
88+
set_value(self.compiler, comp, ext.cxx)
89+
90+
find_exe = None
91+
if getattr(ext, 'cc', None) is not None:
92+
try:
93+
import distutils._msvccompiler as msvc
94+
if not hasattr(msvc, "_find_exe"):
95+
msvc = msvc.msvc
96+
# install hook
97+
find_exe = msvc._find_exe
98+
99+
def _find_exe(exe, *args, **kwargs):
100+
if exe == 'cl.exe':
101+
exe = ext.cc
102+
return find_exe(exe, *args, **kwargs)
103+
104+
msvc._find_exe = _find_exe
105+
except (AttributeError, ImportError) as e:
106+
pass
107+
108+
# In general, distutils uses -Wstrict-prototypes, but this option
109+
# is not valid for C++ code, only for C. Remove it if it's there
110+
# to avoid a spurious warning on every compilation.
111+
for flag in cfg.cfg.get('compiler', "ignoreflags").split():
112+
for target in ('compiler_so', 'linker_so'):
113+
try:
114+
while True:
115+
getattr(self.compiler, target).remove(flag)
116+
except (AttributeError, ValueError):
117+
pass
118+
119+
# Remove -arch i386 if 'x86_64' is specified, otherwise incorrect
120+
# code is generated, at least on OSX
121+
if hasattr(self.compiler, 'compiler_so'):
122+
archs = defaultdict(list)
123+
for i, flag in enumerate(self.compiler.compiler_so[1:]):
124+
if self.compiler.compiler_so[i] == '-arch':
125+
archs[flag].append(i + 1)
126+
if 'x86_64' in archs and 'i386' in archs:
127+
for i in archs['i386']:
128+
self.compiler.compiler_so[i] = 'x86_64'
129+
130+
try:
131+
return super(PythranBuildExtMixIn, self).build_extension(ext)
132+
finally:
133+
# Revert compiler settings
134+
for key in prev.keys():
135+
set_value(self.compiler, key, prev[key])
136+
137+
# uninstall hook
138+
if find_exe is not None:
139+
import distutils._msvccompiler as msvc
140+
msvc._find_exe = find_exe
141+
142+
143+
class PythranBuildExtMeta(type):
144+
145+
def __getitem__(self, base):
146+
class PythranBuildExt(PythranBuildExtMixIn, base):
147+
pass
148+
149+
return PythranBuildExt
150+
151+
152+
class PythranBuildExt(PythranBuildExtMixIn, LegacyBuildExt, metaclass=PythranBuildExtMeta):
153+
pass
154+
155+
156+
blas_requirements = {"/".join(map(cxxid, elem)) for elem in blas_requires}
157+
158+
includes_matcher = re.compile(r'^#include <pythonic/include/(.*)\.hpp>$',
159+
re.MULTILINE)
160+
161+
def requires_blas(source):
162+
if not os.path.exists(source) or os.path.splitext(source)[1] != ".cpp":
163+
return False # conservative
164+
165+
with open(source) as fd:
166+
content = fd.read()
167+
168+
return not blas_requirements.isdisjoint(includes_matcher.findall(content))
169+
170+
171+
class PythranExtension(Extension):
172+
'''
173+
Description of a Pythran extension
174+
175+
Similar to setuptools.extension.Extension except that the sources are .py files
176+
They must be processable by pythran, of course.
177+
178+
The compilation process ends up in a native Python module.
179+
'''
180+
181+
def __init__(self, name, sources, *args, **kwargs):
182+
self._kwargs = kwargs.copy()
183+
184+
if all(not requires_blas(source) for source in sources):
185+
# Inserting at head so that user-specified config in CLI takes
186+
# precedence.
187+
kwargs['config'] = ['compiler.blas=none'] + kwargs.get('config', [])
188+
cfg_ext = cfg.make_extension(python=True, **kwargs)
189+
self.cxx = cfg_ext.pop('cxx', None)
190+
self.cc = cfg_ext.pop('cc', None)
191+
Extension.__init__(self, name, sources, *args, **cfg_ext)
192+
self.__dict__.pop("sources", None)
193+
194+
def _update_blas_requirements(self, source):
195+
if requires_blas(source):
196+
cfg_ext = cfg.make_extension(python=True, **self._kwargs)
197+
for k, v in cfg_ext.items():
198+
setattr(self, k, v)
199+
200+
@property
201+
def sources(self):
202+
import pythran.toolchain as tc
203+
cxx_sources = []
204+
for source in self._sources:
205+
base, ext = os.path.splitext(source)
206+
if ext != '.py':
207+
cxx_sources.append(source)
208+
continue
209+
output_file = base + '.cpp' # target name
210+
211+
if os.path.exists(source) and (not os.path.exists(output_file)
212+
or os.path.getmtime(output_file) < os.path.getmtime(source)):
213+
# get the last name in the path
214+
if '.' in self.name:
215+
module_name = os.path.splitext(self.name)[-1][1:]
216+
else:
217+
module_name = self.name
218+
tc.compile_pythranfile(source, output_file,
219+
module_name, cpponly=True)
220+
221+
self._update_blas_requirements(output_file)
222+
cxx_sources.append(output_file)
223+
return cxx_sources
224+
225+
@sources.setter
226+
def sources(self, sources):
227+
self._sources = sources
228+
for source in sources:
229+
self._update_blas_requirements(source)

.github/minimal_pythran.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
# pythran export func(int)
3+
4+
def func(arg):
5+
return arg

.github/workflows/ci-windows.yml

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,25 @@ jobs:
1313

1414
steps:
1515
- uses: actions/checkout@v4
16-
- uses: prefix-dev/setup-pixi@v0.8.1
16+
- uses: prefix-dev/setup-pixi@v0.8.8
1717
with:
18-
pixi-version: v0.38.0
18+
pixi-version: v0.47.0
1919
cache: false
20-
- name: Tests
20+
21+
- name: Setup
2122
run: |
2223
pixi run install-editable
24+
25+
- name: Minimal Pythran check
26+
run: |
27+
# todo: patch to be removed when Pythran >0.18.0 will be used
28+
# see https://github.com/serge-sans-paille/pythran/issues/2360
29+
mv .github/dist.py .pixi/envs/default/Lib/site-packages/pythran
30+
pixi run python -c "from pythran import dist as m; print(m.MODIFIED); assert m.MODIFIED"
31+
pixi run pythran .github/minimal_pythran.py -vv
32+
33+
- name: Tests
34+
run: |
2335
pixi run pytest tests
2436
pixi run mpirun -np 2 pytest tests
2537
echo "TRANSONIC_BACKEND=python" >> $GITHUB_ENV

0 commit comments

Comments
 (0)