diff --git a/conans/client/conan_api.py b/conans/client/conan_api.py index b75a3eb299c..72c21aa74cb 100644 --- a/conans/client/conan_api.py +++ b/conans/client/conan_api.py @@ -20,6 +20,7 @@ from conans.client.cmd.uploader import CmdUpload from conans.client.cmd.user import user_set, users_clean, users_list, token_present from conans.client.conanfile.package import run_package_method +from conans.client.conf.required_version import check_required_conan_version from conans.client.graph.graph import RECIPE_EDITABLE from conans.client.graph.graph_binaries import GraphBinariesAnalyzer from conans.client.graph.graph_manager import GraphManager @@ -233,6 +234,7 @@ def __init__(self, cache_folder=None, output=None, user_io=None, http_requester= # Migration system migrator = ClientMigrator(self.cache_folder, Version(client_version), self.out) migrator.migrate() + check_required_conan_version(self.cache_folder, self.out) if not get_env(CONAN_V2_MODE_ENVVAR, False): # FIXME Remove in Conan 2.0 sys.path.append(os.path.join(self.cache_folder, "python")) diff --git a/conans/client/conf/__init__.py b/conans/client/conf/__init__.py index 97dccd54f0a..9bbd6e40c24 100644 --- a/conans/client/conf/__init__.py +++ b/conans/client/conf/__init__.py @@ -177,6 +177,7 @@ def get_default_settings_yml(force_v1=False): {% endif %} # config_install_interval = 1h + # required_conan_version = >=1.26 [storage] # This is the default path, but you can write your own. It must be an absolute path or a @@ -709,3 +710,10 @@ def config_install_interval(self): except Exception as e: raise ConanException("Incorrect definition of general.config_install_interval: %s" % interval) + + @property + def required_conan_version(self): + try: + return self.get_item("general.required_conan_version") + except ConanException: + return None diff --git a/conans/client/conf/required_version.py b/conans/client/conf/required_version.py new file mode 100644 index 00000000000..bb6b1bf6bfe --- /dev/null +++ b/conans/client/conf/required_version.py @@ -0,0 +1,30 @@ +from conans.client.cache.cache import ClientCache +from semver import satisfies, Range +from conans import __version__ as client_version +from conans.errors import ConanException + + +def check_required_conan_version(cache_folder, out): + """ Check if the required Conan version in config file matches to the current Conan version + + When required_conan_version is not configured, it's skipped + When required_conan_version is configured, Conan's version must matches the required + version + When it doesn't match, an ConanException is raised + + :param cache_folder: Conan cache folder + :param out: Output stream + :return: None + """ + cache = ClientCache(cache_folder, out) + required_version = cache.config.required_conan_version + if required_version: + try: + Range(required_version, False) + except ValueError: + raise ConanException("The required version expression '{}' is not valid." + .format(required_version)) + result = satisfies(client_version, required_version) + if not result: + raise ConanException("The current Conan version ({}) does not match to the required" + " version ({}).".format(client_version, required_version)) diff --git a/conans/test/functional/configuration/required_version_test.py b/conans/test/functional/configuration/required_version_test.py new file mode 100644 index 00000000000..a00189e9a6f --- /dev/null +++ b/conans/test/functional/configuration/required_version_test.py @@ -0,0 +1,48 @@ +import unittest +import mock +from conans.test.utils.tools import TestClient +from conans.errors import ConanException + + +class RequiredVersionTest(unittest.TestCase): + + @mock.patch("conans.client.conf.required_version.client_version", "1.26.0") + def test_wrong_version(self): + required_version = "1.23.0" + client = TestClient() + client.run("config set general.required_conan_version={}".format(required_version)) + with self.assertRaises(ConanException) as error: + client.run("help") + self.assertIn("The current Conan version ({}) " + "does not match to the required version ({})." + .format("1.26.0", required_version), str(error.exception)) + + @mock.patch("conans.client.conf.required_version.client_version", "1.22.0") + def test_exact_version(self): + client = TestClient() + client.run("config set general.required_conan_version=1.22.0") + client.run("help") + self.assertNotIn("ERROR", client.out) + + @mock.patch("conans.client.conf.required_version.client_version", "2.1.0") + def test_lesser_version(self): + client = TestClient() + client.run("config set general.required_conan_version=<3") + client.run("help") + self.assertNotIn("ERROR", client.out) + + @mock.patch("conans.client.conf.required_version.client_version", "1.0.0") + def test_greater_version(self): + client = TestClient() + client.run("config set general.required_conan_version=>0.1.0") + client.run("help") + self.assertNotIn("ERROR", client.out) + + def test_bad_format(self): + client = TestClient() + required_version = "1.0.0.0-foobar" + client.run("config set general.required_conan_version={}".format(required_version)) + with self.assertRaises(ConanException) as error: + client.run("help", assert_error=True) + self.assertIn("The required version expression '{}' is not valid.".format(required_version), + str(error.exception))