Skip to content

Commit f2afa2d

Browse files
author
Vano
committed
more CMake support code
1 parent ca3ad79 commit f2afa2d

File tree

3 files changed

+73
-59
lines changed

3 files changed

+73
-59
lines changed

SConstruct

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from cppscript import GlobRecursive, generate_header, generate_header_emitter
1+
from cppscript import GlobRecursive, generate_header_scons, generate_header_emitter
22
import os
33

44
SRC_DIR = '../src'
@@ -18,7 +18,7 @@ env['gen_header'] = os.path.join(SRC_DIR, 'scripts.gen.h') # Path to generated h
1818
env['auto_methods'] = True # Generate bindings to public methods automatically
1919
# Or require GMETHOD() before methods
2020
env.Append(BUILDERS={'CppScript' : Builder(
21-
action=generate_header,
21+
action=generate_header_scons,
2222
emitter=generate_header_emitter)})
2323

2424
generated = env.CppScript(scripts)

cppscript.py

Lines changed: 66 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from clang.cindex import Index, TranslationUnit, CursorKind, TokenKind, AccessSpecifier
2-
import os, sys, json
2+
import os, sys, json, hashlib, shutil
33

44
if '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

191192
def 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):
455456
def 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\nusing 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

611621
def 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' \\\nGENERATE_GETTER_DECLARATION({g}, {n})' for g, n in content['gen_getters']] + [f' \\\nGENERATE_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

cppscript_bindings.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import argparse, os, sys
2+
3+
# Hack to not import SCons
24
os.environ['NOT_SCONS'] = '1'
35
from cppscript import generate_header_cmake
46

@@ -7,7 +9,7 @@
79
description='Generates C++ bindings code for GDExtension')
810

911
parser.add_argument('--src', type=str, nargs=1, required=True)
10-
parser.add_argument('--gen_dir', type=str, nargs=1, required=True)
12+
parser.add_argument('--gen-dir', type=str, nargs=1, required=True)
1113
parser.add_argument('--defs-file', type=str, nargs=1, required=True)
1214
parser.add_argument('--gen-header', type=str, nargs=1, required=True)
1315
parser.add_argument('--auto-methods', type=bool, default=True)
@@ -19,7 +21,8 @@
1921
'gen_dir' : args.gen_dir[0],
2022
'defs_file' : args.defs_file[0],
2123
'gen_header' : args.gen_header[0],
22-
'auto_methods' : args.auto_methods}
24+
'auto_methods' : args.auto_methods
25+
}
2326

2427
sys.exit(generate_header_cmake(args.gen_header, args.sources, env))
2528

0 commit comments

Comments
 (0)