@app.command(no_args_is_help=True, epilog=example)
def install(
recipe_name: Annotated[str, typer.Argument(help="Name of recipe to use for installation.", show_default=False)],
version: Annotated[str, typer.Argument(help="Version of software to install.", show_default=False)],
version_string: Annotated[
Union[str, None],
typer.Option("--verstr",
help="Version string when full version isn't valid python Version.",
show_default="version")
] = None,
debug: Annotated[bool, typer.Option(help="Enable debug mode.", show_default=False)] = False,
name: Annotated[
Union[str, None],
typer.Option(
"-n",
"--name",
help="Name of the software to install. Same as 'recipe_name' by default.",
rich_help_panel="Recipe",
),
] = None,
method: Annotated[
Union[InstallMethod, None],
typer.Option(
"-m",
"--method",
case_sensitive=False,
help="Install method to use.",
rich_help_panel="Recipe",
),
] = None,
tags: Annotated[
Union[List[str], None],
typer.Option(
"-t",
"--tags",
help="Tags associated with software. Multiple can be specified.",
rich_help_panel="Recipe",
),
] = None,
category: Annotated[
Union[Category, None],
typer.Option(
"--category",
help="Category of software. e.g. life sciences, statistics, utilities",
rich_help_panel="Recipe",
),
] = None,
docs: Annotated[
Union[List[str], None],
typer.Option(
"--docs",
help="URL to documentation for software. Multiple can be specified.",
rich_help_panel="Recipe",
),
] = None,
refs: Annotated[
Union[List[str], None],
typer.Option(
"--refs",
help="URL to scientific literature for software. Multiple can be specified.",
rich_help_panel="Recipe",
),
] = None,
install_hook: Annotated[
Union[Path, None],
typer.Option(
"--hook",
help="Path to bash script for post-install hook/modification. Exports 'HPCMAN_POST_INSTALL_PREFIX' to dir "
"where the software is installed.",
rich_help_panel="Recipe",
exists=True,
file_okay=True,
dir_okay=False,
),
] = None,
patch: Annotated[
Union[List[Path], None],
typer.Option(
"--patch",
help="Path to patches to apply during post-install hook/modification. Runs in --patch-dir. Can supply "
"multiple.",
rich_help_panel="Recipe",
file_okay=True,
dir_okay=False,
exists=True,
),
] = None,
patch_dir: Annotated[
Union[str, None],
typer.Option(
"--patch-dir",
help="Dir to run the patches in. 'None' means they run in HPCMAN_POST_INSTALL_PREFIX.",
rich_help_panel="Recipe",
),
] = None,
patch_opts: Annotated[
Union[str, None],
typer.Option(
"--patch-opts",
help=f"Option for the patches. 'None' means '{DEFAULT_PATCH_OPTS}'",
rich_help_panel="Recipe",
),
] = None,
patch_order: Annotated[
Union[PatchOrder, None],
typer.Option(
"--patch-order",
help=f"Option for the patches. 'None' means '{PatchOrder.BEFORE.value}'",
rich_help_panel="Recipe",
),
] = None,
export: Annotated[
Union[str, None],
typer.Option(
"--export",
help="Extra information (e.g. metadata) to include in the recipe and to export to the post install script.",
rich_help_panel="Recipe",
),
] = None,
help_check: Annotated[
Union[HelpCheck, None],
typer.Option(
"--help-check",
show_choices=True,
help="Method to check for the help of the software.",
rich_help_panel="Recipe",
),
] = None,
version_check: Annotated[
Union[VersionCheck, None],
typer.Option(
"--version-check",
show_choices=True,
help="Method to check for the version of the software.",
rich_help_panel="Recipe",
),
] = None,
msgtype: Annotated[
Union[MsgType, None],
typer.Option(
"--msgtype",
show_choices=True,
help="Output stream where help message is found.",
rich_help_panel="Recipe",
),
] = None,
recipe_dir: Annotated[
Path,
typer.Option(
"--recipe-dir",
help="Path to recipe directory.",
file_okay=False,
dir_okay=True,
exists=True,
),
] = Path(RECIPE_DIR),
save_recipe: Annotated[
bool,
typer.Option(
" /--no-save",
help="Save recipe to directory in --recipe-dir upon successful install.",
),
] = True,
update: Annotated[
bool,
typer.Option(
"--update",
help="Update recipe if command-line options are supplied and it is loaded from file.",
),
] = False,
pkgs: Annotated[
Union[List[str], None],
typer.Option(
"-p",
"--pkgs",
help="Conda/Pixi packages to install. Can specify multiple, and give version restrictions. To use the "
"version specified in this install command, use 'package={ver}'",
rich_help_panel="Recipe",
),
] = None,
exes: Annotated[
Union[List[str], None],
typer.Option(
"-e",
"--exes",
help="Executables to generate shell scripts for after install or to link into the --bindir.",
rich_help_panel="Recipe",
),
] = None,
libs: Annotated[
Union[List[str], None],
typer.Option(
"-l",
"--libs",
help="Conda packages to install that do not provide binaries. Can specify multiple, and give version "
"restrictions. To use the version specified in this install command, use 'package={ver}'",
rich_help_panel="Conda",
),
] = None,
pypi: Annotated[
Union[List[str], None],
typer.Option(
"--pypi",
help="Pypi dependencies to add when packages are unavailable in conda.",
rich_help_panel="Conda",
),
] = None,
fix_perl: Annotated[
Union[bool, None],
typer.Option(
"--fix-perl",
help="Unset Perl env vars in conda env to eliminate issues with perl libs.",
rich_help_panel="Conda",
),
] = None,
fix_python: Annotated[
Union[bool, None],
typer.Option(
"--fix-python",
help="Exclude python user site-packages from the conda env.",
rich_help_panel="Conda",
),
] = None,
fix_r: Annotated[
Union[bool, None],
typer.Option(
"--fix-r",
"--fix-R",
help="Exclude R_LIBS from being found in the conda env.",
rich_help_panel="Conda",
),
] = None,
channels: Annotated[
Union[List[str], None],
typer.Option(
"-c",
"--channel",
help=f"Conda channels to use. Overrides config file -> {DEFAULT_CONFIG}",
rich_help_panel="Conda",
),
] = None,
priority: Annotated[
Union[Priority, None],
typer.Option(
"--priority",
help="Channel priority to use for conda environment.",
rich_help_panel="Conda",
show_choices=True,
show_default=True,
),
] = None,
cuda: Annotated[
Union[Cuda, None],
typer.Option(
"--cuda",
help="Cuda version to set for system requirements.",
rich_help_panel="Conda",
show_choices=True,
show_default=True,
),
] = None,
yaml: Annotated[
Union[typer.FileText, None],
typer.Option(
"--yaml",
help="Conda yaml file with channels and dependencies.",
rich_help_panel="Conda",
show_default=False,
),
] = None,
global_install: Annotated[
Union[bool, None],
typer.Option(
"--global-install/--no-global-install",
help="Install in global pixi/uv environment",
rich_help_panel="Recipe",
),
] = None,
python_ver: Annotated[
Union[PythonVer, None],
typer.Option(
"--python-ver",
help="Python version to use for pypi isolated executable.",
rich_help_panel="Pipx",
),
] = None,
pipx_backend: Annotated[
Union[PipxBackend, None],
typer.Option(
"--pipx-backend",
help="Backend to use for pipx installs.",
rich_help_panel="Pipx",
),
] = None,
arch: Annotated[
CPUArch,
typer.Option(
"--arch",
hidden=True, # This shouldn't be changed until we get cross-compiling working.
help="CPU Architecture to target for installs.",
rich_help_panel="Install Settings",
show_default=f"Current machine '{platform.node()}' CPU arch",
),
] = CPUArch(platform.machine()),
linkexe: Annotated[
bool,
typer.Option(
"--linkexe/--no-linkexe",
help="Link the executables to [bold cyan]bindir[/bold cyan] upon install.",
rich_help_panel="Install Settings",
),
] = False,
linkdir: Annotated[
bool,
typer.Option(
" /--no-linkdir",
help="Link the target 'software-ver' dir to 'software' in the --install_prefix",
rich_help_panel="Install Settings",
),
] = True,
prefix: Annotated[
Path,
typer.Option(
"--prefix",
help="Install prefix for software.",
rich_help_panel="Install Settings",
),
] = Path(INSTALL_PREFIX),
bindir: Annotated[
Path,
typer.Option(
"--bindir",
help=f"Prefix for final location for linked software. '{platform.machine()}/bin' will be appended to this "
"path.",
rich_help_panel="Install Settings",
),
] = Path(BINDIR_PREFIX),
logfile: Annotated[
Path,
typer.Option(
"--logfile",
file_okay=True,
dir_okay=False,
writable=True,
readable=True,
help="Path to file to write log messages.",
),
] = Path(LOGFILE),
dryrun: Annotated[
bool,
typer.Option(
"--dry-run",
help="Do not install, just print commands.",
rich_help_panel="Install Settings",
),
] = False,
force: Annotated[
bool,
typer.Option(
"--force",
help="Force install even when directory is present.",
rich_help_panel="Install Settings",
),
] = False,
skip: Annotated[
Union[Skip, None],
typer.Option(
"--skip",
help="Skip steps up to and including this step.",
rich_help_panel="Install Settings",
show_default=False,
show_choices=True,
),
] = None,
update_db: Annotated[
bool,
typer.Option(
"--update-db/--no-update-db",
help="Update sqlite database with install information.",
rich_help_panel="Database",
),
] = True,
sqlitedb: Annotated[
Path,
typer.Option(
"--db-path",
help="Path to sqlite database.",
rich_help_panel="Database",
),
] = Path(DB_PATH),
verbose: Annotated[
int,
typer.Option(
"-v",
"--verbose",
count=True,
max=6,
clamp=True,
help="Increase verbosity. Can specify more than once.",
show_default=False,
metavar="",
),
] = 1,
quiet: Annotated[
int,
typer.Option(
"-q",
"--quiet",
count=True,
max=9,
clamp=True,
help="Decrease verbosity. Can specify more than once.",
show_default=False,
metavar="",
),
] = 0,
) -> None:
"""
Alias: [bold yellow]hsi[/bold yellow]. Install software recipe or generate a new recipe from command-line options.
"""
# Log to file
from eliot import to_file
to_file(logfile.open("at"))
global logger
from .recipe import (
load_conda_yaml,
get_recipe_list,
load_recipe,
handle_recipe_errors,
update_recipe,
get_finished_recipe,
)
logger = init_logger(logger, verbosity=verbose - quiet)
if yaml is not None:
logger.trace(f"loading from yaml file: {yaml}")
conda_recipe = load_conda_yaml(yaml)
if conda_recipe[0] is not None:
if channels is not None:
channels.extend(conda_recipe[0])
else:
channels = conda_recipe[0]
if conda_recipe[1] is not None:
if pkgs is not None:
pkgs.extend(conda_recipe[1])
else:
pkgs = conda_recipe[1]
if channels is not None and "defaults" in channels:
channels.remove("defaults")
if patch is not None:
patches = [x.read_text() for x in patch]
else:
patches = None
# Excluding names and method, pass those separately
recipe_params = {
"tags": tags,
"category": category,
"docs": docs,
"refs": refs,
"patches": patches,
"patch_dir": patch_dir,
"patch_opts": patch_opts,
"patch_order": patch_order,
"install_hook": install_hook.read_text() if install_hook is not None else install_hook,
"export": export,
"help_check": help_check,
"version_check": version_check,
"msgtype": msgtype,
"pkgs": pkgs,
"exes": exes,
"libs": libs,
"pypi": pypi,
"channels": channels,
"priority": priority,
"cuda": cuda,
"fix_r": fix_r,
"fix_python": fix_python,
"fix_perl": fix_perl,
"python_ver": python_ver,
"pipx_backend": pipx_backend,
}
logger.trace(f"params: {recipe_params}")
recipe_file = recipe_dir / recipe_name / RECIPE_FN
if recipe_name in get_recipe_list(recipe_dir=recipe_dir) and recipe_file.exists():
if method is not None:
logger.warning(f"Method '{method}' provided, but recipe already found. "
"Methods cannot be updated from the cli.")
logger.info(f"Loading '{recipe_name}' from file")
try:
recipe = load_recipe(recipe_file=recipe_file)
except ValidationError as e:
handle_recipe_errors(e)
if attr_updates := {k: v for k, v in recipe_params.items() if v is not None}:
logger.trace(attr_updates)
if update:
logger.info(f"Updating recipe {recipe_name}")
try:
recipe = update_recipe(recipe, **attr_updates)
except ValidationError as e:
handle_recipe_errors(e)
else:
logger.error(f" => Attempting to update recipe '{recipe_name}' without '--update'.")
logger.error("Do not supply new values from command-line unless attempting to update.")
exit(1)
else:
if method is None:
logger.error(f" => Recipe '{recipe_name}' not found.")
logger.error("If you are not loading a known recipe, you must supply an install --method:")
logger.error(f"{[x.value for x in InstallMethod]}")
exit(1)
if name is None:
name = recipe_name
logger.info(f"Loading recipe '{recipe_name}' from command-line values")
try:
recipe = get_finished_recipe(
recipe_name=recipe_name,
name=name,
method=method,
**recipe_params,
)
except ValidationError as e:
handle_recipe_errors(e)
logger.info(f"Installing software: {locals()}")
if version_string is None:
version_string = version
from .install import install_software
install_software(
recipe=recipe,
ver=version,
version_string=version_string,
bindir=bindir.absolute(),
linkexe=linkexe,
linkdir=linkdir,
install_prefix=prefix.absolute(),
dryrun=dryrun,
skip=skip,
global_install=global_install,
force=force,
save_recipe=save_recipe,
recipe_dir=recipe_dir.absolute(),
update_db=update_db,
sqlitedb=sqlitedb.resolve(),
arch=arch,
debug=debug,
)