Skip to content

Commit 10dd67c

Browse files
committed
fix gcc binary extraction logic
1 parent 8659d73 commit 10dd67c

File tree

1 file changed

+67
-38
lines changed

1 file changed

+67
-38
lines changed

setup.py

Lines changed: 67 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import sys
2929
import glob
3030
import shutil
31+
import platform, re
3132
from setuptools import Extension, Command, setup, find_packages
3233
from Cython.Distutils import build_ext
3334
import numpy as np
@@ -39,59 +40,87 @@
3940

4041
USE_OPENMP = True
4142

43+
def homebrew_prefix():
44+
# Prefer explicit env, then sane defaults for Intel vs Apple silicon
45+
return os.environ.get(
46+
"HOMEBREW_PREFIX",
47+
"/opt/homebrew" if platform.machine() == "arm64" else "/usr/local"
48+
)
4249

43-
def extract_gcc_binaries():
44-
"""Try to find GCC on OSX for OpenMP support."""
50+
def extract_gcc_binary():
51+
# Return full path to latest g++-NN from Homebrew or MacPorts
4552
patterns = [
46-
"/opt/local/bin/g++-mp-[0-9].[0-9]",
47-
"/opt/local/bin/g++-mp-[0-9]",
48-
"/usr/local/bin/g++-[0-9].[0-9]",
49-
"/usr/local/bin/g++-[0-9]",
53+
f"{homebrew_prefix()}/bin/g++-[0-9]*", # Homebrew (both /usr/local and /opt/homebrew)
54+
"/usr/local/bin/g++-[0-9]*", # Legacy Intel HB
55+
"/opt/local/bin/g++-mp-[0-9]*", # MacPorts
5056
]
51-
if sys.platform.startswith("darwin"):
52-
gcc_binaries = []
53-
for pattern in patterns:
54-
gcc_binaries += glob.glob(pattern)
55-
gcc_binaries.sort()
56-
if gcc_binaries:
57-
_, gcc = os.path.split(gcc_binaries[-1])
58-
return gcc
59-
else:
60-
return None
61-
else:
57+
cands = []
58+
for p in patterns:
59+
cands.extend(glob.glob(p))
60+
if not cands:
6261
return None
63-
62+
cands.sort()
63+
return cands[-1] # newest version string-wise
6464

6565
if sys.platform.startswith("win"):
66-
# compile args from
67-
# https://msdn.microsoft.com/en-us/library/fwkeyyhe.aspx
6866
compile_args = ["/O2", "/openmp"]
6967
link_args = []
7068
else:
71-
gcc = extract_gcc_binaries()
72-
73-
compile_args = [
74-
"-Wno-unused-function",
75-
"-Wno-maybe-uninitialized",
76-
"-O3",
77-
"-ffast-math",
78-
]
69+
compile_args = ["-Wno-unused-function", "-Wno-maybe-uninitialized", "-O3", "-ffast-math"]
7970
link_args = []
8071

8172
if sys.platform.startswith("darwin"):
82-
if gcc is not None:
83-
os.environ["CC"] = gcc
84-
os.environ["CXX"] = gcc
73+
gcc_path = extract_gcc_binary()
74+
75+
# Choose a valid deployment target
76+
if platform.machine() == "arm64":
77+
mac_min = "11.0" # arm64 cannot target < 11.0
8578
else:
86-
if not os.path.exists("/usr/bin/g++"):
87-
print(
88-
"No GCC available. Install gcc from Homebrew using brew install gcc."
89-
)
79+
mac_min = "10.13" # bump from 10.7; keep reasonably old but supported
80+
81+
if gcc_path is not None:
82+
# Use Homebrew/MacPorts GCC for OpenMP (libgomp)
83+
os.environ["CC"] = gcc_path
84+
os.environ["CXX"] = gcc_path
85+
86+
# rpath to GCC’s libgomp dir
87+
prefix = os.path.dirname(os.path.dirname(gcc_path)) # .../bin -> prefix
88+
# For Homebrew GCC the libs live under <prefix>/opt/gcc/lib/gcc/<MAJOR>
89+
# Try to extract MAJOR from the binary name (g++-14, g++-13, etc.)
90+
m = re.search(r'(\d+)(?:\.\d+)?$', os.path.basename(gcc_path))
91+
gcc_major = m.group(1) if m else ""
92+
hb_opt_gcc = f"{homebrew_prefix()}/opt/gcc/lib/gcc/{gcc_major}"
93+
mp_libgcc = "/opt/local/lib/gcc{}".format(gcc_major) if prefix.startswith("/opt/local") else None
94+
95+
if os.path.isdir(hb_opt_gcc):
96+
link_args.append(f"-Wl,-rpath,{hb_opt_gcc}")
97+
elif mp_libgcc and os.path.isdir(mp_libgcc):
98+
link_args.append(f"-Wl,-rpath,{mp_libgcc}")
99+
100+
compile_args.append("-fopenmp")
101+
link_args.append("-fopenmp")
102+
# Deployment target is still needed for ABI consistency
103+
compile_args.extend(["-stdlib=libc++", f"-mmacosx-version-min={mac_min}"])
104+
link_args.extend(["-stdlib=libc++", f"-mmacosx-version-min={mac_min}"])
105+
else:
106+
# No GCC found → default to Apple clang. Either disable OpenMP or use libomp if present.
90107
USE_OPENMP = False
108+
compile_args.extend(["-O2", "-stdlib=libc++", f"-mmacosx-version-min={mac_min}"])
109+
link_args.extend(["-O2", "-stdlib=libc++", f"-mmacosx-version-min={mac_min}"])
110+
111+
# Optional: enable OpenMP with clang + libomp if installed
112+
hb = homebrew_prefix()
113+
omp_inc = f"{hb}/opt/libomp/include"
114+
omp_lib = f"{hb}/opt/libomp/lib"
115+
if os.path.exists(os.path.join(omp_inc, "omp.h")) and os.path.isdir(omp_lib):
116+
# clang needs -Xpreprocessor -fopenmp and links against -lomp
117+
compile_args += ["-Xpreprocessor", "-fopenmp", f"-I{omp_inc}"]
118+
link_args += [f"-L{omp_lib}", "-lomp"]
119+
USE_OPENMP = True
91120

92-
if USE_OPENMP:
93-
compile_args.append("-fopenmp")
94-
link_args.append("-fopenmp")
121+
# Common C++ standard
122+
compile_args.append("-std=c++11")
123+
link_args.append("-std=c++11")
95124

96125

97126
extensions = [

0 commit comments

Comments
 (0)