2626
2727import os
2828import sys
29- import glob
3029import shutil
30+ import platform
3131from setuptools import Extension , Command , setup , find_packages
3232from Cython .Distutils import build_ext
3333import numpy as np
3737 long_description = fh .read ()
3838
3939
40- USE_OPENMP = True
40+ USE_OPENMP = False # we'll turn it on only if we find a working libomp
4141
42+ def candidates_from_env ():
43+ # Let users/CI point to a custom libomp
44+ incs = []
45+ libs = []
46+ omp_dir = os .environ .get ("OMP_DIR" )
47+ if omp_dir :
48+ incs += [os .path .join (omp_dir , "include" )]
49+ libs += [os .path .join (omp_dir , "lib" )]
50+ inc_env = os .environ .get ("OMP_INCLUDE" )
51+ lib_env = os .environ .get ("OMP_LIB" )
52+ if inc_env : incs .append (inc_env )
53+ if lib_env : libs .append (lib_env )
54+ # Conda environments
55+ conda = os .environ .get ("CONDA_PREFIX" )
56+ if conda :
57+ incs += [os .path .join (conda , "include" )]
58+ libs += [os .path .join (conda , "lib" )]
59+ # Homebrew (Apple silicon and Intel) and MacPorts (optional, don’t require them)
60+ incs += ["/opt/homebrew/opt/libomp/include" , "/usr/local/opt/libomp/include" , "/opt/local/include" ]
61+ libs += ["/opt/homebrew/opt/libomp/lib" , "/usr/local/opt/libomp/lib" , "/opt/local/lib" ]
62+ # Common fallbacks
63+ incs += ["/usr/local/include" , "/usr/include" ]
64+ libs += ["/usr/local/lib" , "/usr/lib" ]
65+ # De-dup while keeping order
66+ def uniq (xs ):
67+ seen = set (); out = []
68+ for x in xs :
69+ if x and x not in seen :
70+ out .append (x ); seen .add (x )
71+ return out
72+ return uniq (incs ), uniq (libs )
4273
43- def extract_gcc_binaries ():
44- """Try to find GCC on OSX for OpenMP support."""
45- 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]" ,
50- ]
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 :
62- return None
74+ def find_libomp ():
75+ incs , libs = candidates_from_env ()
76+ inc_dir = next ((d for d in incs if os .path .exists (os .path .join (d , "omp.h" ))), None )
77+ # Prefer a real libomp over stubs
78+ lib_names = ["libomp.dylib" , "libomp.a" ]
79+ lib_dir = None
80+ for d in libs :
81+ if any (os .path .exists (os .path .join (d , n )) for n in lib_names ):
82+ lib_dir = d
83+ break
84+ return inc_dir , lib_dir
6385
86+ compile_args = []
87+ link_args = []
6488
6589if sys .platform .startswith ("win" ):
66- # compile args from
67- # https://msdn.microsoft.com/en-us/library/fwkeyyhe.aspx
6890 compile_args = ["/O2" , "/openmp" ]
6991 link_args = []
70- else :
71- gcc = extract_gcc_binaries ()
72- if gcc is not None :
73- rpath = "/usr/local/opt/gcc/lib/gcc/" + gcc [- 1 ] + "/"
74- link_args = ["-Wl,-rpath," + rpath ]
75- else :
76- link_args = []
92+ elif sys .platform .startswith ("darwin" ):
93+ # Always use Clang on macOS
94+ os .environ .setdefault ("CC" , "clang" )
95+ os .environ .setdefault ("CXX" , "clang++" )
7796
78- compile_args = [
79- "-Wno-unused-function" ,
80- "-Wno-maybe-uninitialized" ,
81- "-O3" ,
82- "-ffast-math" ,
83- ]
97+ # Force single-arch arm64 on Apple silicon unless caller overrides
98+ if platform .machine () == "arm64" and not os .environ .get ("ARCHFLAGS" ):
99+ os .environ ["ARCHFLAGS" ] = "-arch arm64"
84100
85- if sys .platform .startswith ("darwin" ):
86- if gcc is not None :
87- os .environ ["CC" ] = gcc
88- os .environ ["CXX" ] = gcc
89- else :
90- if not os .path .exists ("/usr/bin/g++" ):
91- print (
92- "No GCC available. Install gcc from Homebrew using brew install gcc."
93- )
94- USE_OPENMP = False
95- # required arguments for default gcc of OSX
96- compile_args .extend (["-O2" , "-stdlib=libc++" , "-mmacosx-version-min=10.7" ])
97- link_args .extend (["-O2" , "-stdlib=libc++" , "-mmacosx-version-min=10.7" ])
101+ mac_min = os .environ .get ("MACOSX_DEPLOYMENT_TARGET" , "12.0" )
98102
99- if USE_OPENMP :
100- compile_args .append ("-fopenmp" )
101- link_args .append ("-fopenmp" )
103+ # Base flags good for Clang/libc++
104+ compile_args += [
105+ "-O3" , "-ffast-math" ,
106+ "-Wno-unused-function" ,
107+ "-std=c++11" ,
108+ "-stdlib=libc++" ,
109+ f"-mmacosx-version-min={ mac_min } " ,
110+ ]
111+ link_args += [
112+ "-std=c++11" ,
113+ "-stdlib=libc++" ,
114+ f"-mmacosx-version-min={ mac_min } " ,
115+ ]
102116
103- compile_args .append ("-std=c++11" )
104- link_args .append ("-std=c++11" )
117+ # Optional OpenMP (only if a usable libomp is present)
118+ if os .environ .get ("CORNAC_DISABLE_OPENMP" ) != "1" :
119+ inc_dir , lib_dir = find_libomp ()
120+ if inc_dir and lib_dir :
121+ compile_args += ["-Xpreprocessor" , "-fopenmp" , f"-I{ inc_dir } " ]
122+ link_args += [f"-L{ lib_dir } " , "-lomp" , f"-Wl,-rpath,{ lib_dir } " ]
123+ USE_OPENMP = True
124+ elif os .environ .get ("CORNAC_FORCE_OPENMP" ) == "1" :
125+ raise RuntimeError (
126+ "CORNAC_FORCE_OPENMP=1 but libomp was not found; set OMP_INCLUDE/OMP_LIB or OMP_DIR."
127+ )
128+ else :
129+ # Linux/Unix: prefer OpenMP via compiler default (GCC/Clang + libgomp)
130+ compile_args += [
131+ "-O3" , "-ffast-math" ,
132+ "-Wno-unused-function" ,
133+ "-Wno-maybe-uninitialized" ,
134+ "-std=c++11" ,
135+ "-fopenmp" ,
136+ ]
137+ link_args += ["-std=c++11" , "-fopenmp" ]
138+ USE_OPENMP = True
105139
106140
107141extensions = [
@@ -333,4 +367,4 @@ def run(self):
333367 extras_require = {"tests" : ["pytest" , "pytest-pep8" , "pytest-xdist" , "pytest-cov" , "Flask" ]},
334368 cmdclass = cmdclass ,
335369 packages = find_packages (),
336- )
370+ )
0 commit comments