-
Notifications
You must be signed in to change notification settings - Fork 49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
API for customizing the conda base environment. #38
Open
ssurbhi560
wants to merge
4
commits into
conda-incubator:main
Choose a base branch
from
ssurbhi560:customize-base-env-api
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -18,13 +18,20 @@ | |||||
from pathlib import Path | ||||||
from subprocess import check_output, run, PIPE, STDOUT | ||||||
from textwrap import dedent | ||||||
from typing import Dict, AnyStr | ||||||
from typing import Dict, AnyStr, Iterable | ||||||
from urllib.request import urlopen | ||||||
from urllib.error import HTTPError | ||||||
from distutils.spawn import find_executable | ||||||
from IPython.display import display | ||||||
|
||||||
from IPython import get_ipython | ||||||
|
||||||
try: | ||||||
from ruamel.yaml import YAML | ||||||
from ruamel.yaml.comments import CommentedMap | ||||||
except ImportError as e: | ||||||
raise RuntimeError("Could not find ruamel.yaml, plese install using `!pip install ruamel.yaml`!") from e | ||||||
|
||||||
try: | ||||||
import ipywidgets as widgets | ||||||
HAS_IPYWIDGETS = True | ||||||
|
@@ -43,6 +50,7 @@ | |||||
"Surbhi Sharma <[email protected]>" | ||||||
) | ||||||
|
||||||
yaml=YAML() | ||||||
|
||||||
PREFIX = "/opt/conda" | ||||||
|
||||||
|
@@ -87,12 +95,115 @@ def _run_subprocess(command, logs_filename): | |||||
assert (task.returncode == 0), f"💥💔💥 The installation failed! Logs are available at `{logs_file_path}/{logs_filename}`." | ||||||
|
||||||
|
||||||
def _update_environment( | ||||||
prefix:os.PathLike = PREFIX, | ||||||
environment_file: str = None, | ||||||
python_version: str = None, | ||||||
specs: Iterable[str] = None, | ||||||
channels: Iterable[str] = None, | ||||||
pip_args: Iterable[str] = None, | ||||||
extra_conda_args: Iterable[str] = None, | ||||||
ssurbhi560 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
): | ||||||
""" | ||||||
Install the dependencies in conda base environment during | ||||||
the condacolab installion. | ||||||
|
||||||
Parameters | ||||||
---------- | ||||||
prefix | ||||||
Target location for the installation. | ||||||
environment_file | ||||||
Path or URL of the environment.yaml file to use for | ||||||
updating the conda base enviornment. | ||||||
python_version | ||||||
Python version to use in the conda base environment, eg. "3.9". | ||||||
specs | ||||||
List of additional specifications (packages) to install. | ||||||
channels | ||||||
Comma separated list of channels to use in the conda | ||||||
base environment. | ||||||
pip_args | ||||||
List of additional packages to be installed using pip. | ||||||
extra_conda_args | ||||||
Any extra conda arguments to be used during the installation. | ||||||
""" | ||||||
|
||||||
# When environment.yaml file is not provided. | ||||||
if environment_file is None: | ||||||
env_details = {} | ||||||
if channels: | ||||||
env_details["channels"] = channels | ||||||
if specs: | ||||||
env_details["dependencies"] = specs | ||||||
if python_version: | ||||||
env_details["dependencies"] += [f"python={python_version}"] | ||||||
if pip_args: | ||||||
pip_args_dict = {"pip": pip_args} | ||||||
env_details["dependencies"].append(pip_args_dict) | ||||||
environment_file_path = "/environment.yaml" | ||||||
|
||||||
with open(environment_file_path, 'w') as f: | ||||||
yaml.indent(mapping=2, sequence=4, offset=2) | ||||||
yaml.dump(env_details, f) | ||||||
else: | ||||||
# If URL is given for environment.yaml file | ||||||
if environment_file.startswith(("http://", "https://")): | ||||||
environment_file_path = "/environment.yaml" | ||||||
ssurbhi560 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
try: | ||||||
with urlopen(environment_file) as response, open(environment_file_path, "wb") as out: | ||||||
shutil.copyfileobj(response, out) | ||||||
except HTTPError as e: | ||||||
raise HTTPError("The URL you entered is not working, please check it again.") from e | ||||||
|
||||||
# If path is given for environment.yaml file | ||||||
else: | ||||||
environment_file_path = environment_file | ||||||
|
||||||
with open(environment_file_path, 'r') as f: | ||||||
env_details = yaml.load(f.read()) | ||||||
|
||||||
for key in env_details: | ||||||
if channels and key == "channels": | ||||||
env_details["channels"].extend(channels) | ||||||
if key == "dependencies": | ||||||
if specs: | ||||||
env_details["dependencies"].extend(specs) | ||||||
if python_version: | ||||||
env_details["dependencies"].extend([f"python={python_version}"]) | ||||||
if pip_args: | ||||||
for element in env_details["dependencies"]: | ||||||
# if pip dependencies are already specified. | ||||||
if type(element) is CommentedMap and "pip" in element: | ||||||
ssurbhi560 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
element["pip"].extend(pip_args) | ||||||
# if there are no pip dependencies specified in the yaml file. | ||||||
else: | ||||||
pip_args_dict = CommentedMap([("pip", [*pip_args])]) | ||||||
env_details["dependencies"].append(pip_args_dict) | ||||||
break | ||||||
ssurbhi560 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
with open(environment_file_path, 'w') as f: | ||||||
ssurbhi560 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
f.truncate(0) | ||||||
yaml.dump(env_details, f) | ||||||
|
||||||
extra_conda_args = extra_conda_args or () | ||||||
|
||||||
_run_subprocess( | ||||||
[f"{prefix}/bin/python", "-m", "conda_env", "update", "-n", "base", "-f", environment_file_path, *extra_conda_args], | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's pass
Suggested change
|
||||||
"environment_file_update.log", | ||||||
) | ||||||
|
||||||
|
||||||
def install_from_url( | ||||||
installer_url: AnyStr, | ||||||
prefix: os.PathLike = PREFIX, | ||||||
env: Dict[AnyStr, AnyStr] = None, | ||||||
run_checks: bool = True, | ||||||
restart_kernel: bool = True, | ||||||
environment_file: str = None, | ||||||
python_version: str = None, | ||||||
specs: Iterable[str] = None, | ||||||
channels: Iterable[str] = None, | ||||||
pip_args: Iterable[str] = None, | ||||||
extra_conda_args: Iterable[str] = None, | ||||||
): | ||||||
""" | ||||||
Download and run a constructor-like installer, patching | ||||||
|
@@ -186,6 +297,36 @@ def install_from_url( | |||||
"pip_task.log" | ||||||
) | ||||||
|
||||||
print("📌 Adjusting configuration...") | ||||||
cuda_version = ".".join(os.environ.get("CUDA_VERSION", "*.*.*").split(".")[:2]) | ||||||
prefix = Path(prefix) | ||||||
condameta = prefix / "conda-meta" | ||||||
condameta.mkdir(parents=True, exist_ok=True) | ||||||
|
||||||
|
||||||
with open(condameta / "pinned", "a") as f: | ||||||
f.write(f"cudatoolkit {cuda_version}.*\n") | ||||||
|
||||||
with open(prefix / ".condarc", "a") as f: | ||||||
f.write("always_yes: true\n") | ||||||
ssurbhi560 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
if environment_file and not specs and not channels and not pip_args and not python_version: | ||||||
extra_conda_args = extra_conda_args or () | ||||||
_run_subprocess( | ||||||
[f"{prefix}/bin/python", "-m", "conda_env", "update", "-n", "base", "-f", environment_file], | ||||||
"environment_file_update.log", | ||||||
) | ||||||
else: | ||||||
_update_environment( | ||||||
prefix=prefix, | ||||||
environment_file=environment_file, | ||||||
specs=specs, | ||||||
channels=channels, | ||||||
python_version=python_version, | ||||||
pip_args=pip_args, | ||||||
extra_conda_args=extra_conda_args, | ||||||
) | ||||||
|
||||||
ssurbhi560 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
env = env or {} | ||||||
bin_path = f"{prefix}/bin" | ||||||
|
||||||
|
@@ -220,18 +361,26 @@ def install_from_url( | |||||
else: | ||||||
print("🔁 Please restart kernel by clicking on Runtime > Restart runtime.") | ||||||
|
||||||
|
||||||
def install_mambaforge( | ||||||
prefix: os.PathLike = PREFIX, env: Dict[AnyStr, AnyStr] = None, run_checks: bool = True, restart_kernel: bool = True, | ||||||
prefix: os.PathLike = PREFIX, | ||||||
env: Dict[AnyStr, AnyStr] = None, | ||||||
run_checks: bool = True, | ||||||
restart_kernel: bool = True, | ||||||
specs: Iterable[str] = None, | ||||||
python_version: str = None, | ||||||
channels: Iterable[str] = None, | ||||||
environment_file: str = None, | ||||||
extra_conda_args: Iterable[str] = None, | ||||||
pip_args: Iterable[str] = None, | ||||||
|
||||||
): | ||||||
""" | ||||||
Install Mambaforge, built for Python 3.7. | ||||||
|
||||||
Mambaforge consists of a Miniconda-like distribution optimized | ||||||
and preconfigured for conda-forge packages, and includes ``mamba``, | ||||||
a faster ``conda`` implementation. | ||||||
|
||||||
Unlike the official Miniconda, this is built with the latest ``conda``. | ||||||
|
||||||
Parameters | ||||||
---------- | ||||||
prefix | ||||||
|
@@ -244,7 +393,6 @@ def install_mambaforge( | |||||
to add those yourself in the raw string. They will | ||||||
end up added to a line like ``exec env VAR=VALUE python3...``. | ||||||
For example, a value with spaces should be passed as:: | ||||||
|
||||||
env={"VAR": '"a value with spaces"'} | ||||||
run_checks | ||||||
Run checks to see if installation was run previously. | ||||||
|
@@ -256,15 +404,35 @@ def install_mambaforge( | |||||
automatically and get a button instead to do it. | ||||||
""" | ||||||
installer_url = r"https://github.com/jaimergp/miniforge/releases/latest/download/Mambaforge-colab-Linux-x86_64.sh" | ||||||
install_from_url(installer_url, prefix=prefix, env=env, run_checks=run_checks, restart_kernel=restart_kernel) | ||||||
|
||||||
install_from_url( | ||||||
installer_url, | ||||||
prefix=prefix, | ||||||
env=env, | ||||||
run_checks=run_checks, | ||||||
restart_kernel=restart_kernel, | ||||||
specs=specs, | ||||||
python_version=python_version, | ||||||
channels=channels, | ||||||
environment_file=environment_file, | ||||||
extra_conda_args=extra_conda_args, | ||||||
pip_args=pip_args, | ||||||
) | ||||||
|
||||||
# Make mambaforge the default | ||||||
install = install_mambaforge | ||||||
|
||||||
|
||||||
def install_miniforge( | ||||||
prefix: os.PathLike = PREFIX, env: Dict[AnyStr, AnyStr] = None, run_checks: bool = True, restart_kernel: bool = True, | ||||||
prefix: os.PathLike = PREFIX, | ||||||
env: Dict[AnyStr, AnyStr] = None, | ||||||
run_checks: bool = True, | ||||||
restart_kernel: bool = True, | ||||||
specs: Iterable[str] = None, | ||||||
python_version: str = None, | ||||||
channels: Iterable[str] = None, | ||||||
environment_file: str = None, | ||||||
extra_conda_args: Iterable[str] = None, | ||||||
pip_args: Iterable[str] = None, | ||||||
): | ||||||
""" | ||||||
Install Mambaforge, built for Python 3.7. | ||||||
|
@@ -298,11 +466,32 @@ def install_miniforge( | |||||
automatically and get a button instead to do it. | ||||||
""" | ||||||
installer_url = r"https://github.com/jaimergp/miniforge/releases/latest/download/Miniforge-colab-Linux-x86_64.sh" | ||||||
install_from_url(installer_url, prefix=prefix, env=env, run_checks=run_checks, restart_kernel=restart_kernel) | ||||||
install_from_url( | ||||||
installer_url, | ||||||
prefix=prefix, | ||||||
env=env, | ||||||
run_checks=run_checks, | ||||||
restart_kernel=restart_kernel, | ||||||
specs=specs, | ||||||
python_version=python_version, | ||||||
channels=channels, | ||||||
environment_file=environment_file, | ||||||
extra_conda_args=extra_conda_args, | ||||||
pip_args=pip_args, | ||||||
) | ||||||
|
||||||
|
||||||
def install_miniconda( | ||||||
prefix: os.PathLike = PREFIX, env: Dict[AnyStr, AnyStr] = None, run_checks: bool = True, restart_kernel: bool = True, | ||||||
prefix: os.PathLike = PREFIX, | ||||||
env: Dict[AnyStr, AnyStr] = None, | ||||||
run_checks: bool = True, | ||||||
restart_kernel: bool = True, | ||||||
specs: Iterable[str] = None, | ||||||
python_version: str = None, | ||||||
channels: Iterable[str] = None, | ||||||
environment_file: str = None, | ||||||
extra_conda_args: Iterable[str] = None, | ||||||
pip_args: Iterable[str] = None, | ||||||
): | ||||||
""" | ||||||
Install Miniconda 4.12.0 for Python 3.7. | ||||||
|
@@ -331,11 +520,32 @@ def install_miniconda( | |||||
automatically and get a button instead to do it. | ||||||
""" | ||||||
installer_url = r"https://repo.anaconda.com/miniconda/Miniconda3-py37_4.12.0-Linux-x86_64.sh" | ||||||
install_from_url(installer_url, prefix=prefix, env=env, run_checks=run_checks, restart_kernel=restart_kernel) | ||||||
install_from_url( | ||||||
installer_url, | ||||||
prefix=prefix, | ||||||
env=env, | ||||||
run_checks=run_checks, | ||||||
restart_kernel=restart_kernel, | ||||||
specs=specs, | ||||||
python_version=python_version, | ||||||
channels=channels, | ||||||
environment_file=environment_file, | ||||||
extra_conda_args=extra_conda_args, | ||||||
pip_args=pip_args, | ||||||
) | ||||||
|
||||||
|
||||||
def install_anaconda( | ||||||
prefix: os.PathLike = PREFIX, env: Dict[AnyStr, AnyStr] = None, run_checks: bool = True, restart_kernel: bool = True, | ||||||
prefix: os.PathLike = PREFIX, | ||||||
env: Dict[AnyStr, AnyStr] = None, | ||||||
run_checks: bool = True, | ||||||
restart_kernel: bool = True, | ||||||
specs: Iterable[str] = None, | ||||||
python_version: str = None, | ||||||
channels: Iterable[str] = None, | ||||||
environment_file: str = None, | ||||||
extra_conda_args: Iterable[str] = None, | ||||||
pip_args: Iterable[str] = None, | ||||||
): | ||||||
""" | ||||||
Install Anaconda 2022.05, the latest version built | ||||||
|
@@ -365,7 +575,19 @@ def install_anaconda( | |||||
automatically and get a button instead to do it. | ||||||
""" | ||||||
installer_url = r"https://repo.anaconda.com/archive/Anaconda3-2022.05-Linux-x86_64.sh" | ||||||
install_from_url(installer_url, prefix=prefix, env=env, run_checks=run_checks, restart_kernel=restart_kernel) | ||||||
install_from_url( | ||||||
installer_url, | ||||||
prefix=prefix, | ||||||
env=env, | ||||||
run_checks=run_checks, | ||||||
restart_kernel=restart_kernel, | ||||||
specs=specs, | ||||||
python_version=python_version, | ||||||
channels=channels, | ||||||
environment_file=environment_file, | ||||||
extra_conda_args=extra_conda_args, | ||||||
pip_args=pip_args, | ||||||
) | ||||||
|
||||||
|
||||||
def check(prefix: os.PathLike = PREFIX, verbose: bool = True): | ||||||
|
@@ -382,10 +604,6 @@ def check(prefix: os.PathLike = PREFIX, verbose: bool = True): | |||||
Print success message if True | ||||||
""" | ||||||
assert find_executable("conda"), "💥💔💥 Conda not found!" | ||||||
|
||||||
pymaj, pymin = sys.version_info[:2] | ||||||
sitepackages = f"{prefix}/lib/python{pymaj}.{pymin}/site-packages" | ||||||
assert sitepackages in sys.path, f"💥💔💥 PYTHONPATH was not patched! Value: {sys.path}" | ||||||
assert all( | ||||||
not path.startswith("/usr/local/") for path in sys.path | ||||||
), f"💥💔💥 PYTHONPATH include system locations: {[path for path in sys.path if path.startswith('/usr/local')]}!" | ||||||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two things:
pyproject.toml
dependencies to ensure everything is available in case Google changes the bundled packages.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did it here: #41! :)