• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

midstar / pycstruct / #148

24 Mar 2024 08:39PM UTC coverage: 96.64% (-0.004%) from 96.644%
#148

push

coveralls-python

midstar
Fixed coveralls issue

978 of 1012 relevant lines covered (96.64%)

0.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.03
/pycstruct/cparser.py
1
"""pycstruct cparser
2

3
Copyright 2021 by Joel Midstjärna.
4
All rights reserved.
5
This file is part of the pycstruct python library and is
6
released under the "MIT License Agreement". Please see the LICENSE
7
file that should have been included as part of this package.
8
"""
9

10
import xml.etree.ElementTree as ET
1✔
11

12
import hashlib
1✔
13
import logging
1✔
14
import math
1✔
15
import os
1✔
16
import shutil
1✔
17
import subprocess
1✔
18
import tempfile
1✔
19

20
from pycstruct.pycstruct import StructDef, BitfieldDef, EnumDef
1✔
21

22
###############################################################################
23
# Global constants
24

25
logger = logging.getLogger("pycstruct")
1✔
26

27
###############################################################################
28
# Internal functions
29

30

31
def _run_castxml(
1✔
32
    input_files,
33
    xml_filename,
34
    castxml_cmd="castxml",
35
    castxml_extra_args=None,
36
    **subprocess_kwargs,
37
):
38
    """Run castcml as a 'shell command'"""
39
    if shutil.which(castxml_cmd) is None:
1✔
40
        raise RuntimeError(
1✔
41
            f'Executable "{castxml_cmd}" not found.\n'
42
            + "External software castxml is not installed.\n"
43
            + "You need to install it and put it in your PATH."
44
        )
45
    if castxml_extra_args is None:
1✔
46
        castxml_extra_args = []
1✔
47
    args = [castxml_cmd]
1✔
48
    args += castxml_extra_args
1✔
49
    args += input_files
1✔
50
    args.append("--castxml-gccxml")
1✔
51
    args.append("-o")
1✔
52
    args.append(xml_filename)
1✔
53

54
    # to preserve behavior before subprocess_kwargs were allowed to be passed
55
    # in, default to redirecting stderr to STDOUT.
56
    if "stderr" not in subprocess_kwargs:
1✔
57
        subprocess_kwargs["stderr"] = subprocess.STDOUT
1✔
58

59
    try:
1✔
60
        output = subprocess.check_output(args, **subprocess_kwargs)
1✔
61
    except subprocess.CalledProcessError as exception:
×
62
        raise RuntimeError(
×
63
            "Unable to run:\n"
64
            + f"{' '.join(args)}\n\n"
65
            + "Output:\n"
66
            + exception.output.decode()
67
        ) from exception
68

69
    if not os.path.isfile(xml_filename):
1✔
70
        raise RuntimeError(
1✔
71
            "castxml did not report any error but "
72
            + f"{xml_filename} was never produced.\n\n"
73
            + f"castxml output was:\n{output.decode()}"
74
        )
75

76

77
def _get_hash(list_of_strings):
1✔
78
    """Get a reproducible short name of a list of strings. The hash also
79
    includes the user name (from OS) to avoid name conflicts in multi
80
    user environments.
81
    """
82
    long_string = "".join(list_of_strings) + os.getlogin()
1✔
83
    sha256 = hashlib.sha256(long_string.encode())
1✔
84
    hexdigest = sha256.hexdigest()
1✔
85
    return hexdigest[:10]
1✔
86

87

88
def _listify(list_or_str):
1✔
89
    """If list_or_str is a string it will be put in a list"""
90
    if isinstance(list_or_str, str):
1✔
91
        list_or_str = [list_or_str]
1✔
92
    return list_or_str
1✔
93

94

95
###############################################################################
96
# CastXMLParser class (internal)
97

98

99
class _CastXmlParser:
1✔
100
    """Parses XML:s produced by CastXML and creates a new dictionary where
101
    each key represent a type name of a defined type (struct, bitstruct,
102
    enum, union etc.). The value represents the metadata about the type,
103
    such as members etc.
104
    """
105

106
    # pylint: disable=too-few-public-methods, broad-except
107

108
    def __init__(self, xml_filename):
1✔
109
        self._xml_filename = xml_filename
1✔
110
        self._anonymous_count = 0
1✔
111
        self._embedded_bf_count = 0
1✔
112
        self._embedded_bf = []
1✔
113
        self.root = None
1✔
114

115
    def parse(self):
1✔
116
        """Parse the XML file provided in the constructor"""
117
        # pylint: disable=too-many-branches, too-many-locals
118
        self.root = ET.parse(self._xml_filename).getroot()
1✔
119

120
        supported_types = {}
1✔
121

122
        # Parse enums
123
        xml_enums = self.root.findall("Enumeration")
1✔
124
        for xml_enum in xml_enums:
1✔
125
            typeid = xml_enum.attrib["id"]
1✔
126
            supported_types[typeid] = self._parse_enum(xml_enum)
1✔
127

128
        # Parse unions
129
        xml_unions = self.root.findall("Union")
1✔
130
        for xml_union in xml_unions:
1✔
131
            typeid = xml_union.attrib["id"]
1✔
132
            supported_types[typeid] = self._parse_union(xml_union)
1✔
133

134
        # Parse structs and bitfields:
135
        xml_structs_and_bitfields = self.root.findall("Struct")
1✔
136
        for xml_struct_or_bitfield in xml_structs_and_bitfields:
1✔
137
            typeid = xml_struct_or_bitfield.attrib["id"]
1✔
138
            if self._is_bitfield(xml_struct_or_bitfield):
1✔
139
                supported_types[typeid] = self._parse_bitfield(xml_struct_or_bitfield)
1✔
140
            else:
141
                supported_types[typeid] = self._parse_struct(xml_struct_or_bitfield)
1✔
142

143
        # Add all embedded bitfields in supported_types
144
        for bitfield in self._embedded_bf:
1✔
145
            supported_types[bitfield["name"]] = bitfield
1✔
146

147
        # Change mapping from id to name
148
        type_meta = {}
1✔
149
        for _, datatype in supported_types.items():
1✔
150
            name = datatype["name"]
1✔
151
            if name in type_meta:
1✔
152
                # Name is not unique, make it unique
153
                for i in range(1, 1000):
1✔
154
                    new_name = name + str(i)
1✔
155
                    if new_name not in type_meta:
1✔
156
                        name = new_name
1✔
157
                        break
1✔
158
            datatype["name"] = name  # Change old name to new
1✔
159
            type_meta[name] = datatype
1✔
160

161
        # Secure all references points to names instead of id's
162
        for _, datatype in type_meta.items():
1✔
163
            for member in datatype["members"]:
1✔
164
                if "reference" in member:
1✔
165
                    typeid = member["reference"]
1✔
166
                    name = supported_types[typeid]["name"]
1✔
167
                    member["reference"] = name
1✔
168

169
        return type_meta
1✔
170

171
    def _is_bitfield(self, xml_struct_or_bitfield):
1✔
172
        """Returns true if this is a "true" bitfield, i.e. the
173
        struct only contains bitfield members"""
174
        for field in self._get_fields(xml_struct_or_bitfield):
1✔
175
            if "bits" not in field.attrib:
1✔
176
                # Only bitfield fields has the bits attribute set
177
                return False
1✔
178
        return True
1✔
179

180
    def _get_fields(self, xml_item):
1✔
181
        fields = []
1✔
182
        for member_id in self._get_attrib(xml_item, "members", "").split():
1✔
183
            xml_member = self._get_elem_with_id(member_id)
1✔
184
            if xml_member.tag != "Field":
1✔
185
                continue  # Probably just a struct/union definition
1✔
186
            fields.append(xml_member)
1✔
187
        return fields
1✔
188

189
    def _parse_enum(self, xml_enum):
1✔
190
        enum = {}
1✔
191
        enum["type"] = "enum"
1✔
192
        self._set_common_meta(xml_enum, enum)
1✔
193
        enum["members"] = []
1✔
194
        has_negative = False
1✔
195
        for xml_member in xml_enum.findall("EnumValue"):
1✔
196
            member = {}
1✔
197
            member["name"] = xml_member.attrib["name"]
1✔
198
            member["value"] = int(xml_member.attrib["init"])
1✔
199
            if member["value"] < 0:
1✔
200
                has_negative = True
1✔
201
            enum["members"].append(member)
1✔
202
        if has_negative:
1✔
203
            enum["signed"] = True
1✔
204
        else:
205
            enum["signed"] = False
1✔
206
        return enum
1✔
207

208
    def _parse_union(self, xml_union):
1✔
209
        union = {}
1✔
210
        union["type"] = "union"
1✔
211
        self._set_common_meta(xml_union, union)
1✔
212
        self._set_struct_union_members(xml_union, union)
1✔
213
        return union
1✔
214

215
    def _parse_struct(self, xml_struct):
1✔
216
        struct = {}
1✔
217
        struct["type"] = "struct"
1✔
218
        self._set_common_meta(xml_struct, struct)
1✔
219
        self._set_struct_union_members(xml_struct, struct)
1✔
220
        return struct
1✔
221

222
    def _parse_bitfield_members(self, xml_bitfield_members):
1✔
223
        result = []
1✔
224
        for field in xml_bitfield_members:
1✔
225
            member = {}
1✔
226
            member["name"] = field.attrib["name"]
1✔
227
            member["bits"] = int(field.attrib["bits"])
1✔
228
            member["signed"] = True
1✔
229

230
            # Figure out if it is signed
231
            type_elem = self._get_basic_type_element(field.attrib["type"])
1✔
232
            if type_elem.tag == "FundamentalType":
1✔
233
                # Figure out sign
234
                if "unsigned" in type_elem.attrib["name"]:
1✔
235
                    member["signed"] = False
1✔
236
            else:
237
                logger.warning(
×
238
                    "Unable to parse sign of bitfield member %s. Will be signed.",
239
                    member["name"],
240
                )
241

242
            result.append(member)
1✔
243
        return result
1✔
244

245
    def _parse_bitfield(self, xml_bitfield):
1✔
246
        bitfield = {}
1✔
247
        bitfield["type"] = "bitfield"
1✔
248
        self._set_common_meta(xml_bitfield, bitfield)
1✔
249
        bitfield["members"] = self._parse_bitfield_members(
1✔
250
            self._get_fields(xml_bitfield)
251
        )
252

253
        return bitfield
1✔
254

255
    def _set_common_meta(self, xml_input, dict_output):
1✔
256
        """Set common metadata available for all types"""
257
        typeid = xml_input.attrib["id"]
1✔
258
        name = self._get_attrib(xml_input, "name", "")
1✔
259
        if name == "":
1✔
260
            # Does not have a name - check for TypeDef
261
            name = self._get_typedef_name(typeid)
1✔
262
        dict_output["name"] = name
1✔
263
        dict_output["size"] = int(int(self._get_attrib(xml_input, "size", "0")) / 8)
1✔
264
        dict_output["align"] = int(int(self._get_attrib(xml_input, "align", "8")) / 8)
1✔
265
        dict_output["supported"] = True
1✔
266

267
    def _set_struct_union_members(self, xml_input, dict_output):
1✔
268
        """Set members - common for struct and unions"""
269
        dict_output["members"] = []
1✔
270
        fields = self._get_fields(xml_input)
1✔
271
        while len(fields) > 0:
1✔
272
            field = fields.pop(0)
1✔
273
            member = {}
1✔
274
            if "bits" in field.attrib:
1✔
275
                # This is a bitfield, we need to create our
276
                # own bitfield definition for this field and
277
                # all bitfield members directly after this
278
                # member
279
                bf_fields = []
1✔
280
                nbr_bits = 0
1✔
281
                fields.insert(0, field)
1✔
282
                while len(fields) > 0 and "bits" in fields[0].attrib:
1✔
283
                    bf_field = fields.pop(0)
1✔
284
                    nbr_bits += int(self._get_attrib(bf_field, "bits", "0"))
1✔
285
                    bf_fields.append(bf_field)
1✔
286
                bitfield = {}
1✔
287
                bitfield["type"] = "bitfield"
1✔
288
                bitfield["name"] = f"auto_bitfield_{self._embedded_bf_count}"
1✔
289
                self._embedded_bf_count += 1
1✔
290
                # WARNING! The size is compiler specific and not covered
291
                # by the C standard. We guess:
292
                bitfield["size"] = int(math.ceil(nbr_bits / 8.0))
1✔
293
                bitfield["align"] = dict_output["align"]  # Same as parent
1✔
294
                bitfield["supported"] = True
1✔
295
                bitfield["members"] = self._parse_bitfield_members(bf_fields)
1✔
296
                self._embedded_bf.append(bitfield)
1✔
297

298
                member["name"] = f"__{bitfield['name']}"
1✔
299
                member["type"] = "bitfield"
1✔
300
                member["reference"] = bitfield["name"]
1✔
301
                member["same_level"] = True
1✔
302
            else:
303
                member["name"] = field.attrib["name"]
1✔
304
                member_type = self._get_type(field.attrib["type"])
1✔
305
                member["type"] = member_type["type_name"]
1✔
306
                if "shape" in member_type:
1✔
307
                    member["shape"] = member_type["shape"]
1✔
308
                if "reference" in member_type:
1✔
309
                    member["reference"] = member_type["reference"]
1✔
310
            dict_output["members"].append(member)
1✔
311

312
    def _get_attrib(self, elem, attrib, default):
1✔
313
        if attrib in elem.attrib:
1✔
314
            return elem.attrib[attrib]
1✔
315
        return default
1✔
316

317
    def _get_elem_with_id(self, typeid):
1✔
318
        elem = self.root.find(f"*[@id='{typeid}']")
1✔
319
        if elem is None:
1✔
320
            raise RuntimeError(f"No XML element with id attribute {typeid} identified")
×
321
        return elem
1✔
322

323
    def _get_elem_with_attrib(self, tag, attrib, value):
1✔
324
        elem = self.root.find(f"{tag}[@{attrib}='{value}']")
1✔
325
        if elem is None:
1✔
326
            raise RuntimeError(
1✔
327
                f"No {tag} XML element with {attrib} attribute {value} identified"
328
            )
329
        return elem
1✔
330

331
    def _get_typedef_name(self, type_id):
1✔
332
        """Find out the typedef name of a type which do not have a name"""
333

334
        # First check if there is a connected ElaboratedType element
335
        try:
1✔
336
            type_id = self._get_elem_with_attrib(
1✔
337
                "ElaboratedType", "type", type_id
338
            ).attrib["id"]
339
        except Exception:
1✔
340
            pass
1✔
341

342
        # Now find the TypeDef element connected to the type or ElaboratedType element
343
        name = ""
1✔
344
        try:
1✔
345
            name = self._get_elem_with_attrib("Typedef", "type", type_id).attrib["name"]
1✔
346
        except Exception:
1✔
347
            name = f"anonymous_{self._anonymous_count}"
1✔
348
            self._anonymous_count += 1
1✔
349
        return name
1✔
350

351
    def _fundamental_type_to_pycstruct_type(self, elem, is_array):
1✔
352
        """Map the fundamental type to pycstruct type"""
353
        typename = elem.attrib["name"]
1✔
354
        typesize = elem.attrib["size"]
1✔
355
        pycstruct_type_name = "int"
1✔
356
        if "float" in typename or "double" in typename:
1✔
357
            pycstruct_type_name = "float"
1✔
358
        elif is_array and "char" in typename:
1✔
359
            if "unsigned" in typename:
1✔
360
                # "unsigned char[]" are considered uint8 array
361
                pycstruct_type_name = "uint"
1✔
362
            elif "signed" in typename:
1✔
363
                # "signed char[]" are considered int8 array
364
                pycstruct_type_name = "int"
1✔
365
            else:
366
                # "char[]" are considered UTF-8 data (string)
367
                pycstruct_type_name = "utf-"
1✔
368
        elif "unsigned" in typename:
1✔
369
            pycstruct_type_name = "uint"
1✔
370
        else:
371
            pycstruct_type_name = "int"
1✔
372

373
        return f"{pycstruct_type_name}{typesize}"
1✔
374

375
    def _get_basic_type_element(self, type_id):
1✔
376
        """Finds the basic type element possible hidden behind TypeDef's or ElaboratedType's"""
377
        elem = self._get_elem_with_id(type_id)
1✔
378
        while elem.tag in ("Typedef", "ElaboratedType"):
1✔
379
            elem = self._get_elem_with_id(elem.attrib["type"])
1✔
380
        return elem
1✔
381

382
    def _get_type(self, type_id, is_array=False):
1✔
383
        elem = self._get_basic_type_element(type_id)
1✔
384

385
        member_type = {}
1✔
386

387
        if elem.tag == "ArrayType":
1✔
388
            size = int(elem.attrib["max"]) - int(elem.attrib["min"]) + 1
1✔
389
            subtype = self._get_type(elem.attrib["type"], is_array=True)
1✔
390
            shape = (size,)
1✔
391
            if "shape" in subtype:
1✔
392
                shape = shape + subtype["shape"]
1✔
393
            member_type["shape"] = shape
1✔
394
            member_type["type_name"] = subtype["type_name"]
1✔
395
            if "reference" in subtype:
1✔
396
                member_type["reference"] = subtype["reference"]
1✔
397
            return member_type
1✔
398

399
        if elem.tag == "CvQualifiedType":  # volatile
1✔
400
            elem = self._get_basic_type_element(elem.attrib["type"])
1✔
401

402
        if elem.tag == "FundamentalType":
1✔
403
            member_type["type_name"] = self._fundamental_type_to_pycstruct_type(
1✔
404
                elem, is_array
405
            )
406
        elif elem.tag == "PointerType":
1✔
407
            member_type["type_name"] = f"uint{elem.attrib['size']}"
1✔
408
        elif elem.tag == "Struct":
1✔
409
            member_type["type_name"] = "struct"
1✔
410
            member_type["reference"] = elem.attrib["id"]
1✔
411
        elif elem.tag == "Union":
1✔
412
            member_type["type_name"] = "union"
1✔
413
            member_type["reference"] = elem.attrib["id"]
1✔
414
        elif elem.tag == "Enumeration":
1✔
415
            member_type["type_name"] = "enum"
1✔
416
            member_type["reference"] = elem.attrib["id"]
1✔
417
        else:
418
            raise RuntimeError(f"Member type {elem.tag} is not supported.")
×
419

420
        return member_type
1✔
421

422

423
###############################################################################
424
# _TypeMetaParser class (internal)
425

426

427
class _TypeMetaParser:
1✔
428
    """This class takes a dictionary with metadata about the types and
429
    generate pycstruct instances.
430
    """
431

432
    # pylint: disable=too-few-public-methods, broad-except
433

434
    def __init__(self, type_meta, byteorder):
1✔
435
        self._type_meta = type_meta
1✔
436
        self._instances = {}
1✔
437
        self._byteorder = byteorder
1✔
438

439
    def parse(self):
1✔
440
        """Parse the type_meta file provided in the constructor"""
441
        for name, datatype in self._type_meta.items():
1✔
442
            if datatype["supported"]:
1✔
443
                try:
1✔
444
                    self._to_instance(name)
1✔
445
                except Exception as exception:
1✔
446
                    logger.warning(
1✔
447
                        """Unable to convert %s, type %s, to pycstruct defintion:
448
  - %s
449
  - Type will be ignored.""",
450
                        name,
451
                        datatype["type"],
452
                        str(exception.args[0]),
453
                    )
454
                    datatype["supported"] = False
1✔
455
        return self._instances
1✔
456

457
    def _to_instance(self, name):
1✔
458
        """Create a pycstruct instance of type with name. Will recursively
459
        create instances of referenced types.
460

461
        Returns the instance.
462
        """
463
        # pylint: disable=too-many-branches
464
        if name in self._instances:
1✔
465
            return self._instances[name]  # Parsed before
1✔
466

467
        meta = self._type_meta[name]
1✔
468

469
        if not meta["supported"]:
1✔
470
            return None  # Not supported
×
471

472
        instance = None
1✔
473

474
        # Structs or union
475
        if meta["type"] == "struct" or meta["type"] == "union":
1✔
476
            is_union = meta["type"] == "union"
1✔
477
            instance = StructDef(self._byteorder, meta["align"], union=is_union)
1✔
478
            for member in meta["members"]:
1✔
479
                shape = member.get("shape", None)
1✔
480
                if "reference" in member:
1✔
481
                    other_instance = self._to_instance(member["reference"])
1✔
482
                    if other_instance is None:
1✔
483
                        raise RuntimeError(
×
484
                            f"Member {member['name']} is of type {member['type']} "
485
                            f"{member['reference']} that is not supported"
486
                        )
487
                    same_level = False
1✔
488
                    if ("same_level" in member) and member["same_level"]:
1✔
489
                        same_level = True
1✔
490
                    instance.add(
1✔
491
                        other_instance,
492
                        member["name"],
493
                        shape=shape,
494
                        same_level=same_level,
495
                    )
496
                else:
497
                    instance.add(member["type"], member["name"], shape=shape)
1✔
498

499
        # Enum
500
        elif meta["type"] == "enum":
1✔
501
            instance = EnumDef(self._byteorder, meta["size"], meta["signed"])
1✔
502
            for member in meta["members"]:
1✔
503
                instance.add(member["name"], member["value"])
1✔
504

505
        # Bitfield
506
        elif meta["type"] == "bitfield":
1✔
507
            instance = BitfieldDef(self._byteorder, meta["size"])
1✔
508
            for member in meta["members"]:
1✔
509
                instance.add(member["name"], member["bits"], member["signed"])
1✔
510

511
        # Not supported
512
        else:
513
            logger.warning(
×
514
                "Unable to create instance for %s (type %s). Not supported.",
515
                meta["name"],
516
                meta["type"],
517
            )
518
            meta["supported"] = False
×
519
            return None
×
520

521
        # Sanity check size:
522
        if meta["size"] != instance.size():
1✔
523
            logger.warning(
×
524
                "%s size, %s, does match indicated size %s",
525
                meta["name"],
526
                instance.size(),
527
                meta["size"],
528
            )
529

530
        self._instances[name] = instance
1✔
531
        return instance
1✔
532

533

534
###############################################################################
535
# Public functions
536

537

538
def parse_file(
1✔
539
    input_files,
540
    byteorder="native",
541
    castxml_cmd="castxml",
542
    castxml_extra_args=None,
543
    cache_path="",
544
    use_cached=False,
545
    **subprocess_kwargs,
546
):
547
    """Parse one or more C source files (C or C++) and generate pycstruct
548
    instances as a result.
549

550
    The result is a dictionary where the keys are the names of the
551
    struct, unions etc. typedef'ed names are also supported.
552

553
    The values of the resulting dictionary are the actual pycstruct
554
    instance connected to the name.
555

556
    This function requires that the external tool
557
    `castxml <https://github.com/CastXML/CastXML>`_ is installed.
558

559
    Alignment will automatically be detected and configured for the pycstruct
560
    instances.
561

562
    Note that following pycstruct types will be used for char arrays:
563

564
    - 'unsigned char []' = uint8 array
565
    - 'signed char []' = int8 array
566
    - 'char []' = utf-8 data (string)
567

568
    :param input_files: Source file name or a list of file names.
569
    :type input_files: str or list
570
    :param byteorder: Byteorder of all elements Valid values are 'native',
571
                      'little' and 'big'. If not specified the 'native'
572
                      byteorder is used.
573
    :type byteorder: str, optional
574
    :param castxml_cmd: Path to the castxml binary. If not specified
575
                        castxml must be within the PATH.
576
    :type castxml_cmd: str, optional
577
    :param castxml_extra_args: Extra arguments to provide to castxml.
578
                               For example definitions etc. Check
579
                               castxml documentation for which
580
                               arguments that are supported.
581
    :type castxml_extra_args: list, optional
582
    :param cache_path: Path where to store temporary files. If not
583
                       provided, the default system temporary
584
                       directory is used.
585
    :type cache_path: str, optional
586
    :param use_cached: If this is True, use previously cached
587
                       output from castxml to avoid re-running
588
                       castxml (since it could be time consuming).
589
                       Default is False.
590
    :type use_cached: boolean, optional
591
    :param subprocess_kwargs: keyword arguments that will be passed down to the
592
                              `subprocess.check_outputs()` call used to run
593
                              castxml. By default, stderr will be redirected to
594
                              stdout. To get the subprocess default behavior,
595
                              pass `stderr=None`.
596
    :return: A dictionary keyed on names of the structs, unions
597
             etc. The values are the actual pycstruct instances.
598
    :rtype: dict
599
    """
600
    # pylint: disable=too-many-arguments
601

602
    input_files = _listify(input_files)
×
603
    xml_filename = _get_hash(input_files) + ".xml"
×
604

605
    if castxml_extra_args is None:
×
606
        castxml_extra_args = []
×
607

608
    if cache_path == "":
×
609
        # Use temporary path to store xml
610
        cache_path = tempfile.gettempdir()
×
611

612
    xml_path = os.path.join(cache_path, xml_filename)
×
613

614
    # Generate XML
615
    if not use_cached or not os.path.isfile(xml_path):
×
616
        _run_castxml(
×
617
            input_files, xml_path, castxml_cmd, castxml_extra_args, **subprocess_kwargs
618
        )
619

620
    # Parse XML
621
    castxml_parser = _CastXmlParser(xml_path)
×
622
    type_meta = castxml_parser.parse()
×
623

624
    # Generate pycstruct instances
625
    type_meta_parser = _TypeMetaParser(type_meta, byteorder)
×
626
    pycstruct_instances = type_meta_parser.parse()
×
627

628
    return pycstruct_instances
×
629

630

631
def parse_str(
1✔
632
    c_str,
633
    byteorder="native",
634
    castxml_cmd="castxml",
635
    castxml_extra_args=None,
636
    cache_path="",
637
    use_cached=False,
638
):
639
    """Parse a string containing C source code, such as struct or
640
    union defintions. Any valid C code is supported.
641

642
    The result is a dictionary where the keys are the names of the
643
    struct, unions etc. typedef'ed names are also supported.
644

645
    The values of the resulting dictionary are the actual pycstruct
646
    instance connected to the name.
647

648
    This function requires that the external tool
649
    `castxml <https://github.com/CastXML/CastXML>`_ is installed.
650

651
    Alignment will automatically be detected and configured for the pycstruct
652
    instances.
653

654
    Note that following pycstruct types will be used for char arrays:
655

656
    - 'unsigned char []' = uint8 array
657
    - 'signed char []' = int8 array
658
    - 'char []' = utf-8 data (string)
659

660
    :param c_str: A string of C source code.
661
    :type c_str: str
662
    :param byteorder: Byteorder of all elements Valid values are 'native',
663
                      'little' and 'big'. If not specified the 'native'
664
                      byteorder is used.
665
    :type byteorder: str, optional
666
    :param castxml_cmd: Path to the castxml binary. If not specified
667
                        castxml must be within the PATH.
668
    :type castxml_cmd: str, optional
669
    :param castxml_extra_args: Extra arguments to provide to castxml.
670
                               For example definitions etc. Check
671
                               castxml documentation for which
672
                               arguments that are supported.
673
    :type castxml_extra_args: list, optional
674
    :param cache_path: Path where to store temporary files. If not
675
                       provided, the default system temporary
676
                       directory is used.
677
    :type cache_path: str, optional
678
    :param use_cached: If this is True, use previously cached
679
                       output from castxml to avoid re-running
680
                       castxml (since it could be time consuming).
681
                       Default is False.
682
    :type use_cached: boolean, optional
683
    :return: A dictionary keyed on names of the structs, unions
684
             etc. The values are the actual pycstruct instances.
685
    :rtype: dict
686
    """
687
    # pylint: disable=too-many-arguments
688

689
    if castxml_extra_args is None:
×
690
        castxml_extra_args = []
×
691

692
    if cache_path == "":
×
693
        # Use temporary path to store xml
694
        cache_path = tempfile.gettempdir()
×
695

696
    c_filename = _get_hash([c_str]) + ".c"
×
697
    c_path = os.path.join(cache_path, c_filename)
×
698

699
    with open(c_path, "w", encoding="utf-8") as file:
×
700
        file.write(c_str)
×
701

702
    return parse_file(
×
703
        c_path, byteorder, castxml_cmd, castxml_extra_args, cache_path, use_cached
704
    )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc