Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* #5958 New tool: cppstd minimum version required Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Fix bad indentation Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Add gnu cppstd to the tests Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Add gnu extensions Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Mock distro for Linux Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Fix tests on MacOS Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Validate cppstd by str Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Add valid_minimum_cppstd Signed-off-by: Uilian Ries <uilianries@gmail.com> * Update conans/client/tools/settings.py Co-Authored-By: Javier G. Sogo <jgsogo@gmail.com> * Update conans/client/tools/settings.py Co-Authored-By: Javier G. Sogo <jgsogo@gmail.com> * Update conans/tools.py Co-Authored-By: Javier G. Sogo <jgsogo@gmail.com> * #5958 Apply @jgsogo suggestions Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Fix broken tests for cppstd Signed-off-by: Uilian Ries <uilianries@gmail.com> * Update conans/tools.py Co-Authored-By: Javier G. Sogo <jgsogo@gmail.com> * #5958 Remove duplicated import Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Fix C++98 comparison Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Add unit tests for check_min_cppstd Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Fix Conanfile test Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Add unit tests for valid_min_cppstd Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Remove duplicated functional tests Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Test improvements from code review Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 remove unused import Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 cppstd must be a number Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Dont use assert for production code - Raise ConanExcetion when some input is incorrect Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Validate target OS using ConanFile settings - The target OS should be considered as important for cpp version Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Use default compiler cppstd when not included in settings Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Fix functional test Signed-off-by: Uilian Ries <uilianries@gmail.com> * Review * Fix bug * #5958 Detect temporary C++ standard Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Remove temporary C++ standard Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Do not validate OS for GNU extensions Signed-off-by: Uilian Ries <uilianries@gmail.com> * #5958 Update GNU extensions description Signed-off-by: Uilian Ries <uilianries@gmail.com>
- Loading branch information
1 parent
87a5c85
commit 410be07
Showing
5 changed files
with
283 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
from conans.client.build.cppstd_flags import cppstd_default | ||
from conans.errors import ConanInvalidConfiguration, ConanException | ||
|
||
|
||
def check_min_cppstd(conanfile, cppstd, gnu_extensions=False): | ||
""" Check if current cppstd fits the minimal version required. | ||
In case the current cppstd doesn't fit the minimal version required | ||
by cppstd, a ConanInvalidConfiguration exception will be raised. | ||
1. If settings.compiler.cppstd, the tool will use settings.compiler.cppstd to compare | ||
2. It not settings.compiler.cppstd, the tool will use compiler to compare (reading the | ||
default from cppstd_default) | ||
3. If not settings.compiler is present (not declared in settings) will raise because it | ||
cannot compare. | ||
:param conanfile: ConanFile instance with cppstd to be compared | ||
:param cppstd: Minimal cppstd version required | ||
:param gnu_extensions: GNU extension is required (e.g gnu17) | ||
""" | ||
if not str(cppstd).isdigit(): | ||
raise ConanException("cppstd parameter must be a number") | ||
|
||
def less_than(lhs, rhs): | ||
def extract_cpp_version(_cppstd): | ||
return str(_cppstd).replace("gnu", "") | ||
|
||
def add_millennium(_cppstd): | ||
return "19%s" % _cppstd if _cppstd == "98" else "20%s" % _cppstd | ||
|
||
lhs = add_millennium(extract_cpp_version(lhs)) | ||
rhs = add_millennium(extract_cpp_version(rhs)) | ||
return lhs < rhs | ||
|
||
def check_required_gnu_extension(_cppstd): | ||
if gnu_extensions and "gnu" not in _cppstd: | ||
raise ConanInvalidConfiguration("The cppstd GNU extension is required") | ||
|
||
def deduced_cppstd(): | ||
cppstd = conanfile.settings.get_safe("compiler.cppstd") | ||
if cppstd: | ||
return cppstd | ||
|
||
compiler = conanfile.settings.get_safe("compiler") | ||
compiler_version = conanfile.settings.get_safe("compiler.version") | ||
if not compiler or not compiler_version: | ||
raise ConanException("Could not obtain cppstd because there is no declared " | ||
"compiler in the 'settings' field of the recipe.") | ||
return cppstd_default(compiler, compiler_version) | ||
|
||
current_cppstd = deduced_cppstd() | ||
check_required_gnu_extension(current_cppstd) | ||
|
||
if less_than(current_cppstd, cppstd): | ||
raise ConanInvalidConfiguration("Current cppstd ({}) is lower than the required C++ " | ||
"standard ({}).".format(current_cppstd, cppstd)) | ||
|
||
|
||
def valid_min_cppstd(conanfile, cppstd, gnu_extensions=False): | ||
""" Validate if current cppstd fits the minimal version required. | ||
:param conanfile: ConanFile instance with cppstd to be compared | ||
:param cppstd: Minimal cppstd version required | ||
:param gnu_extensions: GNU extension is required (e.g gnu17). This option ONLY works on Linux. | ||
:return: True, if current cppstd matches the required cppstd version. Otherwise, False. | ||
""" | ||
try: | ||
check_min_cppstd(conanfile, cppstd, gnu_extensions) | ||
except ConanInvalidConfiguration: | ||
return False | ||
return True |
51 changes: 51 additions & 0 deletions
51
conans/test/functional/tools/cppstd_minimum_version_test.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import unittest | ||
from parameterized import parameterized | ||
from textwrap import dedent | ||
|
||
from conans.test.utils.tools import TestClient | ||
|
||
|
||
class CppStdMinimumVersionTests(unittest.TestCase): | ||
|
||
CONANFILE = dedent(""" | ||
import os | ||
from conans import ConanFile | ||
from conans.tools import check_min_cppstd, valid_min_cppstd | ||
class Fake(ConanFile): | ||
name = "fake" | ||
version = "0.1" | ||
settings = "compiler" | ||
def configure(self): | ||
check_min_cppstd(self, "17", False) | ||
self.output.info("valid standard") | ||
assert valid_min_cppstd(self, "17", False) | ||
""") | ||
|
||
PROFILE = dedent(""" | ||
[settings] | ||
compiler=gcc | ||
compiler.version=9 | ||
compiler.libcxx=libstdc++ | ||
{} | ||
""") | ||
|
||
def setUp(self): | ||
self.client = TestClient() | ||
self.client.save({"conanfile.py": CppStdMinimumVersionTests.CONANFILE}) | ||
|
||
@parameterized.expand(["17", "gnu17"]) | ||
def test_cppstd_from_settings(self, cppstd): | ||
profile = CppStdMinimumVersionTests.PROFILE.replace("{}", "compiler.cppstd=%s" % cppstd) | ||
self.client.save({"myprofile": profile}) | ||
self.client.run("create . user/channel -pr myprofile") | ||
self.assertIn("valid standard", self.client.out) | ||
|
||
@parameterized.expand(["11", "gnu11"]) | ||
def test_invalid_cppstd_from_settings(self, cppstd): | ||
profile = CppStdMinimumVersionTests.PROFILE.replace("{}", "compiler.cppstd=%s" % cppstd) | ||
self.client.save({"myprofile": profile}) | ||
self.client.run("create . user/channel -pr myprofile", assert_error=True) | ||
self.assertIn("Invalid configuration: Current cppstd (%s) is lower than the required C++ " | ||
"standard (17)." % cppstd, self.client.out) |
158 changes: 158 additions & 0 deletions
158
conans/test/unittests/client/tools/cppstd_required_test.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
import unittest | ||
from mock import mock | ||
from parameterized import parameterized | ||
|
||
from conans.test.utils.conanfile import MockConanfile, MockSettings | ||
from conans.client.tools import OSInfo | ||
from conans.errors import ConanInvalidConfiguration, ConanException | ||
|
||
from conans.tools import check_min_cppstd, valid_min_cppstd | ||
|
||
|
||
class UserInputTests(unittest.TestCase): | ||
|
||
def test_check_cppstd_type(self): | ||
""" cppstd must be a number | ||
""" | ||
conanfile = MockConanfile(MockSettings({})) | ||
with self.assertRaises(ConanException) as raises: | ||
check_min_cppstd(conanfile, "gnu17", False) | ||
self.assertEqual("cppstd parameter must be a number", str(raises.exception)) | ||
|
||
|
||
class CheckMinCppStdTests(unittest.TestCase): | ||
|
||
def _create_conanfile(self, compiler, version, os, cppstd, libcxx=None): | ||
settings = MockSettings({"arch": "x86_64", | ||
"build_type": "Debug", | ||
"os": os, | ||
"compiler": compiler, | ||
"compiler.version": version, | ||
"compiler.cppstd": cppstd}) | ||
if libcxx: | ||
settings.values["compiler.libcxx"] = libcxx | ||
conanfile = MockConanfile(settings) | ||
return conanfile | ||
|
||
@parameterized.expand(["98", "11", "14", "17"]) | ||
def test_check_min_cppstd_from_settings(self, cppstd): | ||
""" check_min_cppstd must accept cppstd less/equal than cppstd in settings | ||
""" | ||
conanfile = self._create_conanfile("gcc", "9", "Linux", "17", "libstdc++") | ||
check_min_cppstd(conanfile, cppstd, False) | ||
|
||
@parameterized.expand(["98", "11", "14"]) | ||
def test_check_min_cppstd_from_outdated_settings(self, cppstd): | ||
""" check_min_cppstd must raise when cppstd is greater when supported on settings | ||
""" | ||
conanfile = self._create_conanfile("gcc", "9", "Linux", cppstd, "libstdc++") | ||
with self.assertRaises(ConanInvalidConfiguration) as raises: | ||
check_min_cppstd(conanfile, "17", False) | ||
self.assertEqual("Current cppstd ({}) is lower than the required C++ standard " | ||
"(17).".format(cppstd), str(raises.exception)) | ||
|
||
@parameterized.expand(["98", "11", "14", "17"]) | ||
def test_check_min_cppstd_from_settings_with_extension(self, cppstd): | ||
""" current cppstd in settings must has GNU extension when extensions is enabled | ||
""" | ||
conanfile = self._create_conanfile("gcc", "9", "Linux", "gnu17", "libstdc++") | ||
check_min_cppstd(conanfile, cppstd, True) | ||
|
||
conanfile.settings.values["compiler.cppstd"] = "17" | ||
with self.assertRaises(ConanException) as raises: | ||
check_min_cppstd(conanfile, cppstd, True) | ||
self.assertEqual("The cppstd GNU extension is required", str(raises.exception)) | ||
|
||
def test_check_min_cppstd_unsupported_standard(self): | ||
""" check_min_cppstd must raise when the compiler does not support a standard | ||
""" | ||
conanfile = self._create_conanfile("gcc", "9", "Linux", None, "libstdc++") | ||
with self.assertRaises(ConanInvalidConfiguration) as raises: | ||
check_min_cppstd(conanfile, "42", False) | ||
self.assertEqual("Current cppstd (gnu14) is lower than the required C++ standard (42).", | ||
str(raises.exception)) | ||
|
||
def test_check_min_cppstd_gnu_compiler_extension(self): | ||
""" Current compiler must support GNU extension on Linux when extensions is required | ||
""" | ||
conanfile = self._create_conanfile("gcc", "9", "Linux", None, "libstdc++") | ||
with mock.patch("platform.system", mock.MagicMock(return_value="Linux")): | ||
with mock.patch.object(OSInfo, '_get_linux_distro_info'): | ||
with mock.patch("conans.client.tools.settings.cppstd_default", return_value="17"): | ||
with self.assertRaises(ConanException) as raises: | ||
check_min_cppstd(conanfile, "17", True) | ||
self.assertEqual("The cppstd GNU extension is required", str(raises.exception)) | ||
|
||
def test_no_compiler_declared(self): | ||
conanfile = self._create_conanfile(None, None, "Linux", None, "libstdc++") | ||
with self.assertRaises(ConanException) as raises: | ||
check_min_cppstd(conanfile, "14", False) | ||
self.assertEqual("Could not obtain cppstd because there is no declared compiler in the " | ||
"'settings' field of the recipe.", str(raises.exception)) | ||
|
||
|
||
class ValidMinCppstdTests(unittest.TestCase): | ||
|
||
def _create_conanfile(self, compiler, version, os, cppstd, libcxx=None): | ||
settings = MockSettings({"arch": "x86_64", | ||
"build_type": "Debug", | ||
"os": os, | ||
"compiler": compiler, | ||
"compiler.version": version, | ||
"compiler.cppstd": cppstd}) | ||
if libcxx: | ||
settings.values["compiler.libcxx"] = libcxx | ||
conanfile = MockConanfile(settings) | ||
return conanfile | ||
|
||
@parameterized.expand(["98", "11", "14", "17"]) | ||
def test_valid_min_cppstd_from_settings(self, cppstd): | ||
""" valid_min_cppstd must accept cppstd less/equal than cppstd in settings | ||
""" | ||
conanfile = self._create_conanfile("gcc", "9", "Linux", "17", "libstdc++") | ||
self.assertTrue(valid_min_cppstd(conanfile, cppstd, False)) | ||
|
||
@parameterized.expand(["98", "11", "14"]) | ||
def test_valid_min_cppstd_from_outdated_settings(self, cppstd): | ||
""" valid_min_cppstd returns False when cppstd is greater when supported on settings | ||
""" | ||
conanfile = self._create_conanfile("gcc", "9", "Linux", cppstd, "libstdc++") | ||
self.assertFalse(valid_min_cppstd(conanfile, "17", False)) | ||
|
||
@parameterized.expand(["98", "11", "14", "17"]) | ||
def test_valid_min_cppstd_from_settings_with_extension(self, cppstd): | ||
""" valid_min_cppstd must returns True when current cppstd in settings has GNU extension and | ||
extensions is enabled | ||
""" | ||
conanfile = self._create_conanfile("gcc", "9", "Linux", "gnu17", "libstdc++") | ||
self.assertTrue(valid_min_cppstd(conanfile, cppstd, True)) | ||
|
||
conanfile.settings.values["compiler.cppstd"] = "17" | ||
self.assertFalse(valid_min_cppstd(conanfile, cppstd, True)) | ||
|
||
def test_valid_min_cppstd_unsupported_standard(self): | ||
""" valid_min_cppstd must returns False when the compiler does not support a standard | ||
""" | ||
conanfile = self._create_conanfile("gcc", "9", "Linux", None, "libstdc++") | ||
self.assertFalse(valid_min_cppstd(conanfile, "42", False)) | ||
|
||
def test_valid_min_cppstd_gnu_compiler_extension(self): | ||
""" valid_min_cppstd must returns False when current compiler does not support GNU extension | ||
on Linux and extensions is required | ||
""" | ||
conanfile = self._create_conanfile("gcc", "9", "Linux", None, "libstdc++") | ||
with mock.patch("platform.system", mock.MagicMock(return_value="Linux")): | ||
with mock.patch.object(OSInfo, '_get_linux_distro_info'): | ||
with mock.patch("conans.client.tools.settings.cppstd_default", return_value="gnu1z"): | ||
self.assertFalse(valid_min_cppstd(conanfile, "20", True)) | ||
|
||
@parameterized.expand(["98", "11", "14", "17"]) | ||
def test_min_cppstd_mingw_windows(self, cppstd): | ||
""" GNU extensions HAS effect on Windows when running a cross-building for Linux | ||
""" | ||
with mock.patch("platform.system", mock.MagicMock(return_value="Windows")): | ||
conanfile = self._create_conanfile("gcc", "9", "Linux", "gnu17", "libstdc++") | ||
self.assertTrue(valid_min_cppstd(conanfile, cppstd, True)) | ||
|
||
conanfile.settings.values["compiler.cppstd"] = "17" | ||
self.assertFalse(valid_min_cppstd(conanfile, cppstd, True)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters