Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
cppstd tools ported to develop (#11446)
* Ported cppstd from 2.0 * Added test and build import * cppstd
- Loading branch information
Showing
3 changed files
with
383 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
from conan.tools.build.cpu import build_jobs | ||
from conan.tools.build.cross_building import cross_building, can_run | ||
from conan.tools.build.cppstd import check_min_cppstd, valid_min_cppstd, default_cppstd, \ | ||
supported_cppstd |
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,189 @@ | ||
from conans.errors import ConanInvalidConfiguration, ConanException | ||
from conans.model.version import Version | ||
|
||
|
||
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. | ||
4. If can not detect the default cppstd for settings.compiler, a exception will be raised. | ||
:param conanfile: The current recipe object. Always use ``self``. | ||
: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 | ||
|
||
current_cppstd = conanfile.settings.get_safe("compiler.cppstd") | ||
if current_cppstd is None: | ||
raise ConanInvalidConfiguration("The compiler.cppstd is not defined for this configuration") | ||
|
||
if gnu_extensions and "gnu" not in current_cppstd: | ||
raise ConanInvalidConfiguration("The cppstd GNU extension is required") | ||
|
||
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: The current recipe object. Always use ``self``. | ||
: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 | ||
|
||
|
||
def default_cppstd(conanfile, compiler=None, compiler_version=None): | ||
""" | ||
Get the default ``compiler.cppstd`` for the "conanfile.settings.compiler" and "conanfile | ||
settings.compiler_version" or for the parameters "compiler" and "compiler_version" if specified. | ||
:param conanfile: The current recipe object. Always use ``self``. | ||
:param compiler: Name of the compiler e.g. gcc | ||
:param compiler_version: Version of the compiler e.g. 12 | ||
:return: The default ``compiler.cppstd`` for the specified compiler | ||
""" | ||
from conans.client.conf.detect import _cppstd_default | ||
compiler = compiler or conanfile.settings.get_safe("compiler") | ||
compiler_version = compiler_version or conanfile.settings.get_safe("compiler.version") | ||
if not compiler or not compiler_version: | ||
raise ConanException("Called default_cppstd with no compiler or no compiler.version") | ||
return _cppstd_default(compiler, Version(compiler_version)) | ||
|
||
|
||
def supported_cppstd(conanfile, compiler=None, compiler_version=None): | ||
""" | ||
Get the a list of supported ``compiler.cppstd`` for the "conanfile.settings.compiler" and | ||
"conanfile.settings.compiler_version" or for the parameters "compiler" and "compiler_version" | ||
if specified. | ||
:param conanfile: The current recipe object. Always use ``self``. | ||
:param compiler: Name of the compiler e.g: gcc | ||
:param compiler_version: Version of the compiler e.g: 12 | ||
:return: a list of supported ``cppstd`` values. | ||
""" | ||
compiler = compiler or conanfile.settings.get_safe("compiler") | ||
compiler_version = compiler_version or conanfile.settings.get_safe("compiler.version") | ||
if not compiler or not compiler_version: | ||
raise ConanException("Called supported_cppstd with no compiler or no compiler.version") | ||
|
||
func = {"apple-clang": _apple_clang_supported_cppstd, | ||
"gcc": _gcc_supported_cppstd, | ||
"msvc": _msvc_supported_cppstd, | ||
"clang": _clang_supported_cppstd, | ||
"mcst-lcc": _mcst_lcc_supported_cppstd}.get(compiler) | ||
if func: | ||
return func(Version(compiler_version)) | ||
return None | ||
|
||
|
||
def _apple_clang_supported_cppstd(version): | ||
""" | ||
["98", "gnu98", "11", "gnu11", "14", "gnu14", "17", "gnu17", "20", "gnu20"] | ||
""" | ||
if version < "4.0": | ||
return [] | ||
if version < "5.1": | ||
return ["98", "gnu98", "11", "gnu11"] | ||
if version < "6.1": | ||
return ["98", "gnu98", "11", "gnu11", "14", "gnu14"] | ||
if version < "10.0": | ||
return ["98", "gnu98", "11", "gnu11", "14", "gnu14", "17", "gnu17"] | ||
|
||
return ["98", "gnu98", "11", "gnu11", "14", "gnu14", "17", "gnu17", "20", "gnu20"] | ||
|
||
|
||
def _gcc_supported_cppstd(version): | ||
""" | ||
["98", "gnu98", "11", "gnu11", "14", "gnu14", "17", "gnu17", "20", "gnu20", "23", "gnu23"] | ||
""" | ||
if version < "3.4": | ||
return [] | ||
if version < "4.3": | ||
return ["98", "gnu98"] | ||
if version < "4.8": | ||
return ["98", "gnu98", "11", "gnu11"] | ||
if version < "5": | ||
return ["98", "gnu98", "11", "gnu11", "14", "gnu14"] | ||
if version < "8": | ||
return ["98", "gnu98", "11", "gnu11", "14", "gnu14", "17", "gnu17"] | ||
if version < "11": | ||
return ["98", "gnu98", "11", "gnu11", "14", "gnu14", "17", "gnu17", "20", "gnu20"] | ||
|
||
return ["98", "gnu98", "11", "gnu11", "14", "gnu14", "17", "gnu17", "20", "gnu20", "23", "gnu23"] | ||
|
||
|
||
def _msvc_supported_cppstd(version): | ||
""" | ||
[14, 17, 20, 23] | ||
""" | ||
if version < "190": | ||
return [] | ||
if version < "191": | ||
return ["14", "17"] | ||
if version < "193": | ||
return ["14", "17", "20"] | ||
|
||
return ["14", "17", "20", "23"] | ||
|
||
|
||
def _clang_supported_cppstd(version): | ||
""" | ||
["98", "gnu98", "11", "gnu11", "14", "gnu14", "17", "gnu17", "20", "gnu20", "23", "gnu23"] | ||
""" | ||
if version < "2.1": | ||
return [] | ||
if version < "3.4": | ||
return ["98", "gnu98", "11", "gnu11"] | ||
if version < "3.5": | ||
return ["98", "gnu98", "11", "gnu11", "14", "gnu14"] | ||
if version < "6": | ||
return ["98", "gnu98", "11", "gnu11", "14", "gnu14", "17", "gnu17"] | ||
if version < "12": | ||
return ["98", "gnu98", "11", "gnu11", "14", "gnu14", "17", "gnu17", "20", "gnu20"] | ||
|
||
return ["98", "gnu98", "11", "gnu11", "14", "gnu14", "17", "gnu17", "20", "gnu20", "23", "gnu23"] | ||
|
||
|
||
def _mcst_lcc_supported_cppstd(version): | ||
""" | ||
["98", "gnu98", "11", "gnu11", "14", "gnu14", "17", "gnu17", "20", "gnu20", "23", "gnu23"] | ||
""" | ||
|
||
if version < "1.21": | ||
return ["98", "gnu98"] | ||
if version < "1.24": | ||
return ["98", "gnu98", "11", "gnu11", "14", "gnu14"] | ||
if version < "1.25": | ||
return ["98", "gnu98", "11", "gnu11", "14", "gnu14", "17", "gnu17"] | ||
|
||
# FIXME: When cppstd 23 was introduced???? | ||
|
||
return ["98", "gnu98", "11", "gnu11", "14", "gnu14", "17", "gnu17", "20", "gnu20"] |
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,192 @@ | ||
import pytest | ||
|
||
from conan.tools.build import supported_cppstd, check_min_cppstd, valid_min_cppstd | ||
from conans.errors import ConanException, ConanInvalidConfiguration | ||
from conans.test.utils.mocks import MockSettings, MockConanfile | ||
|
||
|
||
@pytest.mark.parametrize("compiler,compiler_version,values", [ | ||
("clang", "2.0", []), | ||
("clang", "2.1", ['98', 'gnu98', '11', 'gnu11']), | ||
("clang", "2.2", ['98', 'gnu98', '11', 'gnu11']), | ||
("clang", "3.1", ['98', 'gnu98', '11', 'gnu11']), | ||
("clang", "3.4", ['98', 'gnu98', '11', 'gnu11', "14", "gnu14"]), | ||
("clang", "3.5", ['98', 'gnu98', '11', 'gnu11', "14", "gnu14", "17", "gnu17"]), | ||
("clang", "4.9", ['98', 'gnu98', '11', 'gnu11', "14", "gnu14", "17", "gnu17"]), | ||
("clang", "5", ['98', 'gnu98', '11', 'gnu11', "14", "gnu14", "17", "gnu17"]), | ||
("clang", "6", ['98', 'gnu98', '11', 'gnu11', "14", "gnu14", "17", "gnu17", "20", "gnu20"]), | ||
("clang", "12", ['98', 'gnu98', '11', 'gnu11', "14", "gnu14", "17", "gnu17", "20", | ||
"gnu20", "23", "gnu23"]) | ||
]) | ||
def test_supported_cppstd_clang(compiler, compiler_version, values): | ||
settings = MockSettings({"compiler": compiler, "compiler.version": compiler_version}) | ||
conanfile = MockConanfile(settings) | ||
sot = supported_cppstd(conanfile) | ||
assert sot == values | ||
|
||
|
||
def test_supported_cppstd_with_specific_values(): | ||
settings = MockSettings({}) | ||
conanfile = MockConanfile(settings) | ||
sot = supported_cppstd(conanfile, "clang", "3.1") | ||
assert sot == ['98', 'gnu98', '11', 'gnu11'] | ||
|
||
|
||
def test_supported_cppstd_error(): | ||
settings = MockSettings({}) | ||
conanfile = MockConanfile(settings) | ||
with pytest.raises(ConanException) as exc: | ||
supported_cppstd(conanfile) | ||
assert "Called supported_cppstd with no compiler or no compiler.version" in str(exc) | ||
|
||
|
||
@pytest.mark.parametrize("compiler,compiler_version,values", [ | ||
("gcc", "2.0", []), | ||
("gcc", "3.4", ['98', 'gnu98']), | ||
("gcc", "4.2", ['98', 'gnu98']), | ||
("gcc", "4.3", ['98', 'gnu98', '11', 'gnu11']), | ||
("gcc", "4.8", ['98', 'gnu98', '11', 'gnu11', "14", "gnu14"]), | ||
("gcc", "5", ['98', 'gnu98', '11', 'gnu11', "14", "gnu14", "17", "gnu17"]), | ||
("gcc", "8", ['98', 'gnu98', '11', 'gnu11', "14", "gnu14", "17", "gnu17", "20", "gnu20"]), | ||
("gcc", "11", ['98', 'gnu98', '11', 'gnu11', "14", "gnu14", "17", "gnu17", "20", "gnu20", | ||
"23", "gnu23"]) | ||
]) | ||
def test_supported_cppstd_gcc(compiler, compiler_version, values): | ||
settings = MockSettings({"compiler": compiler, "compiler.version": compiler_version}) | ||
conanfile = MockConanfile(settings) | ||
sot = supported_cppstd(conanfile) | ||
assert sot == values | ||
|
||
|
||
@pytest.mark.parametrize("compiler,compiler_version,values", [ | ||
("apple-clang", "3.9", []), | ||
("apple-clang", "4.0", ['98', 'gnu98', '11', 'gnu11']), | ||
("apple-clang", "5.0", ['98', 'gnu98', '11', 'gnu11']), | ||
("apple-clang", "5.1", ['98', 'gnu98', '11', 'gnu11', "14", "gnu14"]), | ||
("apple-clang", "6.1", ['98', 'gnu98', '11', 'gnu11', "14", "gnu14", "17", "gnu17"]), | ||
("apple-clang", "9.5", ['98', 'gnu98', '11', 'gnu11', "14", "gnu14", "17", "gnu17"]), | ||
("apple-clang", "10", ['98', 'gnu98', '11', 'gnu11', "14", "gnu14", "17", "gnu17", "20", | ||
"gnu20"]), | ||
]) | ||
def test_supported_cppstd_apple_clang(compiler, compiler_version, values): | ||
settings = MockSettings({"compiler": compiler, "compiler.version": compiler_version}) | ||
conanfile = MockConanfile(settings) | ||
sot = supported_cppstd(conanfile) | ||
assert sot == values | ||
|
||
|
||
@pytest.mark.parametrize("compiler,compiler_version,values", [ | ||
("msvc", "180", []), | ||
("msvc", "190", ['14', '17']), | ||
("msvc", "191", ['14', '17', '20']), | ||
("msvc", "193", ['14', '17', '20', '23']), | ||
]) | ||
def test_supported_cppstd_msvc(compiler, compiler_version, values): | ||
settings = MockSettings({"compiler": compiler, "compiler.version": compiler_version}) | ||
conanfile = MockConanfile(settings) | ||
sot = supported_cppstd(conanfile) | ||
assert sot == values | ||
|
||
|
||
@pytest.mark.parametrize("compiler,compiler_version,values", [ | ||
("mcst-lcc", "1.20", ['98', 'gnu98']), | ||
("mcst-lcc", "1.21", ['98', 'gnu98', '11', 'gnu11', "14", "gnu14"]), | ||
("mcst-lcc", "1.23", ['98', 'gnu98', '11', 'gnu11', "14", "gnu14"]), | ||
("mcst-lcc", "1.24", ['98', 'gnu98', '11', 'gnu11', "14", "gnu14", "17", "gnu17"]), | ||
("mcst-lcc", "1.25", ['98', 'gnu98', '11', 'gnu11', "14", "gnu14", "17", "gnu17", "20", "gnu20"]) | ||
]) | ||
def test_supported_cppstd_mcst(compiler, compiler_version, values): | ||
settings = MockSettings({"compiler": compiler, "compiler.version": compiler_version}) | ||
conanfile = MockConanfile(settings) | ||
sot = supported_cppstd(conanfile) | ||
assert sot == values | ||
|
||
|
||
def test_check_cppstd_type(): | ||
""" cppstd must be a number | ||
""" | ||
conanfile = MockConanfile(MockSettings({})) | ||
with pytest.raises(ConanException) as exc: | ||
check_min_cppstd(conanfile, "gnu17", False) | ||
|
||
assert "cppstd parameter must be a number", str(exc) | ||
|
||
|
||
def _create_conanfile(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 | ||
|
||
|
||
@pytest.mark.parametrize("cppstd", ["98", "11", "14", "17"]) | ||
def test_check_min_cppstd_from_settings(cppstd): | ||
""" check_min_cppstd must accept cppstd less/equal than cppstd in settings | ||
""" | ||
conanfile = _create_conanfile("gcc", "9", "Linux", "17", "libstdc++") | ||
check_min_cppstd(conanfile, cppstd, False) | ||
|
||
|
||
@pytest.mark.parametrize("cppstd", ["98", "11", "14"]) | ||
def test_check_min_cppstd_from_outdated_settings(cppstd): | ||
""" check_min_cppstd must raise when cppstd is greater when supported on settings | ||
""" | ||
conanfile = _create_conanfile("gcc", "9", "Linux", cppstd, "libstdc++") | ||
with pytest.raises(ConanInvalidConfiguration) as exc: | ||
check_min_cppstd(conanfile, "17", False) | ||
assert "Current cppstd ({}) is lower than the required C++ standard (17)." \ | ||
"".format(cppstd) == str(exc.value) | ||
|
||
|
||
@pytest.mark.parametrize("cppstd", ["98", "11", "14", "17"]) | ||
def test_check_min_cppstd_from_settings_with_extension(cppstd): | ||
""" current cppstd in settings must has GNU extension when extensions is enabled | ||
""" | ||
conanfile = _create_conanfile("gcc", "9", "Linux", "gnu17", "libstdc++") | ||
check_min_cppstd(conanfile, cppstd, True) | ||
|
||
conanfile.settings.values["compiler.cppstd"] = "17" | ||
with pytest.raises(ConanException) as raises: | ||
check_min_cppstd(conanfile, cppstd, True) | ||
assert "The cppstd GNU extension is required" == str(raises.value) | ||
|
||
|
||
@pytest.mark.parametrize("cppstd", ["98", "11", "14", "17"]) | ||
def test_valid_min_cppstd_from_settings(cppstd): | ||
""" valid_min_cppstd must accept cppstd less/equal than cppstd in settings | ||
""" | ||
conanfile = _create_conanfile("gcc", "9", "Linux", "17", "libstdc++") | ||
assert valid_min_cppstd(conanfile, cppstd, False) | ||
|
||
|
||
@pytest.mark.parametrize("cppstd", ["98", "11", "14"]) | ||
def test_valid_min_cppstd_from_outdated_settings(cppstd): | ||
""" valid_min_cppstd returns False when cppstd is greater when supported on settings | ||
""" | ||
conanfile = _create_conanfile("gcc", "9", "Linux", cppstd, "libstdc++") | ||
assert not valid_min_cppstd(conanfile, "17", False) | ||
|
||
|
||
@pytest.mark.parametrize("cppstd", ["98", "11", "14", "17"]) | ||
def test_valid_min_cppstd_from_settings_with_extension(cppstd): | ||
""" valid_min_cppstd must returns True when current cppstd in settings has GNU extension and | ||
extensions is enabled | ||
""" | ||
conanfile = _create_conanfile("gcc", "9", "Linux", "gnu17", "libstdc++") | ||
assert valid_min_cppstd(conanfile, cppstd, True) | ||
|
||
conanfile.settings.values["compiler.cppstd"] = "17" | ||
assert not valid_min_cppstd(conanfile, cppstd, True) | ||
|
||
|
||
def test_valid_min_cppstd_unsupported_standard(): | ||
""" valid_min_cppstd must returns False when the compiler does not support a standard | ||
""" | ||
conanfile = _create_conanfile("gcc", "9", "Linux", None, "libstdc++") | ||
assert not valid_min_cppstd(conanfile, "42", False) |