Skip to content
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

MAINT: Vendor mamba.api #1372

Merged
merged 11 commits into from
Feb 4, 2024
Merged
35 changes: 35 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,41 @@ jobs:
- name: Run tests
run: python -m pytest -v --timeout=300 --webdriver=ChromeHeadless --durations=100 test

test_env:
name: test_environments
runs-on: "ubuntu-latest"
strategy:
fail-fast: false
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0

- uses: mamba-org/setup-micromamba@v1
with:
init-shell: >-
bash
environment-name: test-env
cache-environment: true
create-args: >-
python
pip
libmambapy
conda-build

- name: Install dependencies
run: python -m pip install ".[test,hg]" --pre
shell: micromamba-shell {0}

- name: Install asv
run: pip install .
shell: micromamba-shell {0}

- name: Run tests
run: pytest -k environment_bench -vvvvv
shell: micromamba-shell {0}

docs:
runs-on: ubuntu-latest
steps:
Expand Down
36 changes: 0 additions & 36 deletions .github/workflows/ci_win.yml

This file was deleted.

16 changes: 16 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
0.6.2 (TBD)
----------------------------

New Features
^^^^^^^^^^^^

API Changes
^^^^^^^^^^^

Bug Fixes
^^^^^^^^^
- The ``mamba`` plugin works correctly for newer versions (>=1.5) of ``libmambapy`` (#1372)

Other Changes and Additions
^^^^^^^^^^^^^^^^^^^^^^^^^^^

0.6.1 (2023-09-11)
----------------------------

Expand Down
259 changes: 259 additions & 0 deletions asv/plugins/_mamba_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
# Licensed under a 3-clause BSD style license FOR Mamba - see LICENSE in mamba-org/mamba
# Also covered by the 3-clause BSD style license FOR boa - see LICENSE in mamba-org/boa
# Copyright 2019 QuantStack and the Mamba contributors.
# Very lightly edited / simplified for use within asv
import os
import urllib.parse
import collections

import libmambapy
from conda.base.constants import ChannelPriority
from conda.base.context import context
from conda.core.index import check_allowlist
from conda.gateways.connection.session import CondaHttpAuth


def get_index(
channel_urls=(),
prepend=True,
platform=None,
use_local=False,
use_cache=False,
unknown=None,
prefix=None,
repodata_fn="repodata.json",
):
if isinstance(platform, str):
platform = [platform, "noarch"]

all_channels = []
if use_local:
all_channels.append("local")
all_channels.extend(channel_urls)
if prepend:
all_channels.extend(context.channels)
check_allowlist(all_channels)

# Remove duplicates but retain order
all_channels = list(collections.OrderedDict.fromkeys(all_channels))

dlist = libmambapy.DownloadTargetList()

index = []

def fixup_channel_spec(spec):
at_count = spec.count("@")
if at_count > 1:
first_at = spec.find("@")
spec = (
spec[:first_at]
+ urllib.parse.quote(spec[first_at])
+ spec[first_at + 1 :]
)
if platform:
spec = spec + "[" + ",".join(platform) + "]"
return spec

all_channels = list(map(fixup_channel_spec, all_channels))
pkgs_dirs = libmambapy.MultiPackageCache(context.pkgs_dirs)
libmambapy.create_cache_dir(str(pkgs_dirs.first_writable_path))

for channel in libmambapy.get_channels(all_channels):
for channel_platform, url in channel.platform_urls(with_credentials=True):
full_url = CondaHttpAuth.add_binstar_token(url)

sd = libmambapy.SubdirData(
channel, channel_platform, full_url, pkgs_dirs, repodata_fn
)

needs_finalising = sd.download_and_check_targets(dlist)
index.append(
(
sd,
{
"platform": channel_platform,
"url": url,
"channel": channel,
"needs_finalising": needs_finalising,
},
)
)

for sd, info in index:
if info["needs_finalising"]:
sd.finalize_checks()
dlist.add(sd)

is_downloaded = dlist.download(libmambapy.MAMBA_DOWNLOAD_FAILFAST)

if not is_downloaded:
raise RuntimeError("Error downloading repodata.")

return index


def load_channels(
pool,
channels,
repos,
has_priority=None,
prepend=True,
platform=None,
use_local=False,
use_cache=True,
repodata_fn="repodata.json",
):
index = get_index(
channel_urls=channels,
prepend=prepend,
platform=platform,
use_local=use_local,
repodata_fn=repodata_fn,
use_cache=use_cache,
)

if has_priority is None:
has_priority = context.channel_priority in [
ChannelPriority.STRICT,
ChannelPriority.FLEXIBLE,
]

subprio_index = len(index)
if has_priority:
# first, count unique channels
n_channels = len(set([entry["channel"].canonical_name for _, entry in index]))
current_channel = index[0][1]["channel"].canonical_name
channel_prio = n_channels

for subdir, entry in index:
# add priority here
if has_priority:
if entry["channel"].canonical_name != current_channel:
channel_prio -= 1
current_channel = entry["channel"].canonical_name
priority = channel_prio
else:
priority = 0
if has_priority:
subpriority = 0
else:
subpriority = subprio_index
subprio_index -= 1

if not subdir.loaded() and entry["platform"] != "noarch":
# ignore non-loaded subdir if channel is != noarch
continue

if context.verbosity != 0 and not context.json:
print(
"Channel: {}, platform: {}, prio: {} : {}".format(
entry["channel"], entry["platform"], priority, subpriority
)
)
print("Cache path: ", subdir.cache_path())

repo = subdir.create_repo(pool)
repo.set_priority(priority, subpriority)
repos.append(repo)

return index


class MambaSolver:
def __init__(self, channels, platform, context, output_folder=None):
self.channels = channels
self.platform = platform
self.context = context
self.output_folder = output_folder or "local"
self.pool = libmambapy.Pool()
self.repos = []
self.index = load_channels(
self.pool, self.channels, self.repos, platform=platform
)

self.local_index = []
self.local_repos = {}
# load local repo, too
self.replace_channels()

def replace_installed(self, prefix):
prefix_data = libmambapy.PrefixData(prefix)
vp = libmambapy.get_virtual_packages()
prefix_data.add_packages(vp)
repo = libmambapy.Repo(self.pool, prefix_data)
repo.set_installed()

def replace_channels(self):
self.local_index = get_index(
(self.output_folder,), platform=self.platform, prepend=False
)

for _, v in self.local_repos.items():
v.clear(True)

start_prio = len(self.channels) + len(self.index)
for subdir, channel in self.local_index:
if not subdir.loaded():
continue

# support new mamba
if isinstance(channel, dict):
channelstr = channel["url"]
channelurl = channel["url"]
else:
channelstr = str(channel)
channelurl = channel.url(with_credentials=True)

cp = subdir.cache_path()
if cp.endswith(".solv"):
os.remove(subdir.cache_path())
cp = cp.replace(".solv", ".json")

self.local_repos[channelstr] = libmambapy.Repo(
self.pool, channelstr, cp, channelurl
)

self.local_repos[channelstr].set_priority(start_prio, 0)
start_prio -= 1

def solve(self, specs, pkg_cache_path=None):
"""Solve given a set of specs.
Parameters
----------
specs : list of str
A list of package specs. You can use `conda.models.match_spec.MatchSpec`
to get them to the right form by calling
`MatchSpec(mypec).conda_build_form()`
Returns
-------
transaction : libmambapy.Transaction
The mamba transaction.
Raises
------
RuntimeError :
If the solver did not find a solution.
"""
solver_options = [(libmambapy.SOLVER_FLAG_ALLOW_DOWNGRADE, 1)]
api_solver = libmambapy.Solver(self.pool, solver_options)
_specs = specs

api_solver.add_jobs(_specs, libmambapy.SOLVER_INSTALL)
success = api_solver.try_solve()

if not success:
error_string = "Mamba failed to solve:\n"
for s in _specs:
error_string += f" - {s}\n"
error_string += "\nwith channels:\n"
for c in self.channels:
error_string += f" - {c}\n"
error_string += api_solver.explain_problems()
print(error_string)
raise RuntimeError("Solver could not find solution." + error_string)

if pkg_cache_path is None:
# use values from conda
pkg_cache_path = self.context.pkgs_dirs

package_cache = libmambapy.MultiPackageCache(pkg_cache_path)
return libmambapy.Transaction(api_solver, package_cache)
3 changes: 1 addition & 2 deletions asv/plugins/mamba.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
except ImportError:
from yaml import Loader

from mamba.api import libmambapy, MambaSolver

from ._mamba_helpers import libmambapy, MambaSolver
from .. import environment, util
from ..console import log

Expand Down
2 changes: 1 addition & 1 deletion docs/source/installing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ the ``python`` requirements are as noted in the ``pyproject.toml``.
For managing the environments, one of the following packages is required:

- `libmambapy <https://mamba.readthedocs.io/en/latest/python_api.html>`__,
which is typically part of ``mamba``
which is typically part of ``mamba``. In this case ``conda`` must be present too.

- `virtualenv <http://virtualenv.org/>`__, which is required since
venv is not compatible with other versions of Python.
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ test = [
"scipy; platform_python_implementation != \"PyPy\"",
"feedparser",
"selenium",
"flaky",
"pytest-rerunfailures",
"python-hglib; platform_system != 'Windows'",
"rpy2; platform_system != 'Windows' and platform_python_implementation != 'PyPy'",
Expand Down
Loading
Loading