1 # The MIT License (MIT)
3 # Copyright (c) 2014-2020 Philippe Proulx <pproulx@efficios.com>
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:
13 # The above copyright notice and this permission notice shall be
14 # included in all copies or substantial portions of the Software.
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.
27 import barectf
.config_parse_common
as barectf_config_parse_common
28 import barectf
.argpar
as barectf_argpar
29 from typing
import Any
, List
, Iterable
, NoReturn
31 from barectf
.typing
import Index
, Count
36 # Colors and prints the error message `msg` and exits with status code
38 def _print_error(msg
: str) -> NoReturn
:
39 termcolor
.cprint('Error: ', 'red', end
='', file=sys
.stderr
)
40 termcolor
.cprint(msg
, 'red', attrs
=['bold'], file=sys
.stderr
)
44 # Pretty-prints the barectf configuration error `exc` and exits with
46 def _print_config_error(exc
: barectf
._ConfigurationParseError
) -> NoReturn
:
47 # reverse: most precise message comes last
48 for ctx
in reversed(exc
.context
):
51 if ctx
.message
is not None:
52 msg
= f
' {ctx.message}'
55 termcolor
.cprint(f
'{ctx.name}', color
, attrs
=['bold'], file=sys
.stderr
, end
='')
56 termcolor
.cprint(':', color
, file=sys
.stderr
, end
='')
57 termcolor
.cprint(msg
, color
, file=sys
.stderr
)
62 # Pretty-prints the unknown exception `exc`.
63 def _print_unknown_exc(exc
: Exception) -> NoReturn
:
67 _print_error(f
'Unknown exception: {exc}')
70 # Finds and returns all the option items in `items` having the long name
72 def _find_opt_items(items
: Iterable
[barectf_argpar
._Item
],
73 long_name
: str) -> List
[barectf_argpar
._OptItem
]:
74 ret_items
: List
[barectf_argpar
._OptItem
] = []
77 if type(item
) is barectf_argpar
._OptItem
:
78 item
= typing
.cast(barectf_argpar
._OptItem
, item
)
80 if item
.descr
.long_name
== long_name
:
81 ret_items
.append(item
)
88 # For an option item without an argument:
91 # For an option item with an argument:
94 # Uses the last option item having the long name `long_name` found in
97 # Returns `default` if there's no such option item.
98 def _opt_item_val(items
: Iterable
[barectf_argpar
._Item
], long_name
: str,
99 default
: Any
= None) -> Any
:
100 opt_items
= _find_opt_items(items
, long_name
)
102 if len(opt_items
) == 0:
105 opt_item
= opt_items
[-1]
107 if opt_item
.descr
.has_arg
:
108 return opt_item
.arg_text
113 class _CliError(Exception):
117 def _cfg_file_path_from_parse_res(parse_res
: barectf_argpar
._ParseRes
) -> str:
120 for item
in parse_res
.items
:
121 if type(item
) is barectf_argpar
._NonOptItem
:
122 if cfg_file_path
is not None:
123 raise _CliError('Multiple configuration file paths provided')
125 cfg_file_path
= typing
.cast(barectf_argpar
._NonOptItem
, item
).text
127 if cfg_file_path
is None:
128 raise _CliError('Missing configuration file path')
130 if not os
.path
.isfile(cfg_file_path
):
131 raise _CliError(f
'`{cfg_file_path}` is not an existing, regular file')
136 # Returns a `_CfgCmdCfg` object from the command-line parsing results
138 def _cfg_cmd_cfg_from_parse_res(parse_res
: barectf_argpar
._ParseRes
) -> '_CfgCmdCfg':
139 # check configuration file path
140 cfg_file_path
= _cfg_file_path_from_parse_res(parse_res
)
142 # inclusion directories (`--include-dir` option needs an argument)
143 inclusion_dirs
= typing
.cast(List
[str],
144 [item
.arg_text
for item
in _find_opt_items(parse_res
.items
, 'include-dir')])
146 for dir in inclusion_dirs
:
147 if not os
.path
.isdir(dir):
148 raise _CliError(f
'`{dir}` is not an existing directory')
150 inclusion_dirs
.append(os
.getcwd())
153 ignore_inclusion_file_not_found
= _opt_item_val(parse_res
.items
, 'ignore-include-not-found',
156 return _CfgCmdCfg(cfg_file_path
, inclusion_dirs
, ignore_inclusion_file_not_found
)
159 def _print_gen_cmd_usage():
160 print('''Usage: barectf generate [--code-dir=DIR] [--headers-dir=DIR]
161 [--metadata-dir=DIR] [--prefix=PREFIX]
162 [--include-dir=DIR]... [--ignore-include-not-found]
164 barectf generate --help
166 Generate the C source and CTF metadata stream files of a tracer from the
167 configuration file CONFIG-FILE-PATH.
170 -c DIR, --code-dir=DIR Write C source files to DIR instead of the CWD
171 -H DIR, --headers-dir=DIR Write C header files to DIR instead of the CWD
172 -h, --help Show this help and quit
173 --ignore-include-not-found Continue to process the configuration file when
174 included files are not found
175 -I DIR, --include-dir=DIR Add DIR to the list of directories to be
176 searched for inclusion files
177 -m DIR, --metadata-dir=DIR Write the metadata stream file to DIR instead of
179 -p PREFIX, --prefix=PREFIX Set the configuration prefix to PREFIX''')
182 # Returns a source and metadata stream file generating command object
183 # from the specific command-line arguments `orig_args`.
184 def _gen_cmd_cfg_from_args(orig_args
: barectf_argpar
.OrigArgs
) -> '_GenCmd':
185 # parse original arguments
187 barectf_argpar
.OptDescr('h', 'help'),
188 barectf_argpar
.OptDescr('c', 'code-dir', True),
189 barectf_argpar
.OptDescr('H', 'headers-dir', True),
190 barectf_argpar
.OptDescr('I', 'include-dir', True),
191 barectf_argpar
.OptDescr('m', 'metadata-dir', True),
192 barectf_argpar
.OptDescr('p', 'prefix', True),
193 barectf_argpar
.OptDescr(long_name
='dump-config'),
194 barectf_argpar
.OptDescr(long_name
='ignore-include-not-found'),
196 res
= barectf_argpar
.parse(orig_args
, opt_descrs
)
197 assert len(res
.ingested_orig_args
) == len(orig_args
)
200 if len(_find_opt_items(res
.items
, 'help')) > 0:
201 _print_gen_cmd_usage()
204 # get common configuration file command CLI configuration
205 cfg_cmd_cfg
= _cfg_cmd_cfg_from_parse_res(res
)
208 c_source_dir
= _opt_item_val(res
.items
, 'code-dir', os
.getcwd())
209 c_header_dir
= _opt_item_val(res
.items
, 'headers-dir', os
.getcwd())
210 metadata_stream_dir
= _opt_item_val(res
.items
, 'metadata-dir', os
.getcwd())
212 for dir in [c_source_dir
, c_header_dir
, metadata_stream_dir
]:
213 if not os
.path
.isdir(dir):
214 raise _CliError(f
'`{dir}` is not an existing directory')
217 dump_config
= _opt_item_val(res
.items
, 'dump-config', False)
218 v2_prefix
= _opt_item_val(res
.items
, 'prefix')
220 return _GenCmd(_GenCmdCfg(cfg_cmd_cfg
.cfg_file_path
, c_source_dir
, c_header_dir
,
221 metadata_stream_dir
, cfg_cmd_cfg
.inclusion_dirs
,
222 cfg_cmd_cfg
.ignore_inclusion_file_not_found
, dump_config
, v2_prefix
))
225 def _show_effective_cfg_cmd_usage():
226 print('''Usage: barectf show-effective-configuration [--include-dir=DIR]...
227 [--ignore-include-not-found]
228 [--indent-spaces=COUNT] CONFIG-FILE-PATH
229 barectf show-effective-configuration --help
231 Print the effective configuration file for a the configuration file
235 -h, --help Show this help and quit
236 --ignore-include-not-found Continue to process the configuration file when
237 included files are not found
238 -I DIR, --include-dir=DIR Add DIR to the list of directories to be
239 searched for inclusion files
240 --indent-spaces=COUNT Use COUNT spaces at a time to indent YAML lines
244 # Returns an effective configuration showing command object from the
245 # specific command-line arguments `orig_args`.
246 def _show_effective_cfg_cfg_from_args(orig_args
: barectf_argpar
.OrigArgs
) -> '_ShowEffectiveCfgCmd':
247 # parse original arguments
249 barectf_argpar
.OptDescr('h', 'help'),
250 barectf_argpar
.OptDescr('I', 'include-dir', True),
251 barectf_argpar
.OptDescr(long_name
='indent-spaces', has_arg
=True),
252 barectf_argpar
.OptDescr(long_name
='ignore-include-not-found'),
254 res
= barectf_argpar
.parse(orig_args
, opt_descrs
)
255 assert len(res
.ingested_orig_args
) == len(orig_args
)
258 if len(_find_opt_items(res
.items
, 'help')) > 0:
259 _show_effective_cfg_cmd_usage()
262 # get common configuration command CLI configuration
263 cfg_cmd_cfg
= _cfg_cmd_cfg_from_parse_res(res
)
266 indent_space_count
= _opt_item_val(res
.items
, 'indent-spaces', 2)
269 indent_space_count
= int(indent_space_count
)
270 except (ValueError, TypeError):
271 raise _CliError(f
'Invalid `--indent-spaces` option argument: `{indent_space_count}`')
273 if indent_space_count
< 1 or indent_space_count
> 8:
274 raise _CliError(f
'Invalid `--indent-spaces` option argument (`{indent_space_count}`): expecting a value in [1, 8]')
276 return _ShowEffectiveCfgCmd(_ShowEffectiveCfgCmdCfg(cfg_cmd_cfg
.cfg_file_path
,
277 cfg_cmd_cfg
.inclusion_dirs
,
278 cfg_cmd_cfg
.ignore_inclusion_file_not_found
,
279 Count(indent_space_count
)))
282 def _show_cfg_version_cmd_usage():
283 print('''Usage: barectf show-configuration-version CONFIG-FILE-PATH
284 barectf show-configuration-version --help
286 Print the major version (2 or 3) of the configuration file CONFIG-FILE-PATH.
289 -h, --help Show this help and quit''')
292 # Returns a configuration version showing command object from the
293 # specific command-line arguments `orig_args`.
294 def _show_cfg_version_cfg_from_args(orig_args
: barectf_argpar
.OrigArgs
) -> '_ShowCfgVersionCmd':
295 # parse original arguments
297 barectf_argpar
.OptDescr('h', 'help'),
299 res
= barectf_argpar
.parse(orig_args
, opt_descrs
)
300 assert len(res
.ingested_orig_args
) == len(orig_args
)
303 if len(_find_opt_items(res
.items
, 'help')) > 0:
304 _show_cfg_version_cmd_usage()
307 # check configuration file path
308 cfg_file_path
= _cfg_file_path_from_parse_res(res
)
310 return _ShowCfgVersionCmd(_ShowCfgVersionCmdCfg(cfg_file_path
))
313 def _print_general_usage():
314 print('''Usage: barectf COMMAND COMMAND-ARGS
319 -h, --help Show this help and quit
320 -V, --version Show version and quit
325 Generate the C source and CTF metadata stream files of a tracer from a
328 show-effective-configuration:
329 show-effective-config:
331 Print the effective configuration file for a given configuration file and
332 inclusion directories.
334 show-configuration-version:
337 Print the major version of a given configuration file.
339 Run `barectf COMMAND --help` to show the help of COMMAND.''')
342 # Returns a command object from the command-line arguments `orig_args`.
344 # All the `orig_args` elements are considered.
345 def _cmd_from_args(orig_args
: barectf_argpar
.OrigArgs
) -> '_Cmd':
346 # We use our `argpar` module here instead of Python's `argparse`
347 # because we need to support the two following use cases:
349 # $ barectf config.yaml
350 # $ barectf generate config.yaml
352 # In other words, the default command is `generate` (for backward
353 # compatibility reasons). The argument parser must not consider
354 # `config.yaml` as being a command name.
355 general_opt_descrs
= [
356 barectf_argpar
.OptDescr('V', 'version'),
357 barectf_argpar
.OptDescr('h', 'help'),
359 res
= barectf_argpar
.parse(orig_args
, general_opt_descrs
, False)
361 # find command name, collecting preceding (common) option items
362 cmd_from_args_funcs
= {
363 'generate': _gen_cmd_cfg_from_args
,
364 'gen': _gen_cmd_cfg_from_args
,
365 'show-effective-configuration': _show_effective_cfg_cfg_from_args
,
366 'show-effective-config': _show_effective_cfg_cfg_from_args
,
367 'show-effective-cfg': _show_effective_cfg_cfg_from_args
,
368 'show-configuration-version': _show_cfg_version_cfg_from_args
,
369 'show-config-version': _show_cfg_version_cfg_from_args
,
370 'show-cfg-version': _show_cfg_version_cfg_from_args
,
372 general_opt_items
: List
[barectf_argpar
._OptItem
] = []
373 cmd_first_orig_arg_index
= None
374 cmd_from_args_func
= None
376 for item
in res
.items
:
377 if type(item
) is barectf_argpar
._NonOptItem
:
378 item
= typing
.cast(barectf_argpar
._NonOptItem
, item
)
379 cmd_from_args_func
= cmd_from_args_funcs
.get(item
.text
)
381 if cmd_from_args_func
is None:
382 cmd_first_orig_arg_index
= item
.orig_arg_index
384 cmd_first_orig_arg_index
= Index(item
.orig_arg_index
+ 1)
388 assert type(item
) is barectf_argpar
._OptItem
389 general_opt_items
.append(typing
.cast(barectf_argpar
._OptItem
, item
))
392 if len(_find_opt_items(general_opt_items
, 'help')) > 0:
393 _print_general_usage()
397 if len(_find_opt_items(general_opt_items
, 'version')) > 0:
398 print(f
'barectf {barectf.__version__}')
401 # create command object
402 cmd_orig_args
= orig_args
[cmd_first_orig_arg_index
:]
404 if cmd_from_args_func
is None:
405 # default `generate` command
406 return _gen_cmd_cfg_from_args(cmd_orig_args
)
408 return cmd_from_args_func(cmd_orig_args
)
415 class _CfgCmdCfg(_CmdCfg
):
416 def __init__(self
, cfg_file_path
: str, inclusion_dirs
: List
[str],
417 ignore_inclusion_file_not_found
: bool):
418 self
._cfg
_file
_path
= cfg_file_path
419 self
._inclusion
_dirs
= inclusion_dirs
420 self
._ignore
_inclusion
_file
_not
_found
= ignore_inclusion_file_not_found
423 def cfg_file_path(self
) -> str:
424 return self
._cfg
_file
_path
427 def inclusion_dirs(self
) -> List
[str]:
428 return self
._inclusion
_dirs
431 def ignore_inclusion_file_not_found(self
) -> bool:
432 return self
._ignore
_inclusion
_file
_not
_found
436 def __init__(self
, cfg
: _CmdCfg
):
440 def cfg(self
) -> _CmdCfg
:
444 raise NotImplementedError
447 class _GenCmdCfg(_CfgCmdCfg
):
448 def __init__(self
, cfg_file_path
: str, c_source_dir
: str, c_header_dir
: str,
449 metadata_stream_dir
: str, inclusion_dirs
: List
[str],
450 ignore_inclusion_file_not_found
: bool, dump_config
: bool, v2_prefix
: str):
451 super().__init
__(cfg_file_path
, inclusion_dirs
, ignore_inclusion_file_not_found
)
452 self
._c
_source
_dir
= c_source_dir
453 self
._c
_header
_dir
= c_header_dir
454 self
._metadata
_stream
_dir
= metadata_stream_dir
455 self
._dump
_config
= dump_config
456 self
._v
2_prefix
= v2_prefix
459 def c_source_dir(self
) -> str:
460 return self
._c
_source
_dir
463 def c_header_dir(self
) -> str:
464 return self
._c
_header
_dir
467 def metadata_stream_dir(self
) -> str:
468 return self
._metadata
_stream
_dir
471 def dump_config(self
) -> bool:
472 return self
._dump
_config
475 def v2_prefix(self
) -> str:
476 return self
._v
2_prefix
479 # Source and metadata stream file generating command.
482 # create configuration
484 with
open(self
.cfg
.cfg_file_path
) as f
:
485 if self
.cfg
.dump_config
:
486 # print effective configuration file
487 print(barectf
.effective_configuration_file(f
, True, self
.cfg
.inclusion_dirs
,
488 self
.cfg
.ignore_inclusion_file_not_found
))
490 # barectf.configuration_from_file() reads the file again
494 config
= barectf
.configuration_from_file(f
, True, self
.cfg
.inclusion_dirs
,
495 self
.cfg
.ignore_inclusion_file_not_found
)
496 except barectf
._ConfigurationParseError
as exc
:
497 _print_config_error(exc
)
498 except Exception as exc
:
499 _print_unknown_exc(exc
)
501 if self
.cfg
.v2_prefix
is not None:
504 # For historical reasons, the `--prefix` option applies the
505 # barectf 2 configuration prefix rules. Therefore, get the
506 # equivalent barectf 3 prefixes first.
507 v3_prefixes
= barectf_config_parse_common
._v
3_prefixes
_from
_v
2_prefix
(self
.cfg
.v2_prefix
)
508 cg_opts
= config
.options
.code_generation_options
509 cg_opts
= barectf
.ConfigurationCodeGenerationOptions(v3_prefixes
.identifier
,
510 v3_prefixes
.file_name
,
511 cg_opts
.default_data_stream_type
,
512 cg_opts
.header_options
,
513 cg_opts
.clock_type_c_types
)
514 config
= barectf
.Configuration(config
.trace
, barectf
.ConfigurationOptions(cg_opts
))
516 # create a barectf code generator
517 code_gen
= barectf
.CodeGenerator(config
)
519 def write_file(dir, file):
520 with
open(os
.path
.join(dir, file.name
), 'w') as f
:
521 f
.write(file.contents
)
523 def write_files(dir, files
):
525 write_file(dir, file)
528 # generate and write metadata stream file
529 write_file(self
.cfg
.metadata_stream_dir
, code_gen
.generate_metadata_stream())
531 # generate and write C header files
532 write_files(self
.cfg
.c_header_dir
, code_gen
.generate_c_headers())
534 # generate and write C source files
535 write_files(self
.cfg
.c_source_dir
, code_gen
.generate_c_sources())
536 except Exception as exc
:
537 # We know `config` is valid, therefore the code generator cannot
538 # fail for a reason known to barectf.
539 _print_unknown_exc(exc
)
542 class _ShowEffectiveCfgCmdCfg(_CfgCmdCfg
):
543 def __init__(self
, cfg_file_path
: str, inclusion_dirs
: List
[str],
544 ignore_inclusion_file_not_found
: bool, indent_space_count
: Count
):
545 super().__init
__(cfg_file_path
, inclusion_dirs
, ignore_inclusion_file_not_found
)
546 self
._indent
_space
_count
= indent_space_count
549 def indent_space_count(self
) -> Count
:
550 return self
._indent
_space
_count
553 # Effective configuration showing command.
554 class _ShowEffectiveCfgCmd(_Cmd
):
557 with
open(self
.cfg
.cfg_file_path
) as f
:
558 print(barectf
.effective_configuration_file(f
, True, self
.cfg
.inclusion_dirs
,
559 self
.cfg
.ignore_inclusion_file_not_found
,
560 self
.cfg
.indent_space_count
))
561 except barectf
._ConfigurationParseError
as exc
:
562 _print_config_error(exc
)
563 except Exception as exc
:
564 _print_unknown_exc(exc
)
567 class _ShowCfgVersionCmdCfg(_CmdCfg
):
568 def __init__(self
, cfg_file_path
: str):
569 self
._cfg
_file
_path
= cfg_file_path
572 def cfg_file_path(self
) -> str:
573 return self
._cfg
_file
_path
576 class _ShowCfgVersionCmd(_Cmd
):
579 with
open(self
.cfg
.cfg_file_path
) as f
:
580 print(barectf
.configuration_file_major_version(f
))
581 except barectf
._ConfigurationParseError
as exc
:
582 _print_config_error(exc
)
583 except Exception as exc
:
584 _print_unknown_exc(exc
)
588 # create command from arguments
590 cmd
= _cmd_from_args(sys
.argv
[1:])
591 except barectf_argpar
._Error
as exc
:
592 _print_error(f
'Command-line: For argument `{exc.orig_arg}`: {exc.msg}')
593 except _CliError
as exc
:
594 _print_error(f
'Command-line: {exc}')