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

"Listen" to conda recommendation to use some other solver upon conda install failing; stderr output from "conda install" would be captured and logged #210

Merged
merged 7 commits into from
Nov 7, 2024
64 changes: 55 additions & 9 deletions src/datalad_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1963,17 +1963,36 @@ def install_package(
i = 0
while True:
try:
runcmd(*cmd)
result = runcmd(*cmd, stderr=subprocess.PIPE, universal_newlines=True)
if result.stderr:
log.error("conda install stderr: %s", result.stderr)
except subprocess.CalledProcessError as e:
if i < 3:
log.error(
"Command failed with exit status %d; sleeping and retrying",
e.returncode,
)
i += 1
sleep(5)
log.error(
"Command failed with exit status %d and stderr:\n%s",
e.returncode,
e.stderr,
)
# Custom workaround for https://github.com/datalad/datalad-installer/issues/206
if solver_offered := maybe_offered_solver(e.stderr):
if "--solver" in cmd:
log.info(
"--solver was already in the command, not retrying with offered %s",
solver_offered,
)
# on this type of error, not point of retrying
raise
else:
cmd += ["--solver", solver_offered]
else:
raise
if i < 3:
log.error(
"Sleeping and retrying",
e.returncode,
)
i += 1
sleep(5)
else:
raise
else:
break
binpath = conda.bindir
Expand Down Expand Up @@ -2987,6 +3006,33 @@ def parse_links(html: str, base_url: Optional[str] = None) -> list[Link]:
return links


def maybe_offered_solver(e_stderr: Any) -> Optional[str]:
# May be would not be needed in the future.
solver_offered = None
if e_stderr and (
solvers_match := re.search(
r"CondaValueError: You have chosen a non-default solver backend \(\S*\) .*"
"Choose one of: (?P<solvers>.*)",
str(e_stderr),
)
):
solvers_offered = solvers_match.groupdict()["solvers"]
solvers_offered = [c.strip() for c in re.split("[ \t,]", solvers_offered)]
if len(solvers_offered):
solver_offered = solvers_offered[0]
if len(solvers_offered) > 1:
# TODO: might add a logic in case of multiple to try them one by one
log.info(
"More than once choice given (%s), will use the first one.",
solvers_offered,
)
else:
log.info("Will use solver %s", solver_offered)
else:
log.info("No solver choices offered, continuing as is")
return solver_offered


@dataclass
class Link:
"""A hyperlink extracted from an HTML page"""
Expand Down
66 changes: 30 additions & 36 deletions test/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,40 +61,30 @@ def test_install_miniconda(tmp_path: Path) -> None:


@pytest.mark.miniconda
def test_install_miniconda_python_match(tmp_path: Path) -> None:
miniconda_path = tmp_path / "conda"
if sys.version_info[:2] == (3, 7):
component = "miniconda=py37_23.1.0-1"
else:
component = "miniconda"
r = main(
[
"datalad_installer.py",
component,
"--batch",
"--path",
str(miniconda_path),
"--python-match",
"minor",
]
)
assert r == 0
if ON_WINDOWS:
pypath = miniconda_path / "python.exe"
else:
pypath = miniconda_path / "bin" / "python"
assert pypath.exists()
assert subprocess.run(
[str(pypath), "-c", "import sys; print(sys.version_info[:2])"],
stdout=subprocess.PIPE,
universal_newlines=True,
check=True,
).stdout.strip() == repr(sys.version_info[:2])


@pytest.mark.skipif(sys.version_info[:2] != (3, 12), reason="Only run on Python 3.12")
@pytest.mark.miniconda
def test_install_miniconda_python_match_conda_forge(tmp_path: Path) -> None:
@pytest.mark.parametrize(
"extra_opts,extra_spec",
[
pytest.param(
[], [], marks=pytest.mark.skipif(ON_LINUX, reason="non-Linux only")
),
pytest.param(["-c", "conda-forge"], []),
# to save testing time we will also bundle with handling of the case when
# it might fail to find solver,
# ref: https://github.com/datalad/datalad-installer/issues/206
pytest.param(
[],
[
"git-annex",
"-m",
"conda",
],
marks=pytest.mark.skipif(not ON_LINUX, reason="Linux only"),
),
],
)
def test_install_miniconda_python_match(
extra_opts: list[str], extra_spec: list[str], tmp_path: Path
) -> None:
miniconda_path = tmp_path / "conda"
r = main(
[
Expand All @@ -105,9 +95,9 @@ def test_install_miniconda_python_match_conda_forge(tmp_path: Path) -> None:
str(miniconda_path),
"--python-match",
"minor",
"-c",
"conda-forge",
]
+ extra_opts
+ extra_spec,
)
assert r == 0
if ON_WINDOWS:
Expand All @@ -122,6 +112,10 @@ def test_install_miniconda_python_match_conda_forge(tmp_path: Path) -> None:
check=True,
).stdout.strip() == repr(sys.version_info[:2])

if extra_spec and extra_spec[0] == "git-annex":
# for the git-annex component
assert (miniconda_path / "bin" / "git-annex").exists()


@pytest.mark.miniconda
def test_install_miniconda_autogen_path(monkeypatch: pytest.MonkeyPatch) -> None:
Expand Down
Loading