2018-06-26 04:14:29 -07:00
|
|
|
"""
|
|
|
|
|
Wrap conda commandline program
|
|
|
|
|
"""
|
2024-02-05 20:29:32 +00:00
|
|
|
|
2018-07-19 17:30:09 -07:00
|
|
|
import contextlib
|
2023-05-15 08:51:35 +00:00
|
|
|
import hashlib
|
|
|
|
|
import json
|
2023-03-23 12:30:35 +01:00
|
|
|
import logging
|
2023-05-15 08:51:35 +00:00
|
|
|
import os
|
|
|
|
|
import subprocess
|
2018-07-19 17:30:09 -07:00
|
|
|
import tempfile
|
2023-03-23 12:30:35 +01:00
|
|
|
import time
|
|
|
|
|
|
2019-05-24 14:37:42 +03:00
|
|
|
import requests
|
2023-03-23 12:30:35 +01:00
|
|
|
|
2019-05-19 13:45:57 -07:00
|
|
|
from tljh import utils
|
2018-06-26 18:35:58 -07:00
|
|
|
|
2018-07-19 17:30:09 -07:00
|
|
|
|
2020-05-08 16:48:52 +05:30
|
|
|
def sha256_file(fname):
|
2018-07-19 17:30:09 -07:00
|
|
|
"""
|
2020-05-08 16:48:52 +05:30
|
|
|
Return sha256 of a given filename
|
2018-07-19 17:30:09 -07:00
|
|
|
|
|
|
|
|
Copied from https://stackoverflow.com/a/3431838
|
|
|
|
|
"""
|
2020-05-08 16:48:52 +05:30
|
|
|
hash_sha256 = hashlib.sha256()
|
2018-07-19 17:30:09 -07:00
|
|
|
with open(fname, "rb") as f:
|
|
|
|
|
for chunk in iter(lambda: f.read(4096), b""):
|
2020-05-08 16:48:52 +05:30
|
|
|
hash_sha256.update(chunk)
|
|
|
|
|
return hash_sha256.hexdigest()
|
2018-07-19 17:30:09 -07:00
|
|
|
|
|
|
|
|
|
2023-03-27 13:10:31 +02:00
|
|
|
def get_conda_package_versions(prefix):
|
|
|
|
|
"""Get conda package versions, via `conda list --json`"""
|
2023-03-21 14:21:00 +01:00
|
|
|
versions = {}
|
2018-07-19 17:30:09 -07:00
|
|
|
try:
|
2023-03-27 13:10:31 +02:00
|
|
|
out = subprocess.check_output(
|
|
|
|
|
[os.path.join(prefix, "bin", "conda"), "list", "--json"],
|
|
|
|
|
text=True,
|
2021-11-01 09:42:45 +01:00
|
|
|
)
|
2018-07-19 17:30:09 -07:00
|
|
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
2023-03-21 14:21:00 +01:00
|
|
|
return versions
|
2023-03-27 13:10:31 +02:00
|
|
|
|
|
|
|
|
packages = json.loads(out)
|
|
|
|
|
for package in packages:
|
|
|
|
|
versions[package["name"]] = package["version"]
|
2023-03-21 14:21:00 +01:00
|
|
|
return versions
|
2018-07-19 17:30:09 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
2020-05-08 16:48:52 +05:30
|
|
|
def download_miniconda_installer(installer_url, sha256sum):
|
2018-07-19 17:30:09 -07:00
|
|
|
"""
|
2020-05-08 16:48:52 +05:30
|
|
|
Context manager to download miniconda installer from a given URL
|
2018-07-19 17:30:09 -07:00
|
|
|
|
|
|
|
|
This should be used as a contextmanager. It downloads miniconda installer
|
2020-05-08 16:48:52 +05:30
|
|
|
of given version, verifies the sha256sum & provides path to it to the `with`
|
2018-07-19 17:30:09 -07:00
|
|
|
block to run.
|
|
|
|
|
"""
|
2023-03-23 12:30:35 +01:00
|
|
|
logger = logging.getLogger("tljh")
|
|
|
|
|
logger.info(f"Downloading conda installer {installer_url}")
|
2023-03-21 14:21:00 +01:00
|
|
|
with tempfile.NamedTemporaryFile("wb", suffix=".sh") as f:
|
2023-03-23 12:30:35 +01:00
|
|
|
tic = time.perf_counter()
|
|
|
|
|
r = requests.get(installer_url)
|
|
|
|
|
r.raise_for_status()
|
|
|
|
|
f.write(r.content)
|
2021-10-19 13:47:11 +02:00
|
|
|
# Remain in the NamedTemporaryFile context, but flush changes, see:
|
|
|
|
|
# https://docs.python.org/3/library/os.html#os.fsync
|
2021-10-19 15:07:44 +05:30
|
|
|
f.flush()
|
2021-10-19 13:47:11 +02:00
|
|
|
os.fsync(f.fileno())
|
2023-03-23 12:30:35 +01:00
|
|
|
t = time.perf_counter() - tic
|
|
|
|
|
logger.info(f"Downloaded conda installer {installer_url} in {t:.1f}s")
|
2018-07-19 17:30:09 -07:00
|
|
|
|
2023-03-23 12:34:44 +01:00
|
|
|
if sha256sum and sha256_file(f.name) != sha256sum:
|
2021-11-03 23:55:34 +01:00
|
|
|
raise Exception("sha256sum hash mismatch! Downloaded file corrupted")
|
2018-07-19 17:30:09 -07:00
|
|
|
|
|
|
|
|
yield f.name
|
|
|
|
|
|
|
|
|
|
|
2019-01-30 16:42:35 +01:00
|
|
|
def fix_permissions(prefix):
|
|
|
|
|
"""Fix permissions in the install prefix
|
|
|
|
|
|
|
|
|
|
For all files in the prefix, ensure that:
|
|
|
|
|
- everything is owned by current user:group
|
|
|
|
|
- nothing is world-writeable
|
|
|
|
|
|
|
|
|
|
Run after each install command.
|
|
|
|
|
"""
|
2021-11-01 09:42:45 +01:00
|
|
|
utils.run_subprocess(["chown", "-R", f"{os.getuid()}:{os.getgid()}", prefix])
|
2019-05-19 13:45:57 -07:00
|
|
|
utils.run_subprocess(["chmod", "-R", "o-w", prefix])
|
2019-01-30 16:42:35 +01:00
|
|
|
|
|
|
|
|
|
2018-07-19 17:30:09 -07:00
|
|
|
def install_miniconda(installer_path, prefix):
|
|
|
|
|
"""
|
|
|
|
|
Install miniconda with installer at installer_path under prefix
|
|
|
|
|
"""
|
2021-11-03 23:55:34 +01:00
|
|
|
utils.run_subprocess(["/bin/bash", installer_path, "-u", "-b", "-p", prefix])
|
2018-07-19 17:30:09 -07:00
|
|
|
# fix permissions on initial install
|
|
|
|
|
# a few files have the wrong ownership and permissions initially
|
|
|
|
|
# when the installer is run as root
|
2019-01-30 16:42:35 +01:00
|
|
|
fix_permissions(prefix)
|
2018-06-26 04:14:29 -07:00
|
|
|
|
|
|
|
|
|
2023-06-09 14:45:30 +02:00
|
|
|
def ensure_conda_packages(prefix, packages, force_reinstall=False):
|
2018-06-26 04:14:29 -07:00
|
|
|
"""
|
2018-06-27 03:05:24 -07:00
|
|
|
Ensure packages (from conda-forge) are installed in the conda prefix.
|
2021-10-27 02:29:04 +02:00
|
|
|
|
|
|
|
|
Note that conda seem to update dependencies by default, so there is probably
|
|
|
|
|
no need to have a update parameter exposed for this function.
|
2018-06-26 04:14:29 -07:00
|
|
|
"""
|
2023-03-27 13:10:31 +02:00
|
|
|
conda_executable = os.path.join(prefix, "bin", "mamba")
|
|
|
|
|
if not os.path.isfile(conda_executable):
|
|
|
|
|
# fallback on conda if mamba is not present (e.g. for mamba to install itself)
|
|
|
|
|
conda_executable = os.path.join(prefix, "bin", "conda")
|
|
|
|
|
|
2023-06-09 14:08:03 +02:00
|
|
|
cmd = [conda_executable, "install", "--yes"]
|
|
|
|
|
|
2023-06-09 14:45:30 +02:00
|
|
|
if force_reinstall:
|
2023-06-09 14:16:27 +02:00
|
|
|
# use force-reinstall, e.g. for conda/mamba to ensure everything is okay
|
|
|
|
|
# avoids problems with RemoveError upgrading conda from old versions
|
|
|
|
|
cmd += ["--force-reinstall"]
|
2023-06-09 14:08:03 +02:00
|
|
|
|
2018-06-26 04:14:29 -07:00
|
|
|
abspath = os.path.abspath(prefix)
|
2023-03-27 13:10:31 +02:00
|
|
|
|
|
|
|
|
utils.run_subprocess(
|
2023-06-09 14:08:03 +02:00
|
|
|
cmd
|
|
|
|
|
+ [
|
2021-11-03 23:55:34 +01:00
|
|
|
"-c",
|
|
|
|
|
"conda-forge", # Make customizable if we ever need to
|
|
|
|
|
"--prefix",
|
2021-11-01 09:42:45 +01:00
|
|
|
abspath,
|
|
|
|
|
]
|
2023-03-27 13:10:31 +02:00
|
|
|
+ packages,
|
2023-03-27 15:43:46 +02:00
|
|
|
input="",
|
2021-11-01 09:42:45 +01:00
|
|
|
)
|
2019-01-30 16:42:35 +01:00
|
|
|
fix_permissions(prefix)
|
2018-06-26 17:38:56 -07:00
|
|
|
|
|
|
|
|
|
2021-10-27 02:29:04 +02:00
|
|
|
def ensure_pip_packages(prefix, packages, upgrade=False):
|
2018-06-26 17:38:56 -07:00
|
|
|
"""
|
|
|
|
|
Ensure pip packages are installed in the given conda prefix.
|
|
|
|
|
"""
|
|
|
|
|
abspath = os.path.abspath(prefix)
|
2021-11-03 23:55:34 +01:00
|
|
|
pip_executable = [os.path.join(abspath, "bin", "python"), "-m", "pip"]
|
|
|
|
|
pip_cmd = pip_executable + ["install"]
|
2021-10-27 02:29:04 +02:00
|
|
|
if upgrade:
|
2021-11-03 23:55:34 +01:00
|
|
|
pip_cmd.append("--upgrade")
|
2021-10-27 02:29:04 +02:00
|
|
|
utils.run_subprocess(pip_cmd + packages)
|
2019-01-30 16:42:35 +01:00
|
|
|
fix_permissions(prefix)
|
2018-07-18 01:00:48 -07:00
|
|
|
|
|
|
|
|
|
2021-10-27 02:29:04 +02:00
|
|
|
def ensure_pip_requirements(prefix, requirements_path, upgrade=False):
|
2018-07-18 01:00:48 -07:00
|
|
|
"""
|
|
|
|
|
Ensure pip packages from given requirements_path are installed in given conda prefix.
|
|
|
|
|
|
|
|
|
|
requirements_path can be a file or a URL.
|
|
|
|
|
"""
|
|
|
|
|
abspath = os.path.abspath(prefix)
|
2021-11-03 23:55:34 +01:00
|
|
|
pip_executable = [os.path.join(abspath, "bin", "python"), "-m", "pip"]
|
|
|
|
|
pip_cmd = pip_executable + ["install"]
|
2021-10-27 02:29:04 +02:00
|
|
|
if upgrade:
|
2021-11-03 23:55:34 +01:00
|
|
|
pip_cmd.append("--upgrade")
|
|
|
|
|
utils.run_subprocess(pip_cmd + ["--requirement", requirements_path])
|
2019-01-30 16:42:35 +01:00
|
|
|
fix_permissions(prefix)
|