-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsetup.py
More file actions
279 lines (237 loc) · 11.2 KB
/
Copy pathsetup.py
File metadata and controls
279 lines (237 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
from setuptools import setup, Extension
from Cython.Build import cythonize
import numpy
import platform
import os
import subprocess
import re
def fix_sizeof_voidp_check(c_file_path):
"""
Post-process generated C file to replace non-portable SIZEOF_VOID_P check.
Replaces the enum-based division-by-zero trick that fails on some Windows toolchains:
enum { __pyx_check_sizeof_voidp = 1 / (int)(SIZEOF_VOID_P == sizeof(void*)) };
With a portable compile-time assertion:
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
_Static_assert(SIZEOF_VOID_P == sizeof(void*), "SIZEOF_VOID_P mismatch");
#else
typedef char __pyx_check_sizeof_voidp[(SIZEOF_VOID_P == sizeof(void*)) ? 1 : -1];
#endif
"""
if not os.path.exists(c_file_path):
return False
try:
with open(c_file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Pattern to match the problematic enum check
pattern = r'enum\s*\{\s*__pyx_check_sizeof_voidp\s*=\s*1\s*/\s*\(int\)\(SIZEOF_VOID_P\s*==\s*sizeof\(void\*\)\)\s*\}\s*;'
# Replacement: Remove the problematic check since we ensure SIZEOF_VOID_P is correct via define_macros
# The original enum check was: enum { __pyx_check_sizeof_voidp = 1 / (int)(SIZEOF_VOID_P == sizeof(void*)) };
# We replace it with a comment explaining that SIZEOF_VOID_P is guaranteed to be correct
replacement = (
'/* SIZEOF_VOID_P check removed - value is explicitly set via compiler define_macros to match platform */'
)
# Check if the pattern exists
if re.search(pattern, content):
content = re.sub(pattern, replacement, content)
with open(c_file_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f"Fixed SIZEOF_VOID_P check in {c_file_path}")
return True
except Exception as e:
print(f"Warning: Could not fix SIZEOF_VOID_P check in {c_file_path}: {e}")
return False
# Determine platform-specific libraries
libraries = ['glpk']
if platform.system() != 'Windows':
libraries.append('m') # Math library not needed on Windows
# Get GLPK paths from environment or brew on macOS
include_dirs = [numpy.get_include(), 'src']
library_dirs = []
extra_link_args = []
# macOS-specific configuration
if platform.system() == 'Darwin':
# Check for explicitly set GLPK paths (set by install script for cross-compilation)
glpk_include = os.environ.get('GLPK_INCLUDE_DIR')
glpk_libdir = os.environ.get('GLPK_LIBRARY_DIR')
# If not in environment, try reading from config file
if not (glpk_include and glpk_libdir):
config_file = '/tmp/glpk_config.txt'
if os.path.exists(config_file):
print(f"Reading GLPK config from {config_file}")
with open(config_file, 'r') as f:
for line in f:
line = line.strip()
if line.startswith('GLPK_INCLUDE_DIR='):
glpk_include = line.split('=', 1)[1]
elif line.startswith('GLPK_LIBRARY_DIR='):
glpk_libdir = line.split('=', 1)[1]
if glpk_include and glpk_libdir:
print(f"Using explicitly set GLPK paths:")
print(f" Include: {glpk_include}")
print(f" Library: {glpk_libdir}")
include_dirs.append(glpk_include)
library_dirs.append(glpk_libdir)
extra_link_args.append(f'-Wl,-rpath,{glpk_libdir}')
else:
# Try pkg-config first (most reliable for getting correct flags)
try:
pkg_config_cflags = subprocess.check_output(
['pkg-config', '--cflags', 'glpk'],
text=True,
stderr=subprocess.DEVNULL
).strip()
pkg_config_libs = subprocess.check_output(
['pkg-config', '--libs-only-L', 'glpk'],
text=True,
stderr=subprocess.DEVNULL
).strip()
# Extract include dirs from pkg-config
for flag in pkg_config_cflags.split():
if flag.startswith('-I'):
include_dirs.append(flag[2:])
# Extract library dirs from pkg-config
for flag in pkg_config_libs.split():
if flag.startswith('-L'):
lib_dir = flag[2:]
library_dirs.append(lib_dir)
extra_link_args.append(f'-Wl,-rpath,{lib_dir}')
print(f"Using pkg-config for GLPK: includes={include_dirs}, libs={library_dirs}")
except (subprocess.CalledProcessError, FileNotFoundError):
# Fallback to brew or environment variables if pkg-config not available
print("pkg-config not available, using brew/environment variables")
# Try to get brew prefix from environment or by calling brew
brew_prefix = os.environ.get('HOMEBREW_PREFIX')
if not brew_prefix:
try:
# Get the brew prefix for the current architecture
brew_prefix = subprocess.check_output(['brew', '--prefix'], text=True).strip()
except (subprocess.CalledProcessError, FileNotFoundError):
# Try architecture-specific defaults
import platform as plat
machine = plat.machine()
if machine == 'arm64':
brew_prefix = '/opt/homebrew'
else:
brew_prefix = '/usr/local'
print(f"Using brew prefix: {brew_prefix}")
# Check if CFLAGS/LDFLAGS are set (e.g., by cibuildwheel)
cflags = os.environ.get('CFLAGS', '')
ldflags = os.environ.get('LDFLAGS', '')
# Extract include dirs from CFLAGS
if '-I' in cflags:
for flag in cflags.split():
if flag.startswith('-I'):
include_dirs.append(flag[2:])
else:
include_dirs.append(os.path.join(brew_prefix, 'include'))
# Extract library dirs from LDFLAGS
if '-L' in ldflags:
for flag in ldflags.split():
if flag.startswith('-L'):
lib_dir = flag[2:]
library_dirs.append(lib_dir)
# Add rpath for each library dir found in LDFLAGS
extra_link_args.append(f'-Wl,-rpath,{lib_dir}')
else:
lib_dir = os.path.join(brew_prefix, 'lib')
library_dirs.append(lib_dir)
extra_link_args.append(f'-Wl,-rpath,{lib_dir}')
# Add rpath for delocate-repaired wheels (where dylibs are bundled)
extra_link_args.append('-Wl,-rpath,@loader_path/../.dylibs')
# Windows-specific configuration
elif platform.system() == 'Windows':
# On Windows, rely on CFLAGS/LDFLAGS environment variables set by cibuildwheel
# These point to MSYS2/MinGW GLPK installation
cflags = os.environ.get('CFLAGS', '')
ldflags = os.environ.get('LDFLAGS', '')
print(f"Windows build with CFLAGS={cflags}, LDFLAGS={ldflags}")
# Extract include dirs from CFLAGS
if '-I' in cflags:
for flag in cflags.split():
if flag.startswith('-I'):
include_dirs.append(flag[2:])
else:
# Fallback to default MSYS2 MinGW64 paths if no CFLAGS set
include_dirs.append('C:/msys64/mingw64/include')
# Extract library dirs from LDFLAGS
if '-L' in ldflags:
for flag in ldflags.split():
if flag.startswith('-L'):
library_dirs.append(flag[2:])
else:
# Fallback to default MSYS2 MinGW64 paths if no LDFLAGS set
library_dirs.append('C:/msys64/mingw64/lib')
print(f"Windows GLPK paths: includes={include_dirs}, libs={library_dirs}")
# Platform-specific compile args and macros
extra_compile_args = ['-std=c99', '-O3']
define_macros = []
if platform.system() == 'Windows':
# On Windows AMD64, explicitly set architecture to ensure SIZEOF_VOID_P matches
# This fixes Cython's compile-time assertion: enum { __pyx_check_sizeof_voidp = 1 / (int)(SIZEOF_VOID_P == sizeof(void*)) }
import struct
pointer_size = struct.calcsize('P')
if pointer_size == 8:
# 64-bit
extra_compile_args.append('-m64')
elif pointer_size == 4:
# 32-bit
extra_compile_args.append('-m32')
# Explicitly define SIZEOF_VOID_P to match the Python interpreter's pointer size
# This ensures the macro matches what sizeof(void*) will be at compile time
define_macros.append(('SIZEOF_VOID_P', str(pointer_size)))
print(f"Windows build: Adding -m{pointer_size*8} flag and SIZEOF_VOID_P={pointer_size}")
ext = Extension(name="benpy",
sources=["src/benpy.pyx",
"src/bensolve-2.1.0/bslv_vlp.c",
"src/bensolve-2.1.0/bslv_algs.c",
"src/bensolve-2.1.0/bslv_lists.c",
"src/bensolve-2.1.0/bslv_poly.c",
"src/bensolve-2.1.0/bslv_lp.c"
],
include_dirs=include_dirs,
library_dirs=library_dirs,
libraries=libraries,
define_macros=define_macros,
extra_compile_args=extra_compile_args,
extra_link_args=extra_link_args
)
# Cython compiler directives to handle Windows/MinGW compatibility
compiler_directives = {
'language_level': 3,
'embedsignature': True,
}
# Windows-specific Cython configuration to fix SIZEOF_VOID_P compile-time assertion
# The issue occurs when Cython generates C code with SIZEOF_VOID_P that doesn't match
# the target platform's sizeof(void*) during C compilation on Windows
if platform.system() == 'Windows':
compiler_directives['preliminary_late_includes_cy28'] = True
# Use build_dir to ensure clean builds on Windows
cythonize_kwargs = {
'include_path': ['src'],
'compiler_directives': compiler_directives,
'nthreads': 0, # Force single-threaded to avoid race conditions
'build_dir': 'build',
'force': True, # Force regeneration on Windows
}
else:
cythonize_kwargs = {
'include_path': ['src'],
'compiler_directives': compiler_directives,
}
# Cythonize the extensions
ext_modules = cythonize([ext], **cythonize_kwargs)
# Post-process generated C file to fix SIZEOF_VOID_P check
# This fixes the non-portable enum trick that causes build failures with MinGW/GCC on Windows
# We apply this fix on all platforms since the C file may be built on different systems
# Check both possible locations where Cython might generate the C file
c_file_paths = ['src/benpy.c', 'build/src/benpy.c']
fixed = False
for c_file_path in c_file_paths:
if os.path.exists(c_file_path):
if fix_sizeof_voidp_check(c_file_path):
fixed = True
if not fixed:
print(f"Warning: Generated C file not found at any of: {c_file_paths}")
setup(
ext_modules=ext_modules
)