Skip to content

Commit e113ebe

Browse files
authored
Fix compilation of wheel on MacOS (#230)
* build macos wheel on macos-15 * bumb macos target version to 15 * back to macos-14, bumb cibw to last version * temporary CI macos matrix to find the root cause * new round of test matrix for macos * add numpy flowdir computation * perform openmp smoke test on macos * additional openmp smoke tests * revert diagnostics modification. Prepare fix for merge * streamline the changeset relative to main
1 parent e75f574 commit e113ebe

2 files changed

Lines changed: 151 additions & 57 deletions

File tree

.github/workflows/build_wheels.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
fail-fast: false
1414
matrix:
1515
# macos-13 is intel, macos-14 is apple silicon, windows-11-arm
16-
os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, windows-11-arm]
16+
os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, windows-11-arm, macos-14]
1717

1818
steps:
1919
- uses: actions/checkout@v4

setup.py

Lines changed: 150 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,23 @@
99
from Cython.Build import cythonize
1010

1111

12+
def parse_env_bool(name: str) -> bool | None:
13+
"""Return a boolean override from an environment variable."""
14+
value = os.getenv(name)
15+
if value is None:
16+
return None
17+
18+
normalized = value.strip().lower()
19+
if normalized in {"1", "true", "yes", "on"}:
20+
return True
21+
if normalized in {"0", "false", "no", "off"}:
22+
return False
23+
24+
raise ValueError(
25+
f"Environment variable {name!r} must be one of 1/0, true/false, yes/no, on/off"
26+
)
27+
28+
1229
class BuildConfig:
1330
"""Centralized build configuration management for itzi package.
1431
@@ -22,16 +39,25 @@ def __init__(self):
2239
self.is_wheel_build = os.getenv("ITZI_BDIST_WHEEL") is not None
2340
self.platform = self.detect_platform()
2441
self.architecture = self.detect_architecture()
42+
self.openmp_override = parse_env_bool("ITZI_USE_OPENMP")
43+
self.use_openmp = self.detect_openmp_usage()
2544
self.compiler_type = None # Set during build
45+
self.base_compile_args_plain = ["-O3", "-w"]
2646
self.base_compile_args_unix = ["-O3", "-w", "-fopenmp"]
27-
self.base_compile_args_macos = [
28-
"-O3",
29-
"-w",
30-
"-Xpreprocessor",
31-
"-fopenmp",
32-
]
47+
self.base_compile_args_macos = ["-O3", "-w", "-Xpreprocessor", "-fopenmp"]
3348
self.base_compile_args_msvc = ["/openmp", "/Ox"]
34-
self.base_link_args_unix = ["-lgomp", "-fopenmp"]
49+
self.base_compile_args_msvc_plain = ["/Ox"]
50+
self.base_link_args_unix = ["-lgomp"]
51+
52+
def detect_openmp_usage(self) -> bool:
53+
"""Return whether OpenMP should be enabled for this build."""
54+
if self.openmp_override is not None:
55+
return self.openmp_override
56+
57+
if self.is_wheel_build and self.platform == "macos" and self.architecture == "arm64":
58+
return False
59+
60+
return True
3561

3662
def detect_platform(self):
3763
"""Detect current platform (Linux, Windows, macOS)"""
@@ -73,20 +99,39 @@ def _get_source_optimization_flags(self):
7399

74100
if self.compiler_type == "msvc":
75101
# Conservative MSVC flags for source builds
76-
compile_args = self.base_compile_args_msvc
102+
if self.use_openmp:
103+
compile_args = self.base_compile_args_msvc.copy()
104+
else:
105+
compile_args = self.base_compile_args_msvc_plain.copy()
77106
link_args = []
78107
elif self.compiler_type == "mingw32":
79-
compile_args = self.base_compile_args_unix + ["-lgomp", "-lpthread", "-march=native"]
80-
link_args = ["-lgomp", "-lpthread"]
108+
if self.use_openmp:
109+
compile_args = self.base_compile_args_unix + [
110+
"-lgomp",
111+
"-lpthread",
112+
"-march=native",
113+
]
114+
link_args = ["-lgomp", "-lpthread"]
115+
else:
116+
compile_args = self.base_compile_args_plain + ["-lpthread", "-march=native"]
117+
link_args = ["-lpthread"]
81118
elif self.compiler_type == "unix":
82119
if self.platform == "macos":
83120
# macOS specific handling
84-
compile_args = self.base_compile_args_macos + ["-march=native"]
85-
link_args = ["-lomp"]
121+
if self.use_openmp:
122+
compile_args = self.base_compile_args_macos + ["-march=native"]
123+
link_args = ["-lomp"]
124+
else:
125+
compile_args = self.base_compile_args_plain + ["-march=native"]
126+
link_args = []
86127
else:
87128
# Linux and other Unix systems
88-
compile_args = self.base_compile_args_unix + ["-march=native"]
89-
link_args = self.base_link_args_unix
129+
if self.use_openmp:
130+
compile_args = self.base_compile_args_unix + ["-march=native"]
131+
link_args = self.base_link_args_unix
132+
else:
133+
compile_args = self.base_compile_args_plain + ["-march=native"]
134+
link_args = []
90135

91136
return compile_args, link_args
92137

@@ -96,55 +141,76 @@ def _get_wheel_optimization_flags(self):
96141
link_args = []
97142

98143
if self.compiler_type == "msvc":
144+
if self.use_openmp:
145+
compile_args = self.base_compile_args_msvc.copy()
146+
else:
147+
compile_args = self.base_compile_args_msvc_plain.copy()
99148
if self.architecture == "x86_64":
100-
compile_args = self.base_compile_args_msvc + ["/arch:AVX2"]
149+
compile_args.append("/arch:AVX2")
101150
elif self.architecture == "arm64":
102-
compile_args = self.base_compile_args_msvc + ["/arch:armv8.2"]
103-
else:
104-
compile_args = self.base_compile_args_msvc
151+
compile_args.append("/arch:armv8.2")
105152
link_args = []
106153

107154
elif self.compiler_type == "mingw32":
108155
if self.architecture == "x86_64":
109-
compile_args = self.base_compile_args_unix + [
110-
"-lgomp",
111-
"-lpthread",
112-
"-march=x86-64-v3",
113-
]
156+
if self.use_openmp:
157+
compile_args = self.base_compile_args_unix + [
158+
"-lgomp",
159+
"-lpthread",
160+
"-march=x86-64-v3",
161+
]
162+
else:
163+
compile_args = self.base_compile_args_plain + ["-lpthread", "-march=x86-64-v3"]
114164
elif self.architecture == "arm64":
115-
compile_args = self.base_compile_args_unix + ["-march=armv8-a+simd"]
165+
if self.use_openmp:
166+
compile_args = self.base_compile_args_unix + ["-march=armv8-a+simd"]
167+
else:
168+
compile_args = self.base_compile_args_plain + ["-march=armv8-a+simd"]
169+
else:
170+
if self.use_openmp:
171+
compile_args = self.base_compile_args_unix + ["-lgomp", "-lpthread"]
172+
else:
173+
compile_args = self.base_compile_args_plain + ["-lpthread"]
174+
if self.use_openmp:
175+
link_args = ["-lgomp", "-lpthread"]
116176
else:
117-
compile_args = self.base_compile_args_unix
118-
link_args = ["-lgomp", "-lpthread"]
177+
link_args = ["-lpthread"]
119178

120179
elif self.compiler_type == "unix":
121180
if self.platform == "macos":
122181
if self.architecture == "arm64":
123-
compile_args = self.base_compile_args_macos + ["-march=armv8-a+simd"]
182+
if self.use_openmp:
183+
compile_args = self.base_compile_args_macos + ["-march=armv8-a+simd"]
184+
else:
185+
compile_args = self.base_compile_args_plain + ["-march=armv8-a+simd"]
124186
else:
125-
compile_args = self.base_compile_args_macos
126-
link_args = ["-lomp"]
187+
if self.use_openmp:
188+
compile_args = self.base_compile_args_macos
189+
else:
190+
compile_args = self.base_compile_args_plain.copy()
191+
link_args = ["-lomp"] if self.use_openmp else []
127192
else:
128193
# Linux and other Unix systems
129194
if self.architecture == "x86_64":
130-
compile_args = self.base_compile_args_unix + ["-march=x86-64-v3"]
195+
if self.use_openmp:
196+
compile_args = self.base_compile_args_unix + ["-march=x86-64-v3"]
197+
else:
198+
compile_args = self.base_compile_args_plain + ["-march=x86-64-v3"]
131199
elif self.architecture == "arm64":
132-
compile_args = self.base_compile_args_unix + ["-march=armv8-a+simd"]
200+
if self.use_openmp:
201+
compile_args = self.base_compile_args_unix + ["-march=armv8-a+simd"]
202+
else:
203+
compile_args = self.base_compile_args_plain + ["-march=armv8-a+simd"]
133204
else:
134-
compile_args = self.base_compile_args_unix
135-
link_args = self.base_link_args_unix
205+
if self.use_openmp:
206+
compile_args = self.base_compile_args_unix.copy()
207+
else:
208+
compile_args = self.base_compile_args_plain.copy()
209+
link_args = self.base_link_args_unix if self.use_openmp else []
136210

137211
return compile_args, link_args
138212

139213

140-
# Legacy compiler options for backward compatibility
141-
copt = {
142-
"msvc": ["/openmp", "/Ox"],
143-
"mingw32": ["-O3", "-w", "-fopenmp", "-lgomp", "-lpthread"],
144-
"unix": ["-O3", "-w", "-fopenmp"],
145-
}
146-
lopt = {"mingw32": ["-lgomp", "-lpthread"], "unix": ["-lgomp", "-fopenmp"]}
147-
148214
macos_includes = [
149215
"/opt/homebrew/include",
150216
"/usr/local/include",
@@ -167,6 +233,7 @@ def build_extensions(self):
167233
print(f"Build mode: {'wheel' if build_config.is_wheel_build else 'source'}")
168234
print(f"Platform: {build_config.platform}")
169235
print(f"Architecture: {build_config.architecture}")
236+
print(f"OpenMP enabled: {build_config.use_openmp}")
170237

171238
# Get optimization flags from BuildConfig
172239
try:
@@ -181,7 +248,11 @@ def build_extensions(self):
181248
ext.extra_link_args = link_args
182249

183250
# Add macOS-specific include and library paths if needed
184-
if compiler == "unix" and platform.system() == "Darwin":
251+
if (
252+
compiler == "unix"
253+
and platform.system() == "Darwin"
254+
and build_config.use_openmp
255+
):
185256
for path in macos_includes:
186257
if os.path.exists(path):
187258
ext.include_dirs.append(path)
@@ -197,22 +268,45 @@ def build_extensions(self):
197268
)
198269
# Fallback to legacy system
199270
for ext in self.extensions:
200-
if compiler in ["msvc", "mingw32"]:
201-
ext.extra_compile_args = copt[compiler]
202-
ext.extra_link_args = lopt.get(compiler)
203-
if compiler in ["unix"]:
271+
if compiler == "msvc":
272+
if build_config.use_openmp:
273+
ext.extra_compile_args = build_config.base_compile_args_msvc.copy()
274+
else:
275+
ext.extra_compile_args = build_config.base_compile_args_msvc_plain.copy()
276+
ext.extra_link_args = []
277+
elif compiler == "mingw32":
278+
if build_config.use_openmp:
279+
ext.extra_compile_args = build_config.base_compile_args_unix + [
280+
"-lgomp",
281+
"-lpthread",
282+
]
283+
ext.extra_link_args = ["-lgomp", "-lpthread"]
284+
else:
285+
ext.extra_compile_args = build_config.base_compile_args_plain + [
286+
"-lpthread"
287+
]
288+
ext.extra_link_args = ["-lpthread"]
289+
elif compiler == "unix":
204290
if platform.system() == "Darwin":
205-
ext.extra_compile_args.extend(["-Xpreprocessor", "-fopenmp"])
206-
ext.extra_link_args.append("-lomp")
207-
for path in macos_includes:
208-
if os.path.exists(path):
209-
ext.include_dirs.append(path)
210-
for path in macos_libs:
211-
if os.path.exists(path):
212-
ext.library_dirs.append(path)
291+
if build_config.use_openmp:
292+
ext.extra_compile_args = build_config.base_compile_args_macos.copy()
293+
ext.extra_link_args = ["-lomp"]
294+
for path in macos_includes:
295+
if os.path.exists(path):
296+
ext.include_dirs.append(path)
297+
for path in macos_libs:
298+
if os.path.exists(path):
299+
ext.library_dirs.append(path)
300+
else:
301+
ext.extra_compile_args = build_config.base_compile_args_plain.copy()
302+
ext.extra_link_args = []
213303
else:
214-
ext.extra_compile_args = copt[compiler]
215-
ext.extra_link_args = lopt[compiler]
304+
if build_config.use_openmp:
305+
ext.extra_compile_args = build_config.base_compile_args_unix.copy()
306+
ext.extra_link_args = build_config.base_link_args_unix
307+
else:
308+
ext.extra_compile_args = build_config.base_compile_args_plain.copy()
309+
ext.extra_link_args = []
216310

217311
build_ext.build_extensions(self)
218312

0 commit comments

Comments
 (0)