22# SPDX-License-Identifier: Apache-2.0
33
44# This module implements basic PEP 517 backend support to defer CUDA-dependent
5- # logic (header parsing, code generation, cythonization) to build time. See:
5+ # logic (cythonization) to build time. See:
66# - https://peps.python.org/pep-0517/
77# - https://setuptools.pypa.io/en/latest/build_meta.html#dynamic-build-dependencies-and-other-build-meta-tweaks
88# - https://github.com/NVIDIA/cuda-python/issues/1635
@@ -74,207 +74,6 @@ def _get_cuda_path() -> str:
7474 return cuda_path
7575
7676
77- # -----------------------------------------------------------------------
78- # Header parsing helpers (called only from _build_cuda_bindings)
79-
80- _REQUIRED_HEADERS = {
81- "runtime" : [
82- "driver_types.h" ,
83- "vector_types.h" ,
84- "cuda_runtime.h" ,
85- "surface_types.h" ,
86- "texture_types.h" ,
87- "library_types.h" ,
88- "cuda_runtime_api.h" ,
89- "device_types.h" ,
90- "driver_functions.h" ,
91- "cuda_profiler_api.h" ,
92- ],
93- # nvrtc: headers no longer parsed at build time (pre-generated by cybind).
94- # During compilation, Cython will reference C headers that are not
95- # explicitly parsed above. These are the known dependencies:
96- #
97- # - crt/host_defines.h
98- # - builtin_types.h
99- # - cuda_device_runtime_api.h
100- }
101-
102-
103- class _Struct :
104- def __init__ (self , name , members ):
105- self ._name = name
106- self ._member_names = []
107- self ._member_types = []
108- self ._member_declarators = []
109- for var_name , var_type , _ in members :
110- base_type = var_type [0 ]
111- base_type = base_type .removeprefix ("struct " )
112- base_type = base_type .removeprefix ("union " )
113-
114- self ._member_names += [var_name ]
115- self ._member_types += [base_type ]
116- self ._member_declarators += [tuple (var_type [1 :])]
117-
118- def member_type (self , member_name ):
119- try :
120- return self ._member_types [self ._member_names .index (member_name )]
121- except ValueError :
122- return None
123-
124- def member_array_length (self , member_name ):
125- try :
126- declarators = self ._member_declarators [self ._member_names .index (member_name )]
127- except ValueError :
128- return None
129-
130- for declarator in declarators :
131- if isinstance (declarator , list ) and len (declarator ) == 1 :
132- return declarator [0 ]
133- return None
134-
135- def discoverMembers (self , memberDict , prefix , seen = None ):
136- if seen is None :
137- seen = set ()
138- elif self ._name in seen :
139- return []
140-
141- discovered = []
142- next_seen = set (seen )
143- next_seen .add (self ._name )
144-
145- for memberName , memberType in zip (self ._member_names , self ._member_types ):
146- if memberName :
147- discovered .append ("." .join ([prefix , memberName ]))
148-
149- t = memberType .replace ("const " , "" ).replace ("volatile " , "" ).strip ().rstrip (" *" )
150- if t in memberDict and t != self ._name :
151- discovered += memberDict [t ].discoverMembers (
152- memberDict , discovered [- 1 ] if memberName else prefix , next_seen
153- )
154-
155- return discovered
156-
157- def __repr__ (self ):
158- return f"{ self ._name } : { self ._member_names } with types { self ._member_types } "
159-
160-
161- def _fetch_header_paths (required_headers , include_path_list ):
162- header_dict = {}
163- missing_headers = []
164- for library , header_list in required_headers .items ():
165- header_paths = []
166- for header in header_list :
167- path_candidate = [os .path .join (path , header ) for path in include_path_list ]
168- for path in path_candidate :
169- if os .path .exists (path ):
170- header_paths += [path ]
171- break
172- else :
173- missing_headers += [header ]
174-
175- header_dict [library ] = header_paths
176-
177- if missing_headers :
178- error_message = "Couldn't find required headers: "
179- error_message += ", " .join (missing_headers )
180- cuda_path = _get_cuda_path ()
181- raise RuntimeError (f'{ error_message } \n Is CUDA_PATH setup correctly? (CUDA_PATH="{ cuda_path } ")' )
182-
183- return header_dict
184-
185-
186- def _parse_headers (header_dict , include_path_list , parser_caching ):
187- from pyclibrary import CParser
188-
189- found_types = []
190- found_functions = []
191- found_values = []
192- found_struct = []
193- struct_list = {}
194-
195- replace = {
196- " __device_builtin__ " : " " ,
197- "CUDARTAPI " : " " ,
198- "typedef __device_builtin__ enum cudaError cudaError_t;" : "typedef cudaError cudaError_t;" ,
199- "typedef __device_builtin__ enum cudaOutputMode cudaOutputMode_t;" : "typedef cudaOutputMode cudaOutputMode_t;" ,
200- "typedef enum cudaError cudaError_t;" : "typedef cudaError cudaError_t;" ,
201- "typedef enum cudaOutputMode cudaOutputMode_t;" : "typedef cudaOutputMode cudaOutputMode_t;" ,
202- "typedef enum cudaDataType_t cudaDataType_t;" : "" ,
203- "typedef enum libraryPropertyType_t libraryPropertyType_t;" : "" ,
204- " enum " : " " ,
205- ", enum " : ", " ,
206- "\\ (enum " : "(" ,
207- # Since we only support 64 bit architectures, we can inline the sizeof(T*) to 8 and then compute the
208- # result in Python. The arithmetic expression is preserved to help with clarity and understanding
209- r"char reserved\[52 - sizeof\(CUcheckpointGpuPair \*\)\];" : rf"char reserved[{ 52 - 8 } ];" ,
210- r"char reserved\[64 - sizeof\(CUcheckpointGpuPair \*\) - sizeof\(unsigned int\)\];" : rf"char reserved[{ 64 - 8 - 4 } ];" ,
211- }
212-
213- print (f'Parsing headers in "{ include_path_list } " (Caching = { parser_caching } )' , flush = True )
214- for library , header_paths in header_dict .items ():
215- print (f"Parsing { library } headers" , flush = True )
216- parser = CParser (
217- header_paths , cache = "./cache_{}" .format (library .split ("." )[0 ]) if parser_caching else None , replace = replace
218- )
219-
220- if library == "driver" :
221- CUDA_VERSION = parser .defs ["macros" ].get ("CUDA_VERSION" , "Unknown" )
222- print (f"Found CUDA_VERSION: { CUDA_VERSION } " , flush = True )
223-
224- found_types += set (parser .defs ["types" ])
225- found_types += set (parser .defs ["structs" ])
226- found_types += set (parser .defs ["unions" ])
227- found_types += set (parser .defs ["enums" ])
228- found_functions += set (parser .defs ["functions" ])
229- found_values += set (parser .defs ["values" ])
230-
231- for key , value in parser .defs ["structs" ].items ():
232- struct_list [key ] = _Struct (key , value ["members" ])
233- for key , value in parser .defs ["unions" ].items ():
234- struct_list [key ] = _Struct (key , value ["members" ])
235-
236- for key , value in struct_list .items ():
237- if key .startswith (("anon_union" , "anon_struct" )):
238- continue
239-
240- found_struct += [key ]
241- discovered = value .discoverMembers (struct_list , key )
242- if discovered :
243- found_struct += discovered
244-
245- # TODO(#1312): make this work properly
246- found_types .append ("CUstreamAtomicReductionDataType_enum" )
247-
248- return found_types , found_functions , found_values , found_struct , struct_list
249-
250-
251- # -----------------------------------------------------------------------
252- # Code generation helpers
253-
254-
255- def _fetch_input_files (path ):
256- return [os .path .join (path , f ) for f in os .listdir (path ) if f .endswith (".in" )]
257-
258-
259- def _generate_output (infile , template_vars ):
260- from Cython import Tempita
261-
262- assert infile .endswith (".in" )
263- outfile = infile [:- 3 ]
264-
265- with open (infile , encoding = "utf-8" ) as f :
266- pxdcontent = Tempita .Template (f .read ()).substitute (template_vars )
267-
268- if os .path .exists (outfile ):
269- with open (outfile , encoding = "utf-8" ) as f :
270- if f .read () == pxdcontent :
271- print (f"Skipping { infile } (No change)" , flush = True )
272- return
273- with open (outfile , "w" , encoding = "utf-8" ) as f :
274- print (f"Generating { infile } " , flush = True )
275- f .write (pxdcontent )
276-
277-
27877# -----------------------------------------------------------------------
27978# Extension preparation helpers
28079
@@ -328,9 +127,8 @@ def _prep_extensions(sources, libraries, include_dirs, library_dirs, extra_compi
328127def _build_cuda_bindings (debug = False ):
329128 """Build all cuda-bindings extensions.
330129
331- All CUDA-dependent logic (header parsing, code generation, cythonization)
332- is deferred to this function so that metadata queries do not require a
333- CUDA toolkit installation.
130+ All CUDA-dependent logic (cythonization) is deferred to this function so
131+ that metadata queries do not require a CUDA toolkit installation.
334132 """
335133 from Cython .Build import cythonize
336134
@@ -348,54 +146,10 @@ def _build_cuda_bindings(debug=False):
348146 else :
349147 nthreads = int (os .environ .get ("CUDA_PYTHON_PARALLEL_LEVEL" , "0" ) or "0" )
350148
351- parser_caching = bool (os .environ .get ("CUDA_PYTHON_PARSER_CACHING" , False ))
352149 compile_for_coverage = bool (int (os .environ .get ("CUDA_PYTHON_COVERAGE" , "0" )))
353150
354- # Parse CUDA headers
355- include_path_list = [os .path .join (cuda_path , "include" )]
356- header_dict = _fetch_header_paths (_REQUIRED_HEADERS , include_path_list )
357- found_types , found_functions , found_values , found_struct , struct_list = _parse_headers (
358- header_dict , include_path_list , parser_caching
359- )
360- struct_field_types = {}
361- struct_field_array_lengths = {}
362- for struct_name , struct in struct_list .items ():
363- for member_name in struct ._member_names :
364- key = f"{ struct_name } .{ member_name } "
365- struct_field_types [key ] = struct .member_type (member_name )
366- struct_field_array_lengths [key ] = struct .member_array_length (member_name )
367-
368- # Generate code from .in templates
369- path_list = [
370- os .path .join ("cuda" ),
371- os .path .join ("cuda" , "bindings" ),
372- os .path .join ("cuda" , "bindings" , "_bindings" ),
373- os .path .join ("cuda" , "bindings" , "_internal" ),
374- os .path .join ("cuda" , "bindings" , "_lib" ),
375- os .path .join ("cuda" , "bindings" , "utils" ),
376- ]
377- input_files = []
378- for path in path_list :
379- input_files += _fetch_input_files (path )
380-
381- import platform
382-
383- template_vars = {
384- "found_types" : found_types ,
385- "found_functions" : found_functions ,
386- "found_values" : found_values ,
387- "found_struct" : found_struct ,
388- "struct_list" : struct_list ,
389- "struct_field_types" : struct_field_types ,
390- "struct_field_array_lengths" : struct_field_array_lengths ,
391- "os" : os ,
392- "sys" : sys ,
393- "platform" : platform ,
394- }
395- for file in input_files :
396- _generate_output (file , template_vars )
397-
398151 # Prepare compile/link arguments
152+ include_path_list = [os .path .join (cuda_path , "include" )]
399153 include_dirs = [
400154 os .path .dirname (sysconfig .get_path ("include" )),
401155 ] + include_path_list
@@ -442,21 +196,26 @@ def _cleanup_dst_files():
442196
443197 # Build extension list
444198 extensions = []
445- static_runtime_libraries = ["cudart_static" , "rt" ] if sys .platform == "linux" else ["cudart_static" ]
446199 cuda_bindings_files = glob .glob ("cuda/bindings/*.pyx" )
447200 if sys .platform == "win32" :
448201 cuda_bindings_files = [f for f in cuda_bindings_files if "cufile" not in f ]
202+
203+ def get_static_libraries (f ):
204+ if os .path .basename (f ) in ("runtime.pyx" , "runtime_ptds.pyx" ):
205+ if sys .platform == "linux" :
206+ return ["cudart_static" , "rt" ]
207+ else :
208+ return ["cudart_static" ]
209+ return None
210+
449211 sources_list = [
450- # private
451- (["cuda/bindings/_bindings/cyruntime.pyx" ], static_runtime_libraries ),
452- (["cuda/bindings/_bindings/cyruntime_ptds.pyx" ], static_runtime_libraries ),
453212 # utils
454213 (["cuda/bindings/utils/*.pyx" ], None ),
455214 # public
456215 * (([f ], None ) for f in cuda_bindings_files ),
457216 # internal files used by generated bindings
458217 (["cuda/bindings/_internal/utils.pyx" ], None ),
459- * (([f ], None ) for f in dst_files if f .endswith (".pyx" )),
218+ * (([f ], get_static_libraries ( f ) ) for f in dst_files if f .endswith (".pyx" )),
460219 ]
461220
462221 for sources , libraries in sources_list :
0 commit comments