From 7dd72578443f2c18f5ff3c0a0e7cebd828bf30dd Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Wed, 10 Jun 2020 18:42:39 -0300 Subject: [PATCH 1/4] #7136 Required Conan version The general configuration `required_conan_version` validates the current Conan client version to the required version. If the current version is out of the range, a warning message is displayed before executing any command. Signed-off-by: Uilian Ries --- conans/client/command.py | 2 + conans/client/conf/__init__.py | 9 +++++ conans/client/conf/required_version.py | 27 +++++++++++++ .../configuration/required_version_test.py | 40 +++++++++++++++++++ 4 files changed, 78 insertions(+) create mode 100644 conans/client/conf/required_version.py create mode 100644 conans/test/functional/configuration/required_version_test.py diff --git a/conans/client/command.py b/conans/client/command.py index c055e641936..057f90b9c0c 100644 --- a/conans/client/command.py +++ b/conans/client/command.py @@ -15,6 +15,7 @@ UPLOAD_POLICY_NO_OVERWRITE, UPLOAD_POLICY_NO_OVERWRITE_RECIPE, UPLOAD_POLICY_SKIP from conans.client.conan_api import Conan, default_manifest_folder, _make_abs_path, ProfileData from conans.client.conf.config_installer import is_config_install_scheduled +from conans.client.conf.required_version import check_required_conan_version from conans.client.conan_command_output import CommandOutputer from conans.client.output import Color from conans.client.printer import Printer @@ -2031,6 +2032,7 @@ def run(self, *args): if is_config_install_scheduled(self._conan) and \ (command != "config" or (command == "config" and args[0] != "install")): self._conan.config_install(None, None) + check_required_conan_version(self._conan, self._out) method(args[0][1:]) except KeyboardInterrupt as exc: diff --git a/conans/client/conf/__init__.py b/conans/client/conf/__init__.py index 97dccd54f0a..83cc1bf01ec 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 @@ -261,6 +262,7 @@ class ConanClientConfigParser(ConfigParser, object): ("CONAN_MSBUILD_VERBOSITY", "msbuild_verbosity", None), ("CONAN_CACERT_PATH", "cacert_path", None), ("CONAN_DEFAULT_PACKAGE_ID_MODE", "default_package_id_mode", None), + ("CONAN_REQUIRED_CONAN_VERSION", "required_conan_version", None), # ("CONAN_DEFAULT_PROFILE_PATH", "default_profile", DEFAULT_PROFILE_NAME), ], "hooks": [ @@ -709,3 +711,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..adf9c19655d --- /dev/null +++ b/conans/client/conf/required_version.py @@ -0,0 +1,27 @@ +from conans.client.cache.cache import ClientCache +from conans.client.graph.range_resolver import satisfying +from conans import __version__ as client_version + + +def check_required_conan_version(api, 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, a Warning is raised + + :param api: Conan API instance + :param out: Output stream + :return: None + """ + cache = ClientCache(api.cache_folder, api.out) + required_version = cache.config.required_conan_version + if required_version: + output = "" + result = satisfying([client_version], required_version, output) + if not result: + out.warn("The current Conan version ({}) does not match to the required version ({})." + .format(client_version, required_version)) + elif result != client_version: + out.warn(result) 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..f22115f789d --- /dev/null +++ b/conans/test/functional/configuration/required_version_test.py @@ -0,0 +1,40 @@ +import unittest +from conans.test.utils.tools import TestClient +from conans import __version__ as client_version + + +class RequiredVersionTest(unittest.TestCase): + + def setUp(self): + self.client = TestClient() + + def test_wrong_version(self): + # include_prerelease is required due the suffix -dev + required_version = ">=10.0.0,include_prerelease=True" + self.client.run("config set general.required_conan_version={}".format(required_version)) + self.client.run("help") + self.assertIn("WARN: The current Conan version ({}) " + "does not match to the required version ({})." + .format(client_version, required_version), self.client.out) + + def test_exact_version(self): + self.client.run("config set general.required_conan_version={}".format(client_version)) + self.client.run("help") + self.assertNotIn("WARN", self.client.out) + + def test_lesser_version(self): + self.client.run("config set general.required_conan_version=<3,include_prerelease=True") + self.client.run("help") + self.assertNotIn("WARN", self.client.out) + + def test_greater_version(self): + self.client.run("config set general.required_conan_version=>0.1.0,include_prerelease=True") + self.client.run("help") + self.assertNotIn("WARN", self.client.out) + + def test_bad_format(self): + required_version = "1.0.0.0-foobar" + self.client.run("config set general.required_conan_version={}".format(required_version)) + self.client.run("help", assert_error=True) + self.assertIn("ERROR: version range expression '1.0.0.0-foobar' is not valid", + self.client.out) From 94dbc4d0f67009c760a4e99cb1f10bc501aa0695 Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Thu, 11 Jun 2020 18:35:40 -0300 Subject: [PATCH 2/4] #7136 Improve functional tests Signed-off-by: Uilian Ries --- conans/client/command.py | 2 - conans/client/conan_api.py | 2 + conans/client/conf/required_version.py | 13 ++--- .../configuration/required_version_test.py | 54 +++++++++++-------- 4 files changed, 40 insertions(+), 31 deletions(-) diff --git a/conans/client/command.py b/conans/client/command.py index 057f90b9c0c..c055e641936 100644 --- a/conans/client/command.py +++ b/conans/client/command.py @@ -15,7 +15,6 @@ UPLOAD_POLICY_NO_OVERWRITE, UPLOAD_POLICY_NO_OVERWRITE_RECIPE, UPLOAD_POLICY_SKIP from conans.client.conan_api import Conan, default_manifest_folder, _make_abs_path, ProfileData from conans.client.conf.config_installer import is_config_install_scheduled -from conans.client.conf.required_version import check_required_conan_version from conans.client.conan_command_output import CommandOutputer from conans.client.output import Color from conans.client.printer import Printer @@ -2032,7 +2031,6 @@ def run(self, *args): if is_config_install_scheduled(self._conan) and \ (command != "config" or (command == "config" and args[0] != "install")): self._conan.config_install(None, None) - check_required_conan_version(self._conan, self._out) method(args[0][1:]) except KeyboardInterrupt as exc: 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/required_version.py b/conans/client/conf/required_version.py index adf9c19655d..53062a7c787 100644 --- a/conans/client/conf/required_version.py +++ b/conans/client/conf/required_version.py @@ -1,27 +1,28 @@ from conans.client.cache.cache import ClientCache from conans.client.graph.range_resolver import satisfying from conans import __version__ as client_version +from conans.errors import ConanException -def check_required_conan_version(api, out): +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, a Warning is raised + When it doesn't match, an ConanException is raised - :param api: Conan API instance + :param cache_folder: Conan cache folder :param out: Output stream :return: None """ - cache = ClientCache(api.cache_folder, api.out) + cache = ClientCache(cache_folder, out) required_version = cache.config.required_conan_version if required_version: output = "" result = satisfying([client_version], required_version, output) if not result: - out.warn("The current Conan version ({}) does not match to the required version ({})." + raise ConanException("The current Conan version ({}) does not match to the required version ({})." .format(client_version, required_version)) elif result != client_version: - out.warn(result) + raise ConanException(result) diff --git a/conans/test/functional/configuration/required_version_test.py b/conans/test/functional/configuration/required_version_test.py index f22115f789d..62d6565f848 100644 --- a/conans/test/functional/configuration/required_version_test.py +++ b/conans/test/functional/configuration/required_version_test.py @@ -1,40 +1,48 @@ import unittest +import mock from conans.test.utils.tools import TestClient -from conans import __version__ as client_version +from conans.errors import ConanException class RequiredVersionTest(unittest.TestCase): - def setUp(self): - self.client = TestClient() - + @mock.patch("conans.client.conf.required_version.client_version", "1.26.0") def test_wrong_version(self): - # include_prerelease is required due the suffix -dev - required_version = ">=10.0.0,include_prerelease=True" - self.client.run("config set general.required_conan_version={}".format(required_version)) - self.client.run("help") - self.assertIn("WARN: The current Conan version ({}) " + 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(client_version, required_version), self.client.out) + .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): - self.client.run("config set general.required_conan_version={}".format(client_version)) - self.client.run("help") - self.assertNotIn("WARN", self.client.out) + client = TestClient() + client.run("config set general.required_conan_version={}".format("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): - self.client.run("config set general.required_conan_version=<3,include_prerelease=True") - self.client.run("help") - self.assertNotIn("WARN", self.client.out) + 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): - self.client.run("config set general.required_conan_version=>0.1.0,include_prerelease=True") - self.client.run("help") - self.assertNotIn("WARN", self.client.out) + 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" - self.client.run("config set general.required_conan_version={}".format(required_version)) - self.client.run("help", assert_error=True) - self.assertIn("ERROR: version range expression '1.0.0.0-foobar' is not valid", - self.client.out) + 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("version range expression '1.0.0.0-foobar' is not valid", + str(error.exception)) From 5431589a45312d30720395dc9d61285affaf1060 Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Thu, 11 Jun 2020 18:47:46 -0300 Subject: [PATCH 3/4] #7136 Remove env vars Signed-off-by: Uilian Ries --- conans/client/conf/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/conans/client/conf/__init__.py b/conans/client/conf/__init__.py index 83cc1bf01ec..9bbd6e40c24 100644 --- a/conans/client/conf/__init__.py +++ b/conans/client/conf/__init__.py @@ -262,7 +262,6 @@ class ConanClientConfigParser(ConfigParser, object): ("CONAN_MSBUILD_VERBOSITY", "msbuild_verbosity", None), ("CONAN_CACERT_PATH", "cacert_path", None), ("CONAN_DEFAULT_PACKAGE_ID_MODE", "default_package_id_mode", None), - ("CONAN_REQUIRED_CONAN_VERSION", "required_conan_version", None), # ("CONAN_DEFAULT_PROFILE_PATH", "default_profile", DEFAULT_PROFILE_NAME), ], "hooks": [ From b5f3ff0f111e5bb646eed53b0da97a405f214705 Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Fri, 12 Jun 2020 09:49:11 -0300 Subject: [PATCH 4/4] #7136 Use semver methods Signed-off-by: Uilian Ries --- conans/client/conf/required_version.py | 16 +++++++++------- .../configuration/required_version_test.py | 6 +++--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/conans/client/conf/required_version.py b/conans/client/conf/required_version.py index 53062a7c787..bb6b1bf6bfe 100644 --- a/conans/client/conf/required_version.py +++ b/conans/client/conf/required_version.py @@ -1,5 +1,5 @@ from conans.client.cache.cache import ClientCache -from conans.client.graph.range_resolver import satisfying +from semver import satisfies, Range from conans import __version__ as client_version from conans.errors import ConanException @@ -19,10 +19,12 @@ def check_required_conan_version(cache_folder, out): cache = ClientCache(cache_folder, out) required_version = cache.config.required_conan_version if required_version: - output = "" - result = satisfying([client_version], required_version, output) + 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)) - elif result != client_version: - raise ConanException(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 index 62d6565f848..a00189e9a6f 100644 --- a/conans/test/functional/configuration/required_version_test.py +++ b/conans/test/functional/configuration/required_version_test.py @@ -15,12 +15,12 @@ def test_wrong_version(self): 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)) + .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={}".format("1.22.0")) + client.run("config set general.required_conan_version=1.22.0") client.run("help") self.assertNotIn("ERROR", client.out) @@ -44,5 +44,5 @@ def test_bad_format(self): 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("version range expression '1.0.0.0-foobar' is not valid", + self.assertIn("The required version expression '{}' is not valid.".format(required_version), str(error.exception))