88from setuptools import setup , Extension
99from setuptools import Command
1010from setuptools .command .build_ext import build_ext
11- from setuptools .command .bdist_wheel import bdist_wheel
1211
1312
1413class CleanCommand (Command ):
@@ -29,34 +28,21 @@ def run(self):
2928 shutil .rmtree (egg_info , ignore_errors = True )
3029
3130
32- class CustomBdistWheel (bdist_wheel ):
33- def run (self ):
34- # Ensure all build steps are run before bdist_wheel
35- self .run_command ("build_ext" )
36- super ().run ()
37-
38-
3931class CMakeExtension (Extension ):
4032 def __init__ (
4133 self ,
4234 name ,
4335 * ,
36+ setup = None ,
4437 source_dir = None ,
4538 install_dir = None ,
46- url = None ,
47- sha256 = None ,
4839 cmake_args = [],
4940 ):
5041 # Don't invoke the original build_ext for this special extension.
5142 super ().__init__ (name , sources = [])
52- if source_dir and url :
53- raise ValueError (
54- "CMakeExtension should have either a source_dir or a url, not both."
55- )
43+ self .setup = setup
5644 self .source_dir = source_dir
5745 self .install_dir = install_dir
58- self .url = url
59- self .sha256 = sha256
6046 self .cmake_args = cmake_args
6147
6248
@@ -81,9 +67,17 @@ def _build_cmake(self, ext: CMakeExtension):
8167 shutil .rmtree (build_dir , ignore_errors = True )
8268 build_dir .mkdir (parents = True , exist_ok = True )
8369
84- lib_dir = Path (
85- self .get_finalized_command ("build_py" ).get_package_dir ("numba.openmp.libs" )
86- )
70+ if ext .setup :
71+ ext .setup .setup ()
72+
73+ if self .inplace :
74+ lib_dir = Path (
75+ self .get_finalized_command ("build_py" ).get_package_dir (
76+ "numba.openmp.libs"
77+ )
78+ )
79+ else :
80+ lib_dir = Path (self .build_lib ) / "numba/openmp/libs"
8781
8882 extra_cmake_args = self ._env_toolchain_args (ext )
8983 # Set RPATH.
@@ -149,79 +143,90 @@ def _env_toolchain_args(self, ext):
149143 return args
150144
151145
152- def _prepare_source_openmp (sha256 = None ):
146+ class PrepareOpenMP :
147+ setup_done = False
153148 LLVM_VERSION = os .environ .get ("LLVM_VERSION" , None )
154- assert LLVM_VERSION is not None , "LLVM_VERSION environment variable must be set."
155- url = f"https://github.com/llvm/llvm-project/releases/download/llvmorg-{ LLVM_VERSION } /llvm-project-{ LLVM_VERSION } .src.tar.xz"
156-
157- tmp = Path ("_downloads/libomp" ) / f"llvm-project-{ LLVM_VERSION } .tar.gz"
158- tmp .parent .mkdir (parents = True , exist_ok = True )
159-
160- # Download the source tarball if it does not exist.
161- if not tmp .exists ():
162- print (f"Downloading llvm-project version { LLVM_VERSION } url:" , url )
163- with urllib .request .urlopen (url ) as r :
164- with tmp .open ("wb" ) as f :
165- f .write (r .read ())
166- else :
167- print (f"Using downloaded llvm-project at { tmp } " )
168-
169- if sha256 :
170- import hashlib
171-
172- hasher = hashlib .sha256 ()
173- with tmp .open ("rb" ) as f :
174- hasher .update (f .read ())
175- if hasher .hexdigest () != sha256 :
176- raise ValueError (f"SHA256 mismatch for { url } " )
177-
178- print ("Extracting llvm-project..." )
179- with tarfile .open (tmp ) as tf :
180- # The root dir llvm-project-20.1.8.src
181- root_name = tf .getnames ()[0 ]
182-
183- # Extract only needed subdirectories
184- members = [
185- m
186- for m in tf .getmembers ()
187- if m .name .startswith (f"{ root_name } /openmp/" )
188- or m .name .startswith (f"{ root_name } /offload/" )
189- or m .name .startswith (f"{ root_name } /runtimes/" )
190- or m .name .startswith (f"{ root_name } /cmake/" )
191- or m .name .startswith (f"{ root_name } /llvm/cmake/" )
192- or m .name .startswith (f"{ root_name } /llvm/utils/" )
193- or m .name .startswith (f"{ root_name } /libc/" )
194- ]
195-
196- parentdir = tmp .parent
197- # Base arguments for extractall.
198- kwargs = {"path" : parentdir , "members" : members }
199-
200- # Check if data filter is available.
201- if hasattr (tarfile , "data_filter" ):
202- # If this exists, the 'filter' argument is guaranteed to work
203- kwargs ["filter" ] = "data"
204-
205- tf .extractall (** kwargs )
206-
207- source_dir = parentdir / root_name
208- print ("Extracted llvm-project to:" , source_dir )
209-
210- print ("Applying patches to llvm-project..." )
211- for patch in sorted (
212- Path (f"src/numba/openmp/libs/openmp/patches/{ LLVM_VERSION } " )
213- .absolute ()
214- .glob ("*.patch" )
215- ):
216- print ("applying patch" , patch )
217- subprocess .run (
218- ["patch" , "-p1" , "-i" , str (patch )],
219- cwd = source_dir ,
220- check = True ,
221- stdin = subprocess .DEVNULL ,
149+
150+ @classmethod
151+ def setup (cls ):
152+ if not cls .setup_done :
153+ cls ._prepare_source_openmp ()
154+ cls .setup_done = True
155+
156+ @classmethod
157+ def get_source_dir (cls ):
158+ return Path (
159+ f"_downloads/libomp/llvm-project-{ cls .LLVM_VERSION } .src/runtimes"
160+ ).absolute ()
161+
162+ @classmethod
163+ def _prepare_source_openmp (cls ):
164+ assert cls .LLVM_VERSION is not None , (
165+ "LLVM_VERSION environment variable must be set."
222166 )
167+ url = f"https://github.com/llvm/llvm-project/releases/download/llvmorg-{ cls .LLVM_VERSION } /llvm-project-{ cls .LLVM_VERSION } .src.tar.xz"
223168
224- return f"{ source_dir } /runtimes"
169+ tmp = Path ("_downloads/libomp" ) / f"llvm-project-{ cls .LLVM_VERSION } .tar.gz"
170+ tmp .parent .mkdir (parents = True , exist_ok = True )
171+
172+ # Download the source tarball if it does not exist.
173+ if not tmp .exists ():
174+ print (
175+ f"Downloading llvm-project version { cls .LLVM_VERSION } url:" ,
176+ url ,
177+ flush = True ,
178+ )
179+ with urllib .request .urlopen (url ) as r :
180+ with tmp .open ("wb" ) as f :
181+ f .write (r .read ())
182+ else :
183+ print (f"Using downloaded llvm-project at { tmp } " , flush = True )
184+
185+ print ("Extracting llvm-project..." , flush = True )
186+ with tarfile .open (tmp ) as tf :
187+ # The root dir llvm-project-20.1.8.src
188+ root_name = tf .getnames ()[0 ]
189+
190+ # Extract only needed subdirectories
191+ members = [
192+ m
193+ for m in tf .getmembers ()
194+ if m .name .startswith (f"{ root_name } /openmp/" )
195+ or m .name .startswith (f"{ root_name } /offload/" )
196+ or m .name .startswith (f"{ root_name } /runtimes/" )
197+ or m .name .startswith (f"{ root_name } /cmake/" )
198+ or m .name .startswith (f"{ root_name } /llvm/cmake/" )
199+ or m .name .startswith (f"{ root_name } /llvm/utils/" )
200+ or m .name .startswith (f"{ root_name } /libc/" )
201+ ]
202+
203+ parentdir = tmp .parent
204+ # Base arguments for extractall.
205+ kwargs = {"path" : parentdir , "members" : members }
206+
207+ # Check if data filter is available.
208+ if hasattr (tarfile , "data_filter" ):
209+ # If this exists, the 'filter' argument is guaranteed to work
210+ kwargs ["filter" ] = "data"
211+
212+ tf .extractall (** kwargs )
213+
214+ source_dir = parentdir / root_name
215+ print ("Extracted llvm-project to:" , source_dir , flush = True )
216+
217+ print ("Applying patches to llvm-project..." , flush = True )
218+ for patch in sorted (
219+ Path (f"src/numba/openmp/libs/openmp/patches/{ cls .LLVM_VERSION } " )
220+ .absolute ()
221+ .glob ("*.patch" )
222+ ):
223+ print ("applying patch" , patch , flush = True )
224+ subprocess .run (
225+ ["patch" , "-p1" , "-i" , str (patch )],
226+ cwd = source_dir ,
227+ check = True ,
228+ stdin = subprocess .DEVNULL ,
229+ )
225230
226231
227232def _check_true (env_var ):
@@ -234,18 +239,15 @@ def _check_true(env_var):
234239ext_modules = [CMakeExtension ("pass" , source_dir = "src/numba/openmp/libs/pass" )]
235240
236241
237- # Prepare source directory if either bundled libomp or libomptarget is enabled.
238- if _check_true ("ENABLE_BUNDLED_LIBOMP" ) or _check_true ("ENABLE_BUNDLED_LIBOMPTARGET" ):
239- openmp_source_dir = _prepare_source_openmp ()
240-
241242# Optionally enable bundled libomp build via ENABLE_BUNDLED_LIBOMP=1. We want
242243# to avoid bundling for conda builds to avoid duplicate OpenMP runtime conflicts
243244# (e.g., numba 0.62+ and libopenblas already require llvm-openmp).
244245if _check_true ("ENABLE_BUNDLED_LIBOMP" ):
245246 ext_modules .append (
246247 CMakeExtension (
247248 "libomp" ,
248- source_dir = openmp_source_dir ,
249+ setup = PrepareOpenMP ,
250+ source_dir = PrepareOpenMP .get_source_dir (),
249251 install_dir = "openmp" ,
250252 cmake_args = [
251253 "-DOPENMP_STANDALONE_BUILD=ON" ,
@@ -265,7 +267,8 @@ def _check_true(env_var):
265267 ext_modules .append (
266268 CMakeExtension (
267269 "libomptarget" ,
268- source_dir = openmp_source_dir ,
270+ setup = PrepareOpenMP ,
271+ source_dir = PrepareOpenMP .get_source_dir (),
269272 install_dir = "openmp" ,
270273 cmake_args = [
271274 "-DOPENMP_STANDALONE_BUILD=ON" ,
@@ -283,6 +286,5 @@ def _check_true(env_var):
283286 cmdclass = {
284287 "clean" : CleanCommand ,
285288 "build_ext" : BuildCMakeExt ,
286- ** ({"bdist_wheel" : CustomBdistWheel } if CustomBdistWheel else {}),
287289 },
288290)
0 commit comments