mirror of
https://github.com/cirosantilli/linux-kernel-module-cheat.git
synced 2026-01-28 12:34:26 +01:00
print cli equivalent for commands called via python cli
This commit is contained in:
163
cli_function.py
163
cli_function.py
@@ -1,6 +1,8 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import bisect
|
||||||
|
import collections
|
||||||
import imp
|
import imp
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -108,46 +110,27 @@ class CliFunction:
|
|||||||
https://stackoverflow.com/questions/12834785/having-options-in-argparse-with-a-dash
|
https://stackoverflow.com/questions/12834785/having-options-in-argparse-with-a-dash
|
||||||
** boolean defaults automatically use store_true or store_false, and add a --no-* CLI
|
** boolean defaults automatically use store_true or store_false, and add a --no-* CLI
|
||||||
option to invert them if set from the config
|
option to invert them if set from the config
|
||||||
|
* from a Python call, get the corresponding CLI. See get_cli.
|
||||||
|
|
||||||
This somewhat duplicates: https://click.palletsprojects.com but:
|
This somewhat duplicates: https://click.palletsprojects.com but:
|
||||||
|
|
||||||
* that decorator API is insane
|
* that decorator API is insane
|
||||||
* CLI + Python for single functions was wontfixed: https://github.com/pallets/click/issues/40
|
* CLI + Python for single functions was wontfixed: https://github.com/pallets/click/issues/40
|
||||||
'''
|
'''
|
||||||
def __call__(self, **args):
|
def __call__(self, **kwargs):
|
||||||
'''
|
'''
|
||||||
Python version of the function call.
|
Python version of the function call. Not called by cli() indirectly,
|
||||||
|
so can be overridden to distinguish between Python and CLI calls.
|
||||||
|
|
||||||
:type arguments: Dict
|
:type arguments: Dict
|
||||||
'''
|
'''
|
||||||
args_with_defaults = args.copy()
|
return self._do_main(kwargs)
|
||||||
# Add missing args from config file.
|
|
||||||
if 'config_file' in args_with_defaults and args_with_defaults['config_file'] is not None:
|
def _do_main(self, kwargs):
|
||||||
config_file = args_with_defaults['config_file']
|
return self.main(**self._get_args(kwargs))
|
||||||
else:
|
|
||||||
config_file = self._config_file
|
|
||||||
if os.path.exists(config_file):
|
|
||||||
config_configs = {}
|
|
||||||
config = imp.load_source('config', config_file)
|
|
||||||
config.set_args(config_configs)
|
|
||||||
for key in config_configs:
|
|
||||||
if key not in self._all_keys:
|
|
||||||
raise Exception('Unknown key in config file: ' + key)
|
|
||||||
if (not key in args_with_defaults) or args_with_defaults[key] is None:
|
|
||||||
args_with_defaults[key] = config_configs[key]
|
|
||||||
# Add missing args from hard-coded defaults.
|
|
||||||
for argument in self._arguments:
|
|
||||||
key = argument.key
|
|
||||||
if (not key in args_with_defaults) or args_with_defaults[key] is None:
|
|
||||||
if argument.optional:
|
|
||||||
args_with_defaults[key] = argument.default
|
|
||||||
else:
|
|
||||||
raise Exception('Value not given for mandatory argument: ' + key)
|
|
||||||
return self.main(**args_with_defaults)
|
|
||||||
|
|
||||||
def __init__(self, config_file=None, description=None):
|
def __init__(self, config_file=None, description=None):
|
||||||
self._all_keys = set()
|
self._arguments = collections.OrderedDict()
|
||||||
self._arguments = []
|
|
||||||
self._config_file = config_file
|
self._config_file = config_file
|
||||||
self._description = description
|
self._description = description
|
||||||
if self._config_file is not None:
|
if self._config_file is not None:
|
||||||
@@ -158,7 +141,35 @@ class CliFunction:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '\n'.join(str(arg) for arg in self._arguments)
|
return '\n'.join(str(arg[key]) for key in self._arguments)
|
||||||
|
|
||||||
|
def _get_args(self, kwargs):
|
||||||
|
args_with_defaults = kwargs.copy()
|
||||||
|
# Add missing args from config file.
|
||||||
|
config_file = None
|
||||||
|
if 'config_file' in args_with_defaults and args_with_defaults['config_file'] is not None:
|
||||||
|
config_file = args_with_defaults['config_file']
|
||||||
|
else:
|
||||||
|
config_file = self._config_file
|
||||||
|
if os.path.exists(config_file):
|
||||||
|
config_configs = {}
|
||||||
|
config = imp.load_source('config', config_file)
|
||||||
|
config.set_args(config_configs)
|
||||||
|
for key in config_configs:
|
||||||
|
if key not in self._arguments:
|
||||||
|
raise Exception('Unknown key in config file: ' + key)
|
||||||
|
if (not key in args_with_defaults) or args_with_defaults[key] is None:
|
||||||
|
args_with_defaults[key] = config_configs[key]
|
||||||
|
# Add missing args from hard-coded defaults.
|
||||||
|
for key in self._arguments:
|
||||||
|
argument = self._arguments[key]
|
||||||
|
if (not key in args_with_defaults) or args_with_defaults[key] is None:
|
||||||
|
if argument.optional:
|
||||||
|
args_with_defaults[key] = argument.default
|
||||||
|
else:
|
||||||
|
raise Exception('Value not given for mandatory argument: ' + key)
|
||||||
|
del args_with_defaults['config_file']
|
||||||
|
return args_with_defaults
|
||||||
|
|
||||||
def add_argument(
|
def add_argument(
|
||||||
self,
|
self,
|
||||||
@@ -166,8 +177,7 @@ class CliFunction:
|
|||||||
**kwargs
|
**kwargs
|
||||||
):
|
):
|
||||||
argument = _Argument(*args, **kwargs)
|
argument = _Argument(*args, **kwargs)
|
||||||
self._arguments.append(argument)
|
self._arguments[argument.key] = argument
|
||||||
self._all_keys.add(argument.key)
|
|
||||||
|
|
||||||
def cli(self, cli_args=None):
|
def cli(self, cli_args=None):
|
||||||
'''
|
'''
|
||||||
@@ -178,7 +188,8 @@ class CliFunction:
|
|||||||
description=self._description,
|
description=self._description,
|
||||||
formatter_class=argparse.RawTextHelpFormatter,
|
formatter_class=argparse.RawTextHelpFormatter,
|
||||||
)
|
)
|
||||||
for argument in self._arguments:
|
for key in self._arguments:
|
||||||
|
argument = self._arguments[key]
|
||||||
parser.add_argument(*argument.args, **argument.kwargs)
|
parser.add_argument(*argument.args, **argument.kwargs)
|
||||||
if argument.is_bool:
|
if argument.is_bool:
|
||||||
new_longname = '--no' + argument.longname[1:]
|
new_longname = '--no' + argument.longname[1:]
|
||||||
@@ -192,13 +203,60 @@ class CliFunction:
|
|||||||
del kwargs['help']
|
del kwargs['help']
|
||||||
parser.add_argument(new_longname, dest=argument.key, **kwargs)
|
parser.add_argument(new_longname, dest=argument.key, **kwargs)
|
||||||
args = parser.parse_args(args=cli_args)
|
args = parser.parse_args(args=cli_args)
|
||||||
return self(**vars(args))
|
return self._do_main(vars(args))
|
||||||
|
|
||||||
def cli_exit(self, *args, **kwargs):
|
def cli_exit(self, *args, **kwargs):
|
||||||
'''
|
'''
|
||||||
Same as cli, but also exit the program with int(cli().
|
Same as cli, but also exit the program with status equal to the return value of main.
|
||||||
|
main must return an integer for this to be used.
|
||||||
'''
|
'''
|
||||||
sys.exit(int(self.cli(*args, **kwargs)))
|
sys.exit(self.cli(*args, **kwargs))
|
||||||
|
|
||||||
|
def get_cli(self, **kwargs):
|
||||||
|
'''
|
||||||
|
:rtype: List[Type(str)]
|
||||||
|
:return: the canonical command line arguments arguments that would
|
||||||
|
generate this Python function call.
|
||||||
|
|
||||||
|
(--key, value) option pairs are grouped into tuples, and all
|
||||||
|
other values are grouped in their own tuple (positional_arg,)
|
||||||
|
or (--bool-arg,).
|
||||||
|
|
||||||
|
Arguments with default values are not added, but arguments
|
||||||
|
that are set by the config are also given.
|
||||||
|
|
||||||
|
The optional arguments are sorted alphabetically, followed by
|
||||||
|
positional arguments.
|
||||||
|
|
||||||
|
The long option name is used if both long and short versions
|
||||||
|
are given.
|
||||||
|
'''
|
||||||
|
options = []
|
||||||
|
positional_dict = {}
|
||||||
|
kwargs = self._get_args(kwargs)
|
||||||
|
for key in kwargs:
|
||||||
|
argument = self._arguments[key]
|
||||||
|
default = argument.default
|
||||||
|
value = kwargs[key]
|
||||||
|
if value != default:
|
||||||
|
if argument.is_option:
|
||||||
|
if argument.is_bool:
|
||||||
|
val = (argument.longname,)
|
||||||
|
else:
|
||||||
|
val = (argument.longname, str(value))
|
||||||
|
bisect.insort(options, val)
|
||||||
|
else:
|
||||||
|
if type(value) is list:
|
||||||
|
positional_dict[key] = [tuple(v,) for v in value]
|
||||||
|
else:
|
||||||
|
positional_dict[key] = [(str(value),)]
|
||||||
|
# Python built-in data structures suck.
|
||||||
|
# https://stackoverflow.com/questions/27726245/getting-the-key-index-in-a-python-ordereddict/27726534#27726534
|
||||||
|
positional = []
|
||||||
|
for key in self._arguments.keys():
|
||||||
|
if key in positional_dict:
|
||||||
|
positional.extend(positional_dict[key])
|
||||||
|
return options + positional
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_key(*args, **kwargs):
|
def get_key(*args, **kwargs):
|
||||||
@@ -232,7 +290,6 @@ amazing function!
|
|||||||
self.add_argument('pos-optional', default=0, help='Help for pos-optional', type=int),
|
self.add_argument('pos-optional', default=0, help='Help for pos-optional', type=int),
|
||||||
self.add_argument('args-star', help='Help for args-star', nargs='*'),
|
self.add_argument('args-star', help='Help for args-star', nargs='*'),
|
||||||
def main(self, **kwargs):
|
def main(self, **kwargs):
|
||||||
del kwargs['config_file']
|
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
one_cli_function = OneCliFunction()
|
one_cli_function = OneCliFunction()
|
||||||
@@ -259,7 +316,7 @@ amazing function!
|
|||||||
out = one_cli_function(pos_mandatory=1, asdf='B')
|
out = one_cli_function(pos_mandatory=1, asdf='B')
|
||||||
assert out['asdf'] == 'B'
|
assert out['asdf'] == 'B'
|
||||||
out['asdf'] = default['asdf']
|
out['asdf'] = default['asdf']
|
||||||
assert(out == default)
|
assert out == default
|
||||||
|
|
||||||
# asdf and qwer
|
# asdf and qwer
|
||||||
out = one_cli_function(pos_mandatory=1, asdf='B', qwer='R')
|
out = one_cli_function(pos_mandatory=1, asdf='B', qwer='R')
|
||||||
@@ -267,7 +324,7 @@ amazing function!
|
|||||||
assert out['qwer'] == 'R'
|
assert out['qwer'] == 'R'
|
||||||
out['asdf'] = default['asdf']
|
out['asdf'] = default['asdf']
|
||||||
out['qwer'] = default['qwer']
|
out['qwer'] = default['qwer']
|
||||||
assert(out == default)
|
assert out == default
|
||||||
|
|
||||||
if '--bool':
|
if '--bool':
|
||||||
out = one_cli_function(pos_mandatory=1, bool=False)
|
out = one_cli_function(pos_mandatory=1, bool=False)
|
||||||
@@ -275,22 +332,40 @@ amazing function!
|
|||||||
assert out == cli_out
|
assert out == cli_out
|
||||||
assert out['bool'] == False
|
assert out['bool'] == False
|
||||||
out['bool'] = default['bool']
|
out['bool'] = default['bool']
|
||||||
assert(out == default)
|
assert out == default
|
||||||
|
|
||||||
if '--bool-nargs':
|
if '--bool-nargs':
|
||||||
|
|
||||||
out = one_cli_function(pos_mandatory=1, bool_nargs=True)
|
out = one_cli_function(pos_mandatory=1, bool_nargs=True)
|
||||||
assert out['bool_nargs'] == True
|
assert out['bool_nargs'] == True
|
||||||
out['bool_nargs'] = default['bool_nargs']
|
out['bool_nargs'] = default['bool_nargs']
|
||||||
assert(out == default)
|
assert out == default
|
||||||
|
|
||||||
out = one_cli_function(pos_mandatory=1, bool_nargs='asdf')
|
out = one_cli_function(pos_mandatory=1, bool_nargs='asdf')
|
||||||
assert out['bool_nargs'] == 'asdf'
|
assert out['bool_nargs'] == 'asdf'
|
||||||
out['bool_nargs'] = default['bool_nargs']
|
out['bool_nargs'] = default['bool_nargs']
|
||||||
assert(out == default)
|
assert out == default
|
||||||
|
|
||||||
|
# Positional
|
||||||
|
out = one_cli_function(pos_mandatory=1, pos_optional=2, args_star=['3', '4'])
|
||||||
|
assert out['pos_mandatory'] == 1
|
||||||
|
assert out['pos_optional'] == 2
|
||||||
|
assert out['args_star'] == ['3', '4']
|
||||||
|
cli_out = one_cli_function.cli(['1', '2', '3', '4'])
|
||||||
|
assert out == cli_out
|
||||||
|
out['pos_mandatory'] = default['pos_mandatory']
|
||||||
|
out['pos_optional'] = default['pos_optional']
|
||||||
|
out['args_star'] = default['args_star']
|
||||||
|
assert out == default
|
||||||
|
|
||||||
# Force a boolean value set on the config to be False on CLI.
|
# Force a boolean value set on the config to be False on CLI.
|
||||||
assert one_cli_function.cli(['--no-bool-cli', '1'])['bool_cli'] is False
|
assert one_cli_function.cli(['--no-bool-cli', '1'])['bool_cli'] is False
|
||||||
|
|
||||||
# CLI call with argv command line arguments.
|
# get_cli
|
||||||
print(one_cli_function.cli())
|
assert one_cli_function.get_cli(pos_mandatory=1, asdf='B') == [('--asdf', 'B'), ('--bool-cli',), ('1',)]
|
||||||
|
assert one_cli_function.get_cli(pos_mandatory=1, asdf='B', qwer='R') == [('--asdf', 'B'), ('--bool-cli',), ('--qwer', 'R'), ('1',)]
|
||||||
|
assert one_cli_function.get_cli(pos_mandatory=1, bool=False) == [('--bool',), ('--bool-cli',), ('1',)]
|
||||||
|
assert one_cli_function.get_cli(pos_mandatory=1, pos_optional=2, args_star=['3', '4']) == [('--bool-cli',), ('1',), ('2',), ('3',), ('4',)]
|
||||||
|
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
# CLI call with argv command line arguments.
|
||||||
|
print(one_cli_function.cli())
|
||||||
|
|||||||
21
common.py
21
common.py
@@ -7,6 +7,7 @@ import copy
|
|||||||
import datetime
|
import datetime
|
||||||
import glob
|
import glob
|
||||||
import imp
|
import imp
|
||||||
|
import inspect
|
||||||
import json
|
import json
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import os
|
import os
|
||||||
@@ -289,6 +290,17 @@ Use gem5 instead of QEMU. Shortcut for `--emulator gem5`.
|
|||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __call__(self, **kwargs):
|
||||||
|
'''
|
||||||
|
For Python code calls, print the CLI equivalent of the call.
|
||||||
|
'''
|
||||||
|
print_cmd = ['./' + inspect.getfile(self.__class__), LF]
|
||||||
|
for line in self.get_cli(**kwargs):
|
||||||
|
print_cmd.extend(line)
|
||||||
|
print_cmd.append(LF)
|
||||||
|
shell_helpers.ShellHelpers.print_cmd(print_cmd)
|
||||||
|
return super().__call__(**kwargs)
|
||||||
|
|
||||||
def _init_env(self, env):
|
def _init_env(self, env):
|
||||||
'''
|
'''
|
||||||
Update the kwargs from the command line with values derived from them.
|
Update the kwargs from the command line with values derived from them.
|
||||||
@@ -683,13 +695,14 @@ Use gem5 instead of QEMU. Shortcut for `--emulator gem5`.
|
|||||||
'''
|
'''
|
||||||
Time the main of the derived class.
|
Time the main of the derived class.
|
||||||
'''
|
'''
|
||||||
if not kwargs['dry_run']:
|
myargs = kwargs.copy()
|
||||||
|
if not myargs['dry_run']:
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
kwargs.update(consts)
|
myargs.update(consts)
|
||||||
self._init_env(kwargs)
|
self._init_env(myargs)
|
||||||
self.sh = shell_helpers.ShellHelpers(dry_run=self.env['dry_run'])
|
self.sh = shell_helpers.ShellHelpers(dry_run=self.env['dry_run'])
|
||||||
ret = self.timed_main()
|
ret = self.timed_main()
|
||||||
if not kwargs['dry_run']:
|
if not myargs['dry_run']:
|
||||||
end_time = time.time()
|
end_time = time.time()
|
||||||
self._print_time(end_time - start_time)
|
self._print_time(end_time - start_time)
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
3
run
3
run
@@ -618,7 +618,8 @@ Run QEMU with VNC instead of the default SDL. Connect to it with:
|
|||||||
if not self.env['userland']:
|
if not self.env['userland']:
|
||||||
if os.path.exists(self.env['guest_terminal_file']):
|
if os.path.exists(self.env['guest_terminal_file']):
|
||||||
with open(self.env['guest_terminal_file'], 'br') as logfile:
|
with open(self.env['guest_terminal_file'], 'br') as logfile:
|
||||||
if logfile.readlines()[-1].rstrip() == self.env['magic_fail_string']:
|
lines = logfile.readlines()
|
||||||
|
if lines and lines[-1].rstrip() == self.env['magic_fail_string']:
|
||||||
exit_status = 1
|
exit_status = 1
|
||||||
if exit_status != 0:
|
if exit_status != 0:
|
||||||
self.log_error('simulation error detected by parsing logs')
|
self.log_error('simulation error detected by parsing logs')
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ class ShellHelpers:
|
|||||||
Attempt to print shell equivalents of all commands to make things
|
Attempt to print shell equivalents of all commands to make things
|
||||||
easy to debug and understand what is going on.
|
easy to debug and understand what is going on.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
_print_lock = threading.Lock()
|
||||||
|
|
||||||
def __init__(self, dry_run=False):
|
def __init__(self, dry_run=False):
|
||||||
'''
|
'''
|
||||||
:param dry_run: don't run the commands, just potentially print them. Debug aid.
|
:param dry_run: don't run the commands, just potentially print them. Debug aid.
|
||||||
@@ -35,6 +38,16 @@ class ShellHelpers:
|
|||||||
'''
|
'''
|
||||||
self.dry_run = dry_run
|
self.dry_run = dry_run
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _print_thread_safe(cls, string):
|
||||||
|
# Python sucks: a naive print adds a bunch of random spaces to stdout,
|
||||||
|
# and then copy pasting the command fails.
|
||||||
|
# https://stackoverflow.com/questions/3029816/how-do-i-get-a-thread-safe-print-in-python-2-6
|
||||||
|
cls._print_lock.acquire()
|
||||||
|
sys.stdout.write(string + '\n')
|
||||||
|
sys.stdout.flush()
|
||||||
|
cls._print_lock.release()
|
||||||
|
|
||||||
def add_newlines(self, cmd):
|
def add_newlines(self, cmd):
|
||||||
out = []
|
out = []
|
||||||
for arg in cmd:
|
for arg in cmd:
|
||||||
@@ -46,7 +59,8 @@ class ShellHelpers:
|
|||||||
if not self.dry_run:
|
if not self.dry_run:
|
||||||
shutil.copy2(src, dest)
|
shutil.copy2(src, dest)
|
||||||
|
|
||||||
def cmd_to_string(self, cmd, cwd=None, extra_env=None, extra_paths=None):
|
@staticmethod
|
||||||
|
def cmd_to_string(cmd, cwd=None, extra_env=None, extra_paths=None):
|
||||||
'''
|
'''
|
||||||
Format a command given as a list of strings so that it can
|
Format a command given as a list of strings so that it can
|
||||||
be viewed nicely and executed by bash directly and print it to stdout.
|
be viewed nicely and executed by bash directly and print it to stdout.
|
||||||
@@ -93,7 +107,8 @@ class ShellHelpers:
|
|||||||
update=1,
|
update=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
def print_cmd(self, cmd, cwd=None, cmd_file=None, extra_env=None, extra_paths=None):
|
@classmethod
|
||||||
|
def print_cmd(cls, cmd, cwd=None, cmd_file=None, extra_env=None, extra_paths=None):
|
||||||
'''
|
'''
|
||||||
Print cmd_to_string to stdout.
|
Print cmd_to_string to stdout.
|
||||||
|
|
||||||
@@ -106,8 +121,8 @@ class ShellHelpers:
|
|||||||
if type(cmd) is str:
|
if type(cmd) is str:
|
||||||
cmd_string = cmd
|
cmd_string = cmd
|
||||||
else:
|
else:
|
||||||
cmd_string = self.cmd_to_string(cmd, cwd=cwd, extra_env=extra_env, extra_paths=extra_paths)
|
cmd_string = cls.cmd_to_string(cmd, cwd=cwd, extra_env=extra_env, extra_paths=extra_paths)
|
||||||
print('+ ' + cmd_string)
|
cls._print_thread_safe('+ ' + cmd_string)
|
||||||
if cmd_file is not None:
|
if cmd_file is not None:
|
||||||
with open(cmd_file, 'w') as f:
|
with open(cmd_file, 'w') as f:
|
||||||
f.write('#!/usr/bin/env bash\n')
|
f.write('#!/usr/bin/env bash\n')
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ class Main(common.LkmcCliFunction):
|
|||||||
'hello_cpp.cpp',
|
'hello_cpp.cpp',
|
||||||
'print_argv.c',
|
'print_argv.c',
|
||||||
]
|
]
|
||||||
# for emulator in self.env['emulators']:
|
for emulator in self.env['emulators']:
|
||||||
for emulator in ['gem5']:
|
|
||||||
if emulator == 'gem5':
|
if emulator == 'gem5':
|
||||||
extra_args = {
|
extra_args = {
|
||||||
'userland_build_id': 'static',
|
'userland_build_id': 'static',
|
||||||
|
|||||||
Reference in New Issue
Block a user