diff --git a/tljh/conda.py b/tljh/conda.py index df9a027..a543645 100644 --- a/tljh/conda.py +++ b/tljh/conda.py @@ -98,7 +98,7 @@ def install_miniconda(installer_path, prefix): fix_permissions(prefix) -def ensure_conda_packages(prefix, packages): +def ensure_conda_packages(prefix, packages, force_reinstall=False): """ Ensure packages (from conda-forge) are installed in the conda prefix. @@ -110,13 +110,18 @@ def ensure_conda_packages(prefix, packages): # fallback on conda if mamba is not present (e.g. for mamba to install itself) conda_executable = os.path.join(prefix, "bin", "conda") + cmd = [conda_executable, "install", "--yes"] + + if force_reinstall: + # 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"] + abspath = os.path.abspath(prefix) utils.run_subprocess( - [ - conda_executable, - "install", - "-y", + cmd + + [ "-c", "conda-forge", # Make customizable if we ever need to "--prefix", diff --git a/tljh/installer.py b/tljh/installer.py index dc787f4..aff24fc 100644 --- a/tljh/installer.py +++ b/tljh/installer.py @@ -242,21 +242,42 @@ def ensure_user_environment(user_requirements_txt_file): ) to_upgrade.append(pkg) - cf_pkgs_to_upgrade = list(set(to_upgrade) & {"conda", "mamba"}) - if cf_pkgs_to_upgrade: - conda.ensure_conda_packages( - USER_ENV_PREFIX, - # we _could_ explicitly pin Python here, - # but conda already does this by default - cf_pkgs_to_upgrade, - ) - pypi_pkgs_to_upgrade = list(set(to_upgrade) & {"pip"}) - if pypi_pkgs_to_upgrade: - conda.ensure_pip_packages( - USER_ENV_PREFIX, - pypi_pkgs_to_upgrade, - upgrade=True, - ) + # force reinstall conda/mamba to ensure a basically consistent env + # avoids issues with RemoveError: 'requests' is a dependency of conda + # only do this for 'old' conda versions known to have a problem + # we don't know how old, but we know 4.10 is affected and 23.1 is not + if not is_fresh_install and V(package_versions.get("conda", "0")) < V("23.1"): + # force-reinstall doesn't upgrade packages + # it reinstalls them in-place + # only reinstall packages already present + to_reinstall = [] + for pkg in ["conda", "mamba"]: + if pkg in package_versions: + # add version pin to avoid upgrades + to_reinstall.append(f"{pkg}=={package_versions[pkg]}") + logger.info( + f"Reinstalling {', '.join(to_reinstall)} to ensure a consistent environment" + ) + conda.ensure_conda_packages( + USER_ENV_PREFIX, list(to_reinstall), force_reinstall=True + ) + + cf_pkgs_to_upgrade = list(set(to_upgrade) & {"conda", "mamba"}) + if cf_pkgs_to_upgrade: + conda.ensure_conda_packages( + USER_ENV_PREFIX, + # we _could_ explicitly pin Python here, + # but conda already does this by default + cf_pkgs_to_upgrade, + ) + + pypi_pkgs_to_upgrade = list(set(to_upgrade) & {"pip"}) + if pypi_pkgs_to_upgrade: + conda.ensure_pip_packages( + USER_ENV_PREFIX, + pypi_pkgs_to_upgrade, + upgrade=True, + ) # Install/upgrade the jupyterhub version in the user env based on the # version specification used for the hub env.