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

Allow offline usage for skipped binaries #15516

Merged
merged 7 commits into from
Jan 29, 2024
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
7 changes: 5 additions & 2 deletions conans/client/graph/graph_binaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
BINARY_INVALID, BINARY_EDITABLE_BUILD, RECIPE_PLATFORM,
BINARY_PLATFORM)
from conans.errors import NoRemoteAvailable, NotFoundException, \
PackageNotFoundException, conanfile_exception_formatter
PackageNotFoundException, conanfile_exception_formatter, ConanConnectionError


class GraphBinariesAnalyzer(object):
Expand Down Expand Up @@ -64,7 +64,10 @@ def _get_package_from_remotes(self, node, remotes, update):
break
except NotFoundException:
pass

except ConanConnectionError:
ConanOutput().error(f"Failed checking for binary '{pref}' in remote '{r.name}': "
"remote not available")
raise
if not remotes and update:
node.conanfile.output.warning("Can't update, there are no remotes defined")

Expand Down
7 changes: 4 additions & 3 deletions conans/client/remote_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,10 @@ def _call_remote(self, remote, method, *args, **kwargs):
try:
return self._auth_manager.call_rest_api_method(remote, method, *args, **kwargs)
except ConnectionError as exc:
raise ConanConnectionError(("%s\n\nUnable to connect to %s=%s\n" +
"1. Make sure the remote is reachable or,\n" +
"2. Disable it by using conan remote disable,\n" +
raise ConanConnectionError(("%s\n\nUnable to connect to remote %s=%s\n"
"1. Make sure the remote is reachable or,\n"
"2. Disable it with 'conan remote disable <remote>' or,\n"
"3. Use the '-nr/--no-remote' argument\n"
"Then try again."
) % (str(exc), remote.name, remote.url))
except ConanException as exc:
Expand Down
10 changes: 1 addition & 9 deletions conans/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,18 +224,10 @@ def __str__(self):
self.remote_message())


class UserInterfaceErrorException(RequestErrorException):
AbrilRBS marked this conversation as resolved.
Show resolved Hide resolved
"""
420 error
"""
pass


EXCEPTION_CODE_MAPPING = {InternalErrorException: 500,
RequestErrorException: 400,
AuthenticationException: 401,
ForbiddenException: 403,
NotFoundException: 404,
RecipeNotFoundException: 404,
PackageNotFoundException: 404,
UserInterfaceErrorException: 420}
PackageNotFoundException: 404}
6 changes: 3 additions & 3 deletions conans/test/integration/remote/multi_remote_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,11 @@ def test_fail_when_not_notfound(self):

servers["s1"].fake_url = "http://asdlhaljksdhlajkshdljakhsd.com" # Do not exist
client2 = TestClient(servers=servers)
err = client2.run("install --requires=mylib/0.1@conan/testing --build=missing", assert_error=True)
self.assertTrue(err)
client2.run("install --requires=mylib/0.1@conan/testing --build=missing", assert_error=True)
self.assertIn("mylib/0.1@conan/testing: Checking remote: s0", client2.out)
self.assertIn("mylib/0.1@conan/testing: Checking remote: s1", client2.out)
self.assertIn("Unable to connect to s1=http://asdlhaljksdhlajkshdljakhsd.com", client2.out)
self.assertIn("Unable to connect to remote s1=http://asdlhaljksdhlajkshdljakhsd.com",
client2.out)
# s2 is not even tried
self.assertNotIn("mylib/0.1@conan/testing: Trying with 's2'...", client2.out)

Expand Down
79 changes: 79 additions & 0 deletions conans/test/integration/remote/test_offline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import re

from conans.test.assets.genconanfile import GenConanfile
from conans.test.utils.tools import TestClient, TestRequester


def test_offline():
c = TestClient(servers={"default": None}, requester_class=None)
c.save({"conanfile.py": GenConanfile("pkg", "0.1")})
c.run("create .")
c.run("install --requires=pkg/0.1")
# doesn't fail, install without issues
assert "Install finished successfully" in c.out


def test_offline_uploaded():
c = TestClient(default_server_user=True)
c.save({"conanfile.py": GenConanfile("pkg")})
c.run("create . --version=0.1")
c.run("create . --version=0.2")
c.run("upload * -r=default -c")
c.run("remove * -c")
c.run("install --requires=pkg/0.1")
assert "Install finished successfully" in c.out
c.servers = {"default": None}
c.run("install --requires=pkg/0.1")
assert "Install finished successfully" in c.out
# Lets make sure the server is broken
c.run("install --requires=pkg/0.2", assert_error=True)
assert "ERROR: Package 'pkg/0.2' not resolved" in c.out


def test_offline_build_requires():
"""
When there are tool-requires that are not installed locally, Conan will try to check
its existence in the remote, because that package might be needed later. Even if some
packages can be marked later as "skip" and not fail, that cannot be known a priori, so if the
package is not in the cache, it will be checked in servers
https://github.com/conan-io/conan/issues/15339

Approaches to avoid the WARNING:
- conan remote disable <remotes>
- conan install ... -nr (--no-remotes)
- prepopulate the cache with all tool-requires with `-c:a tools.graph:skip_binaries=False`
"""
c = TestClient(default_server_user=True)
c.save({"tool/conanfile.py": GenConanfile("tool", "0.1"),
"lib/conanfile.py": GenConanfile("pkg", "0.1").with_tool_requires("tool/0.1")})
c.run("create tool")
c.run("create lib")
c.run("upload * -r=default -c")
c.run("remove * -c")
c.run("install --requires=pkg/0.1")
assert "Install finished successfully" in c.out

class MyHttpRequester(TestRequester):

def get(self, _, **kwargs):
from requests.exceptions import ConnectionError
raise ConnectionError("ALL BAD")

c.requester_class = MyHttpRequester
# this will fail
c.run("install --requires=pkg/0.1", assert_error=True)
assert "ERROR: Failed checking for binary 'tool/0.1:da39a3ee5e6b4b0d3255bfef95601890afd80709'" \
in c.out
# Explicitly telling that no-remotes, works
c.run("install --requires=pkg/0.1 -nr")
assert "tool/0.1: WARN" not in c.out
assert "Install finished successfully" in c.out

# graph info also breaks
c.run("graph info --requires=pkg/0.1", assert_error=True)
assert "ERROR: Failed checking for binary 'tool/0.1:da39a3ee5e6b4b0d3255bfef95601890afd80709'" \
in c.out

c.run("graph info --requires=pkg/0.1 -nr")
assert "tool/0.1: WARN" not in c.out
assert re.search(r"Skipped binaries(\s*)tool/0.1", c.out)