Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions .github/workflows/linux-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,6 @@ jobs:
python-version: ${{ matrix.python-version }}
environment-file: requirements/environment.yml
auto-activate-base: false
- name: Install Micromamba and setup as executable backend
shell: bash -l {0}
run: |
curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/1.5.10 | tar -xvj bin/micromamba
echo "ENV_BACKEND_EXECUTABLE=${{ github.workspace }}/bin/micromamba" >> $GITHUB_ENV
- name: Install Pixi
shell: bash -l {0}
run: |
# This script installs Pixi in ~/.pixi/bin
curl -fsSL https://pixi.sh/install.sh | sh
- name: Install envs-manager
shell: bash -l {0}
run: |
Expand Down
10 changes: 0 additions & 10 deletions .github/workflows/macos-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,6 @@ jobs:
python-version: ${{ matrix.python-version }}
environment-file: requirements/environment.yml
auto-activate-base: false
- name: Install Micromamba and setup as executable backend
shell: bash -l {0}
run: |
curl -Ls https://micro.mamba.pm/api/micromamba/osx-64/1.5.10 | tar -xvj bin/micromamba
echo "ENV_BACKEND_EXECUTABLE=${{ github.workspace }}/bin/micromamba" >> $GITHUB_ENV
- name: Install Pixi
shell: bash -l {0}
run: |
# This script installs Pixi in ~/.pixi/bin
curl -fsSL https://pixi.sh/install.sh | sh
- name: Install envs-manager
shell: bash -l {0}
run: |
Expand Down
12 changes: 0 additions & 12 deletions .github/workflows/windows-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,6 @@ jobs:
python-version: ${{ matrix.python-version }}
environment-file: requirements/environment.yml
auto-activate-base: false
- name: Install Micromamba and setup as executable backend
shell: bash -l {0}
run: |
mkdir micromamba
curl -Ls https://micro.mamba.pm/api/micromamba/win-64/1.5.10 | tar -xvj -C micromamba
echo "ENV_BACKEND_EXECUTABLE=${{ github.workspace }}\micromamba\Library\bin\micromamba.exe" >> $GITHUB_ENV
echo "MAMBA_ROOT_PREFIX=${{ github.workspace }}\micromamba" >> $GITHUB_ENV
- name: Install Pixi
shell: bash -l {0}
run: |
# This script installs Pixi in ~/.pixi/bin
curl -fsSL https://pixi.sh/install.sh | sh
- name: Install envs-manager
shell: bash -l {0}
run: |
Expand Down
21 changes: 19 additions & 2 deletions envs_manager/backends/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# SPDX-License-Identifier: MIT

from __future__ import annotations

from pathlib import Path
import subprocess
from typing import TypedDict

Expand Down Expand Up @@ -100,11 +102,12 @@ def __init__(
self,
environment_path: str,
envs_directory: str,
external_executable: str | None = None,
bin_directory: str,
):
self.environment_path = environment_path
self.envs_directory = envs_directory
self.external_executable = external_executable
self.bin_directory = bin_directory
self.external_executable = None
self.executable_variant = None
assert self.validate(), f"{self.ID} backend unavailable!"

Expand All @@ -115,6 +118,20 @@ def python_executable_path(self) -> str:
def validate(self) -> bool:
pass

def find_backend_executable(self, exec_name: str):
"""Return the backend executable in bin_directory, if available."""
cmd_list = [exec_name, f"{exec_name}.exe"]
for cmd in cmd_list:
path = Path(self.bin_directory) / cmd
if path.exists():
return str(path)

return

def install_backend_executable(self):
"""Install the backend executable in bin_directory."""
raise NotImplementedError

def create_environment(
self,
packages: list[str] | None = None,
Expand Down
93 changes: 85 additions & 8 deletions envs_manager/backends/conda_like_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@
import logging
import os
from pathlib import Path
import platform
import shutil
import subprocess
import sys
import tarfile

from packaging.version import parse
import requests
import yaml

from envs_manager.backends.api import (
Expand All @@ -29,6 +33,12 @@
class CondaLikeInterface(BackendInstance):
ID = "conda-like"

def __init__(self, environment_path, envs_directory, bin_directory):
super().__init__(environment_path, envs_directory, bin_directory)

# This is needed for Micromamba
os.environ["MAMBA_ROOT_PREFIX"] = str(Path(self.envs_directory).parent)

@property
def python_executable_path(self):
if os.name == "nt":
Expand All @@ -39,15 +49,13 @@ def python_executable_path(self):
return str(python_executable_path)

def validate(self):
if self.external_executable is None:
if os.name == "nt":
cmd_list = ["conda.exe", "conda.bat", "mamba.exe", "micromamba.exe"]
else:
cmd_list = ["conda", "mamba", "micromamba"]
self.external_executable = self.find_backend_executable(exec_name="micromamba")

for cmd in cmd_list:
if shutil.which(cmd):
self.external_executable = shutil.which(cmd)
if self.external_executable is None:
self.install_backend_executable()
self.external_executable = self.find_backend_executable(
exec_name="micromamba"
)

if self.external_executable:
command = [self.external_executable, "--version"]
Expand All @@ -69,8 +77,77 @@ def validate(self):
return True
except Exception as error:
logger.error(error.stderr)

return False

def install_backend_executable(self):
# OS route for the Micromamba URL
machine = platform.machine()
if os.name == "nt":
os_route = "win-64"
elif sys.platform == "darwin":
if machine == "arm64" or machine == "aarch64":
os_route = "osx-arm64"
else:
os_route = "osx-64"
else:
if machine == "x86_64":
os_route = "linux-64"
elif machine == "aarch64":
os_route = "linux-aarch64"
else:
os_route = "linux-ppc64le"

# Download compressed Micromamba file
bin_directory_as_path = Path(self.bin_directory)
compressed_file = "micromamba.tar.bz2"
path_to_compressed_file = bin_directory_as_path / compressed_file

req = requests.get(f"https://micro.mamba.pm/api/micromamba/{os_route}/1.5.10")
with open(path_to_compressed_file, "wb") as f:
f.write(req.content)

# Extract micromamba from compressed file
with tarfile.open(path_to_compressed_file, "r:bz2") as tar:
tar.extractall(path=self.bin_directory)

# Move micromamba to the location we need
exec_extension = ".exe" if os.name == "nt" else ""
end_path = (
bin_directory_as_path / "Library" / "bin" / "micromamba.exe"
if os.name == "nt"
else bin_directory_as_path / "bin" / "micromamba"
)
shutil.move(end_path, bin_directory_as_path / f"micromamba{exec_extension}")

# On Windows we also need the VS14 runtime because micromamba is linked against
# it
if os.name == "nt":
path_to_compressed_vs_runtime = (
"vs2015_runtime-14.28.29325-h8ebdc22_9.tar.bz2"
)
req = requests.get(
f"https://anaconda.org/conda-forge/vs2015_runtime/14.28.29325/download/"
f"win-64/{path_to_compressed_vs_runtime}"
)
with open(path_to_compressed_vs_runtime, "wb") as f:
f.write(req.content)

with tarfile.open(path_to_compressed_vs_runtime, "r:bz2") as tar:
tar.extractall(path=self.bin_directory)

# Clean up
try:
os.remove(path_to_compressed_file)
shutil.rmtree(bin_directory_as_path / "info")
if os.name == "nt":
os.remove(path_to_compressed_vs_runtime)
shutil.rmtree(bin_directory_as_path / "Library")
else:
shutil.rmtree(bin_directory_as_path / "bin")
except Exception:
pass

def create_environment(self, packages=None, channels=None, force=False):
command = [self.external_executable, "create", "-p", self.environment_path]

Expand Down
59 changes: 52 additions & 7 deletions envs_manager/backends/pixi_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
import logging
import os
from pathlib import Path
import shutil
import subprocess
import zipfile

from packaging.version import parse
from rattler import AboutJson
import requests

from envs_manager.backends.api import BackendInstance, BackendActionResult, run_command

Expand All @@ -22,8 +22,8 @@
class PixiInterface(BackendInstance):
ID = "pixi"

def __init__(self, environment_path, envs_directory, external_executable=None):
super().__init__(environment_path, envs_directory, external_executable)
def __init__(self, environment_path, envs_directory, bin_directory):
super().__init__(environment_path, envs_directory, bin_directory)

# We use this to save the Pixi packages cache directory
self._cache_dir = None
Expand All @@ -44,11 +44,11 @@ def python_executable_path(self):
return str(python_executable_path)

def validate(self):
self.external_executable = self.find_backend_executable(exec_name="pixi")

if self.external_executable is None:
cmd_list = ["pixi", "pixi.exe"]
for cmd in cmd_list:
if shutil.which(cmd):
self.external_executable = shutil.which(cmd)
self.install_backend_executable()
self.external_executable = self.find_backend_executable(exec_name="pixi")

if self.external_executable:
command = [self.external_executable, "--version"]
Expand All @@ -64,6 +64,51 @@ def validate(self):

return False

def install_backend_executable(self):
install_script = f"install{'.ps1' if os.name == 'nt' else '.sh'}"
path_to_install_script = Path(self.bin_directory) / install_script

# Download script to install Pixi
req = requests.get(f"https://pixi.sh/{install_script}")
with open(str(path_to_install_script), "w") as f:
f.write(req.text)

# Env vars to control how Pixi is installed
env = os.environ.copy()
env["PIXI_HOME"] = str(Path(self.bin_directory).parent)
env["PIXI_NO_PATH_UPDATE"] = "true"

# Install Pixi
if os.name == "nt":
install_command = [
"powershell.exe",
"-ExecutionPolicy",
"Bypass",
"-File",
str(path_to_install_script),
]
else:
install_command = ["sh", install_script]

try:
run_command(
install_command,
run_env=env,
cwd=self.bin_directory,
)
except subprocess.CalledProcessError as error:
logger.error(error.stderr.strip())
return
except Exception as error:
logger.error(error, exc_info=True)
return

# Cleanup
try:
os.remove(path_to_install_script)
except Exception:
pass

def create_environment(self, packages=None, channels=None, force=False):
# We need to run `pixi init` first
init_command = [
Expand Down
11 changes: 2 additions & 9 deletions envs_manager/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@
import logging
import sys

from envs_manager.manager import (
DEFAULT_BACKENDS_ROOT_PATH,
DEFAULT_BACKEND,
EXTERNAL_EXECUTABLE,
Manager,
)
from envs_manager.manager import DEFAULT_BACKENDS_ROOT_PATH, DEFAULT_BACKEND, Manager


def main(args=None):
Expand Down Expand Up @@ -167,14 +162,12 @@ def main(args=None):
logger.debug(options)
logger.debug(f"Using BACKENDS_ROOT_PATH: {DEFAULT_BACKENDS_ROOT_PATH}")
logger.debug(f"Using ENV_BACKEND: {options.backend}")
logger.debug(f"Using ENV_BACKEND_EXECUTABLE: {EXTERNAL_EXECUTABLE}")

if options.env_name:
manager = Manager(
backend=options.backend,
env_name=options.env_name,
root_path=DEFAULT_BACKENDS_ROOT_PATH,
external_executable=EXTERNAL_EXECUTABLE,
)
if options.command == "create":
manager.create_environment(
Expand Down Expand Up @@ -205,5 +198,5 @@ def main(args=None):
else:
backend = DEFAULT_BACKEND

manager = Manager(backend=backend, external_executable=EXTERNAL_EXECUTABLE)
manager = Manager(backend=backend)
manager.list_environments()
Loading
Loading