Introduce new barectf configuration API and YAML configuration schema
[deliverable/barectf.git] / barectf / config.py
1 # The MIT License (MIT)
2 #
3 # Copyright (c) 2015-2020 Philippe Proulx <pproulx@efficios.com>
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
12 #
13 # The above copyright notice and this permission notice shall be
14 # included in all copies or substantial portions of the Software.
15 #
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
24 import barectf.config_parse as barectf_config_parse
25 import barectf.version as barectf_version
26 import collections.abc
27 import collections
28 import datetime
29 import enum
30
31
32 @enum.unique
33 class ByteOrder(enum.Enum):
34 LITTLE_ENDIAN = 'le'
35 BIG_ENDIAN = 'be'
36
37
38 class _FieldType:
39 @property
40 def alignment(self):
41 raise NotImplementedError
42
43
44 class _BitArrayFieldType(_FieldType):
45 def __init__(self, size, byte_order=None, alignment=1):
46 self._size = size
47 self._byte_order = byte_order
48 self._alignment = alignment
49
50 @property
51 def size(self):
52 return self._size
53
54 @property
55 def byte_order(self):
56 return self._byte_order
57
58 @property
59 def alignment(self):
60 return self._alignment
61
62
63 class DisplayBase(enum.Enum):
64 BINARY = 2
65 OCTAL = 8
66 DECIMAL = 10
67 HEXADECIMAL = 16
68
69
70 class _IntegerFieldType(_BitArrayFieldType):
71 def __init__(self, size, byte_order=None, alignment=None,
72 preferred_display_base=DisplayBase.DECIMAL):
73 effective_alignment = 1
74
75 if alignment is None and size % 8 == 0:
76 effective_alignment = 8
77
78 super().__init__(size, byte_order, effective_alignment)
79 self._preferred_display_base = preferred_display_base
80
81 @property
82 def preferred_display_base(self):
83 return self._preferred_display_base
84
85
86 class UnsignedIntegerFieldType(_IntegerFieldType):
87 def __init__(self, *args):
88 super().__init__(*args)
89 self._mapped_clk_type_name = None
90
91
92 class SignedIntegerFieldType(_IntegerFieldType):
93 pass
94
95
96 class EnumerationFieldTypeMappingRange:
97 def __init__(self, lower, upper):
98 self._lower = lower
99 self._upper = upper
100
101 @property
102 def lower(self):
103 return self._lower
104
105 @property
106 def upper(self):
107 return self._upper
108
109 def __eq__(self, other):
110 if type(other) is not type(self):
111 return False
112
113 return (self._lower, self._upper) == (other._lower, other._upper)
114
115 def __hash__(self):
116 return hash((self._lower, self._upper))
117
118 def contains(self, value):
119 return self._lower <= value <= self._upper
120
121
122 class EnumerationFieldTypeMapping:
123 def __init__(self, ranges):
124 self._ranges = frozenset(ranges)
125
126 @property
127 def ranges(self):
128 return self._ranges
129
130 def ranges_contain_value(self, value):
131 return any([rg.contains(value) for rg in self._ranges])
132
133
134 class EnumerationFieldTypeMappings(collections.abc.Mapping):
135 def __init__(self, mappings):
136 self._mappings = {label: mapping for label, mapping in mappings.items()}
137
138 def __getitem__(self, key):
139 return self._mappings[key]
140
141 def __iter__(self):
142 return iter(self._mappings)
143
144 def __len__(self):
145 return len(self._mappings)
146
147
148 class _EnumerationFieldType(_IntegerFieldType):
149 def __init__(self, size, byte_order=None, alignment=None,
150 preferred_display_base=DisplayBase.DECIMAL, mappings=None):
151 super().__init__(size, byte_order, alignment, preferred_display_base)
152 self._mappings = EnumerationFieldTypeMappings({})
153
154 if mappings is not None:
155 self._mappings = EnumerationFieldTypeMappings(mappings)
156
157 @property
158 def mappings(self):
159 return self._mappings
160
161 def labels_for_value(self, value):
162 labels = set()
163
164 for label, mapping in self._mappings.items():
165 if mapping.ranges_contain_value(value):
166 labels.add(label)
167
168 return labels
169
170
171 class UnsignedEnumerationFieldType(_EnumerationFieldType, UnsignedIntegerFieldType):
172 pass
173
174
175 class SignedEnumerationFieldType(_EnumerationFieldType, SignedIntegerFieldType):
176 pass
177
178
179 class RealFieldType(_BitArrayFieldType):
180 pass
181
182
183 class StringFieldType(_FieldType):
184 @property
185 def alignment(self):
186 return 8
187
188
189 class _ArrayFieldType(_FieldType):
190 def __init__(self, element_field_type):
191 self._element_field_type = element_field_type
192
193 @property
194 def element_field_type(self):
195 return self._element_field_type
196
197 @property
198 def alignment(self):
199 return self._element_field_type.alignment
200
201
202 class StaticArrayFieldType(_ArrayFieldType):
203 def __init__(self, length, element_field_type):
204 super().__init__(element_field_type)
205 self._length = length
206
207 @property
208 def length(self):
209 return self._length
210
211
212 class StructureFieldTypeMember:
213 def __init__(self, field_type):
214 self._field_type = field_type
215
216 @property
217 def field_type(self):
218 return self._field_type
219
220
221 class StructureFieldTypeMembers(collections.abc.Mapping):
222 def __init__(self, members):
223 self._members = collections.OrderedDict()
224
225 for name, member in members.items():
226 assert type(member) is StructureFieldTypeMember
227 self._members[name] = member
228
229 def __getitem__(self, key):
230 return self._members[key]
231
232 def __iter__(self):
233 return iter(self._members)
234
235 def __len__(self):
236 return len(self._members)
237
238
239 class StructureFieldType(_FieldType):
240 def __init__(self, minimum_alignment=1, members=None):
241 self._minimum_alignment = minimum_alignment
242 self._members = StructureFieldTypeMembers({})
243
244 if members is not None:
245 self._members = StructureFieldTypeMembers(members)
246
247 self._set_alignment()
248
249 def _set_alignment(self):
250 self._alignment = self._minimum_alignment
251
252 for member in self._members.values():
253 if member.field_type.alignment > self._alignment:
254 self._alignment = member.field_type.alignment
255
256 @property
257 def minimum_alignment(self):
258 return self._minimum_alignment
259
260 @property
261 def alignment(self):
262 return self._alignment
263
264 @property
265 def members(self):
266 return self._members
267
268
269 class _UniqueByName:
270 def __eq__(self, other):
271 if type(other) is not type(self):
272 return False
273
274 return self._name == other._name
275
276 def __lt__(self, other):
277 assert type(self) is type(other)
278 return self._name < other._name
279
280 def __hash__(self):
281 return hash(self._name)
282
283
284 class EventType(_UniqueByName):
285 def __init__(self, name, log_level=None, specific_context_field_type=None,
286 payload_field_type=None):
287 self._id = None
288 self._name = name
289 self._log_level = log_level
290 self._specific_context_field_type = specific_context_field_type
291 self._payload_field_type = payload_field_type
292
293 @property
294 def id(self):
295 return self._id
296
297 @property
298 def name(self):
299 return self._name
300
301 @property
302 def log_level(self):
303 return self._log_level
304
305 @property
306 def specific_context_field_type(self):
307 return self._specific_context_field_type
308
309 @property
310 def payload_field_type(self):
311 return self._payload_field_type
312
313
314 class ClockTypeOffset:
315 def __init__(self, seconds=0, cycles=0):
316 self._seconds = seconds
317 self._cycles = cycles
318
319 @property
320 def seconds(self):
321 return self._seconds
322
323 @property
324 def cycles(self):
325 return self._cycles
326
327
328 class ClockType(_UniqueByName):
329 def __init__(self, name, frequency=int(1e9), uuid=None, description=None, precision=0,
330 offset=None, origin_is_unix_epoch=False):
331 self._name = name
332 self._frequency = frequency
333 self._uuid = uuid
334 self._description = description
335 self._precision = precision
336 self._offset = ClockTypeOffset()
337
338 if offset is not None:
339 self._offset = offset
340
341 self._origin_is_unix_epoch = origin_is_unix_epoch
342
343 @property
344 def name(self):
345 return self._name
346
347 @property
348 def frequency(self):
349 return self._frequency
350
351 @property
352 def uuid(self):
353 return self._uuid
354
355 @property
356 def description(self):
357 return self._description
358
359 @property
360 def precision(self):
361 return self._precision
362
363 @property
364 def offset(self):
365 return self._offset
366
367 @property
368 def origin_is_unix_epoch(self):
369 return self._origin_is_unix_epoch
370
371
372 DEFAULT_FIELD_TYPE = 'default'
373
374
375 class StreamTypePacketFeatures:
376 def __init__(self, total_size_field_type=DEFAULT_FIELD_TYPE,
377 content_size_field_type=DEFAULT_FIELD_TYPE, beginning_time_field_type=None,
378 end_time_field_type=None, discarded_events_counter_field_type=None):
379 def get_ft(user_ft):
380 if user_ft == DEFAULT_FIELD_TYPE:
381 return UnsignedIntegerFieldType(64)
382
383 return user_ft
384
385 self._total_size_field_type = get_ft(total_size_field_type)
386 self._content_size_field_type = get_ft(content_size_field_type)
387 self._beginning_time_field_type = get_ft(beginning_time_field_type)
388 self._end_time_field_type = get_ft(end_time_field_type)
389 self._discarded_events_counter_field_type = get_ft(discarded_events_counter_field_type)
390
391 @property
392 def total_size_field_type(self):
393 return self._total_size_field_type
394
395 @property
396 def content_size_field_type(self):
397 return self._content_size_field_type
398
399 @property
400 def beginning_time_field_type(self):
401 return self._beginning_time_field_type
402
403 @property
404 def end_time_field_type(self):
405 return self._end_time_field_type
406
407 @property
408 def discarded_events_counter_field_type(self):
409 return self._discarded_events_counter_field_type
410
411
412 class StreamTypeEventFeatures:
413 def __init__(self, type_id_field_type=DEFAULT_FIELD_TYPE, time_field_type=None):
414 def get_ft(user_field_type):
415 if user_field_type == DEFAULT_FIELD_TYPE:
416 return UnsignedIntegerFieldType(64)
417
418 return user_field_type
419
420 self._type_id_field_type = get_ft(type_id_field_type)
421 self._time_field_type = get_ft(time_field_type)
422
423 @property
424 def type_id_field_type(self):
425 return self._type_id_field_type
426
427 @property
428 def time_field_type(self):
429 return self._time_field_type
430
431
432 class StreamTypeFeatures:
433 def __init__(self, packet_features=None, event_features=None):
434 self._packet_features = StreamTypePacketFeatures()
435
436 if packet_features is not None:
437 self._packet_features = packet_features
438
439 self._event_features = StreamTypeEventFeatures()
440
441 if event_features is not None:
442 self._event_features = event_features
443
444 @property
445 def packet_features(self):
446 return self._packet_features
447
448 @property
449 def event_features(self):
450 return self._event_features
451
452
453 class StreamType(_UniqueByName):
454 def __init__(self, name, event_types, default_clock_type=None, features=None,
455 packet_context_field_type_extra_members=None,
456 event_common_context_field_type=None):
457 self._id = None
458 self._name = name
459 self._default_clock_type = default_clock_type
460 self._event_common_context_field_type = event_common_context_field_type
461 self._event_types = frozenset(event_types)
462
463 # assign unique IDs
464 for index, ev_type in enumerate(sorted(self._event_types, key=lambda evt: evt.name)):
465 assert ev_type._id is None
466 ev_type._id = index
467
468 self._set_features(features)
469 self._packet_context_field_type_extra_members = StructureFieldTypeMembers({})
470
471 if packet_context_field_type_extra_members is not None:
472 self._packet_context_field_type_extra_members = StructureFieldTypeMembers(packet_context_field_type_extra_members)
473
474 self._set_pkt_ctx_ft()
475 self._set_ev_header_ft()
476
477 def _set_features(self, features):
478 if features is not None:
479 self._features = features
480 return
481
482 ev_time_ft = None
483 pkt_beginning_time_ft = None
484 pkt_end_time_ft = None
485
486 if self._default_clock_type is not None:
487 # Automatic time field types because the stream type has a
488 # default clock type.
489 ev_time_ft = DEFAULT_FIELD_TYPE
490 pkt_beginning_time_ft = DEFAULT_FIELD_TYPE
491 pkt_end_time_ft = DEFAULT_FIELD_TYPE
492
493 self._features = StreamTypeFeatures(StreamTypePacketFeatures(beginning_time_field_type=pkt_beginning_time_ft,
494 end_time_field_type=pkt_end_time_ft),
495 StreamTypeEventFeatures(time_field_type=ev_time_ft))
496
497 def _set_ft_mapped_clk_type_name(self, ft):
498 if ft is None:
499 return
500
501 if self._default_clock_type is not None:
502 assert isinstance(ft, UnsignedIntegerFieldType)
503 ft._mapped_clk_type_name = self._default_clock_type.name
504
505 def _set_pkt_ctx_ft(self):
506 def add_member_if_exists(name, ft, set_mapped_clk_type_name=False):
507 nonlocal members
508
509 if ft is not None:
510 if set_mapped_clk_type_name:
511 self._set_ft_mapped_clk_type_name(ft)
512
513 members[name] = StructureFieldTypeMember(ft)
514
515 members = collections.OrderedDict([
516 (
517 'packet_size',
518 StructureFieldTypeMember(self._features.packet_features.total_size_field_type)
519 ),
520 (
521 'content_size',
522 StructureFieldTypeMember(self._features.packet_features.content_size_field_type)
523 )
524 ])
525
526 add_member_if_exists('timestamp_begin',
527 self._features.packet_features.beginning_time_field_type, True)
528 add_member_if_exists('timestamp_end', self._features.packet_features.end_time_field_type,
529 True)
530 add_member_if_exists('events_discarded',
531 self._features.packet_features.discarded_events_counter_field_type)
532
533 if self._packet_context_field_type_extra_members is not None:
534 for name, field_type in self._packet_context_field_type_extra_members.items():
535 assert name not in members
536 members[name] = field_type
537
538 self._pkt_ctx_ft = StructureFieldType(8, members)
539
540 def _set_ev_header_ft(self):
541 members = collections.OrderedDict()
542
543 if self._features.event_features.type_id_field_type is not None:
544 members['id'] = StructureFieldTypeMember(self._features.event_features.type_id_field_type)
545
546 if self._features.event_features.time_field_type is not None:
547 ft = self._features.event_features.time_field_type
548 self._set_ft_mapped_clk_type_name(ft)
549 members['timestamp'] = StructureFieldTypeMember(ft)
550
551 self._ev_header_ft = StructureFieldType(8, members)
552
553 @property
554 def id(self):
555 return self._id
556
557 @property
558 def name(self):
559 return self._name
560
561 @property
562 def default_clock_type(self):
563 return self._default_clock_type
564
565 @property
566 def features(self):
567 return self._features
568
569 @property
570 def packet_context_field_type_extra_members(self):
571 return self._packet_context_field_type_extra_members
572
573 @property
574 def event_common_context_field_type(self):
575 return self._event_common_context_field_type
576
577 @property
578 def event_types(self):
579 return self._event_types
580
581
582 class TraceTypeFeatures:
583 def __init__(self, magic_field_type=DEFAULT_FIELD_TYPE, uuid_field_type=None,
584 stream_type_id_field_type=DEFAULT_FIELD_TYPE):
585 def get_field_type(user_field_type, default_field_type):
586 if user_field_type == DEFAULT_FIELD_TYPE:
587 return default_field_type
588
589 return user_field_type
590
591 self._magic_field_type = get_field_type(magic_field_type, UnsignedIntegerFieldType(32))
592 self._uuid_field_type = get_field_type(uuid_field_type,
593 StaticArrayFieldType(16, UnsignedIntegerFieldType(8)))
594 self._stream_type_id_field_type = get_field_type(stream_type_id_field_type,
595 UnsignedIntegerFieldType(64))
596
597 @property
598 def magic_field_type(self):
599 return self._magic_field_type
600
601 @property
602 def uuid_field_type(self):
603 return self._uuid_field_type
604
605 @property
606 def stream_type_id_field_type(self):
607 return self._stream_type_id_field_type
608
609
610 class TraceType:
611 def __init__(self, stream_types, default_byte_order, uuid=None, features=None):
612 self._default_byte_order = default_byte_order
613 self._stream_types = frozenset(stream_types)
614
615 # assign unique IDs
616 for index, stream_type in enumerate(sorted(self._stream_types, key=lambda st: st.name)):
617 assert stream_type._id is None
618 stream_type._id = index
619
620 self._uuid = uuid
621 self._set_features(features)
622 self._set_pkt_header_ft()
623 self._set_fts_effective_byte_order()
624
625 def _set_features(self, features):
626 if features is not None:
627 self._features = features
628 return
629
630 # automatic UUID field type because the trace type has a UUID
631 uuid_ft = None if self._uuid is None else DEFAULT_FIELD_TYPE
632 self._features = TraceTypeFeatures(uuid_field_type=uuid_ft)
633
634 def _set_pkt_header_ft(self):
635 def add_member_if_exists(name, field_type):
636 nonlocal members
637
638 if field_type is not None:
639 members[name] = StructureFieldTypeMember(field_type)
640
641 members = collections.OrderedDict()
642 add_member_if_exists('magic', self._features.magic_field_type)
643 add_member_if_exists('uuid', self._features.uuid_field_type)
644 add_member_if_exists('stream_id', self._features.stream_type_id_field_type)
645 self._pkt_header_ft = StructureFieldType(8, members)
646
647 def _set_fts_effective_byte_order(self):
648 def set_ft_effective_byte_order(ft):
649 if ft is None:
650 return
651
652 if isinstance(ft, _BitArrayFieldType):
653 if ft._byte_order is None:
654 assert self._default_byte_order is not None
655 ft._byte_order = self._default_byte_order
656 elif isinstance(ft, StaticArrayFieldType):
657 set_ft_effective_byte_order(ft.element_field_type)
658 elif isinstance(ft, StructureFieldType):
659 for member in ft.members.values():
660 set_ft_effective_byte_order(member.field_type)
661
662 # packet header field type
663 set_ft_effective_byte_order(self._pkt_header_ft)
664
665 # stream type field types
666 for stream_type in self._stream_types:
667 set_ft_effective_byte_order(stream_type._pkt_ctx_ft)
668 set_ft_effective_byte_order(stream_type._ev_header_ft)
669 set_ft_effective_byte_order(stream_type._event_common_context_field_type)
670
671 # event type field types
672 for ev_type in stream_type.event_types:
673 set_ft_effective_byte_order(ev_type._specific_context_field_type)
674 set_ft_effective_byte_order(ev_type._payload_field_type)
675
676 @property
677 def default_byte_order(self):
678 return self._default_byte_order
679
680 @property
681 def uuid(self):
682 return self._uuid
683
684 @property
685 def stream_types(self):
686 return self._stream_types
687
688 def stream_type(self, name):
689 for cand_stream_type in self._stream_types:
690 if cand_stream_type.name == name:
691 return cand_stream_type
692
693 @property
694 def features(self):
695 return self._features
696
697
698 class TraceEnvironment(collections.abc.Mapping):
699 def __init__(self, environment):
700 self._env = {name: value for name, value in environment.items()}
701
702 def __getitem__(self, key):
703 return self._env[key]
704
705 def __iter__(self):
706 return iter(self._env)
707
708 def __len__(self):
709 return len(self._env)
710
711
712 class Trace:
713 def __init__(self, type, environment=None):
714 self._type = type
715 self._set_env(environment)
716
717 def _set_env(self, environment):
718 init_env = collections.OrderedDict([
719 ('domain', 'bare'),
720 ('tracer_name', 'barectf'),
721 ('tracer_major', barectf_version.__major_version__),
722 ('tracer_minor', barectf_version.__minor_version__),
723 ('tracer_patch', barectf_version.__patch_version__),
724 ('barectf_gen_date', str(datetime.datetime.now().isoformat())),
725 ])
726
727 if environment is None:
728 environment = {}
729
730 init_env.update(environment)
731 self._env = TraceEnvironment(init_env)
732
733 @property
734 def type(self):
735 return self._type
736
737 @property
738 def environment(self):
739 return self._env
740
741
742 class ClockTypeCTypes(collections.abc.Mapping):
743 def __init__(self, c_types):
744 self._c_types = {clk_type: c_type for clk_type, c_type in c_types.items()}
745
746 def __getitem__(self, key):
747 return self._c_types[key]
748
749 def __iter__(self):
750 return iter(self._c_types)
751
752 def __len__(self):
753 return len(self._c_types)
754
755
756 class ConfigurationCodeGenerationHeaderOptions:
757 def __init__(self, identifier_prefix_definition=False,
758 default_stream_type_name_definition=False):
759 self._identifier_prefix_definition = identifier_prefix_definition
760 self._default_stream_type_name_definition = default_stream_type_name_definition
761
762 @property
763 def identifier_prefix_definition(self):
764 return self._identifier_prefix_definition
765
766 @property
767 def default_stream_type_name_definition(self):
768 return self._default_stream_type_name_definition
769
770
771 class ConfigurationCodeGenerationOptions:
772 def __init__(self, identifier_prefix='barectf_', file_name_prefix='barectf',
773 default_stream_type=None, header_options=None, clock_type_c_types=None):
774 self._identifier_prefix = identifier_prefix
775 self._file_name_prefix = file_name_prefix
776 self._default_stream_type = default_stream_type
777
778 self._header_options = ConfigurationCodeGenerationHeaderOptions()
779
780 if header_options is not None:
781 self._header_options = header_options
782
783 self._clock_type_c_types = ClockTypeCTypes({})
784
785 if clock_type_c_types is not None:
786 self._clock_type_c_types = ClockTypeCTypes(clock_type_c_types)
787
788 @property
789 def identifier_prefix(self):
790 return self._identifier_prefix
791
792 @property
793 def file_name_prefix(self):
794 return self._file_name_prefix
795
796 @property
797 def default_stream_type(self):
798 return self._default_stream_type
799
800 @property
801 def header_options(self):
802 return self._header_options
803
804 @property
805 def clock_type_c_types(self):
806 return self._clock_type_c_types
807
808
809 class ConfigurationOptions:
810 def __init__(self, code_generation_options=None):
811 self._code_generation_options = ConfigurationCodeGenerationOptions()
812
813 if code_generation_options is not None:
814 self._code_generation_options = code_generation_options
815
816 @property
817 def code_generation_options(self):
818 return self._code_generation_options
819
820
821 class Configuration:
822 def __init__(self, trace, options=None):
823 self._trace = trace
824 self._options = ConfigurationOptions()
825
826 if options is not None:
827 self._options = options
828
829 clk_type_c_types = self._options.code_generation_options.clock_type_c_types
830
831 for stream_type in trace.type.stream_types:
832 def_clk_type = stream_type.default_clock_type
833
834 if def_clk_type is None:
835 continue
836
837 if def_clk_type not in clk_type_c_types:
838 clk_type_c_types._c_types[def_clk_type] = 'uint32_t'
839
840 @property
841 def trace(self):
842 return self._trace
843
844 @property
845 def options(self):
846 return self._options
847
848
849 def effective_configuration_file(file, inclusion_dirs, ignore_inclusion_not_found,
850 indent_space_count=2):
851 return barectf_config_parse._effective_config_file(file, inclusion_dirs,
852 ignore_inclusion_not_found,
853 indent_space_count)
854
855
856 def configuration_from_file(file, inclusion_dirs, ignore_inclusion_not_found):
857 return barectf_config_parse._from_file(file, inclusion_dirs, ignore_inclusion_not_found)
858
859
860 def configuration_file_major_version(file):
861 return barectf_config_parse._config_file_major_version(file)
This page took 0.047088 seconds and 4 git commands to generate.