Skip to content

Commit

Permalink
Refactor installation routines and allow to run them from the CLI. (a…
Browse files Browse the repository at this point in the history
  • Loading branch information
csadorf authored Jul 14, 2022
1 parent 7f37ca4 commit 97251d1
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 89 deletions.
36 changes: 36 additions & 0 deletions aiidalab_qe/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import click

from .setup_codes import codes_are_setup
from .setup_codes import install as install_qe_codes
from .sssp import install as setup_sssp


@click.group()
def cli():
pass


@cli.command()
@click.option("-f", "--force", is_flag=True)
def install_qe(force):
try:
for msg in install_qe_codes(force=force):
click.echo(msg)
assert codes_are_setup()
click.secho("Codes are setup!", fg="green")
except Exception as error:
raise click.ClickException(f"Failed to set up QE failed: {error}")


@cli.command()
def install_sssp():
try:
for msg, _ in setup_sssp():
click.echo(msg)
click.secho("SSSP pseudo potentials are installed!", fg="green")
except Exception as error:
raise click.ClickException(f"Failed to set up pseudo potentials: {error}")


if __name__ == "__main__":
cli()
132 changes: 66 additions & 66 deletions aiidalab_qe/setup_codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,64 @@ def setup_codes():
_setup_code(code_name)


def install(force=False):
"""Install Quantum ESPRESSO and the corresponding AiiDA codes.
Args:
force: Ignore previously failed attempts and install anyways.
"""
# Check for "do not install file" and skip actual check. The purpose of
# this file is to not re-try this process on every app start in case that
# there are issues.
if not force and FN_DO_NOT_SETUP.exists():
raise RuntimeError("Installation failed in previous attempt.")

yield "Checking installation status..."

conda_installed = which("conda")
try:
with FileLock(FN_LOCKFILE, timeout=5):
# We assume that if the codes are already setup, everything is in
# order. Only if they are not present, should we take action,
# however we only do so if the environment has a conda binary
# present (`which conda`). If that is not the case then we assume
# that this is a custom user environment in which case we also take
# no further action.
if codes_are_setup():
return # Already setup

if not conda_installed:
raise RuntimeError(
"Unable to automatically install Quantum ESPRESSO, conda "
"is not available."
)

if not qe_installed():

# First, install Quantum ESPRESSO.
yield "Installing QE..."
try:
install_qe()
except CalledProcessError as error:
raise RuntimeError(f"Failed to create conda environment: {error}")

# After installing QE, we install the corresponding
# AiiDA codes:
for code_name in CODE_NAMES:
if not _code_is_setup(code_name):
yield f"Setting up AiiDA code ({code_name})..."
_setup_code(code_name)

except Timeout:
# Assume that the installation was triggered by a different process.
yield "Installation was already started, waiting for it to finish..."
with FileLock(FN_LOCKFILE, timeout=120):
if not codes_are_setup():
raise RuntimeError(
"Installation process did not finish in the expected time."
)


class QESetupWidget(ipw.VBox):

installed = traitlets.Bool(allow_none=True).tag(readonly=True)
Expand Down Expand Up @@ -148,75 +206,13 @@ def set_message(self, msg):
self._progress_bar.description = f"{self.prefix}{msg}"

def _refresh_installed(self):
AnimationRate = ProgressBar.AnimationRate # alias
conda_installed = which("conda")

self.set_message("checking installation status...")
try:
self.set_trait("busy", True)

# Check for "do not install file" and skip actual check. The purpose of
# this file is to not re-try this process on every app start in case
# that there are issues.
if FN_DO_NOT_SETUP.exists():
self.set_message("Installation previously failed.")
self.error = "Installation failed in previous attempt."
return

try:
with FileLock(FN_LOCKFILE, timeout=5):
# We assume that if the codes are already setup, everything
# is in order. Only if they are not present, should we take
# action, however we only do so if the environment has a
# conda binary present (`which conda`). If that is not the
# case then we assume that this is a custom user environment
# in which case we also take no further action.
self.installed = codes_are_setup() or not conda_installed
if self.installed:
self.error = ""
self.set_message("Codes are installed.")
else:
self.error = ""
self.set_message("installing...")
# To setup our own codes, we install QE on the local
# host:
if not qe_installed():
self.set_message("Installing QE...")
self._progress_bar.value = AnimationRate(0.05)
try:
install_qe()
except CalledProcessError as error:
raise RuntimeError(
f"Failed to create conda environment: {error}"
)
self.value = 0.7
# After installing QE, we install the corresponding
# AiiDA codes:
for i, code_name in enumerate(CODE_NAMES):
if not _code_is_setup(code_name):
self.set_message(
f"Setting up AiiDA code ({code_name})..."
)
self._progress_bar.value = AnimationRate(0.1)
_setup_code(code_name)
self.value = 0.8 + i * 0.1
# After going through the installation procedure, we
# expect both our version of QE to be installed, as well
# as the codes to be setup.
self.installed = qe_installed() and codes_are_setup()

except Timeout:
# assume that the installation was triggered by a different
# process
self.set_message("installing...")
self._progress_bar.value = AnimationRate(0.01)
with FileLock(FN_LOCKFILE, timeout=120):
self.installed = codes_are_setup() or not conda_installed

# Raise error in case that the installation was not successful
# either in this process or a different one.
if not self.installed:
raise RuntimeError("Installation failed for unknown reasons.")
for msg in install():
self.set_message(msg)

self.installed = True

except Exception as error:
self.set_message("Failed to setup QE on localhost.")
Expand Down Expand Up @@ -281,6 +277,10 @@ def _update(self, change):

if self.error or self.installed:
self._progress_bar.value = 1.0
elif self.busy:
self._progress_bar.value = ProgressBar.AnimationRate(1.0)
else:
self._progress_bar.value = 0

self._progress_bar.bar_style = (
"info"
Expand Down
50 changes: 27 additions & 23 deletions aiidalab_qe/sssp.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def pseudos_to_install():
return EXPECTED_PSEUDOS - labels


def _install_pseudos(pseudo_set):
def install_pseudos(pseudo_set):
env = os.environ.copy()
env["PATH"] = f"{env['PATH']}:{Path.home().joinpath('.local', 'bin')}"

Expand All @@ -46,6 +46,27 @@ def run_(*args, **kwargs):
run_(["aiida-pseudo", "install", p_family.lower(), "-x", p_func, "-p", p_type])


def install():
yield "Checking installation status...", 0.1
try:
with FileLock(FN_LOCKFILE, timeout=5):
if len(pseudos_to_install()) > 0:
yield "Installing...", 0.1
for progress in install_pseudos(pseudos_to_install()):
yield "Installing...", progress

except Timeout:
# Assume that the installation was triggered by a different process.
yield "Installation was already started elsewhere, waiting for it to finish...", ProgressBar.AnimationRate(
1.0
)
with FileLock(FN_LOCKFILE, timeout=120):
if len(pseudos_to_install()) > 0:
raise RuntimeError(
"Installation process did not finish in the expected time."
)


class SSSPInstallWidget(ProgressBar):

installed = traitlets.Bool(allow_none=True).tag(readonly=True)
Expand All @@ -70,29 +91,12 @@ def set_message(self, msg):
self.description = f"{self.prefix}{msg}"

def _refresh_installed(self):
self.set_message("checking installation status...")
self.set_trait("busy", True)

try:
self.set_trait("busy", True)
try:
with FileLock(FN_LOCKFILE, timeout=5):
self.installed = not bool(pseudos_to_install())
if not self.installed:
self.set_message("installing...")
for progress in _install_pseudos(pseudos_to_install()):
self.value = progress
self.installed = not bool(pseudos_to_install())

except Timeout:
# assume that the installation was triggered by a different process
self.set_message("installing...")
self.value = self.AnimationRate(0.01)
with FileLock(FN_LOCKFILE, timeout=120):
self.installed = not bool(pseudos_to_install())

# Raise error in case that the installation was not successful
# either in this process or a different one.
if not self.installed:
raise RuntimeError("Installation failed for unknown reasons.")
for msg, progress in install():
self.set_message(msg)
self.value = progress

except Exception as error:
self.set_trait("error", str(error))
Expand Down
7 changes: 7 additions & 0 deletions post_install
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

echo "Starting installation of QE..."
python -m aiidalab_qe install-qe & disown

echo "Starting installation of pseudo-potentials..."
python -m aiidalab_qe install-sssp & disown

0 comments on commit 97251d1

Please sign in to comment.