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

Add config variable 'general.error_on_override' (CONAN_ERROR_ON_OVERRIDE) #4771

Merged
merged 5 commits into from Mar 25, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions conans/client/conf/__init__.py
Expand Up @@ -94,6 +94,7 @@
# sysrequires_mode = enabled # environment CONAN_SYSREQUIRES_MODE (allowed modes enabled/verify/disabled)
# vs_installation_preference = Enterprise, Professional, Community, BuildTools # environment CONAN_VS_INSTALLATION_PREFERENCE
# verbose_traceback = False # environment CONAN_VERBOSE_TRACEBACK
# error_on_override = False # environment CONAN_ERROR_ON_OVERRIDE
# bash_path = "" # environment CONAN_BASH_PATH (only windows)
# recipe_linter = False # environment CONAN_RECIPE_LINTER
# read_only_cache = True # environment CONAN_READ_ONLY_CACHE
Expand Down Expand Up @@ -184,6 +185,7 @@ def env_vars(self):
"CONAN_USER_HOME_SHORT": self._env_c("general.user_home_short", "CONAN_USER_HOME_SHORT", None),
"CONAN_USE_ALWAYS_SHORT_PATHS": self._env_c("general.use_always_short_paths", "CONAN_USE_ALWAYS_SHORT_PATHS", None),
"CONAN_VERBOSE_TRACEBACK": self._env_c("general.verbose_traceback", "CONAN_VERBOSE_TRACEBACK", None),
"CONAN_ERROR_ON_OVERRIDE": self._env_c("general.error_on_override", "CONAN_ERROR_ON_OVERRIDE", "False"),
# http://www.vtk.org/Wiki/CMake_Cross_Compiling
"CONAN_CMAKE_GENERATOR": self._env_c("general.cmake_generator", "CONAN_CMAKE_GENERATOR", None),
"CONAN_CMAKE_GENERATOR_PLATFORM": self._env_c("general.cmake_generator_platform", "CONAN_CMAKE_GENERATOR_PLATFORM", None),
Expand Down
14 changes: 11 additions & 3 deletions conans/model/requires.py
Expand Up @@ -4,6 +4,7 @@

from conans.errors import ConanException
from conans.model.ref import ConanFileReference
from conans.util.env_reader import get_env


class Requirement(object):
Expand Down Expand Up @@ -112,6 +113,8 @@ def update(self, down_reqs, output, own_ref, down_ref):
assert isinstance(own_ref, ConanFileReference) if own_ref else True
assert isinstance(down_ref, ConanFileReference) if down_ref else True

error_on_override = get_env("CONAN_ERROR_ON_OVERRIDE", False)

new_reqs = down_reqs.copy()
if own_ref:
new_reqs.pop(own_ref.name, None)
Expand All @@ -123,9 +126,14 @@ def update(self, down_reqs, output, own_ref, down_ref):
# update dependency
other_ref = other_req.ref
if other_ref and other_ref != req.ref:
output.info("%s requirement %s overridden by %s to %s "
% (own_ref, req.ref, down_ref or "your conanfile",
other_ref))
msg = "requirement %s overridden by %s to %s " \
% (req.ref, down_ref or "your conanfile", other_ref)

if error_on_override and not other_req.override:
raise ConanException(msg)

msg = "%s %s" % (own_ref, msg)
output.warn(msg)
req.ref = other_ref

new_reqs[name] = req
Expand Down
88 changes: 74 additions & 14 deletions conans/test/functional/graph/conflict_diamond_test.py
@@ -1,38 +1,98 @@
import os
import textwrap
import unittest

from conans.client.tools import environment_append
from conans.paths import CONANFILE
from conans.test.utils.tools import TestClient
from conans.test.utils.tools import TestClient, load
import json


class ConflictDiamondTest(unittest.TestCase):
conanfile = textwrap.dedent("""
from conans import ConanFile

def setUp(self):
self.client = TestClient()
class HelloReuseConan(ConanFile):
name = "%s"
version = "%s"
requires = %s
""")

def _export(self, name, version, deps=None, export=True):
deps = ", ".join(['"%s"' % d for d in deps or []]) or '""'
conanfile = """
from conans import ConanFile, CMake
import os

class HelloReuseConan(ConanFile):
name = "%s"
version = "%s"
requires = %s
""" % (name, version, deps)
conanfile = self.conanfile % (name, version, deps)
files = {CONANFILE: conanfile}
self.client.save(files, clean_first=True)
if export:
self.client.run("export . lasote/stable")

def reuse_test(self):
def setUp(self):
self.client = TestClient()
self._export("Hello0", "0.1")
self._export("Hello0", "0.2")
self._export("Hello1", "0.1", ["Hello0/0.1@lasote/stable"])
self._export("Hello2", "0.1", ["Hello0/0.2@lasote/stable"])

def test_conflict(self):
""" There is a conflict in the graph: branches with requirement in different
version, Conan will raise
"""
self._export("Hello3", "0.1", ["Hello1/0.1@lasote/stable", "Hello2/0.1@lasote/stable"],
export=False)

self.client.run("install . --build missing", assert_error=True)
self.assertIn("Conflict in Hello2/0.1@lasote/stable", self.client.user_io.out)
self.assertNotIn("Generated conaninfo.txt", self.client.user_io.out)

def test_override_silent(self):
""" There is a conflict in the graph, but the consumer project depends on the conflicting
library, so all the graph will use the version from the consumer project
"""
self._export("Hello3", "0.1",
["Hello1/0.1@lasote/stable", "Hello2/0.1@lasote/stable",
"Hello0/0.1@lasote/stable"], export=False)
self.client.run("install . --build missing", assert_error=False)
self.assertIn("Hello2/0.1@lasote/stable requirement Hello0/0.2@lasote/stable overridden"
" by Hello3/0.1@None/None to Hello0/0.1@lasote/stable",
self.client.user_io.out)

def test_error_on_override(self):
""" Given a conflict in dependencies that is overridden by the consumer project, instead
of silently output a message, the user can force an error using
the env variable 'CONAN_ERROR_ON_OVERRIDE'
"""
with environment_append({'CONAN_ERROR_ON_OVERRIDE': "True"}):
self._export("Hello3", "0.1",
["Hello1/0.1@lasote/stable", "Hello2/0.1@lasote/stable",
"Hello0/0.1@lasote/stable"], export=False)
self.client.run("install . --build missing", assert_error=True)
self.assertIn("ERROR: Hello2/0.1@lasote/stable: requirement Hello0/0.2@lasote/stable"
" overridden by Hello3/0.1@None/None to Hello0/0.1@lasote/stable",
self.client.user_io.out)

def test_override_explicit(self):
""" Given a conflict in dependencies that is overridden by the consumer project (with
the explicit keyword 'override'), it won't raise because it is explicit, even if the
user has set env variable 'CONAN_ERROR_ON_OVERRIDE' to True
"""
with environment_append({'CONAN_ERROR_ON_OVERRIDE': "True"}):
conanfile = self.conanfile % ("Hello3", "0.1",
'(("Hello1/0.1@lasote/stable"), '
'("Hello2/0.1@lasote/stable"), '
'("Hello0/0.1@lasote/stable", "override"),)')
self.client.save({CONANFILE: conanfile})
self.client.run("install . --build missing")
self.assertIn("Hello2/0.1@lasote/stable requirement Hello0/0.2@lasote/stable overridden"
" by Hello3/0.1@None/None to Hello0/0.1@lasote/stable",
self.client.user_io.out)

# ...but there is no way to tell Conan that 'Hello3' wants to depend also on 'Hello0'.
json_file = os.path.join(self.client.current_folder, 'tmp.json')
self.client.run('info . --only=requires --json="{}"'.format(json_file))
data = json.loads(load(json_file))
hello0 = data[0]
self.assertEqual(hello0["reference"], "Hello0/0.1@lasote/stable")
self.assertListEqual(sorted(hello0["required_by"]),
sorted(["Hello2/0.1@lasote/stable", "Hello1/0.1@lasote/stable"]))