Skip to content
Snippets Groups Projects
mpsd_software.py 44.6 KiB
Newer Older
def environment_status(mpsd_release: str, root_dir: Union[str, Path]) -> dict:
    """Show status of release in installation.

    Parameters
    ----------
    mpsd_release : str
        A string representing the MPSD release version.
    root_dir : pathlib.Path
        A Path object pointing to the root directory of the installation.
        Expect a subfolder root/mpsd_release in which we search for the
        toolchains.

    Returns
    -------
    toolchain_map : dict
        A dictionary containing available microarchitectures as keys and
        a list of available package_sets as values for each microarchitecture.

        Note: only toolchains can be reported at the moment (i.e. package_sets
        such as global and global_generic are missing, even if installed).

    """
    msg = f"Showing status of release {mpsd_release} in {root_dir}"
    logging.info(msg)
    plog = logging.getLogger("print")
    release_base_dir = root_dir / mpsd_release
    microarch = get_native_microarchitecture()
    toolchain_dir = release_base_dir / microarch
    spack_dir = toolchain_dir / "spack"
    # if the mpsd_release does not exist:
    if not release_base_dir.exists():
        logging.debug(f"Directory {str(release_base_dir)} does not exist.")
        logging.error(f"MPSD release '{mpsd_release}' is not installed.")
        return None

    # if the mpds_release directory exists but the spack repository is not fully
    # cloned - indicates some kind of incomplete installation:
    if not spack_dir.exists():
        logging.debug(f"Looking for files in {spack_dir}")
        logging.error(
            f"MPSD release '{mpsd_release}' has not been completely installed."
    # find all folders for all microarch in the release directory
    # except for the blacklisted files
    black_listed_files = [
        config_vars["cmd_log_file"],
        "spack-environments",
        "logs",
        "mpsd-spack-cache",
    ]
    list_of_microarchs_candidates = os.listdir(release_base_dir)
    list_of_microarchs = [
        x for x in list_of_microarchs_candidates if x not in black_listed_files
    ]
    logging.debug(f"{list_of_microarchs=}")

    toolchain_map = {}
    for microarch in list_of_microarchs:
        # get a list of all the toolchains in the microarch
        possible_toolchains = (release_base_dir / microarch).glob(
            "lmod/Core/toolchains/*.lua"
        )
        # append toolchain which is the name of the file without the .lua extension
        toolchain_map[microarch] = [toolchain.stem for toolchain in possible_toolchains]

    logging.debug(f"{toolchain_map=}")

    # pretty print the toolchain map key as the heading
    # and the value as the list of toolchains
Hans Fangohr's avatar
Hans Fangohr committed
    plog.info(f"Installed toolchains ({mpsd_release}):\n")
    for microarch, toolchains in toolchain_map.items():
        plog.info(f"- {microarch}")
        for toolchain in toolchains:
Hans Fangohr's avatar
Hans Fangohr committed
            plog.info(f"    {toolchain}")
        plog.info(f"    [module use {str(release_base_dir / microarch / 'lmod/Core')}]")
        plog.info("")
    return toolchain_map
def initialize_environment(root_dir: Path) -> None:
    """Initialize the software environment.

    This creates a hidden file ``.mpsd-software-root`` to tag the location for
    as the root of the installation. All compiled files, logs etc are written in
    or below this subdirectory.

    Parameters
    ----------
    root_dir : pathlib.Path
        A Path object pointing to the current directory where the script was called.

    """
    # check if the root_dir is not already initialized
    init_file = root_dir / config_vars["init_file"]
    if init_file.exists():
        logging.getLogger("print").info(
            f"Error: Directory {str(root_dir)} is already initialized."
        )
        sys.exit(1)
    else:
        # create the init file
        init_file.touch()
        # note the execution in the execution summary log
        # create the log file and fill it with the headers
        record_script_execution_summary(root_dir=root_dir)
        # record the msg in the log file
        record_script_execution_summary(
            root_dir=root_dir,
            msg=f"Initialising MPSD software instance at {root_dir}.",
def get_root_dir() -> Path:
    """Get the root directory of the installation.

    Look for the hidden file ``.mpsd-software-root``
    (defined in config_vars["init_file"])
    in the current directory, or any parent directory.
    If found, return the path to the root directory
    of the MPSD software instance.
    If not found, exit with an error message.

    Returns
    -------
    root_dir : pathlib.Path
        A Path object pointing to the root directory of the installation.
        This folder contains the hidden file ``.mpsd-software-root``,
        ``mpsd_releases`` ( for eg ``dev-23a``) and ``mpsd-spack-cache``.


    """
    # check if the root_dir is not already initialized
    script_call_dir = Path.cwd()
    init_file = script_call_dir / config_vars["init_file"]
    if init_file.exists():
        return script_call_dir

    # if not, look for the init file in the parent directories
    for parent_folder in script_call_dir.parents:
        init_file = parent_folder / config_vars["init_file"]
        if init_file.exists():
            script_call_dir = parent_folder
            return script_call_dir

    # if not found in any parent directory, exit with an error message
    logging.getLogger("print").info(
        "Error: Couldnt find MPSD software instance"
        "in the current directory or any parent directory. \n"
        f"Directory {str(script_call_dir)} is not a MPSD software instance. \n"
        "Please run 'mpsd-software init' to "
        "initialise the software instance here, \n"
        "or switch to a directory which is already initialised.\n \n"
        f"Hint: Look for the directory containing `{config_vars['cmd_log_file']}`"
        + f"and the hidden file `{config_vars['init_file']}`."
        " to check if a directory is initialised"
    )
    sys.exit(1)

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",
            )
            # all commands except init need a release version
            if cmd != "init":
                subp.add_argument(
                    "release",
                    type=str,
                    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="+",
                )
                subp.add_argument(
                    "--enable-build-cache",
                    action="store_true",
                        "Enable Spack build cache. Useful for reinstallation but "
                        "consumes time and disk space."
    # 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":
        initialize_environment(Path(os.getcwd()))
        sys.exit(0)

    # root_dir is the place where this MPSD software instance has its root
    root_dir = get_root_dir()
    # set up logging ( with file handler) for all actions (except init)
    log_file = get_installer_log_file_path(
        args.release,
        args.action,
        root_dir,
    )
    set_up_logging(
        args.loglevel,
        log_file,
    )
    # sanity check for common mistakes in command line arguments
    if args.release.endswith("/"):  # happens easily with autocompletion
        logging.error(
            f"You provided mpsd-release='{args.release}'. "
            f"Did you mean '{args.release.removesuffix('/')}'?"
        )
        sys.exit(1)
    # 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":
Hans Fangohr's avatar
Hans Fangohr committed
        _ = environment_status(args.release, root_dir)
    elif args.action == "prepare":
        prepare_environment(args.release, root_dir)
Hans Fangohr's avatar
Hans Fangohr committed
    elif args.action == "available":
        get_available_package_sets(args.release)
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)
if __name__ == "__main__":