VirtualBox

source: vbox/trunk/src/libs/dxvk-2.3.1/include/vulkan/registry/generator.py

最後變更 在這個檔案是 105107,由 vboxsync 提交於 9 月 前

libs/dxvk-2.3.1: Make it build, bugref:10716

  • 屬性 svn:eol-style 設為 LF
  • 屬性 svn:executable 設為 *
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 53.2 KB
 
1#!/usr/bin/python3 -i
2#
3# Copyright 2013-2024 The Khronos Group Inc.
4#
5# SPDX-License-Identifier: Apache-2.0
6"""Base class for source/header/doc generators, as well as some utility functions."""
7
8from __future__ import unicode_literals
9
10import io
11import os
12import pdb
13import re
14import shutil
15import sys
16import tempfile
17try:
18 from pathlib import Path
19except ImportError:
20 from pathlib2 import Path # type: ignore
21
22from spec_tools.util import getElemName, getElemType
23
24
25def write(*args, **kwargs):
26 file = kwargs.pop('file', sys.stdout)
27 end = kwargs.pop('end', '\n')
28 file.write(' '.join(str(arg) for arg in args))
29 file.write(end)
30
31
32def noneStr(s):
33 """Return string argument, or "" if argument is None.
34
35 Used in converting etree Elements into text.
36 s - string to convert"""
37 if s:
38 return s
39 return ""
40
41
42def enquote(s):
43 """Return string argument with surrounding quotes,
44 for serialization into Python code."""
45 if s:
46 if isinstance(s, str):
47 return f"'{s}'"
48 else:
49 return s
50 return None
51
52
53def regSortCategoryKey(feature):
54 """Sort key for regSortFeatures.
55 Sorts by category of the feature name string:
56
57 - Core API features (those defined with a `<feature>` tag)
58 - (sort VKSC after VK - this is Vulkan-specific)
59 - ARB/KHR/OES (Khronos extensions)
60 - other (EXT/vendor extensions)"""
61
62 if feature.elem.tag == 'feature':
63 if feature.name.startswith('VKSC'):
64 return 0.5
65 else:
66 return 0
67
68 if feature.category.upper() in ['ARB', 'KHR', 'OES']:
69 return 1
70
71 return 2
72
73
74def regSortOrderKey(feature):
75 """Sort key for regSortFeatures - key is the sortorder attribute."""
76
77 return feature.sortorder
78
79
80def regSortNameKey(feature):
81 """Sort key for regSortFeatures - key is the extension name."""
82
83 return feature.name
84
85
86def regSortFeatureVersionKey(feature):
87 """Sort key for regSortFeatures - key is the feature version.
88 `<extension>` elements all have version number 0."""
89
90 return float(feature.versionNumber)
91
92
93def regSortExtensionNumberKey(feature):
94 """Sort key for regSortFeatures - key is the extension number.
95 `<feature>` elements all have extension number 0."""
96
97 return int(feature.number)
98
99
100def regSortFeatures(featureList):
101 """Default sort procedure for features.
102
103 - Sorts by explicit sort order (default 0) relative to other features
104 - then by feature category ('feature' or 'extension'),
105 - then by version number (for features)
106 - then by extension number (for extensions)"""
107 featureList.sort(key=regSortExtensionNumberKey)
108 featureList.sort(key=regSortFeatureVersionKey)
109 featureList.sort(key=regSortCategoryKey)
110 featureList.sort(key=regSortOrderKey)
111
112
113class MissingGeneratorOptionsError(RuntimeError):
114 """Error raised when a Generator tries to do something that requires GeneratorOptions but it is None."""
115
116 def __init__(self, msg=None):
117 full_msg = 'Missing generator options object self.genOpts'
118 if msg:
119 full_msg += ': ' + msg
120 super().__init__(full_msg)
121
122
123class MissingRegistryError(RuntimeError):
124 """Error raised when a Generator tries to do something that requires a Registry object but it is None."""
125
126 def __init__(self, msg=None):
127 full_msg = 'Missing Registry object self.registry'
128 if msg:
129 full_msg += ': ' + msg
130 super().__init__(full_msg)
131
132
133class MissingGeneratorOptionsConventionsError(RuntimeError):
134 """Error raised when a Generator tries to do something that requires a Conventions object but it is None."""
135
136 def __init__(self, msg=None):
137 full_msg = 'Missing Conventions object self.genOpts.conventions'
138 if msg:
139 full_msg += ': ' + msg
140 super().__init__(full_msg)
141
142
143class GeneratorOptions:
144 """Base class for options used during header/documentation production.
145
146 These options are target language independent, and used by
147 Registry.apiGen() and by base OutputGenerator objects."""
148
149 def __init__(self,
150 conventions=None,
151 filename=None,
152 directory='.',
153 genpath=None,
154 apiname=None,
155 mergeApiNames=None,
156 profile=None,
157 versions='.*',
158 emitversions='.*',
159 defaultExtensions=None,
160 addExtensions=None,
161 removeExtensions=None,
162 emitExtensions=None,
163 emitSpirv=None,
164 emitFormats=None,
165 reparentEnums=True,
166 sortProcedure=regSortFeatures,
167 requireCommandAliases=False,
168 requireDepends=True,
169 ):
170 """Constructor.
171
172 Arguments:
173
174 - conventions - may be mandatory for some generators:
175 an object that implements ConventionsBase
176 - filename - basename of file to generate, or None to write to stdout.
177 - directory - directory in which to generate filename
178 - genpath - path to previously generated files, such as apimap.py
179 - apiname - string matching `<api>` 'apiname' attribute, e.g. 'gl'.
180 - mergeApiNames - If not None, a comma separated list of API names
181 to merge into the API specified by 'apiname'
182 - profile - string specifying API profile , e.g. 'core', or None.
183 - versions - regex matching API versions to process interfaces for.
184 Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions.
185 - emitversions - regex matching API versions to actually emit
186 interfaces for (though all requested versions are considered
187 when deciding which interfaces to generate). For GL 4.3 glext.h,
188 this might be `'1[.][2-5]|[2-4][.][0-9]'`.
189 - defaultExtensions - If not None, a string which must in its
190 entirety match the pattern in the "supported" attribute of
191 the `<extension>`. Defaults to None. Usually the same as apiname.
192 - addExtensions - regex matching names of additional extensions
193 to include. Defaults to None.
194 - removeExtensions - regex matching names of extensions to
195 remove (after defaultExtensions and addExtensions). Defaults
196 to None.
197 - emitExtensions - regex matching names of extensions to actually emit
198 interfaces for (though all requested versions are considered when
199 deciding which interfaces to generate). Defaults to None.
200 - emitSpirv - regex matching names of extensions and capabilities
201 to actually emit interfaces for.
202 - emitFormats - regex matching names of formats to actually emit
203 interfaces for.
204 - reparentEnums - move <enum> elements which extend an enumerated
205 type from <feature> or <extension> elements to the target <enums>
206 element. This is required for almost all purposes, but the
207 InterfaceGenerator relies on the list of interfaces in the <feature>
208 or <extension> being complete. Defaults to True.
209 - sortProcedure - takes a list of FeatureInfo objects and sorts
210 them in place to a preferred order in the generated output.
211 - requireCommandAliases - if True, treat command aliases
212 as required dependencies.
213 - requireDepends - whether to follow API dependencies when emitting
214 APIs.
215
216 Default is
217 - core API versions
218 - Khronos (ARB/KHR/OES) extensions
219 - All other extensions
220 - By core API version number or extension number in each group.
221
222 The regex patterns can be None or empty, in which case they match
223 nothing."""
224 self.conventions = conventions
225 """may be mandatory for some generators:
226 an object that implements ConventionsBase"""
227
228 self.filename = filename
229 "basename of file to generate, or None to write to stdout."
230
231 self.genpath = genpath
232 """path to previously generated files, such as apimap.py"""
233
234 self.directory = directory
235 "directory in which to generate filename"
236
237 self.apiname = apiname
238 "string matching `<api>` 'apiname' attribute, e.g. 'gl'."
239
240 self.mergeApiNames = mergeApiNames
241 "comma separated list of API names to merge into the API specified by 'apiname'"
242
243 self.profile = profile
244 "string specifying API profile , e.g. 'core', or None."
245
246 self.versions = self.emptyRegex(versions)
247 """regex matching API versions to process interfaces for.
248 Normally `'.*'` or `'[0-9][.][0-9]'` to match all defined versions."""
249
250 self.emitversions = self.emptyRegex(emitversions)
251 """regex matching API versions to actually emit
252 interfaces for (though all requested versions are considered
253 when deciding which interfaces to generate). For GL 4.3 glext.h,
254 this might be `'1[.][2-5]|[2-4][.][0-9]'`."""
255
256 self.defaultExtensions = defaultExtensions
257 """If not None, a string which must in its
258 entirety match the pattern in the "supported" attribute of
259 the `<extension>`. Defaults to None. Usually the same as apiname."""
260
261 self.addExtensions = self.emptyRegex(addExtensions)
262 """regex matching names of additional extensions
263 to include. Defaults to None."""
264
265 self.removeExtensions = self.emptyRegex(removeExtensions)
266 """regex matching names of extensions to
267 remove (after defaultExtensions and addExtensions). Defaults
268 to None."""
269
270 self.emitExtensions = self.emptyRegex(emitExtensions)
271 """regex matching names of extensions to actually emit
272 interfaces for (though all requested versions are considered when
273 deciding which interfaces to generate)."""
274
275 self.emitSpirv = self.emptyRegex(emitSpirv)
276 """regex matching names of extensions and capabilities
277 to actually emit interfaces for."""
278
279 self.emitFormats = self.emptyRegex(emitFormats)
280 """regex matching names of formats
281 to actually emit interfaces for."""
282
283 self.reparentEnums = reparentEnums
284 """boolean specifying whether to remove <enum> elements from
285 <feature> or <extension> when extending an <enums> type."""
286
287 self.sortProcedure = sortProcedure
288 """takes a list of FeatureInfo objects and sorts
289 them in place to a preferred order in the generated output.
290 Default is core API versions, ARB/KHR/OES extensions, all
291 other extensions, alphabetically within each group."""
292
293 self.codeGenerator = False
294 """True if this generator makes compilable code"""
295
296 self.registry = None
297 """Populated later with the registry object."""
298
299 self.requireCommandAliases = requireCommandAliases
300 """True if alias= attributes of <command> tags are transitively
301 required."""
302
303 self.requireDepends = requireDepends
304 """True if dependencies of API tags are transitively required."""
305
306 def emptyRegex(self, pat):
307 """Substitute a regular expression which matches no version
308 or extension names for None or the empty string."""
309 if not pat:
310 return '_nomatch_^'
311
312 return pat
313
314
315class OutputGenerator:
316 """Generate specified API interfaces in a specific style, such as a C header.
317
318 Base class for generating API interfaces.
319 Manages basic logic, logging, and output file control.
320 Derived classes actually generate formatted output.
321 """
322
323 # categoryToPath - map XML 'category' to include file directory name
324 categoryToPath = {
325 'bitmask': 'flags',
326 'enum': 'enums',
327 'funcpointer': 'funcpointers',
328 'handle': 'handles',
329 'define': 'defines',
330 'basetype': 'basetypes',
331 }
332
333 def breakName(self, name, msg):
334 """Break into debugger if this is a special name"""
335
336 # List of string names to break on
337 bad = (
338 )
339
340 if name in bad and True:
341 print('breakName {}: {}'.format(name, msg))
342 pdb.set_trace()
343
344 def __init__(self, errFile=sys.stderr, warnFile=sys.stderr, diagFile=sys.stdout):
345 """Constructor
346
347 - errFile, warnFile, diagFile - file handles to write errors,
348 warnings, diagnostics to. May be None to not write."""
349 self.outFile = None
350 self.errFile = errFile
351 self.warnFile = warnFile
352 self.diagFile = diagFile
353 # Internal state
354 self.featureName = None
355 """The current feature name being generated."""
356
357 self.genOpts = None
358 """The GeneratorOptions subclass instance."""
359
360 self.registry = None
361 """The specification registry object."""
362
363 self.featureDictionary = {}
364 """The dictionary of dictionaries of API features."""
365
366 # Used for extension enum value generation
367 self.extBase = 1000000000
368 self.extBlockSize = 1000
369 self.madeDirs = {}
370
371 # API dictionary, which may be loaded by the beginFile method of
372 # derived generators.
373 self.apidict = None
374
375 # File suffix for generated files, set in beginFile below.
376 self.file_suffix = ''
377
378 def logMsg(self, level, *args):
379 """Write a message of different categories to different
380 destinations.
381
382 - `level`
383 - 'diag' (diagnostic, voluminous)
384 - 'warn' (warning)
385 - 'error' (fatal error - raises exception after logging)
386
387 - `*args` - print()-style arguments to direct to corresponding log"""
388 if level == 'error':
389 strfile = io.StringIO()
390 write('ERROR:', *args, file=strfile)
391 if self.errFile is not None:
392 write(strfile.getvalue(), file=self.errFile)
393 raise UserWarning(strfile.getvalue())
394 elif level == 'warn':
395 if self.warnFile is not None:
396 write('WARNING:', *args, file=self.warnFile)
397 elif level == 'diag':
398 if self.diagFile is not None:
399 write('DIAG:', *args, file=self.diagFile)
400 else:
401 raise UserWarning(
402 '*** FATAL ERROR in Generator.logMsg: unknown level:' + level)
403
404 def enumToValue(self, elem, needsNum, bitwidth = 32,
405 forceSuffix = False, parent_for_alias_dereference=None):
406 """Parse and convert an `<enum>` tag into a value.
407
408 - elem - <enum> Element
409 - needsNum - generate a numeric representation of the element value
410 - bitwidth - size of the numeric representation in bits (32 or 64)
411 - forceSuffix - if True, always use a 'U' / 'ULL' suffix on integers
412 - parent_for_alias_dereference - if not None, an Element containing
413 the parent of elem, used to look for elements this is an alias of
414
415 Returns a list:
416
417 - first element - integer representation of the value, or None
418 if needsNum is False. The value must be a legal number
419 if needsNum is True.
420 - second element - string representation of the value
421
422 There are several possible representations of values.
423
424 - A 'value' attribute simply contains the value.
425 - A 'bitpos' attribute defines a value by specifying the bit
426 position which is set in that value.
427 - An 'offset','extbase','extends' triplet specifies a value
428 as an offset to a base value defined by the specified
429 'extbase' extension name, which is then cast to the
430 typename specified by 'extends'. This requires probing
431 the registry database, and imbeds knowledge of the
432 API extension enum scheme in this function.
433 - An 'alias' attribute contains the name of another enum
434 which this is an alias of. The other enum must be
435 declared first when emitting this enum."""
436 if self.genOpts is None:
437 raise MissingGeneratorOptionsError()
438 if self.genOpts.conventions is None:
439 raise MissingGeneratorOptionsConventionsError()
440
441 name = elem.get('name')
442 numVal = None
443 if 'value' in elem.keys():
444 value = elem.get('value')
445 # print('About to translate value =', value, 'type =', type(value))
446 if needsNum:
447 numVal = int(value, 0)
448 # If there is a non-integer, numeric 'type' attribute (e.g. 'u' or
449 # 'ull'), append it to the string value.
450 # t = enuminfo.elem.get('type')
451 # if t is not None and t != '' and t != 'i' and t != 's':
452 # value += enuminfo.type
453 if forceSuffix:
454 if bitwidth == 64:
455 value = value + 'ULL'
456 else:
457 value = value + 'U'
458 self.logMsg('diag', 'Enum', name, '-> value [', numVal, ',', value, ']')
459 return [numVal, value]
460 if 'bitpos' in elem.keys():
461 value = elem.get('bitpos')
462 bitpos = int(value, 0)
463 numVal = 1 << bitpos
464 value = '0x%08x' % numVal
465 if bitwidth == 64 or bitpos >= 32:
466 value = value + 'ULL'
467 elif forceSuffix:
468 value = value + 'U'
469 self.logMsg('diag', 'Enum', name, '-> bitpos [', numVal, ',', value, ']')
470 return [numVal, value]
471 if 'offset' in elem.keys():
472 # Obtain values in the mapping from the attributes
473 enumNegative = False
474 offset = int(elem.get('offset'), 0)
475 extnumber = int(elem.get('extnumber'), 0)
476 extends = elem.get('extends')
477 if 'dir' in elem.keys():
478 enumNegative = True
479 self.logMsg('diag', 'Enum', name, 'offset =', offset,
480 'extnumber =', extnumber, 'extends =', extends,
481 'enumNegative =', enumNegative)
482 # Now determine the actual enumerant value, as defined
483 # in the "Layers and Extensions" appendix of the spec.
484 numVal = self.extBase + (extnumber - 1) * self.extBlockSize + offset
485 if enumNegative:
486 numVal *= -1
487 value = '%d' % numVal
488 # More logic needed!
489 self.logMsg('diag', 'Enum', name, '-> offset [', numVal, ',', value, ']')
490 return [numVal, value]
491 if 'alias' in elem.keys():
492 alias_of = elem.get('alias')
493 if parent_for_alias_dereference is None:
494 return (None, alias_of)
495 siblings = parent_for_alias_dereference.findall('enum')
496 for sib in siblings:
497 sib_name = sib.get('name')
498 if sib_name == alias_of:
499 return self.enumToValue(sib, needsNum)
500 raise RuntimeError("Could not find the aliased enum value")
501 return [None, None]
502
503 def checkDuplicateEnums(self, enums):
504 """Check enumerated values for duplicates.
505
506 - enums - list of `<enum>` Elements
507
508 returns the list with duplicates stripped"""
509 # Dictionaries indexed by name and numeric value.
510 # Entries are [ Element, numVal, strVal ] matching name or value
511
512 nameMap = {}
513 valueMap = {}
514
515 stripped = []
516 for elem in enums:
517 name = elem.get('name')
518 (numVal, strVal) = self.enumToValue(elem, True)
519
520 if name in nameMap:
521 # Duplicate name found; check values
522 (name2, numVal2, strVal2) = nameMap[name]
523
524 # Duplicate enum values for the same name are benign. This
525 # happens when defining the same enum conditionally in
526 # several extension blocks.
527 if (strVal2 == strVal or (numVal is not None
528 and numVal == numVal2)):
529 True
530 # self.logMsg('info', 'checkDuplicateEnums: Duplicate enum (' + name +
531 # ') found with the same value:' + strVal)
532 else:
533 self.logMsg('warn', 'checkDuplicateEnums: Duplicate enum (' + name
534 + ') found with different values:' + strVal
535 + ' and ' + strVal2)
536
537 # Do not add the duplicate to the returned list
538 continue
539 elif numVal in valueMap:
540 # Duplicate value found (such as an alias); report it, but
541 # still add this enum to the list.
542 (name2, numVal2, strVal2) = valueMap[numVal]
543
544 msg = 'Two enums found with the same value: {} = {} = {}'.format(
545 name, name2.get('name'), strVal)
546 self.logMsg('error', msg)
547
548 # Track this enum to detect followon duplicates
549 nameMap[name] = [elem, numVal, strVal]
550 if numVal is not None:
551 valueMap[numVal] = [elem, numVal, strVal]
552
553 # Add this enum to the list
554 stripped.append(elem)
555
556 # Return the list
557 return stripped
558
559 def misracstyle(self):
560 return False;
561
562 def misracppstyle(self):
563 return False;
564
565 def buildEnumCDecl(self, expand, groupinfo, groupName):
566 """Generate the C declaration for an enum"""
567 if self.genOpts is None:
568 raise MissingGeneratorOptionsError()
569 if self.genOpts.conventions is None:
570 raise MissingGeneratorOptionsConventionsError()
571
572 groupElem = groupinfo.elem
573
574 # Determine the required bit width for the enum group.
575 # 32 is the default, which generates C enum types for the values.
576 bitwidth = 32
577
578 # If the constFlagBits preference is set, 64 is the default for bitmasks
579 if self.genOpts.conventions.constFlagBits and groupElem.get('type') == 'bitmask':
580 bitwidth = 64
581
582 # Check for an explicitly defined bitwidth, which will override any defaults.
583 if groupElem.get('bitwidth'):
584 try:
585 bitwidth = int(groupElem.get('bitwidth'))
586 except ValueError as ve:
587 self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for ', groupName, ' - must be an integer value\n')
588 exit(1)
589
590 usebitmask = False
591 usedefine = False
592
593 # Bitmask flags can be generated as either "static const uint{32,64}_t" values,
594 # or as 32-bit C enums. 64-bit types must use uint64_t values.
595 if groupElem.get('type') == 'bitmask':
596 if bitwidth > 32 or self.misracppstyle():
597 usebitmask = True
598 if self.misracstyle():
599 usedefine = True
600
601 if usedefine or usebitmask:
602 # Validate the bitwidth and generate values appropriately
603 if bitwidth > 64:
604 self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for bitmask type ', groupName, ' - must be less than or equal to 64\n')
605 exit(1)
606 else:
607 return self.buildEnumCDecl_BitmaskOrDefine(groupinfo, groupName, bitwidth, usedefine)
608 else:
609 # Validate the bitwidth and generate values appropriately
610 if bitwidth > 32:
611 self.logMsg('error', 'Invalid value for bitwidth attribute (', groupElem.get('bitwidth'), ') for enum type ', groupName, ' - must be less than or equal to 32\n')
612 exit(1)
613 else:
614 return self.buildEnumCDecl_Enum(expand, groupinfo, groupName)
615
616 def buildEnumCDecl_BitmaskOrDefine(self, groupinfo, groupName, bitwidth, usedefine):
617 """Generate the C declaration for an "enum" that is actually a
618 set of flag bits"""
619 groupElem = groupinfo.elem
620 flagTypeName = groupElem.get('name')
621
622 # Prefix
623 body = "// Flag bits for " + flagTypeName + "\n"
624
625 if bitwidth == 64:
626 body += "typedef VkFlags64 %s;\n" % flagTypeName;
627 else:
628 body += "typedef VkFlags %s;\n" % flagTypeName;
629
630 # Maximum allowable value for a flag (unsigned 64-bit integer)
631 maxValidValue = 2**(64) - 1
632 minValidValue = 0
633
634 # Get a list of nested 'enum' tags.
635 enums = groupElem.findall('enum')
636
637 # Check for and report duplicates, and return a list with them
638 # removed.
639 enums = self.checkDuplicateEnums(enums)
640
641 # Accumulate non-numeric enumerant values separately and append
642 # them following the numeric values, to allow for aliases.
643 # NOTE: this does not do a topological sort yet, so aliases of
644 # aliases can still get in the wrong order.
645 aliasText = ''
646
647 # Loop over the nested 'enum' tags.
648 for elem in enums:
649 # Convert the value to an integer and use that to track min/max.
650 # Values of form -(number) are accepted but nothing more complex.
651 # Should catch exceptions here for more complex constructs. Not yet.
652 (numVal, strVal) = self.enumToValue(elem, True, bitwidth, True)
653 name = elem.get('name')
654
655 # Range check for the enum value
656 if numVal is not None and (numVal > maxValidValue or numVal < minValidValue):
657 self.logMsg('error', 'Allowable range for flag types in C is [', minValidValue, ',', maxValidValue, '], but', name, 'flag has a value outside of this (', strVal, ')\n')
658 exit(1)
659
660 decl = self.genRequirements(name, mustBeFound = False)
661
662 if self.isEnumRequired(elem):
663 protect = elem.get('protect')
664 if protect is not None:
665 body += '#ifdef {}\n'.format(protect)
666
667 if usedefine:
668 decl += "#define {} {}\n".format(name, strVal)
669 elif self.misracppstyle():
670 decl += "static constexpr {} {} {{{}}};\n".format(flagTypeName, name, strVal)
671 else:
672 # Some C compilers only allow initializing a 'static const' variable with a literal value.
673 # So initializing an alias from another 'static const' value would fail to compile.
674 # Work around this by chasing the aliases to get the actual value.
675 while numVal is None:
676 alias = self.registry.tree.find("enums/enum[@name='" + strVal + "']")
677 if alias is not None:
678 (numVal, strVal) = self.enumToValue(alias, True, bitwidth, True)
679 else:
680 self.logMsg('error', 'No such alias {} for enum {}'.format(strVal, name))
681 decl += "static const {} {} = {};\n".format(flagTypeName, name, strVal)
682
683 if numVal is not None:
684 body += decl
685 else:
686 aliasText += decl
687
688 if protect is not None:
689 body += '#endif\n'
690
691 # Now append the non-numeric enumerant values
692 body += aliasText
693
694 # Postfix
695
696 return ("bitmask", body)
697
698 def buildEnumCDecl_Enum(self, expand, groupinfo, groupName):
699 """Generate the C declaration for an enumerated type"""
700 groupElem = groupinfo.elem
701
702 # Break the group name into prefix and suffix portions for range
703 # enum generation
704 expandName = re.sub(r'([0-9]+|[a-z_])([A-Z0-9])', r'\1_\2', groupName).upper()
705 expandPrefix = expandName
706 expandSuffix = ''
707 expandSuffixMatch = re.search(r'[A-Z][A-Z]+$', groupName)
708 if expandSuffixMatch:
709 expandSuffix = '_' + expandSuffixMatch.group()
710 # Strip off the suffix from the prefix
711 expandPrefix = expandName.rsplit(expandSuffix, 1)[0]
712
713 # Prefix
714 body = ["typedef enum %s {" % groupName]
715
716 # @@ Should use the type="bitmask" attribute instead
717 isEnum = ('FLAG_BITS' not in expandPrefix)
718
719 # Allowable range for a C enum - which is that of a signed 32-bit integer
720 maxValidValue = 2**(32 - 1) - 1
721 minValidValue = (maxValidValue * -1) - 1
722
723 # Get a list of nested 'enum' tags.
724 enums = groupElem.findall('enum')
725
726 # Check for and report duplicates, and return a list with them
727 # removed.
728 enums = self.checkDuplicateEnums(enums)
729
730 # Loop over the nested 'enum' tags. Keep track of the minimum and
731 # maximum numeric values, if they can be determined; but only for
732 # core API enumerants, not extension enumerants. This is inferred
733 # by looking for 'extends' attributes.
734 minName = None
735
736 # Accumulate non-numeric enumerant values separately and append
737 # them following the numeric values, to allow for aliases.
738 # NOTE: this does not do a topological sort yet, so aliases of
739 # aliases can still get in the wrong order.
740 aliasText = []
741
742 maxName = None
743 minValue = None
744 maxValue = None
745 for elem in enums:
746 # Convert the value to an integer and use that to track min/max.
747 # Values of form -(number) are accepted but nothing more complex.
748 # Should catch exceptions here for more complex constructs. Not yet.
749 (numVal, strVal) = self.enumToValue(elem, True)
750 name = elem.get('name')
751
752 # Extension enumerants are only included if they are required
753 if self.isEnumRequired(elem):
754 decl = ''
755
756 protect = elem.get('protect')
757 if protect is not None:
758 decl += '#ifdef {}\n'.format(protect)
759
760 # Indent requirements comment, if there is one
761 requirements = self.genRequirements(name, mustBeFound = False)
762 if requirements != '':
763 requirements = ' ' + requirements
764 decl += requirements
765 decl += ' {} = {},'.format(name, strVal)
766
767 if protect is not None:
768 decl += '\n#endif'
769
770 if numVal is not None:
771 body.append(decl)
772 else:
773 aliasText.append(decl)
774
775 # Range check for the enum value
776 if numVal is not None and (numVal > maxValidValue or numVal < minValidValue):
777 self.logMsg('error', 'Allowable range for C enum types is [', minValidValue, ',', maxValidValue, '], but', name, 'has a value outside of this (', strVal, ')\n')
778 exit(1)
779
780 # Do not track min/max for non-numbers (numVal is None)
781 if isEnum and numVal is not None and elem.get('extends') is None:
782 if minName is None:
783 minName = maxName = name
784 minValue = maxValue = numVal
785 elif minValue is None or numVal < minValue:
786 minName = name
787 minValue = numVal
788 elif maxValue is None or numVal > maxValue:
789 maxName = name
790 maxValue = numVal
791
792 # Now append the non-numeric enumerant values
793 body.extend(aliasText)
794
795 # Generate min/max value tokens - legacy use case.
796 if isEnum and expand:
797 body.extend((f' {expandPrefix}_BEGIN_RANGE{expandSuffix} = {minName},',
798 f' {expandPrefix}_END_RANGE{expandSuffix} = {maxName},',
799 f' {expandPrefix}_RANGE_SIZE{expandSuffix} = ({maxName} - {minName} + 1),'))
800
801 # Generate a range-padding value to ensure the enum is 32 bits, but
802 # only in code generators, so it does not appear in documentation
803 if (self.genOpts.codeGenerator or
804 self.conventions.generate_max_enum_in_docs):
805 body.append(f' {expandPrefix}_MAX_ENUM{expandSuffix} = 0x7FFFFFFF')
806
807 # Postfix
808 body.append("} %s;" % groupName)
809
810 # Determine appropriate section for this declaration
811 if groupElem.get('type') == 'bitmask':
812 section = 'bitmask'
813 else:
814 section = 'group'
815
816 return (section, '\n'.join(body))
817
818 def buildConstantCDecl(self, enuminfo, name, alias):
819 """Generate the C declaration for a constant (a single <enum>
820 value).
821
822 <enum> tags may specify their values in several ways, but are
823 usually just integers or floating-point numbers."""
824
825 (_, strVal) = self.enumToValue(enuminfo.elem, False)
826
827 if self.misracppstyle() and enuminfo.elem.get('type') and not alias:
828 # Generate e.g.: static constexpr uint32_t x = ~static_cast<uint32_t>(1U);
829 # This appeases MISRA "underlying type" rules.
830 typeStr = enuminfo.elem.get('type');
831 invert = '~' in strVal
832 number = strVal.strip("()~UL")
833 if typeStr != "float":
834 number += 'U'
835 strVal = "~" if invert else ""
836 strVal += "static_cast<" + typeStr + ">(" + number + ")"
837 body = 'static constexpr ' + typeStr.ljust(9) + name.ljust(33) + ' {' + strVal + '};'
838 elif enuminfo.elem.get('type') and not alias:
839 # Generate e.g.: #define x (~0ULL)
840 typeStr = enuminfo.elem.get('type');
841 invert = '~' in strVal
842 paren = '(' in strVal
843 number = strVal.strip("()~UL")
844 if typeStr != "float":
845 if typeStr == "uint64_t":
846 number += 'ULL'
847 else:
848 number += 'U'
849 strVal = "~" if invert else ""
850 strVal += number
851 if paren:
852 strVal = "(" + strVal + ")";
853 body = '#define ' + name.ljust(33) + ' ' + strVal;
854 else:
855 body = '#define ' + name.ljust(33) + ' ' + strVal
856
857 return body
858
859 def makeDir(self, path):
860 """Create a directory, if not already done.
861
862 Generally called from derived generators creating hierarchies."""
863 self.logMsg('diag', 'OutputGenerator::makeDir(' + path + ')')
864 if path not in self.madeDirs:
865 # This can get race conditions with multiple writers, see
866 # https://stackoverflow.com/questions/273192/
867 if not os.path.exists(path):
868 os.makedirs(path)
869 self.madeDirs[path] = None
870
871 def beginFile(self, genOpts):
872 """Start a new interface file
873
874 - genOpts - GeneratorOptions controlling what is generated and how"""
875
876 self.genOpts = genOpts
877 if self.genOpts is None:
878 raise MissingGeneratorOptionsError()
879 if self.genOpts.conventions is None:
880 raise MissingGeneratorOptionsConventionsError()
881 self.should_insert_may_alias_macro = \
882 self.genOpts.conventions.should_insert_may_alias_macro(self.genOpts)
883 self.file_suffix = self.genOpts.conventions.file_suffix
884
885 # Try to import the API dictionary, apimap.py, if it exists. Nothing
886 # in apimap.py cannot be extracted directly from the XML, and in the
887 # future we should do that.
888 if self.genOpts.genpath is not None:
889 try:
890 sys.path.insert(0, self.genOpts.genpath)
891 import apimap
892 self.apidict = apimap
893 except ImportError:
894 self.apidict = None
895
896 self.conventions = genOpts.conventions
897
898 # Open a temporary file for accumulating output.
899 if self.genOpts.filename is not None:
900 self.outFile = tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', newline='\n', delete=False)
901 else:
902 self.outFile = sys.stdout
903
904 def endFile(self):
905 if self.errFile:
906 self.errFile.flush()
907 if self.warnFile:
908 self.warnFile.flush()
909 if self.diagFile:
910 self.diagFile.flush()
911 if self.outFile:
912 self.outFile.flush()
913 if self.outFile != sys.stdout and self.outFile != sys.stderr:
914 self.outFile.close()
915
916 if self.genOpts is None:
917 raise MissingGeneratorOptionsError()
918
919 # On successfully generating output, move the temporary file to the
920 # target file.
921 if self.genOpts.filename is not None:
922 if sys.platform == 'win32':
923 directory = Path(self.genOpts.directory)
924 if not Path.exists(directory):
925 os.makedirs(directory)
926 shutil.copy(self.outFile.name, self.genOpts.directory + '/' + self.genOpts.filename)
927 os.remove(self.outFile.name)
928 self.genOpts = None
929
930 def beginFeature(self, interface, emit):
931 """Write interface for a feature and tag generated features as having been done.
932
933 - interface - element for the `<version>` / `<extension>` to generate
934 - emit - actually write to the header only when True"""
935 self.emit = emit
936 self.featureName = interface.get('name')
937 # If there is an additional 'protect' attribute in the feature, save it
938 self.featureExtraProtect = interface.get('protect')
939
940 def endFeature(self):
941 """Finish an interface file, closing it when done.
942
943 Derived classes responsible for emitting feature"""
944 self.featureName = None
945 self.featureExtraProtect = None
946
947 def genRequirements(self, name, mustBeFound = True):
948 """Generate text showing what core versions and extensions introduce
949 an API. This exists in the base Generator class because it is used by
950 the shared enumerant-generating interfaces (buildEnumCDecl, etc.).
951 Here it returns an empty string for most generators, but can be
952 overridden by e.g. DocGenerator.
953
954 - name - name of the API
955 - mustBeFound - If True, when requirements for 'name' cannot be
956 determined, a warning comment is generated.
957 """
958
959 return ''
960
961 def validateFeature(self, featureType, featureName):
962 """Validate we are generating something only inside a `<feature>` tag"""
963 if self.featureName is None:
964 raise UserWarning('Attempt to generate', featureType,
965 featureName, 'when not in feature')
966
967 def genType(self, typeinfo, name, alias):
968 """Generate interface for a type
969
970 - typeinfo - TypeInfo for a type
971
972 Extend to generate as desired in your derived class."""
973 self.validateFeature('type', name)
974
975 def genStruct(self, typeinfo, typeName, alias):
976 """Generate interface for a C "struct" type.
977
978 - typeinfo - TypeInfo for a type interpreted as a struct
979
980 Extend to generate as desired in your derived class."""
981 self.validateFeature('struct', typeName)
982
983 # The mixed-mode <member> tags may contain no-op <comment> tags.
984 # It is convenient to remove them here where all output generators
985 # will benefit.
986 for member in typeinfo.elem.findall('.//member'):
987 for comment in member.findall('comment'):
988 member.remove(comment)
989
990 def genGroup(self, groupinfo, groupName, alias):
991 """Generate interface for a group of enums (C "enum")
992
993 - groupinfo - GroupInfo for a group.
994
995 Extend to generate as desired in your derived class."""
996
997 self.validateFeature('group', groupName)
998
999 def genEnum(self, enuminfo, typeName, alias):
1000 """Generate interface for an enum (constant).
1001
1002 - enuminfo - EnumInfo for an enum
1003 - name - enum name
1004
1005 Extend to generate as desired in your derived class."""
1006 self.validateFeature('enum', typeName)
1007
1008 def genCmd(self, cmd, cmdinfo, alias):
1009 """Generate interface for a command.
1010
1011 - cmdinfo - CmdInfo for a command
1012
1013 Extend to generate as desired in your derived class."""
1014 self.validateFeature('command', cmdinfo)
1015
1016 def genSpirv(self, spirv, spirvinfo, alias):
1017 """Generate interface for a spirv element.
1018
1019 - spirvinfo - SpirvInfo for a command
1020
1021 Extend to generate as desired in your derived class."""
1022 return
1023
1024 def genFormat(self, format, formatinfo, alias):
1025 """Generate interface for a format element.
1026
1027 - formatinfo - FormatInfo
1028
1029 Extend to generate as desired in your derived class."""
1030 return
1031
1032 def genSyncStage(self, stageinfo):
1033 """Generate interface for a sync stage element.
1034
1035 - stageinfo - SyncStageInfo
1036
1037 Extend to generate as desired in your derived class."""
1038 return
1039
1040 def genSyncAccess(self, accessinfo):
1041 """Generate interface for a sync stage element.
1042
1043 - accessinfo - AccessInfo
1044
1045 Extend to generate as desired in your derived class."""
1046 return
1047
1048 def genSyncPipeline(self, pipelineinfo):
1049 """Generate interface for a sync stage element.
1050
1051 - pipelineinfo - SyncPipelineInfo
1052
1053 Extend to generate as desired in your derived class."""
1054 return
1055
1056 def makeProtoName(self, name, tail):
1057 """Turn a `<proto>` `<name>` into C-language prototype
1058 and typedef declarations for that name.
1059
1060 - name - contents of `<name>` tag
1061 - tail - whatever text follows that tag in the Element"""
1062 if self.genOpts is None:
1063 raise MissingGeneratorOptionsError()
1064 return self.genOpts.apientry + name + tail
1065
1066 def makeTypedefName(self, name, tail):
1067 """Make the function-pointer typedef name for a command."""
1068 if self.genOpts is None:
1069 raise MissingGeneratorOptionsError()
1070 return '(' + self.genOpts.apientryp + 'PFN_' + name + tail + ')'
1071
1072 def makeCParamDecl(self, param, aligncol):
1073 """Return a string which is an indented, formatted
1074 declaration for a `<param>` or `<member>` block (e.g. function parameter
1075 or structure/union member).
1076
1077 - param - Element (`<param>` or `<member>`) to format
1078 - aligncol - if non-zero, attempt to align the nested `<name>` element
1079 at this column"""
1080 if self.genOpts is None:
1081 raise MissingGeneratorOptionsError()
1082 if self.genOpts.conventions is None:
1083 raise MissingGeneratorOptionsConventionsError()
1084 indent = ' '
1085 paramdecl = indent
1086 prefix = noneStr(param.text)
1087
1088 for elem in param:
1089 text = noneStr(elem.text)
1090 tail = noneStr(elem.tail)
1091
1092 if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail):
1093 # OpenXR-specific macro insertion - but not in apiinc for the spec
1094 tail = self.genOpts.conventions.make_voidpointer_alias(tail)
1095 if elem.tag == 'name' and aligncol > 0:
1096 self.logMsg('diag', 'Aligning parameter', elem.text, 'to column', self.genOpts.alignFuncParam)
1097 # Align at specified column, if possible
1098 paramdecl = paramdecl.rstrip()
1099 oldLen = len(paramdecl)
1100 # This works around a problem where very long type names -
1101 # longer than the alignment column - would run into the tail
1102 # text.
1103 paramdecl = paramdecl.ljust(aligncol - 1) + ' '
1104 newLen = len(paramdecl)
1105 self.logMsg('diag', 'Adjust length of parameter decl from', oldLen, 'to', newLen, ':', paramdecl)
1106
1107 if (self.misracppstyle() and prefix.find('const ') != -1):
1108 # Change pointer type order from e.g. "const void *" to "void const *".
1109 # If the string starts with 'const', reorder it to be after the first type.
1110 paramdecl += prefix.replace('const ', '') + text + ' const' + tail
1111 else:
1112 paramdecl += prefix + text + tail
1113
1114 # Clear prefix for subsequent iterations
1115 prefix = ''
1116
1117 paramdecl = paramdecl + prefix
1118
1119 if aligncol == 0:
1120 # Squeeze out multiple spaces other than the indentation
1121 paramdecl = indent + ' '.join(paramdecl.split())
1122 return paramdecl
1123
1124 def getCParamTypeLength(self, param):
1125 """Return the length of the type field is an indented, formatted
1126 declaration for a `<param>` or `<member>` block (e.g. function parameter
1127 or structure/union member).
1128
1129 - param - Element (`<param>` or `<member>`) to identify"""
1130 if self.genOpts is None:
1131 raise MissingGeneratorOptionsError()
1132 if self.genOpts.conventions is None:
1133 raise MissingGeneratorOptionsConventionsError()
1134
1135 # Allow for missing <name> tag
1136 newLen = 0
1137 paramdecl = ' ' + noneStr(param.text)
1138 for elem in param:
1139 text = noneStr(elem.text)
1140 tail = noneStr(elem.tail)
1141
1142 if self.should_insert_may_alias_macro and self.genOpts.conventions.is_voidpointer_alias(elem.tag, text, tail):
1143 # OpenXR-specific macro insertion
1144 tail = self.genOpts.conventions.make_voidpointer_alias(tail)
1145 if elem.tag == 'name':
1146 # Align at specified column, if possible
1147 newLen = len(paramdecl.rstrip())
1148 self.logMsg('diag', 'Identifying length of', elem.text, 'as', newLen)
1149 paramdecl += text + tail
1150
1151 return newLen
1152
1153 def getMaxCParamTypeLength(self, info):
1154 """Return the length of the longest type field for a member/parameter.
1155
1156 - info - TypeInfo or CommandInfo.
1157 """
1158 lengths = (self.getCParamTypeLength(member)
1159 for member in info.getMembers())
1160 return max(lengths)
1161
1162 def getHandleParent(self, typename):
1163 """Get the parent of a handle object."""
1164 if self.registry is None:
1165 raise MissingRegistryError()
1166
1167 info = self.registry.typedict.get(typename)
1168 if info is None:
1169 return None
1170
1171 elem = info.elem
1172 if elem is not None:
1173 return elem.get('parent')
1174
1175 return None
1176
1177 def iterateHandleAncestors(self, typename):
1178 """Iterate through the ancestors of a handle type."""
1179 current = self.getHandleParent(typename)
1180 while current is not None:
1181 yield current
1182 current = self.getHandleParent(current)
1183
1184 def getHandleAncestors(self, typename):
1185 """Get the ancestors of a handle object."""
1186 return list(self.iterateHandleAncestors(typename))
1187
1188 def getTypeCategory(self, typename):
1189 """Get the category of a type."""
1190 if self.registry is None:
1191 raise MissingRegistryError()
1192
1193 info = self.registry.typedict.get(typename)
1194 if info is None:
1195 return None
1196
1197 elem = info.elem
1198 if elem is not None:
1199 return elem.get('category')
1200 return None
1201
1202 def isStructAlwaysValid(self, structname):
1203 """Try to do check if a structure is always considered valid (i.e. there is no rules to its acceptance)."""
1204 # A conventions object is required for this call.
1205 if not self.conventions:
1206 raise RuntimeError("To use isStructAlwaysValid, be sure your options include a Conventions object.")
1207 if self.registry is None:
1208 raise MissingRegistryError()
1209
1210 if self.conventions.type_always_valid(structname):
1211 return True
1212
1213 category = self.getTypeCategory(structname)
1214 if self.conventions.category_requires_validation(category):
1215 return False
1216
1217 info = self.registry.typedict.get(structname)
1218 if info is None:
1219 self.logMsg('error', f'isStructAlwaysValid({structname}) - structure not found in typedict')
1220
1221 members = info.getMembers()
1222
1223 for member in members:
1224 member_name = getElemName(member)
1225 if member_name in (self.conventions.structtype_member_name,
1226 self.conventions.nextpointer_member_name):
1227 return False
1228
1229 if member.get('noautovalidity'):
1230 return False
1231
1232 member_type = getElemType(member)
1233
1234 if member_type in ('void', 'char') or self.paramIsArray(member) or self.paramIsPointer(member):
1235 return False
1236
1237 if self.conventions.type_always_valid(member_type):
1238 continue
1239
1240 member_category = self.getTypeCategory(member_type)
1241
1242 if self.conventions.category_requires_validation(member_category):
1243 return False
1244
1245 if member_category in ('struct', 'union'):
1246 if self.isStructAlwaysValid(member_type) is False:
1247 return False
1248
1249 return True
1250
1251 def paramIsArray(self, param):
1252 """Check if the parameter passed in is a pointer to an array.
1253
1254 param the XML information for the param
1255 """
1256 return param.get('len') is not None
1257
1258 def paramIsPointer(self, param):
1259 """Check if the parameter passed in is a pointer.
1260
1261 param the XML information for the param
1262 """
1263 tail = param.find('type').tail
1264 return tail is not None and '*' in tail
1265
1266 def isEnumRequired(self, elem):
1267 """Return True if this `<enum>` element is
1268 required, False otherwise
1269
1270 - elem - `<enum>` element to test"""
1271 required = elem.get('required') is not None
1272 self.logMsg('diag', 'isEnumRequired:', elem.get('name'),
1273 '->', required)
1274 return required
1275
1276 # @@@ This code is overridden by equivalent code now run in
1277 # @@@ Registry.generateFeature
1278
1279 required = False
1280
1281 extname = elem.get('extname')
1282 if extname is not None:
1283 # 'supported' attribute was injected when the <enum> element was
1284 # moved into the <enums> group in Registry.parseTree()
1285 if self.genOpts.defaultExtensions == elem.get('supported'):
1286 required = True
1287 elif re.match(self.genOpts.addExtensions, extname) is not None:
1288 required = True
1289 elif elem.get('version') is not None:
1290 required = re.match(self.genOpts.emitversions, elem.get('version')) is not None
1291 else:
1292 required = True
1293
1294 return required
1295
1296 def makeCDecls(self, cmd):
1297 """Return C prototype and function pointer typedef for a
1298 `<command>` Element, as a two-element list of strings.
1299
1300 - cmd - Element containing a `<command>` tag"""
1301 if self.genOpts is None:
1302 raise MissingGeneratorOptionsError()
1303 proto = cmd.find('proto')
1304 params = cmd.findall('param')
1305 # Begin accumulating prototype and typedef strings
1306 pdecl = self.genOpts.apicall
1307 tdecl = 'typedef '
1308
1309 # Insert the function return type/name.
1310 # For prototypes, add APIENTRY macro before the name
1311 # For typedefs, add (APIENTRY *<name>) around the name and
1312 # use the PFN_cmdnameproc naming convention.
1313 # Done by walking the tree for <proto> element by element.
1314 # etree has elem.text followed by (elem[i], elem[i].tail)
1315 # for each child element and any following text
1316 # Leading text
1317 pdecl += noneStr(proto.text)
1318 tdecl += noneStr(proto.text)
1319 # For each child element, if it is a <name> wrap in appropriate
1320 # declaration. Otherwise append its contents and tail contents.
1321 for elem in proto:
1322 text = noneStr(elem.text)
1323 tail = noneStr(elem.tail)
1324 if elem.tag == 'name':
1325 pdecl += self.makeProtoName(text, tail)
1326 tdecl += self.makeTypedefName(text, tail)
1327 else:
1328 pdecl += text + tail
1329 tdecl += text + tail
1330
1331 if self.genOpts.alignFuncParam == 0:
1332 # Squeeze out multiple spaces - there is no indentation
1333 pdecl = ' '.join(pdecl.split())
1334 tdecl = ' '.join(tdecl.split())
1335
1336 # Now add the parameter declaration list, which is identical
1337 # for prototypes and typedefs. Concatenate all the text from
1338 # a <param> node without the tags. No tree walking required
1339 # since all tags are ignored.
1340 # Uses: self.indentFuncProto
1341 # self.indentFuncPointer
1342 # self.alignFuncParam
1343 n = len(params)
1344 # Indented parameters
1345 if n > 0:
1346 indentdecl = '(\n'
1347 indentdecl += ',\n'.join(self.makeCParamDecl(p, self.genOpts.alignFuncParam)
1348 for p in params)
1349 indentdecl += ');'
1350 else:
1351 indentdecl = '(void);'
1352 # Non-indented parameters
1353 paramdecl = '('
1354 if n > 0:
1355 paramnames = []
1356 if self.misracppstyle():
1357 for p in params:
1358 param = ''
1359 firstIter = True;
1360 for t in p.itertext():
1361 if (firstIter):
1362 prefix = t
1363 firstIter = False
1364 else:
1365 # Change pointer type order from e.g. "const void *" to "void const *".
1366 # If the string starts with 'const', reorder it to be after the first type.
1367 if (prefix.find('const ') != -1):
1368 param += prefix.replace('const ', '') + t + ' const '
1369 else:
1370 param += prefix + t
1371 # Clear prefix for subsequent iterations
1372 prefix = ''
1373 paramnames.append(param);
1374 else:
1375 paramnames = (''.join(t for t in p.itertext())
1376 for p in params)
1377 paramdecl += ', '.join(paramnames)
1378 else:
1379 paramdecl += 'void'
1380 paramdecl += ");"
1381 return [pdecl + indentdecl, tdecl + paramdecl]
1382
1383 def newline(self):
1384 """Print a newline to the output file (utility function)"""
1385 write('', file=self.outFile)
1386
1387 def setRegistry(self, registry):
1388 self.registry = registry
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette