mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
9fd3077081
Group all the wrapper options (`--lldb`, `--gdb`, etc.) in an option group, so that they are rendered in an indented section.
153 lines
4.5 KiB
Python
153 lines
4.5 KiB
Python
#
|
|
# This file is distributed under the MIT License. See LICENSE.md for details.
|
|
#
|
|
|
|
import functools
|
|
import inspect
|
|
import os
|
|
import sys
|
|
from dataclasses import dataclass
|
|
from typing import Any, Callable, Mapping
|
|
|
|
import click
|
|
from click_option_group import OptionGroup
|
|
|
|
from revng.pypeline.cli.context import ClickContext
|
|
|
|
from .utils import PypeCommand, add_group_option_fake_title, create_option, sort_option_groups
|
|
|
|
|
|
@dataclass
|
|
class Wrapper:
|
|
param: click.Parameter
|
|
prefix: list[str]
|
|
|
|
|
|
class WrapperOption:
|
|
class WrapperType(click.ParamType):
|
|
def __init__(
|
|
self, *args, name: str, prefix_generator: Callable[[Any], list[str]], **kwargs
|
|
):
|
|
super().__init__(*args, **kwargs)
|
|
self.name = name
|
|
self.prefix_generator = prefix_generator
|
|
|
|
def convert(self, value, param, ctx: ClickContext): # type: ignore
|
|
if not value:
|
|
return
|
|
|
|
if ctx.obj.wrapper is not None:
|
|
wrapper: Wrapper = ctx.obj.wrapper
|
|
raise click.UsageError(
|
|
f"option {param.get_error_hint(ctx)} is incompatible with "
|
|
f"{wrapper.param.get_error_hint(ctx)}, use one or the other",
|
|
ctx,
|
|
)
|
|
|
|
ctx.obj.wrapper = Wrapper(param, self.prefix_generator(value))
|
|
|
|
def __init__(
|
|
self,
|
|
name: str,
|
|
help: str, # noqa: A002
|
|
prefix: list[str] | None = None,
|
|
type_: type = bool,
|
|
):
|
|
self.name = name
|
|
self.help = help
|
|
self.prefix = prefix
|
|
self.type_ = type_
|
|
|
|
def generate_prefix(self, value: Any) -> list[str]:
|
|
assert self.prefix is not None
|
|
return self.prefix
|
|
|
|
def make_option(self, group: OptionGroup, command: click.Command):
|
|
assert self.type_ in (bool, str)
|
|
return create_option(
|
|
group,
|
|
command,
|
|
(f"--{self.name}",),
|
|
type=self.__class__.WrapperType(name=self.name, prefix_generator=self.generate_prefix),
|
|
help=self.help,
|
|
expose_value=False,
|
|
is_flag=self.type_ is bool,
|
|
)
|
|
|
|
|
|
class WrapperRegistry:
|
|
def __init__(self):
|
|
self.wrappers: list[WrapperOption] = []
|
|
self.commands: list[click.Command] = []
|
|
self.group = OptionGroup(
|
|
"Wrappers", help="Run program(s) wrapped inside one of the specified wrappers"
|
|
)
|
|
|
|
def register_command(self, command: click.Command):
|
|
add_group_option_fake_title(command, self.group)
|
|
self.commands.append(command)
|
|
for wrapper in self.wrappers:
|
|
command.params.append(wrapper.make_option(self.group, command))
|
|
sort_option_groups(command)
|
|
|
|
def register_wrapper(self, wrapper: WrapperOption):
|
|
self.wrappers.append(wrapper)
|
|
for command in self.commands:
|
|
command.params.append(wrapper.make_option(self.group, command))
|
|
sort_option_groups(command)
|
|
|
|
def register_wrappers(self, *wrappers: WrapperOption):
|
|
for wrapper in wrappers:
|
|
self.register_wrapper(wrapper)
|
|
|
|
|
|
WRAPPER_REGISTRY = WrapperRegistry()
|
|
|
|
|
|
class WrappableCommand(click.Command):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
WRAPPER_REGISTRY.register_command(self)
|
|
|
|
|
|
class WrappablePypeCommand(WrappableCommand, PypeCommand):
|
|
pass
|
|
|
|
|
|
def exec_wrapper_if_needed(obj):
|
|
if isinstance(obj, click.Command):
|
|
target_function = obj.callback
|
|
is_command = True
|
|
else:
|
|
assert inspect.isfunction(obj)
|
|
target_function = obj
|
|
is_command = False
|
|
|
|
@functools.wraps(target_function)
|
|
def wrapper(*args, **kwargs):
|
|
ctx = click.get_current_context()
|
|
wrapper: Wrapper | None = ctx.obj.wrapper
|
|
# If there is no wrapper or we're already wrapped call the function normally
|
|
if wrapper is None or os.environ.get("_PYPE_WRAPPER") == "1":
|
|
return target_function(*args, **kwargs)
|
|
|
|
env = {**os.environ, "_PYPE_WRAPPER": "1"}
|
|
os.execvpe(wrapper.prefix[0], [*wrapper.prefix, sys.executable, *sys.argv], env)
|
|
return None
|
|
|
|
if is_command:
|
|
obj.callback = wrapper
|
|
return obj
|
|
else:
|
|
return wrapper
|
|
|
|
|
|
def exec_with_wrapper(args: list[str], env: Mapping[str, str] | None = None):
|
|
if env is None:
|
|
env = os.environ
|
|
ctx = click.get_current_context()
|
|
wrapper: Wrapper | None = ctx.obj.wrapper
|
|
if wrapper is not None:
|
|
args = [*wrapper.prefix, *args]
|
|
os.execvpe(args[0], args, env)
|