Files
revng-revng/python/revng/pypeline/main.py
2025-09-10 12:05:15 +02:00

103 lines
3.0 KiB
Python

#
# This file is distributed under the MIT License. See LICENSE.md for details.
#
import importlib.util
import logging
import os
import sys
from pathlib import Path
from typing import Sequence
import click
from . import initialize_pypeline
from .cli.utils import EagerParsedPath, LazyGroup
logger = logging.getLogger("pypeline")
logger.setLevel(logging.DEBUG)
logger.addHandler(logging.StreamHandler(sys.stderr))
def import_pipebox(module_path: str, is_complete: bool) -> object:
"""Import a module from a path. This is used to import the pipebox file.
Args:
env (dict[str, str]): The environment variables.
module_path (str): The path to the module.
is_complete (bool): If True, raise an error if the module is not found.
"""
# Absolute path to the module
module_abspath = Path(module_path).resolve()
if not module_abspath.exists():
# This is a small trick to allow generating the module auto-complete
# without having a pipebox file
if is_complete:
return object()
logger.error(
(
"Pipebox file `%s` does not exist. Either set it using the "
"PIPEBOX env var, or pass the --pipebox option."
),
module_abspath,
)
sys.exit(1)
# We guess that the module name is the file name without the extension
module_name: str = module_abspath.stem
# Dynamic import of the pipebox module
spec = importlib.util.spec_from_file_location(module_name, str(module_abspath))
if spec is None:
if is_complete:
return object()
logger.error("Could not load module `%s` from `%s`", module_name, module_abspath)
sys.exit(1)
module = importlib.util.module_from_spec(spec)
if spec.loader is None:
if is_complete:
return object()
logger.error("Could not load module `%s` from `%s`", module_name, module_abspath)
sys.exit(1)
# Execute the module to load it
spec.loader.exec_module(module)
# Initialize the pypeline as we just imported the pipebox
initialize_pypeline()
return module
@click.group(
cls=LazyGroup,
lazy_subcommands={
"pipeline": "revng.pypeline.cli.pipeline:pipeline",
"project": "revng.pypeline.cli.project:project",
},
)
@click.option(
"--pipebox",
type=EagerParsedPath(
name="pipebox",
parser=lambda path: import_pipebox(path, "_PYPE_COMPLETE" in os.environ),
),
help=(
"Path to the pipebox file. Defaults to the `PIPEBOX` environment "
"variable, then 'pipebox.py'."
),
default=os.environ.get("PIPEBOX", "pipebox.py"),
expose_value=False,
)
def cli():
pass
def main(args: Sequence[str]) -> None:
# pylint: disable=E1120 no-value-for-parameter
# This is ok as click will pass the pipebox argument automatically
cli(args=args)
def run():
"""Run the pipeline from the command line using the shell environment."""
main(sys.argv[1:])
if __name__ == "__main__":
run()