|
28 | 28 | import sys |
29 | 29 | import glob |
30 | 30 | import shutil |
| 31 | +import platform, re |
31 | 32 | from setuptools import Extension, Command, setup, find_packages |
32 | 33 | from Cython.Distutils import build_ext |
33 | 34 | import numpy as np |
|
39 | 40 |
|
40 | 41 | USE_OPENMP = True |
41 | 42 |
|
| 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 | + ) |
42 | 49 |
|
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 |
45 | 52 | 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 |
50 | 56 | ] |
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: |
62 | 61 | return None |
63 | | - |
| 62 | + cands.sort() |
| 63 | + return cands[-1] # newest version string-wise |
64 | 64 |
|
65 | 65 | if sys.platform.startswith("win"): |
66 | | - # compile args from |
67 | | - # https://msdn.microsoft.com/en-us/library/fwkeyyhe.aspx |
68 | 66 | compile_args = ["/O2", "/openmp"] |
69 | 67 | link_args = [] |
70 | 68 | 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"] |
79 | 70 | link_args = [] |
80 | 71 |
|
81 | 72 | 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 |
85 | 78 | 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. |
90 | 107 | 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 |
91 | 120 |
|
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") |
95 | 124 |
|
96 | 125 |
|
97 | 126 | extensions = [ |
|
0 commit comments