# The MIT License (MIT)
#
-# Copyright (c) 2014 Philippe Proulx <philippe.proulx@efficios.com>
+# Copyright (c) 2014-2020 Philippe Proulx <pproulx@efficios.com>
#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-from termcolor import cprint, colored
-import pytsdl.parser
-import pytsdl.tsdl
-import collections
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import pkg_resources
+import termcolor
import argparse
+import os.path
+import barectf
+import barectf.config_parse_common as barectf_config_parse_common
+import barectf.argpar as barectf_argpar
import sys
import os
-import re
-
-
-def _perror(msg, exit_code=1):
- cprint('Error: {}'.format(msg), 'red', attrs=['bold'], file=sys.stderr)
- sys.exit(exit_code)
-
-
-def _pinfo(msg):
- cprint(':: {}'.format(msg), 'blue', attrs=['bold'], file=sys.stderr)
-
-
-def _parse_args():
- ap = argparse.ArgumentParser()
-
- ap.add_argument('-O', '--output', metavar='OUTPUT', action='store',
- default=os.getcwd(),
- help='output directory of C files')
- ap.add_argument('-p', '--prefix', metavar='PREFIX', action='store',
- default='barectf',
- help='custom prefix for C function and structure names')
- ap.add_argument('-s', '--static-inline', action='store_true',
- help='generate static inline C functions')
- ap.add_argument('-c', '--manual-clock', action='store_true',
- help='do not use a clock callback: pass clock value to tracing functions')
- ap.add_argument('metadata', metavar='METADATA', action='store',
- help='CTF metadata input file')
-
- # parse args
- args = ap.parse_args()
-
- # validate output directory
- if not os.path.isdir(args.output):
- _perror('"{}" is not an existing directory'.format(args.output))
-
- # validate prefix
- if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', args.prefix):
- _perror('"{}" is not a valid C identifier'.format(args.prefix))
-
- # validate that metadata file exists
- if not os.path.isfile(args.metadata):
- _perror('"{}" is not an existing file'.format(args.metadata))
-
- return args
-
-
-# TODO: prettify this function
-def _validate_struct(struct):
- # just in case we call this with the wrong type
- if type(struct) is not pytsdl.tsdl.Struct:
- raise RuntimeError('expecting a struct')
-
- # make sure inner structures are at least byte-aligned
- if _get_obj_alignment(struct) < 8:
- raise RuntimeError('inner struct must be at least byte-aligned')
-
- # check each field
- for name, ftype in struct.fields.items():
- if type(ftype) is pytsdl.tsdl.Sequence:
- raise RuntimeError('field "{}" is a dynamic array (not allowed here)'.format(name))
- elif type(ftype) is pytsdl.tsdl.Array:
- # we need to check every element type until we find a terminal one
- end = False
- element = ftype.element
-
- while not end:
- if type(element) is pytsdl.tsdl.Sequence:
- raise RuntimeError('field "{}" contains a dynamic array (not allowed here)'.format(name))
- elif type(element) is pytsdl.tsdl.Variant:
- raise RuntimeError('field "{}" contains a variant (unsupported)'.format(name))
- elif type(element) is pytsdl.tsdl.String:
- raise RuntimeError('field "{}" contains a string (not allowed here)'.format(name))
- elif type(element) is pytsdl.tsdl.Struct:
- _validate_struct(element)
- elif type(element) is pytsdl.tsdl.Integer:
- if _get_integer_size(element) > 64:
- raise RuntimeError('integer field "{}" larger than 64-bit'.format(name))
- elif type(element) is pytsdl.tsdl.FloatingPoint:
- if _get_floating_point_size(element) > 64:
- raise RuntimeError('floating point field "{}" larger than 64-bit'.format(name))
- elif type(element) is pytsdl.tsdl.Enum:
- if _get_enum_size(element) > 64:
- raise RuntimeError('enum field "{}" larger than 64-bit'.format(name))
-
- if type(element) is pytsdl.tsdl.Array:
- # still an array, continue
- element = element.element
- else:
- # found the terminal element
- end = True
- elif type(ftype) is pytsdl.tsdl.Variant:
- raise RuntimeError('field "{}" is a variant (unsupported)'.format(name))
- elif type(ftype) is pytsdl.tsdl.String:
- raise RuntimeError('field "{}" is a string (not allowed here)'.format(name))
- elif type(ftype) is pytsdl.tsdl.Struct:
- _validate_struct(ftype)
- elif type(ftype) is pytsdl.tsdl.Integer:
- if _get_integer_size(ftype) > 64:
- raise RuntimeError('integer field "{}" larger than 64-bit'.format(name))
- elif type(ftype) is pytsdl.tsdl.FloatingPoint:
- if _get_floating_point_size(ftype) > 64:
- raise RuntimeError('floating point field "{}" larger than 64-bit'.format(name))
- elif type(ftype) is pytsdl.tsdl.Enum:
- if _get_enum_size(ftype) > 64:
- raise RuntimeError('enum field "{}" larger than 64-bit'.format(name))
-
-
-def _validate_context_field(struct):
- if type(struct) is not pytsdl.tsdl.Struct:
- raise RuntimeError('expecting a struct')
-
- for name, ftype in struct.fields.items():
- if type(ftype) is pytsdl.tsdl.Variant:
- raise RuntimeError('field "{}" is a variant (unsupported)'.format(name))
- elif type(ftype) is pytsdl.tsdl.Struct:
- # validate inner structure against barectf constraints
- _validate_struct(ftype)
-
-
-def _validate_integer(integer, size=None, align=None, signed=None):
- if type(integer) is not pytsdl.tsdl.Integer:
- raise RuntimeError('expected integer')
-
- if size is not None:
- if integer.size != size:
- raise RuntimeError('expected {}-bit integer'.format(size))
-
- if align is not None:
- if integer.align != align:
- raise RuntimeError('expected integer with {}-bit alignment'.format(align))
-
- if signed is not None:
- if integer.signed != signed:
- raise RuntimeError('expected {} integer'.format('signed' if signed else 'unsigned'))
-
-
-def _validate_packet_header(packet_header):
- try:
- _validate_struct(packet_header)
- except RuntimeError as e:
- _perror('packet header: {}'.format(e))
-
- # magic must be the first field
- if 'magic' in packet_header.fields:
- if list(packet_header.fields.keys())[0] != 'magic':
- _perror('packet header: "magic" must be the first field')
- else:
- _perror('packet header: missing "magic" field')
-
- # magic must be a 32-bit unsigned integer, 32-bit aligned
- try:
- _validate_integer(packet_header['magic'], 32, 32, False)
- except RuntimeError as e:
- _perror('packet header: "magic": {}'.format(e))
-
- # mandatory stream_id
- if 'stream_id' not in packet_header.fields:
- _perror('packet header: missing "stream_id" field')
-
- # stream_id must be an unsigned integer
- try:
- _validate_integer(packet_header['stream_id'], signed=False)
- except RuntimeError as e:
- _perror('packet header: "stream_id": {}'.format(e))
-
-
-def _dot_name_to_str(name):
- return '.'.join(name)
-
-
-def _compare_integers(int1, int2):
- if type(int1) is not pytsdl.tsdl.Integer:
- return False
-
- if type(int2) is not pytsdl.tsdl.Integer:
- return False
-
- size = int1.size == int2.size
- align = int1.align == int2.align
- cmap = int1.map == int2.map
- base = int1.base == int2.base
- encoding = int1.encoding == int2.encoding
- signed = int1.signed == int2.signed
- comps = (size, align, cmap, base, encoding, signed)
-
- # True means 1 for sum()
- return sum(comps) == len(comps)
-
-
-def _validate_packet_context(doc, stream):
- packet_context = stream.packet_context
- sid = stream.id
-
- try:
- _validate_struct(packet_context)
- except RuntimeError as e:
- _perror('stream {}: packet context: {}'.format(sid, e))
-
- fields = packet_context.fields
-
- # if timestamp_begin exists, timestamp_end must exist
- if 'timestamp_begin' in fields or 'timestamp_end' in fields:
- if 'timestamp_begin' not in fields or 'timestamp_end' not in fields:
- _perror('stream {}: packet context: "timestamp_begin" must exist if "timestamp_end" exists'.format(sid))
- else:
- # timestamp_begin and timestamp_end must have the same integer
- # as the event header's timestamp field (should exist by now)
- timestamp = stream.event_header['timestamp']
-
- if not _compare_integers(fields['timestamp_begin'], timestamp):
- _perror('stream {}: packet context: "timestamp_begin": integer type different from event header\'s "timestamp" field'.format(sid))
-
- if not _compare_integers(fields['timestamp_end'], timestamp):
- _perror('stream {}: packet context: "timestamp_end": integer type different from event header\'s "timestamp" field'.format(sid))
-
- # content_size must exist and be an unsigned integer
- if 'content_size' not in fields:
- _perror('stream {}: packet context: missing "content_size" field'.format(sid))
-
- try:
- _validate_integer(fields['content_size'], 32, 32, False)
- except:
- try:
- _validate_integer(fields['content_size'], 64, 64, False)
- except:
- _perror('stream {}: packet context: "content_size": expecting unsigned 32-bit/64-bit integer'.format(sid))
-
- # packet_size must exist and be an unsigned integer
- if 'packet_size' not in fields:
- _perror('stream {}: packet context: missing "packet_size" field'.format(sid))
-
- try:
- _validate_integer(fields['packet_size'], 32, 32, False)
- except:
- try:
- _validate_integer(fields['packet_size'], 64, 64, False)
- except:
- _perror('stream {}: packet context: "packet_size": expecting unsigned 32-bit/64-bit integer'.format(sid))
-
- # if cpu_id exists, must be an unsigned integer
- if 'cpu_id' in fields:
- try:
- _validate_integer(fields['cpu_id'], signed=False)
- except RuntimeError as e:
- _perror('stream {}: packet context: "cpu_id": {}'.format(sid, e))
-
-
-def _validate_event_header(doc, stream):
- event_header = stream.event_header
- sid = stream.id
-
- try:
- _validate_struct(event_header)
- except RuntimeError as e:
- _perror('stream {}: event header: {}'.format(sid, e))
-
- fields = event_header.fields
-
- # id must exist and be an unsigned integer
- if 'id' not in fields:
- _perror('stream {}: event header: missing "id" field'.format(sid))
-
- try:
- _validate_integer(fields['id'], signed=False)
- except RuntimeError as e:
- _perror('stream {}: "id": {}'.format(sid, format(e)))
-
-
- # timestamp must exist, be an unsigned integer and be mapped to a valid clock
- if 'timestamp' not in fields:
- _perror('stream {}: event header: missing "timestamp" field'.format(sid))
-
- try:
- _validate_integer(fields['timestamp'], signed=False)
- except RuntimeError as e:
- _perror('stream {}: "timestamp": {}'.format(sid, format(e)))
-
-
-def _validate_stream_event_context(doc, stream):
- stream_event_context = stream.event_context
- sid = stream.id
-
- if stream_event_context is None:
- return
-
- try:
- _validate_context_field(stream_event_context)
- except RuntimeError as e:
- _perror('stream {}: event context: {}'.format(sid, e))
-
-
-def _validate_all_scopes(doc):
- # packet header
- _validate_packet_header(doc.trace.packet_header)
-
- # stream stuff
- for stream_id, stream in doc.streams.items():
- _validate_event_header(doc, stream)
- _validate_packet_context(doc, stream)
- _validate_stream_event_context(doc, stream)
-
-
-def _validate_metadata(doc):
- _validate_all_scopes(doc)
-
-
-# 3, 4 -> 4
-# 4, 4 -> 4
-# 5, 4 -> 8
-# 6, 4 -> 8
-# 7, 4 -> 8
-# 8, 4 -> 8
-# 9, 4 -> 12
-def _get_alignment(at, align):
- return (at + align - 1) & -align
-
-
-def _offset_vars_tree_to_vars(offset_vars_tree, prefix='',
- offset_vars=collections.OrderedDict()):
- for name, offset in offset_vars_tree.items():
- varname = '{}_{}'.format(prefix, name)
-
- if isinstance(offset, dict):
- _offset_vars_tree_to_vars(offset, varname, offset_vars)
- else:
- offset_vars[varname] = offset
-
- return offset_vars
-
-
-# returns the size of a struct with _static size_
-def _get_struct_size(struct, offset_vars_tree=collections.OrderedDict(),
- base_offset=0):
- offset = 0
-
- for fname, ftype in struct.fields.items():
- field_alignment = _get_obj_alignment(ftype)
- offset = _get_alignment(offset, field_alignment)
-
- if type(ftype) is pytsdl.tsdl.Struct:
- offset_vars_tree[fname] = collections.OrderedDict()
- sz = _get_struct_size(ftype, offset_vars_tree[fname],
- base_offset + offset)
- else:
- # only integers may act as sequence lengths
- if type(ftype) is pytsdl.tsdl.Integer:
- offset_vars_tree[fname] = base_offset + offset
-
- sz = _get_obj_size(ftype)
-
- offset += sz
-
- return offset
-
-
-def _get_array_size(array):
- element = array.element
-
- # effective size of one element includes its alignment after its size
- size = _get_obj_size(element)
- align = _get_obj_alignment(element)
-
- return _get_alignment(size, align) * array.length
-
-
-def _get_enum_size(enum):
- return _get_obj_size(enum.integer)
-
-
-def _get_floating_point_size(floating_point):
- return floating_point.exp_dig + floating_point.mant_dig
-def _get_integer_size(integer):
- return integer.size
+# Colors and prints the error message `msg` and exits with status code
+# 1.
+def _print_error(msg):
+ termcolor.cprint('Error: ', 'red', end='', file=sys.stderr)
+ termcolor.cprint(msg, 'red', attrs=['bold'], file=sys.stderr)
+ sys.exit(1)
-_obj_size_cb = {
- pytsdl.tsdl.Struct: _get_struct_size,
- pytsdl.tsdl.Integer: _get_integer_size,
- pytsdl.tsdl.Enum: _get_enum_size,
- pytsdl.tsdl.FloatingPoint: _get_floating_point_size,
- pytsdl.tsdl.Array: _get_array_size,
-}
+# Pretty-prints the barectf configuration error `exc` and exits with
+# status code 1.
+def _print_config_error(exc):
+ # reverse: most precise message comes last
+ for ctx in reversed(exc.context):
+ msg = ''
+ if ctx.message is not None:
+ msg = f' {ctx.message}'
-def _get_obj_size(obj):
- return _obj_size_cb[type(obj)](obj)
+ color = 'red'
+ termcolor.cprint(f'{ctx.name}', color, attrs=['bold'], file=sys.stderr, end='')
+ termcolor.cprint(':', color, file=sys.stderr, end='')
+ termcolor.cprint(msg, color, file=sys.stderr)
+ sys.exit(1)
-def _get_struct_alignment(struct):
- if struct.align is not None:
- return struct.align
- cur_align = 1
+# Pretty-prints the unknown exception `exc`.
+def _print_unknown_exc(exc):
+ import traceback
- for fname, ftype in struct.fields.items():
- cur_align = max(_get_obj_alignment(ftype), cur_align)
+ traceback.print_exc()
+ _print_error(f'Unknown exception: {exc}')
- return cur_align
+# Finds and returns all the option items in `items` having the long name
+# `long_name`.
+def _find_opt_items(items, long_name):
+ ret_items = []
-def _get_integer_alignment(integer):
- return integer.align
+ for item in items:
+ if type(item) is barectf_argpar._OptItem and item.descr.long_name == long_name:
+ ret_items.append(item)
+ return ret_items
-def _get_floating_point_alignment(floating_point):
- return floating_point.align
+# Returns:
+#
+# For an option item without an argument:
+# `True`.
+#
+# For an option item with an argument:
+# Its argument.
+#
+# Uses the last option item having the long name `long_name` found in
+# `items`.
+#
+# Returns `default` if there's no such option item.
+def _opt_item_val(items, long_name, default=None):
+ opt_items = _find_opt_items(items, long_name)
-def _get_enum_alignment(enum):
- return _get_obj_alignment(enum.integer)
-
-
-def _get_string_alignment(string):
- return 8
-
-def _get_array_alignment(array):
- return _get_obj_alignment(array.element)
-
-
-def _get_sequence_alignment(sequence):
- return _get_obj_alignment(sequence.element)
-
-
-_obj_alignment_cb = {
- pytsdl.tsdl.Struct: _get_struct_alignment,
- pytsdl.tsdl.Integer: _get_integer_alignment,
- pytsdl.tsdl.Enum: _get_enum_alignment,
- pytsdl.tsdl.FloatingPoint: _get_floating_point_alignment,
- pytsdl.tsdl.Array: _get_array_alignment,
- pytsdl.tsdl.Sequence: _get_sequence_alignment,
- pytsdl.tsdl.String: _get_string_alignment,
-}
-
-
-def _get_obj_alignment(obj):
- return _obj_alignment_cb[type(obj)](obj)
-
-
-_CTX_AT = 'ctx->at'
-_CTX_BUF = 'ctx->buf'
-_CTX_BUF_SIZE = 'ctx->buf_size'
-_CTX_BUF_AT = '{}[{} >> 3]'.format(_CTX_BUF, _CTX_AT)
-_CTX_BUF_AT_ADDR = '&{}'.format(_CTX_BUF_AT)
-_ALIGN_OFFSET = 'ALIGN_OFFSET'
-_CHECK_OFFSET_OVERFLOW_FMT = \
- 'CHECK_OFFSET_OVERFLOW({}, {}, {{}});'.format(_CTX_AT, _CTX_BUF_SIZE)
-
-
-def _field_name_to_param_name(fname):
- return 'param_{}'.format(fname)
-
-
-def _get_integer_param_type(integer):
- signed = 'u' if not integer.signed else ''
-
- if integer.size == 8:
- sz = '8'
- elif integer.size == 16:
- sz = '16'
- elif integer.size == 32:
- sz = '32'
- elif integer.size == 64:
- sz = '64'
- else:
- # if the integer is signed and of uncommon size, the sign bit is
- # at a custom position anyway so we use a 64-bit unsigned
- signed = 'u'
-
- if integer.signed:
- sz = '64'
- else:
- if integer.size < 16:
- sz = '8'
- elif integer.size < 32:
- sz = '16'
- elif integer.size < 64:
- sz = '32'
- else:
- sz = '64'
-
- return '{}int{}_t'.format(signed, sz)
-
-
-def _get_enum_param_type(enum):
- return _get_obj_param_type(enum.integer)
-
-
-def _get_floating_point_param_type(fp):
- if fp.exp_dig == 8 and fp.mant_dig == 24 and fp.align == 32:
- return 'float'
- elif fp.exp_dig == 11 and fp.mant_dig == 53 and fp.align == 64:
- return 'double'
- else:
- return 'uint64_t'
-
+ if len(opt_items) == 0:
+ return default
-_obj_param_type_cb = {
- pytsdl.tsdl.Struct: lambda obj: 'const void*',
- pytsdl.tsdl.Integer: _get_integer_param_type,
- pytsdl.tsdl.Enum: _get_enum_param_type,
- pytsdl.tsdl.FloatingPoint: _get_floating_point_param_type,
- pytsdl.tsdl.Array: lambda obj: 'const void*',
- pytsdl.tsdl.Sequence: lambda obj: 'const void*',
- pytsdl.tsdl.String: lambda obj: 'const char*',
-}
+ opt_item = opt_items[-1]
+ if opt_item.descr.has_arg:
+ return opt_item.arg_text
-def _get_obj_param_type(obj):
- return _obj_param_type_cb[type(obj)](obj)
+ return True
-class _CBlock(list):
+class _CliCfg:
pass
-class _CLine(str):
+class _CliGenCmdCfg(_CliCfg):
+ def __init__(self, config_file_path, c_source_dir, c_header_dir, metadata_stream_dir,
+ inclusion_dirs, ignore_inclusion_not_found, dump_config, v2_prefix):
+ self._config_file_path = config_file_path
+ self._c_source_dir = c_source_dir
+ self._c_header_dir = c_header_dir
+ self._metadata_stream_dir = metadata_stream_dir
+ self._inclusion_dirs = inclusion_dirs
+ self._ignore_inclusion_not_found = ignore_inclusion_not_found
+ self._dump_config = dump_config
+ self._v2_prefix = v2_prefix
+
+ @property
+ def config_file_path(self):
+ return self._config_file_path
+
+ @property
+ def c_source_dir(self):
+ return self._c_source_dir
+
+ @property
+ def c_header_dir(self):
+ return self._c_header_dir
+
+ @property
+ def metadata_stream_dir(self):
+ return self._metadata_stream_dir
+
+ @property
+ def inclusion_dirs(self):
+ return self._inclusion_dirs
+
+ @property
+ def ignore_inclusion_not_found(self):
+ return self._ignore_inclusion_not_found
+
+ @property
+ def dump_config(self):
+ return self._dump_config
+
+ @property
+ def v2_prefix(self):
+ return self._v2_prefix
+
+
+def _print_gen_cmd_usage():
+ print('''Usage: barectf generate [--code-dir=DIR] [--headers-dir=DIR]
+ [--metadata-dir=DIR] [--prefix=PREFIX]
+ [--include-dir=DIR]... [--ignore-include-not-found]
+ [--dump-config] CONFIG-FILE-PATH
+
+Options:
+ -c DIR, --code-dir=DIR Write C source files to DIR
+ --dump-config Print the effective configuration file
+ -H DIR, --headers-dir=DIR Write C header files to DIR
+ --ignore-include-not-found Continue to process the configuration file when
+ included files are not found
+ -I DIR, --include-dir=DIR Add DIR to the list of directories to be
+ searched for inclusion files
+ -m DIR, --metadata-dir=DIR Write the metadata stream file to DIR
+ -p PREFIX, --prefix=PREFIX Set the configuration prefix to PREFIX''')
+
+
+class _CliError(Exception):
pass
-def _get_check_offset_overflow_cline(size):
- return _CLine(_CHECK_OFFSET_OVERFLOW_FMT.format(size))
-
-
-def _write_field_struct(doc, fname, struct):
- size = _get_struct_size(struct)
- size_bytes = _get_alignment(size, 8) // 8
-
- dst = _CTX_BUF_AT_ADDR
- src = _field_name_to_param_name(fname)
-
- return [
- # memcpy() is safe since barectf requires inner structures
- # to be byte-aligned
- _get_check_offset_overflow_cline(size),
- _CLine('memcpy({}, {}, {});'.format(dst, src, size_bytes)),
- _CLine('{} += {};'.format(_CTX_AT, size)),
+def _cli_gen_cfg_from_args(orig_args):
+ # parse original arguments
+ opt_descrs = [
+ barectf_argpar.OptDescr('h', 'help'),
+ barectf_argpar.OptDescr('c', 'code-dir', True),
+ barectf_argpar.OptDescr('H', 'headers-dir', True),
+ barectf_argpar.OptDescr('I', 'include-dir', True),
+ barectf_argpar.OptDescr('m', 'metadata-dir', True),
+ barectf_argpar.OptDescr('p', 'prefix', True),
+ barectf_argpar.OptDescr(long_name='dump-config'),
+ barectf_argpar.OptDescr(long_name='ignore-include-not-found'),
]
+ res = barectf_argpar.parse(orig_args, opt_descrs)
+ assert len(res.ingested_orig_args) == len(orig_args)
+ # command help?
+ if len(_find_opt_items(res.items, 'help')) > 0:
+ _print_gen_cmd_usage()
+ sys.exit()
-_bo_suffixes_map = {
- pytsdl.tsdl.ByteOrder.BE: 'be',
- pytsdl.tsdl.ByteOrder.LE: 'le',
-}
-
+ # check configuration file path
+ config_file_path = None
-def _write_field_integer(doc, fname, integer):
- bo = _bo_suffixes_map[integer.byte_order]
- ptr = _CTX_BUF
- t = _get_obj_param_type(integer)
- start = _CTX_AT
- length = _get_obj_size(integer)
- value = _field_name_to_param_name(fname)
- fmt = 'barectf_bitfield_write_{}({}, {}, {}, {}, {});'
+ for item in res.items:
+ if type(item) is barectf_argpar._NonOptItem:
+ if config_file_path is not None:
+ raise _CliError('Multiple configuration file paths provided')
- return [
- _get_check_offset_overflow_cline(length),
- _CLine(fmt.format(bo, ptr, t, start, length, value)),
- _CLine('{} += {};'.format(_CTX_AT, length))
- ]
+ config_file_path = item.text
+ if config_file_path is None:
+ raise _CliError('Missing configuration file path')
-def _write_field_enum(doc, fname, enum):
- return _write_field_obj(doc, fname, enum.integer)
+ if not os.path.isfile(config_file_path):
+ raise _CliError(f'`{config_file_path}` is not an existing, regular file')
+ # directories
+ c_source_dir = _opt_item_val(res.items, 'code-dir', os.getcwd())
+ c_header_dir = _opt_item_val(res.items, 'headers-dir', os.getcwd())
+ metadata_stream_dir = _opt_item_val(res.items, 'metadata-dir', os.getcwd())
+ inclusion_dirs = [item.arg_text for item in _find_opt_items(res.items, 'include-dir')]
-def _write_field_floating_point(doc, fname, floating_point):
- bo = _bo_suffixes_map[floating_point.byte_order]
- ptr = _CTX_BUF
- t = _get_obj_param_type(floating_point)
- start = _CTX_AT
- length = _get_obj_size(floating_point)
- value = _field_name_to_param_name(fname)
- fmt = 'barectf_bitfield_write_{}({}, {}, {}, {}, {});'
+ for dir in [c_source_dir, c_header_dir, metadata_stream_dir] + inclusion_dirs:
+ if not os.path.isdir(dir):
+ raise _CliError(f'`{dir}` is not an existing directory')
- return [
- _get_check_offset_overflow_cline(length),
- _CLine(fmt.format(bo, ptr, t, start, length, value)),
- _CLine('{} += {};'.format(_CTX_AT, length))
- ]
+ inclusion_dirs.append(os.getcwd())
+ # other options
+ ignore_inclusion_not_found = _opt_item_val(res.items, 'ignore-include-not-found', False)
+ dump_config = _opt_item_val(res.items, 'dump-config', False)
+ v2_prefix = _opt_item_val(res.items, 'prefix')
-def _write_field_array(doc, fname, array):
- lines = []
+ return _CliGenCmdCfg(config_file_path, c_source_dir, c_header_dir, metadata_stream_dir,
+ inclusion_dirs, ignore_inclusion_not_found, dump_config, v2_prefix)
- # array index variable declaration
- iv = 'ia_{}'.format(fname)
- lines.append(_CLine('uint32_t {};'.format(iv)))
- # for loop using array's static length
- line = 'for ({iv} = 0; {iv} < {l}; ++{iv}) {{'.format(iv=iv, l=array.length)
- lines.append(_CLine(line))
+def _print_general_usage():
+ print('''Usage: barectf COMMAND COMMAND-ARGS
+ barectf --help
+ barectf --version
- # for loop statements
- for_block = _CBlock()
+General options:
+ -h, --help Show this help and quit
+ -V, --version Show version and quit
- # align bit index before writing to the buffer
- element_align = _get_obj_alignment(array.element)
- line = '{}({}, {});'.format(_ALIGN_OFFSET, _CTX_AT, element_align)
- for_block.append(_CLine(line))
+Available commands:
+ gen, generate Generate the C source and CTF metadata files of a tracer
+ from a configuration file
- # write element to the buffer
- for_block += _write_field_obj(doc, fname, array.element)
- lines.append(for_block)
+Run `barectf COMMAND --help` to show the help of COMMAND.''')
- # for loop end
- lines.append(_CLine('}'))
- return lines
-
-
-def _write_field_sequence(doc, fname, sequence):
- return [
- _CLine('would write sequence here;'),
+def _cli_cfg_from_args():
+ # We use our `argpar` module here instead of Python's `argparse`
+ # because we need to support the two following use cases:
+ #
+ # $ barectf config.yaml
+ # $ barectf generate config.yaml
+ #
+ # In other words, the default command is `generate` (for backward
+ # compatibility reasons). The argument parser must not consider
+ # `config.yaml` as being a command name.
+ general_opt_descrs = [
+ barectf_argpar.OptDescr('V', 'version'),
+ barectf_argpar.OptDescr('h', 'help'),
]
+ orig_args = sys.argv[1:]
+ res = barectf_argpar.parse(orig_args, general_opt_descrs, False)
+
+ # find command name, collecting preceding (common) option items
+ general_opt_items = []
+ cmd_first_orig_arg_index = None
+ cmd_name = None
+
+ for item in res.items:
+ if type(item) is barectf_argpar._NonOptItem:
+ if item.text in ['gen', 'generate']:
+ cmd_name = 'generate'
+ cmd_first_orig_arg_index = item.orig_arg_index + 1
+ else:
+ cmd_first_orig_arg_index = item.orig_arg_index
-
-def _write_field_string(doc, fname, string):
- lines = []
-
- # source pointer (function parameter)
- src = _field_name_to_param_name(fname)
-
- # string index variable declaration
- iv = 'is_{}'.format(fname)
- lines.append(_CLine('uint32_t {};'.format(iv)))
-
- # for loop; loop until the end of the source string is reached
- fmt = "for ({iv} = 0; {src}[{iv}] != '\\0'; ++{iv}, {ctxat} += 8) {{"
- lines.append(_CLine(fmt.format(iv=iv, src=src, ctxat=_CTX_AT)))
-
- # for loop statements
- for_block = _CBlock()
-
- # check offset overflow
- for_block.append(_get_check_offset_overflow_cline(8))
-
- # write byte to the buffer
- line = '{} = {}[{}]'.format(_CTX_BUF_AT, src, iv)
- for_block.append(_CLine(line))
-
- # append for loop
- lines.append(for_block)
- lines.append(_CLine('}'))
-
- # write NULL character to the buffer
- lines.append(_CLine("{} = '\\0';".format(_CTX_BUF_AT)))
- lines.append(_CLine('{} += 8;'.format(_CTX_AT)))
-
- return lines
-
-
-_write_field_obj_cb = {
- pytsdl.tsdl.Struct: _write_field_struct,
- pytsdl.tsdl.Integer: _write_field_integer,
- pytsdl.tsdl.Enum: _write_field_enum,
- pytsdl.tsdl.FloatingPoint: _write_field_floating_point,
- pytsdl.tsdl.Array: _write_field_array,
- pytsdl.tsdl.Sequence: _write_field_sequence,
- pytsdl.tsdl.String: _write_field_string,
-}
-
-
-def _write_field_obj(doc, fname, ftype):
- return _write_field_obj_cb[type(ftype)](doc, fname, ftype)
-
-
-def _struct_to_clines(doc, struct):
- line_groups = []
-
- for fname, ftype in struct.fields.items():
- lines = []
- pname = _field_name_to_param_name(fname)
- align = _get_obj_alignment(ftype)
-
- # align bit index before writing to the buffer
- line = '{}({}, {});'.format(_ALIGN_OFFSET, _CTX_AT, align)
- lines.append(line)
-
- # write offset variables
- if type(ftype) is pytsdl.tsdl.Struct:
- offset_vars_tree = collections.OrderedDict()
- _get_struct_size(ftype, offset_vars_tree)
- offset_vars = _offset_vars_tree_to_vars(offset_vars_tree)
-
- # as many offset as there are child fields because a future
- # sequence could refer to any of those fields
- for lname, offset in offset_vars.items():
- fmt = 'uint32_t off_{}_{} = {} + {};'
- line = fmt.format(fname, lname, _CTX_AT, offset);
- lines.append(_CLine(line))
- elif type(ftype) is pytsdl.tsdl.Integer:
- # offset of this simple field is the current bit index
- line = 'uint32_t off_{} = {};'.format(fname, _CTX_AT)
- lines.append(_CLine(line))
-
- lines += _write_field_obj(doc, fname, ftype)
- line_groups.append(lines)
-
- if not line_groups:
- return line_groups
-
- output_lines = line_groups[0]
-
- for lines in line_groups[1:]:
- output_lines.append('')
- output_lines += lines
-
- return output_lines
-
-
-def _cblock_to_source_lines(cblock, indent=1):
- src = []
- indentstr = '\t' * indent
-
- for line in cblock:
- if type(line) is _CBlock:
- src += _cblock_to_source_lines(line, indent + 1)
+ break
else:
- src.append(indentstr + line)
+ assert type(item) is barectf_argpar._OptItem
+ general_opt_items.append(item)
- return src
+ # general help?
+ if len(_find_opt_items(general_opt_items, 'help')) > 0:
+ _print_general_usage()
+ sys.exit()
+ # version?
+ if len(_find_opt_items(general_opt_items, 'version')) > 0:
+ print(f'barectf {barectf.__version__}')
+ sys.exit()
-def _cblock_to_source(cblock, indent=1):
- lines = _cblock_to_source_lines(cblock, indent)
+ # execute command
+ cmd_orig_args = orig_args[cmd_first_orig_arg_index:]
- return '\n'.join(lines)
+ if cmd_name is None:
+ # default `generate` command
+ return _cli_gen_cfg_from_args(cmd_orig_args)
+ else:
+ assert cmd_name == 'generate'
+ return _cli_gen_cfg_from_args(cmd_orig_args)
-def gen_barectf(metadata, output, prefix, static_inline, manual_clock):
- # open CTF metadata file
+def _run():
+ # parse arguments
try:
- with open(metadata) as f:
- tsdl = f.read()
- except:
- _perror('cannot open/read CTF metadata file "{}"'.format(metadata))
+ cli_cfg = _cli_cfg_from_args()
+ except barectf_argpar._Error as exc:
+ _print_error(f'Command-line: For argument `{exc.orig_arg}`: {exc.msg}')
+ except _CliError as exc:
+ _print_error(f'Command-line: {exc}')
- # parse CTF metadata
- parser = pytsdl.parser.Parser()
+ assert type(cli_cfg) is _CliGenCmdCfg
+ # create configuration
try:
- doc = parser.parse(tsdl)
- except pytsdl.parser.ParseError as e:
- _perror('parse error: {}'.format(e))
-
- # validate CTF metadata against barectf constraints
- _validate_metadata(doc)
+ with open(cli_cfg.config_file_path) as f:
+ if cli_cfg.dump_config:
+ # print effective configuration file
+ print(barectf.effective_configuration_file(f, True, cli_cfg.inclusion_dirs,
+ cli_cfg.ignore_inclusion_not_found))
+
+ # barectf.configuration_from_file() reads the file again
+ # below: rewind.
+ f.seek(0)
+
+ config = barectf.configuration_from_file(f, True, cli_cfg.inclusion_dirs,
+ cli_cfg.ignore_inclusion_not_found)
+ except barectf._ConfigurationParseError as exc:
+ _print_config_error(exc)
+ except Exception as exc:
+ _print_unknown_exc(exc)
+
+ if cli_cfg.v2_prefix is not None:
+ # Override prefixes.
+ #
+ # For historical reasons, the `--prefix` option applies the
+ # barectf 2 configuration prefix rules. Therefore, get the
+ # equivalent barectf 3 prefixes first.
+ v3_prefixes = barectf_config_parse_common._v3_prefixes_from_v2_prefix(cli_cfg.v2_prefix)
+ cg_opts = config.options.code_generation_options
+ cg_opts = barectf.ConfigurationCodeGenerationOptions(v3_prefixes.identifier,
+ v3_prefixes.file_name,
+ cg_opts.default_stream_type,
+ cg_opts.header_options,
+ cg_opts.clock_type_c_types)
+ config = barectf.Configuration(config.trace, barectf.ConfigurationOptions(cg_opts))
+
+ # create a barectf code generator
+ code_gen = barectf.CodeGenerator(config)
+
+ def write_file(dir, file):
+ with open(os.path.join(dir, file.name), 'w') as f:
+ f.write(file.contents)
+
+ def write_files(dir, files):
+ for file in files:
+ write_file(dir, file)
- import json
-
- clines = _struct_to_clines(doc, doc.streams[0].get_event(0).fields)
- print(_cblock_to_source(_CBlock(clines)))
-
-
-def run():
- args = _parse_args()
- gen_barectf(args.metadata, args.output, args.prefix, args.static_inline,
- args.manual_clock)
+ try:
+ # generate and write metadata stream file
+ write_file(cli_cfg.metadata_stream_dir, code_gen.generate_metadata_stream())
+
+ # generate and write C header files
+ write_files(cli_cfg.c_header_dir, code_gen.generate_c_headers())
+
+ # generate and write C source files
+ write_files(cli_cfg.c_source_dir, code_gen.generate_c_sources())
+ except Exception as exc:
+ # We know `config` is valid, therefore the code generator cannot
+ # fail for a reason known to barectf.
+ _print_unknown_exc(exc)