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

Ensure that channels match expected value in conda-build #365

Merged
merged 28 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
be6bbc7
add reproducer test
jaimergp Nov 8, 2023
615ebd6
move test
jaimergp Nov 8, 2023
00a3f78
use canonical channel name when available
jaimergp Nov 8, 2023
1e0f373
pre-commit
jaimergp Nov 8, 2023
45dea9e
add news
jaimergp Nov 8, 2023
487fa6b
only for conda-build
jaimergp Nov 8, 2023
c436dbd
parametrize recipes
jaimergp Nov 8, 2023
d95a0e1
try with just the string
jaimergp Nov 9, 2023
45658cd
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 9, 2023
5fe3b41
Merge branch 'main' into fix-363
jaimergp Nov 9, 2023
e299c0b
fix merge artifact
jaimergp Nov 9, 2023
336646b
use the full channel, not just the name
jaimergp Nov 9, 2023
b188daa
pre-commit
jaimergp Nov 9, 2023
1653234
detect whether channel belongs to multichannel via url
jaimergp Nov 9, 2023
cb328b1
Merge branch 'main' into fix-363
jaimergp Nov 11, 2023
44a6b49
use separate CONDA_BLD_PATH dirs
jaimergp Nov 11, 2023
74b26aa
Merge branch 'fix-363' of github.com:conda/conda-libmamba-solver into…
jaimergp Nov 11, 2023
9c6773e
only the channels test needs to be isolated
jaimergp Nov 12, 2023
cbef76c
cache this called_from_conda_build call a bit
jaimergp Nov 12, 2023
8d9d6c0
make sure we clear the repo before reloading it
jaimergp Nov 12, 2023
cf3f2c8
try shorter env path on windows
jaimergp Nov 13, 2023
9aaa8b4
use restricted unicode on windows
jaimergp Nov 13, 2023
f7bda4a
pre-commit
jaimergp Nov 13, 2023
1da19fe
unicode restrictions on this test only
jaimergp Nov 13, 2023
cee4693
extend stackvana example
jaimergp Nov 13, 2023
093b3e0
pre-commit [ci skip]
jaimergp Nov 13, 2023
a576cfb
retrigger
jaimergp Nov 13, 2023
1bfe170
Merge branch 'main' into fix-363
jaimergp Nov 14, 2023
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
26 changes: 17 additions & 9 deletions conda_libmamba_solver/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,12 @@ def reload_local_channels(self):
Reload a channel that was previously loaded from a local directory.
"""
for noauth_url, info in self._index.items():
if noauth_url.startswith("file://"):
if noauth_url.startswith("file://") or info.channel.scheme == "file":
url, json_path = self._fetch_channel(info.full_url)
new = self._json_path_to_repo_info(url, json_path)
self._repos[self._repos.index(info.repo)] = new.repo
repo_position = self._repos.index(info.repo)
info.repo.clear(True)
new = self._json_path_to_repo_info(url, json_path, try_solv=False)
self._repos[repo_position] = new.repo
self._index[noauth_url] = new
set_channel_priorities(self._index)

Expand Down Expand Up @@ -234,20 +236,26 @@ def _fetch_channel(self, url: str) -> Tuple[str, os.PathLike]:

return url, json_path

def _json_path_to_repo_info(self, url: str, json_path: str) -> Optional[_ChannelRepoInfo]:
def _json_path_to_repo_info(
self, url: str, json_path: str, try_solv: bool = False
) -> Optional[_ChannelRepoInfo]:
channel = Channel.from_url(url)
noauth_url = channel.urls(with_credentials=False, subdirs=(channel.subdir,))[0]
json_path = Path(json_path)
solv_path = json_path.parent / f"{json_path.stem}.solv"
try:
json_stat = json_path.stat()
except OSError as exc:
log.debug("Failed to stat %s", json_path, exc_info=exc)
json_stat = None
try:
solv_stat = solv_path.stat()
except OSError as exc:
log.debug("Failed to stat %s", solv_path, exc_info=exc)
if try_solv:
try:
solv_path = json_path.parent / f"{json_path.stem}.solv"
solv_stat = solv_path.stat()
except OSError as exc:
log.debug("Failed to stat %s", solv_path, exc_info=exc)
solv_stat = None
else:
solv_path = None
solv_stat = None

if solv_stat is None and json_stat is None:
Expand Down
33 changes: 26 additions & 7 deletions conda_libmamba_solver/solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
REPODATA_FN,
UNKNOWN_CHANNEL,
ChannelPriority,
on_win,
)
from conda.base.context import context
from conda.common.compat import on_win
from conda.common.constants import NULL
from conda.common.io import Spinner, timeout
from conda.common.path import paths_equal
Expand Down Expand Up @@ -160,7 +160,6 @@ def solve_final_state(
# From now on we _do_ require a solver and the index
init_api_context()
subdirs = self.subdirs
conda_bld_channels = ()
if self._called_from_conda_build():
log.info("Using solver via 'conda.plan.install_actions' (probably conda build)")
# Problem: Conda build generates a custom index which happens to "forget" about
Expand All @@ -179,6 +178,7 @@ def solve_final_state(
IndexHelper = _CachedLibMambaIndexHelper
else:
IndexHelper = LibMambaIndexHelper
conda_bld_channels = ()

all_channels = [
*conda_bld_channels,
Expand Down Expand Up @@ -826,8 +826,11 @@ def _export_solved_records(
else:
log.warn("Tried to unlink %s but it is not installed or manageable?", filename)

for_conda_build = self._called_from_conda_build()
for channel, filename, json_payload in to_link:
record = self._package_record_from_json_payload(index, channel, filename, json_payload)
record = self._package_record_from_json_payload(
index, channel, filename, json_payload, for_conda_build=for_conda_build
)
# We need this check below to make sure noarch package get reinstalled
# record metadata coming from libmamba is incomplete and won't pass the
# noarch checks -- to fix it, we swap the metadata-only record with its locally
Expand All @@ -848,20 +851,28 @@ def _export_solved_records(
)

# Fixes conda-build tests/test_api_build.py::test_croot_with_spaces
if on_win and self._called_from_conda_build():
if on_win and for_conda_build:
for record in out_state.records.values():
record.channel.location = percent_decode(record.channel.location)
if "%" not in str(record):
continue
if record.channel.location: # multichannels like 'defaults' have no location
record.channel.location = percent_decode(record.channel.location)
record.channel.name = percent_decode(record.channel.name)

def _package_record_from_json_payload(
self, index: LibMambaIndexHelper, channel: str, pkg_filename: str, json_payload: str
self,
index: LibMambaIndexHelper,
channel: str,
pkg_filename: str,
json_payload: str,
for_conda_build: bool = False,
) -> PackageRecord:
"""
The libmamba transactions cannot return full-blown objects from the C/C++ side.
Instead, it returns the instructions to build one on the Python side:

channel_info: dict
Channel data, as built in .index.LibmambaIndexHelper._fetch_channel()
Channel datas, as built in .index.LibmambaIndexHelper._fetch_channel()
This is retrieved from the .index._index mapping, keyed by channel URLs
pkg_filename: str
The filename (.tar.bz2 or .conda) of the selected record.
Expand All @@ -887,6 +898,14 @@ def _package_record_from_json_payload(
# Otherwise, these are records from the index
kwargs["fn"] = pkg_filename
kwargs["channel"] = channel_info.channel
if for_conda_build:
# conda-build expects multichannel instances in the Dist->PackageRecord mapping
# see https://github.com/conda/conda-libmamba-solver/issues/363
for multichannel_name, mc_channels in context.custom_multichannels.items():
urls = [url for c in mc_channels for url in c.urls(with_credentials=False)]
if channel_info.noauth_url in urls:
kwargs["channel"] = multichannel_name
break
kwargs["url"] = join_url(channel_info.full_url, pkg_filename)
if not kwargs.get("subdir"): # missing in old channels
kwargs["subdir"] = channel_info.channel.subdir
Expand Down
19 changes: 19 additions & 0 deletions news/365-canonical-channel-names
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
### Enhancements

* <news item>

### Bug fixes

* Use canonical channel names (if available) in exported `PackageRecord` objects. Fixes an issue with conda-build and custom multichannels. (#363 via #365)

### Deprecations

* <news item>

### Docs

* <news item>

### Other

* <news item>
41 changes: 38 additions & 3 deletions tests/data/conda_build_recipes/stackvana/meta.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{% set name = "stackvana-core" %}
{% set version = "0.2021.43" %}
{% set eups_product = "lsst_distrib" %}

package:
name: {{ name|lower }}
Expand All @@ -15,11 +16,45 @@ outputs:
script:
- echo "BUILDING IMPL" >> $PREFIX/stackvana-core-impl # [unix]
- echo "BUILDING IMPL" >> %PREFIX%/stackvana-core-impl # [win]
test:
commands:
- echo OK
- name: stackvana-core
version: {{ version }}
run_exports:
- {{ pin_subpackage('stackvana-core-impl', exact=True) }}

build:
script:
- echo "BUILDING CORE" >> $PREFIX/stackvana-core # [unix]
- echo "BUILDING CORE" >> %PREFIX%/stackvana-core # [win]
run_exports:
- {{ pin_subpackage('stackvana-core-impl', exact=True) }}
requirements:
run:
- {{ pin_subpackage('stackvana-core-impl', exact=True) }}
test:
commands:
- echo OK
- name: stackvana-{{ eups_product }}
version: {{ version }}
build:
script:
- echo "BUILDING {{ eups_product }}" >> $PREFIX/stackvana-{{ eups_product }} # [unix]
- echo "BUILDING {{ eups_product }}" >> %PREFIX%/stackvana-{{ eups_product }} # [win]
requirements:
host:
- stackvana-core =={{ version }}
run:
- stackvana-core =={{ version }}
test:
commands:
- echo OK
- name: stackvana
version: {{ version }}
build:
script:
- echo "BUILDING STACKVANA" >> $PREFIX/stackvana # [unix]
- echo "BUILDING STACKVANA" >> %PREFIX%/stackvana # [win]
requirements:
- {{ pin_subpackage("stackvana-" ~ eups_product, max_pin="x.x.x") }}
test:
commands:
- echo OK
51 changes: 45 additions & 6 deletions tests/test_channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import pytest
from conda.base.context import reset_context
from conda.common.compat import on_linux
from conda.common.compat import on_linux, on_win
from conda.common.io import env_vars
from conda.core.prefix_data import PrefixData
from conda.models.channel import Channel
Expand All @@ -23,6 +23,8 @@
from .channel_testing.helpers import create_with_channel
from .utils import conda_subprocess, write_env_config

DATA = Path(__file__).parent / "data"


def test_channel_matchspec():
stdout, *_ = conda_inprocess(
Expand Down Expand Up @@ -89,9 +91,19 @@ def test_channels_installed_unavailable():
assert retcode == 0


def _setup_channels_alias(prefix):
def _setup_conda_forge_as_defaults(prefix, force=False):
write_env_config(
prefix,
force=force,
channels=["defaults"],
default_channels=["conda-forge"],
)


def _setup_channels_alias(prefix, force=False):
write_env_config(
prefix,
force=force,
channels=["conda-forge", "defaults"],
channel_alias="https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud",
migrated_channel_aliases=["https://conda.anaconda.org"],
Expand All @@ -103,9 +115,10 @@ def _setup_channels_alias(prefix):
)


def _setup_channels_custom(prefix):
def _setup_channels_custom(prefix, force=False):
write_env_config(
prefix,
force=force,
channels=["conda-forge", "defaults"],
custom_channels={
"conda-forge": "https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud",
Expand Down Expand Up @@ -219,6 +232,32 @@ def test_encoding_file_paths(tmp_path: Path):
assert list((tmp_path / "env" / "conda-meta").glob("test-package-*.json"))


def test_conda_build_with_aliased_channels(tmp_path):
"https://github.com/conda/conda-libmamba-solver/issues/363"
condarc = Path.home() / ".condarc"
condarc_contents = condarc.read_text() if condarc.is_file() else None
env = os.environ.copy()
if on_win:
env["CONDA_BLD_PATH"] = str(Path(os.environ.get("RUNNER_TEMP", tmp_path), "bld"))
else:
env["CONDA_BLD_PATH"] = str(tmp_path / "conda-bld")
try:
_setup_conda_forge_as_defaults(Path.home(), force=True)
conda_subprocess(
"build",
DATA / "conda_build_recipes" / "jedi",
"--override-channels",
"--channel=defaults",
capture_output=False,
env=env,
)
finally:
if condarc_contents:
condarc.write_text(condarc_contents)
else:
condarc.unlink()


def test_http_server_auth_none(http_server_auth_none):
create_with_channel(http_server_auth_none)

Expand Down Expand Up @@ -247,12 +286,12 @@ def test_http_server_auth_token_in_defaults(http_server_auth_token):
)
reset_context()
conda_subprocess("info", capture_output=False)
conda_inprocess(
conda_subprocess(
"create",
_get_temp_prefix(),
"-p",
_get_temp_prefix(use_restricted_unicode=on_win),
"--solver=libmamba",
"test-package",
no_capture=True,
)
finally:
if condarc_contents:
Expand Down
27 changes: 15 additions & 12 deletions tests/test_downstream.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,30 @@
DATA = Path(__file__).parent / "data"


def test_build_recipes():
@pytest.mark.parametrize(
"recipe",
[
pytest.param(x, id=x.name)
for x in sorted((DATA / "conda_build_recipes").iterdir())
if (x / "meta.yaml").is_file()
],
)
def test_build_recipe(recipe):
"""
Adapted from
https://github.com/mamba-org/boa/blob/3213180564/tests/test_mambabuild.py#L6

See /tests/data/conda_build_recipes/LICENSE for more details
"""
recipes_dir = DATA / "conda_build_recipes"

recipes = [str(x) for x in recipes_dir.iterdir() if x.is_dir()]
expected_fail_recipes = ["baddeps"]
env = os.environ.copy()
env["CONDA_SOLVER"] = "libmamba"
expected_fail_recipes = ["baddeps"]
for recipe in recipes:
recipe_name = Path(recipe).name
print(f"Running {recipe_name}")
if recipe_name in expected_fail_recipes:
with pytest.raises(CalledProcessError):
check_call(["conda-build", recipe], env=env)
else:
recipe_name = Path(recipe).name
if recipe_name in expected_fail_recipes:
with pytest.raises(CalledProcessError):
check_call(["conda-build", recipe], env=env)
else:
check_call(["conda-build", recipe], env=env)


def test_conda_lock(tmp_path):
Expand Down
Loading