Skip to content
Snippets Groups Projects
mpsd_software.py 8.8 KiB
Newer Older
"""mpsd-software: entry point for command line application."""
import argparse
import os
from pathlib import Path
from mpsd_software_manager import __version__, command_name

from .cmds.available import get_available_package_sets, get_available_releases
from .cmds.init import initialise_environment
from .cmds.install import install_environment
from .cmds.prepare import prepare_environment
from .cmds.remove import remove_environment
from .cmds.status import environment_status
from .utils.filesystem_utils import get_root_dir
from .utils.logging import (
    get_log_file_path,
    record_script_execution_summary,
    set_up_logging,
)
# command_name = Path(sys.argv[0]).name
Hans Fangohr's avatar
Hans Fangohr committed
about_intro = f"""
Hans Fangohr's avatar
Hans Fangohr committed
Build software as on MPSD HPC.
Hans Fangohr's avatar
Hans Fangohr committed

    This tool builds software package sets (including toolchains for Octopus).
    It follows recipes as used on the MPSD HPC system and the (spack-based)
    Octopus buildbot. Compiled software is organised into MPSD software release
    versions (such as `dev-23a`) and CPU microarchitecture (such as `sandybridge`).
Hans Fangohr's avatar
Hans Fangohr committed
    Compiled packages and toolchains can be activated and used via `module load` as
    on the HPC system.
Hans Fangohr's avatar
Hans Fangohr committed
    Further documentation is available in the README.rst file, online at
    https://gitlab.gwdg.de/mpsd-cs/mpsd-software-manager/-/blob/main/README.rst
Hans Fangohr's avatar
Hans Fangohr committed

Command line usage:

Hans Fangohr's avatar
Hans Fangohr committed

"""


about_epilog = f"""


Examples:

    1. Query what releases are available for installation

       $> {command_name} available

    2. Query what package sets and toolchains are available for installation in
       $> {command_name} available dev-23a
    3. Install foss2022a-serial toolchain from the dev-23a release
       $> {command_name} install dev-23a foss2022a-serial
    4. Check what package sets and toolchains are installed from release dev-23a
       $> {command_name} status dev-23a
Hans Fangohr's avatar
Hans Fangohr committed
       The `status` command also displays the `module use` command needed to load
       the created modules.
# TODO @Ashwin
# Martin: Do we still need this function?
# It seems to be similar to 'prepare'; the parser does not offer it.
def start_new_environment(release, from_release, target_dir):
    """Start new MPSD software environment version."""
    msg = f"Starting new release {release} from {from_release} to {target_dir}"
def main():
    """Execute main entry point."""
Hans Fangohr's avatar
Hans Fangohr committed
    parser = argparse.ArgumentParser(
        description=about_intro,
        epilog=about_epilog,
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
Hans Fangohr's avatar
Hans Fangohr committed
        "-l",
        dest="loglevel",
        choices=["warning", "info", "debug"],
        required=False,
        default="warning",
        help="Set the log level",
    )
Hans Fangohr's avatar
Hans Fangohr committed
    parser.add_argument("--version", action="version", version=__version__)
Ashwin Kumar Karnad's avatar
Ashwin Kumar Karnad committed
    subparsers = parser.add_subparsers(
        dest="action", title="actions", description="valid actions", required=True
    )
    subparsers.required = True
    list_of_cmds = [
        ("init", "Initialise the MPSD software instance in the current directory"),
        ("available", "What is available for installation?"),
        ("install", "Install a software environment"),
        # ("reinstall", "Reinstall a package_set"),
        ("remove", "Remove a package set"),
        # ("start-new", "Start a new MPSD software release version"),
        ("status", "Show status: what is installed?"),
        ("prepare", "Prepare installation of MPSD-release (dev only)"),
    ]
    for cmd, help_text in list_of_cmds:
        subp = subparsers.add_parser(cmd, help=help_text)

        if cmd == "start-new":
            subp.add_argument(
                "--from-release",
                dest="from_release",
                type=str,
                required=True,
                help="Release version to start from",
            )
            subp.add_argument(
                "--to-release",
                dest="to_release",
                type=str,
                required=True,
                help="Release version to create",
            )
            # most commands except need a release version
            if cmd in ["install", "prepare", "reinstall", "remove"]:
                subp.add_argument(
                    "release",
                    type=str,
                    help="Release version to prepare, install, reinstall or remove",
                )
            elif cmd in ["available", "status"]:
                # for some commands the release version is optional
                subp.add_argument(
                    "release",
                    type=str,
                    nargs="?",
                    help="Release version to prepare, install, reinstall or remove",
                )

            if cmd in ["install", "reinstall", "remove"]:
Ashwin Kumar Karnad's avatar
Ashwin Kumar Karnad committed
                # "install" command needs additional documentation
                    f"One or more package sets (like toolchains) to be {cmd}ed. "
                    "Use 'ALL' to refer to all available package sets."
                subp.add_argument(
                    "package_set",  # first option defines attribute
                    # name `args.package_set` in `args = parser_args()`
Ashwin Kumar Karnad's avatar
Ashwin Kumar Karnad committed
                    nargs="+",
                # TODO Move the enable-build-cache flag to only 'install' cmd
                subp.add_argument(
                    "--enable-build-cache",
                    action="store_true",
                        "Enable Spack build cache. Useful for reinstallation but "
                        "consumes time and disk space."
            if cmd in ["status"]:
                subp.add_argument(
                    "package_set",
                    type=str,
                    nargs="?",
                    default="NONE",
                    help="Package set to show status for.",
                )
    # Carry out the action
    args = parser.parse_args()
    # Set up logging without file handle:
    # this is used in the init action and for logging the
    # get_root_dir() function
    set_up_logging(args.loglevel)

    # Check if the action is init
    # if so, call the init function and exit
    if args.action == "init":
        initialise_environment(Path(os.getcwd()))
    # if a release version is specified:
    if args.release:
        # sanity check for common mistakes in command line arguments
        if args.release.endswith("/"):  # happens easily with autocompletion
            args.release = args.release.removesuffix("/")
            logging.warning(f"Removed trailing slash from release: {args.release}")
    # root_dir is the place where this MPSD software instance has its root
    root_dir = get_root_dir()
    # set up logging filename: we record activities that change the installation
    if args.action in ["init", "install", "prepare", "reinstall", "remove"]:
        apex_log_file = get_log_file_path(
            args.release,
            args.action,
            root_dir,
        )
    # some commands do not write any log_files:
    elif args.action in ["available", "status"]:
        apex_log_file = None
    else:
        # sanity check
        raise NotImplementedError(f"Should never happen: unknown {args.action=}")

    set_up_logging(
        args.loglevel,
        apex_log_file,
    if args.action not in ["status", "available"]:
        # record the script execution summary only if
        # the action is one that changes files on disk
        record_script_execution_summary(root_dir, apex_log_file)
    # Check the command and run related function
    if args.action == "remove":
Ashwin Kumar Karnad's avatar
Ashwin Kumar Karnad committed
        remove_environment(args.release, root_dir, args.package_set)
    elif args.action == "start-new":
        start_new_environment(args.from_release, args.to_release, root_dir)
    elif args.action == "install":
Ashwin Kumar Karnad's avatar
Ashwin Kumar Karnad committed
        install_environment(
            args.release, args.package_set, root_dir, args.enable_build_cache
Ashwin Kumar Karnad's avatar
Ashwin Kumar Karnad committed
        )
    elif args.action == "status":
        environment_status(args.release, root_dir, args.package_set)
    elif args.action == "prepare":
        prepare_environment(args.release, root_dir)
Hans Fangohr's avatar
Hans Fangohr committed
    elif args.action == "available":
        if args.release:
            get_available_package_sets(args.release)
        else:
            get_available_releases(print_result=True)
            sys.exit(0)
Hans Fangohr's avatar
Hans Fangohr committed
    else:
        message = (
            f"No known action found ({args.action=}). Should probably never happen."
        )
Hans Fangohr's avatar
Hans Fangohr committed
        logging.error(message)
        raise NotImplementedError(message)
# TODO Martin: This can be removed (will do that separately)
if __name__ == "__main__":