11from clang .cindex import Index , TranslationUnit , CursorKind , TokenKind , AccessSpecifier
2- import os , sys , json
2+ import os , sys , json , hashlib , shutil
33
44if 'NOT_SCONS' not in os .environ .keys ():
55 from SCons .Script import Glob
@@ -135,57 +135,58 @@ def generate_header_emitter(target, source, env):
135135 return [env .File (env ['gen_header' ])] + [env .File (filename_to_gen_filename (str (i ), env )) for i in source ], source
136136
137137
138- def generate_header (target , source , env ):
139- index = Index .create ()
140- try :
141- os .remove (os .path .join (env ['src' ], 'properties.gen.h' ))
142- except :
143- pass
138+ def generate_header_scons (target , source , env ):
139+ return generate_header (target , source , env , get_file_scons )
144140
145- try :
146- try :
147- sourcesigs , sources = target [0 ].get_stored_info ().binfo .bsourcesigs , target [0 ].get_stored_info ().binfo .bsources
148- cached_defs = load_defs_json (env ['defs_file' ])
149141
150- new_defs = {str (s ) : (cached_defs [str (s )]
151- if str (s ) in sources and s .get_csig () == sourcesigs [sources .index (str (s ))].csig and str (s ) in cached_defs .keys ()
152- else parse_and_write_header (index , * get_file_scons (s ), env )) for s in source }
153-
154- except AttributeError :
155- new_defs = {str (s ) : parse_and_write_header (index , * get_file_scons (s ), env ) for s in source }
156-
157- write_register_header (new_defs , env )
158- write_property_header (new_defs , env )
159-
160- with open (env ['defs_file' ], 'w' ) as file :
161- json .dump (new_defs , file , indent = 2 , default = lambda x : x if not isinstance (x , set ) else list (x ))
162-
163- except CppScriptException as e :
164- print (f'\n { e } \n ' , file = sys .stderr )
165- return 1
142+ def generate_header_cmake (target , source , env ):
143+ return generate_header (target , source , env , get_file_cmake )
166144
167145
168- def generate_header_cmake (target , source , env ):
146+ def generate_header (target , source , env , get_file ):
169147 index = Index .create ()
148+ prop_file_name = os .path .join (env ['src' ], 'properties.gen.h' )
170149 try :
171- os . remove ( os . path . join ( env [ 'src' ], 'properties.gen.h' ) )
150+ shutil . move ( prop_file_name , prop_file_name + '.tmp' )
172151 except :
173152 pass
174-
153+
175154 try :
176- cached_defs = load_defs_json (env ['defs_file' ])
177- new_defs = {str (s ) : parse_and_write_header (index , * get_file_cmake (s ), env ) for s in source }
178-
179- write_register_header (new_defs , env )
180- write_property_header (new_defs , env )
155+ cached_defs_all = load_defs_json (env ['defs_file' ])
156+ cached_defs = cached_defs_all .get ('files' , {})
157+ need_regen = False
158+
159+ new_defs_files = {}
160+ for s in source :
161+ filename , file_content = get_file (s )
162+ new_hash = hashlib .md5 (file_content .encode ()).hexdigest ()
163+ if filename not in cached_defs .keys () or new_hash != cached_defs [filename ]['hash' ]:
164+ need_regen = True
165+ new_defs_files |= {filename : {'content' : parse_and_write_header (index , filename , file_content , env ), 'hash' : new_hash }}
166+ else :
167+ new_defs_files |= {filename : cached_defs [filename ]}
168+
169+ new_defs_all = {'hash' : cached_defs_all .get ('hash' , None ), 'files' : new_defs_files }
170+
171+ if write_register_header (new_defs_all , env ) or need_regen :
172+ write_property_header (new_defs_all , env )
173+ try :
174+ os .remove (prop_file_name + '.tmp' )
175+ except :
176+ pass
177+ else :
178+ try :
179+ shutil .move (prop_file_name + '.tmp' , prop_file_name )
180+ except :
181+ pass
181182
182183 with open (env ['defs_file' ], 'w' ) as file :
183- json .dump (new_defs , file , indent = 2 , default = lambda x : x if not isinstance (x , set ) else list (x ))
184-
184+ json .dump (new_defs_all , file , indent = 2 , default = lambda x : x if not isinstance (x , set ) else list (x ))
185+
185186 except CppScriptException as e :
186187 print (f'\n { e } \n ' , file = sys .stderr )
187188 return 1
188-
189+
189190 return 0
190191
191192def parse_header (index , filename , filecontent , src , auto_methods ):
@@ -202,7 +203,7 @@ def parse_class(parent, class_cursors):
202203 case CursorKind .CXX_METHOD :
203204 if cursor .access_specifier == AccessSpecifier .PUBLIC :
204205 class_cursors .append (cursor )
205-
206+
206207 case CursorKind .FIELD_DECL :
207208 class_cursors .append (cursor )
208209
@@ -234,21 +235,21 @@ def parse_cursor(parent):
234235 parse_cursor (cursor )
235236
236237 parse_cursor (translation_unit .cursor )
237- found_class = sorted (classes_and_Gmacros , key = lambda x : x .extent .start .offset , reverse = True )
238+ found_class = sorted (classes_and_Gmacros , key = lambda x : x .extent .start .offset , reverse = True )
238239 classes = []
239240 def add_class (cursor , macros ):
240241 if len (macros ) > 1 :
241242 wrong_macro = macros [- 2 ]
242243 raise CppScriptException ('{}:{}:{}: error: repeated class macro for "{}" class defined at {}:{}'
243244 .format (filename , wrong_macro .location .line , wrong_macro .location .column , cursor .spelling , cursor .location .line , cursor .location .column ))
244245
245-
246+
246247 for macro in macros :
247248 classes .append ((cursor , get_macro_args (filecontent , macro )[1 ], macro .spelling [1 :]))
248249
249250
250251 collapse_list (found_class , lambda x : x .kind == CursorKind .CLASS_DECL , add_class )
251-
252+
252253 parsed_classes = {}
253254 for cursor , base , type in classes :
254255 class_defs = {
@@ -293,7 +294,7 @@ def process_macros(item, macros, properties, is_ignored=False):
293294 if len (args ) < 2 :
294295 raise CppScriptException ('{}:{}:{}: error: incorrect {} macro usage: must be at least 2 arguments: setter and getter'
295296 .format (filename , macro .location .line , macro .location .column , macro .spelling ))
296-
297+
297298 properties |= {
298299 'setter' : args [0 ],
299300 'getter' : args [1 ],
@@ -317,7 +318,7 @@ def process_macros(item, macros, properties, is_ignored=False):
317318 if item .type .spelling [- 1 ] == ')' :
318319 raise CppScriptException ('{}:{}:{}: error: enum at {}:{} must be named'
319320 .format (filename , macro .location .line , macro .location .column , item .location .line , item .location .column ))
320-
321+
321322 properties ['enum_type' ] = 'bitfields'
322323
323324 case 'GSIGNAL' :
@@ -381,7 +382,7 @@ def process_macros(item, macros, properties, is_ignored=False):
381382 'transfer_mode' : 'TRANSFER_MODE_' + transfer_mode if transfer_mode != None else 'TRANSFER_MODE_UNRELIABLE' ,
382383 'call_local' : call_local if call_local != None else 'false' ,
383384 'channel' : channel if channel != None else '0' }
384-
385+
385386 properties ['rpc_config' ] = rpc_config
386387
387388 case 'GVARARG' :
@@ -390,7 +391,7 @@ def process_macros(item, macros, properties, is_ignored=False):
390391 .format (filename , macro .location .line , macro .location .column , macro .spelling , item .location .line , item .location .column ))
391392
392393 properties ['varargs' ] = get_pair_arglist (get_macro_args (filecontent , macro ), 'Variant' )
393-
394+
394395 case 'GIGNORE' :
395396 is_ignored = True
396397
@@ -438,7 +439,7 @@ def apply_macros(item, macros):
438439 class_defs ['properties' ].append (properties )
439440
440441
441-
442+
442443 leftover = collapse_list (class_macros , lambda x : x .kind != CursorKind .MACRO_INSTANTIATION , apply_macros )
443444 for macro in leftover :
444445 if macro .spelling not in ['GSIGNAL' , 'GGROUP' , 'GSUBGROUP' ]:
@@ -455,6 +456,7 @@ def apply_macros(item, macros):
455456def parse_and_write_header (index , filename , filecontent , env ):
456457 defs = parse_header (index , filename , filecontent , env ['src' ], env ['auto_methods' ])
457458 write_header (filename , defs , env )
459+
458460 return defs
459461
460462
@@ -558,7 +560,6 @@ def write_header(file, defs, env):
558560 ([outside_bind ] if outside_bind != '' else [])
559561
560562 file_name = filename_to_gen_filename (file , env )
561- print (file_name )
562563 content = ''
563564 if len (defs ) != 0 :
564565 header_include = '#include <cppscript_bindings.h>\n \n #include <{}>\n \n using namespace godot;\n \n ' .format (os .path .relpath (file , src ).replace ('\\ ' , '/' ))
@@ -569,13 +570,14 @@ def write_header(file, defs, env):
569570 fileopen .write (content )
570571
571572
572- def write_register_header (defs , env ):
573+ def write_register_header (defs_all , env ):
573574 src = env ['src' ]
574575 target = env ['gen_header' ]
575576 scripts_header = ''
576577 classes_register_levels = {name [12 :] : [] for name in INIT_LEVELS }
577578
578- for file , classes in defs .items ():
579+ for file , filecontent in defs_all ['files' ].items ():
580+ classes = filecontent ['content' ]
579581 if len (classes ) == 0 :
580582 continue
581583
@@ -604,18 +606,27 @@ def write_register_header(defs, env):
604606
605607 scripts_header += classes_register_str
606608
607- with open (target , 'w' ) as file :
608- file .write (scripts_header )
609+ new_hash = hashlib .md5 (scripts_header .encode ()).hexdigest ()
610+
611+ if new_hash != defs_all ['hash' ]:
612+ with open (target , 'w' ) as file :
613+ file .write (scripts_header )
614+ defs_all ['hash' ] = new_hash
615+
616+ return True
617+
618+ return False
609619
610620
611621def write_property_header (new_defs , env ):
612622 filepath = os .path .join (env ['src' ], 'properties.gen.h' )
613623 body = ''
614- for _ , file in new_defs .items ():
615- for class_name_full , content in file .items ():
624+ for filename , filecontent in new_defs ['files' ].items ():
625+ classcontent = filecontent ['content' ]
626+ for class_name_full , content in classcontent .items ():
616627 gen_setgets = [f' \\ \n GENERATE_GETTER_DECLARATION({ g } , { n } )' for g , n in content ['gen_getters' ]] + [f' \\ \n GENERATE_SETTER_DECLARATION({ g } , { n } )' for g , n in content ['gen_setters' ]]
617628 body += f'#define GSETGET_{ content ["class_name" ]} ' + '' .join (gen_setgets ) + '\n \n '
618-
629+
619630 with open (filepath , 'w' ) as file :
620631 file .write (body )
621632
0 commit comments