1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
|
#!/usr/bin/env python3
# Copyright (C) 2021 The Qt Company Ltd.
# Copyright (C) 2019 Luxoft Sweden AB
# Copyright (C) 2018 Pelagicore AG
# Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB)
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
import os
import sys
import fnmatch
import click
import logging.config
import tempfile
from pathlib import Path
from qface.generator import FileSystem, Generator
from qface.watch import monitor
from qface.utils import load_filters
import generator.builtin_config as builtin_config
import generator.global_functions as global_functions
from generator.filters import register_filters
from generator.rule_generator import CustomRuleGenerator
from functools import reduce
def deep_get(dictionary, keys, default=None):
return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)
here = Path(__file__).parent
log = logging.getLogger(__file__)
deprecatedAnnotations = {}
def validateType(srcFile, type, errorString):
if type.is_interface:
sys.exit("{0}: {1} of type 'interface' are not supported".format(srcFile, errorString))
if type.is_map:
sys.exit("{0}: {1} of type 'map' are not supported".format(srcFile, errorString))
def validateSystem(srcFile, system):
"""
Searches for types we don't support and reports an error
Also checks all annotations for deprecations
"""
checkDeprecationOfAnnotation(system)
for module in system.modules:
checkDeprecationOfAnnotation(module)
for interface in module.interfaces:
checkDeprecationOfAnnotation(interface)
for property in interface.properties:
checkDeprecationOfAnnotation(property)
validateType(srcFile, property.type, "Properties")
for operation in interface.operations:
checkDeprecationOfAnnotation(operation)
for param in operation.parameters:
checkDeprecationOfAnnotation(param)
validateType(srcFile, param.type, "Arguments")
validateType(srcFile, operation.type, "Return values")
for signal in interface.signals:
checkDeprecationOfAnnotation(signal)
for param in signal.parameters:
checkDeprecationOfAnnotation(param)
validateType(srcFile, param.type, "Arguments")
for struct in module.structs:
checkDeprecationOfAnnotation(struct)
for field in struct.fields:
checkDeprecationOfAnnotation(field)
validateType(srcFile, field.type, "Fields")
def checkDeprecationOfAnnotation(symbol):
type_str = symbol.__class__.__name__
if type_str in deprecatedAnnotations:
anno = deprecatedAnnotations[type_str]['annotation']
if deep_get(symbol.tags, anno):
log.warning("warning: {0} is deprecated and will be removed in future Qt versions".format(anno))
def deprecateAnnotation(type, annotation, sinceVersion):
deprecatedAnnotations[type] = {
'annotation': annotation,
'sinceVersion': sinceVersion
}
def generate(template_search_paths, tplconfig, moduleConfig, annotations, imports, src, dst, target_platform):
log.debug('run {0} {1}'.format(src, dst))
FileSystem.strict = True
Generator.strict = True
# First run without imports to know the name of the modules we want to generate
module_names = []
system = FileSystem.parse(src)
for module in system.modules:
module_names.append(module.name)
# Second run with imports to resolve all needed type information
all_files = imports + src
system = FileSystem.parse(all_files)
for annotations_file in annotations:
log.debug('{0}'.format(annotations_file))
if not os.path.isabs(annotations_file):
annotations_file = Path.getcwd() / str(annotations_file)
if not Path(annotations_file).exists():
print('no such annotation file: {0}'.format(annotations_file))
exit(1)
FileSystem.merge_annotations(system, Path(annotations_file))
srcFile = os.path.basename(src[0])
srcBase = os.path.splitext(srcFile)[0]
global_functions.currentSrcFile = srcFile
ctx = {
# To be backward compatible
'qtASVersion': builtin_config.config["VERSION"],
'ifcodegenVersion': builtin_config.config["VERSION"],
'srcFile': srcFile,
'srcBase': srcBase,
'targetPlatform': target_platform # Add target_platform to context
}
search_path = [tplconfig]
# In case tplconfig is a path, we also want to add the containing folder to the search-path
# This makes sure that also templates referenced by path can use common templates or commmon filters
if os.path.exists(tplconfig):
search_path += [os.path.dirname(tplconfig)]
search_path += template_search_paths
generator = CustomRuleGenerator(search_path=search_path, destination=dst,
context=ctx, modules=module_names)
generator.env.keep_trailing_newline = True
global_functions.register_global_functions(generator)
register_filters(generator)
# Add the current path to the module search path
# This makes it possible to import our filters.py and friends
# from the plugin filters
sys.path.append(os.path.join(os.path.dirname(__file__)))
# Add a module specific extra filter if found
extra_filter_path = os.path.dirname(tplconfig) + '/{0}/filters.py'.format(os.path.basename(tplconfig))
if os.path.exists(extra_filter_path):
extra_filters = load_filters(Path(extra_filter_path))
generator.filters = extra_filters
validateSystem(srcFile, system)
# Make sure the config tag is available for all our symbols
for module in system.modules:
module.add_tag('config')
for val, key in moduleConfig.items():
module.add_attribute('config', val, key)
for interface in module.interfaces:
interface.add_tag('config')
for property in interface.properties:
property.add_tag('config')
for operation in interface.operations:
operation.add_tag('config')
for signal in interface.signals:
signal.add_tag('config')
for struct in module.structs:
struct.add_tag('config')
for field in struct.fields:
field.add_tag('config')
for enum in module.enums:
enum.add_tag('config')
for member in enum.members:
member.add_tag('config')
generator.process_rules(os.path.dirname(tplconfig) + '/{0}.yaml'.format(os.path.basename(tplconfig)), system)
def run(template_search_paths, template, moduleConfig, annotations, imports, src, dst, target_platform):
templatePath = template
foundTemplates = {}
for path in template_search_paths:
for f in os.listdir(path):
if fnmatch.fnmatch(f, '*.yaml'):
t = os.path.splitext(f)[0]
foundTemplates[t] = Path(path) / t
if template in foundTemplates:
templatePath = foundTemplates[template]
if os.path.exists(templatePath):
generate(template_search_paths, templatePath, moduleConfig, annotations, imports, src, dst, target_platform)
else:
print('Invalid Template: {0}. It either needs to be one of the templates found in the template'
'search path or an existing template folder. The following templates have been found: {1}'
.format(templatePath, list(foundTemplates.keys())))
exit(1)
def self_check(ctx, param, value):
if not value or ctx.resilient_parsing:
return
click.echo('Running self check')
try:
# Parse the .config file and throw an error in case it doesn't exist or it is invalid
builtin_config.parse(here)
tmpDir = tempfile.TemporaryDirectory()
tmp = Path(tmpDir.name)
with open(tmp / "test.qface", 'w') as file:
# Write content to the file
file.write("""
module org.selftest 1.0
interface Echo {
string stringProperty
bool boolProperty
TestEnum enumProperty
TestStruct structProperty
string echo(string msg);
}
enum TestEnum {
value1,
value2
}
struct TestStruct {
int testInt
string testString
}
""")
with open(tmp / "selfcheck.yaml", 'w') as file:
# Write content to the file
file.write("""
test:
module:
documents:
- "{{srcBase|lower}}": "module.tpl"
interface:
documents:
- 'tst_{{interface|lower}}': 'interface.tpl'
""")
os.mkdir(tmp / "selfcheck")
with open(tmp / "selfcheck/module.tpl", 'w') as file:
# Write content to the file
file.write("""
{{module.name}}
""")
with open(tmp / "selfcheck/interface.tpl", 'w') as file:
# Write content to the file
file.write("""
{{interface.name}}
""")
run([tmp], 'selfcheck', {"module": "org.selftest", "force": True}, [], [], [tmp / "test.qface"], tmp, "test_platform")
click.echo('Self check finished successfully.')
except Exception as e:
raise SystemExit("""
Self check failed!
This might be caused by a too recent python version or
too recent python packages.
If this happens when building the qtinterfaceframework,
you can try installing older python packages by
running configure again with the following option:
-DQT_USE_MINIMAL_QFACE_PACKAGES=TRUE
The python error was:
{}
""".format(e))
ctx.exit()
@click.command()
@click.option('--reload/--no-reload', default=False, help=
'Specifies whether the generator should keep track of the changes in the IDL file and update '
'output on the fly (--no-reload by default).')
@click.option('--template-search-path', '-T', 'template_search_paths', multiple=True, required=True, help=
'Adds the given path to the list of template search paths. All directories in this list are '
'scanned for template YAML files and can be selected afterwards as a generation templates using'
'--template option.')
@click.option('--template', '-t', multiple=False, help='The template the autogenerator should use for '
'the generation. This can either be one of the templates found in the templates search path '
'or a path to a template folder. ')
@click.option('--module', help='The name of the Qt module the autogenerator is '
'generating. This is automatically used by the qmake integration and passed directly to the '
'qface templates.')
@click.option('--force', is_flag=True, default=False, help='Always write all output files')
@click.option('--annotations', '-A', multiple=True, help=
'Merges the given annotation file with annotions already in the qface file and the '
'implicit annotation file. The annotation files will be merged in the order they are passed '
'to the generator. Providing a duplicate key in the YAML file will override the previously '
'set value. This option can be used multiple times.')
@click.option('--import', '-I', 'imports' , multiple=True, help=
'Adds the given path to the list of import paths. All directories in this list are '
'scanned recursively for QFace files. The QFace files found are then used to resolve '
'the information required when importing a module; this is similar to how C++ include '
'paths work.')
@click.option('--selfcheck', is_flag=True, default=False, callback=self_check, expose_value=False, is_eager=True, help=
'Runs a self check using a builtin qface file and template to verify that the generator is '
'working correctly. ')
@click.option('--target-platform', '-P', help='Specify the target platform for the generation.')
@click.argument('src', nargs=-1, type=click.Path(exists=True))
@click.argument('dst', nargs=1, type=click.Path(exists=True))
def app(src, dst, template_search_paths, template, reload, module, force, annotations, imports, target_platform):
"""
The QtInterfaceFramework Autogenerator (ifcodegen)
It takes several files or directories as src and generates the code
in the given dst directory.
"""
# Parse the .config file and throw an error in case it doesn't exist or it is invalid
builtin_config.parse(here)
if reload:
script = '{0} {1} {2}'.format(Path(__file__).abspath(), ' '.join(src), dst)
monitor(src, script)
else:
moduleConfig = {
"module": module,
"force": force
}
run(template_search_paths, template, moduleConfig, annotations, imports, src, dst, target_platform)
if __name__ == '__main__':
app()
|