diff --git a/conan/tools/build/__init__.py b/conan/tools/build/__init__.py index 9c77a3f4d5a..0ae8bc5f392 100644 --- a/conan/tools/build/__init__.py +++ b/conan/tools/build/__init__.py @@ -6,6 +6,8 @@ from conan.tools.build.flags import cppstd_flag from conan.tools.build.cppstd import check_max_cppstd, check_min_cppstd, \ valid_max_cppstd, valid_min_cppstd, default_cppstd, supported_cppstd +from conan.tools.build.cstd import check_max_cstd, check_min_cstd, \ + valid_max_cstd, valid_min_cstd, supported_cstd from conan.tools.build.cpu import build_jobs from conan.tools.build.cross_building import cross_building, can_run from conan.tools.build.stdcpp_library import stdcpp_library diff --git a/conan/tools/build/cstd.py b/conan/tools/build/cstd.py new file mode 100644 index 00000000000..2d69eaba97d --- /dev/null +++ b/conan/tools/build/cstd.py @@ -0,0 +1,176 @@ +import operator + +from conan.errors import ConanInvalidConfiguration, ConanException +from conans.model.version import Version + + +def check_min_cstd(conanfile, cstd, gnu_extensions=False): + """ Check if current cstd fits the minimal version required. + + In case the current cstd doesn't fit the minimal version required + by cstd, a ConanInvalidConfiguration exception will be raised. + + 1. If settings.compiler.cstd, the tool will use settings.compiler.cstd to compare + 2. It not settings.compiler.cstd, the tool will use compiler to compare (reading the + default from cstd_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 cstd for settings.compiler, a exception will be raised. + + :param conanfile: The current recipe object. Always use ``self``. + :param cstd: Minimal cstd version required + :param gnu_extensions: GNU extension is required (e.g gnu17) + """ + _check_cstd(conanfile, cstd, operator.lt, gnu_extensions) + + +def check_max_cstd(conanfile, cstd, gnu_extensions=False): + """ Check if current cstd fits the maximum version required. + + In case the current cstd doesn't fit the maximum version required + by cstd, a ConanInvalidConfiguration exception will be raised. + + 1. If settings.compiler.cstd, the tool will use settings.compiler.cstd to compare + 2. It not settings.compiler.cstd, the tool will use compiler to compare (reading the + default from cstd_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 cstd for settings.compiler, a exception will be raised. + + :param conanfile: The current recipe object. Always use ``self``. + :param cstd: Maximum cstd version required + :param gnu_extensions: GNU extension is required (e.g gnu17) + """ + _check_cstd(conanfile, cstd, operator.gt, gnu_extensions) + + +def valid_min_cstd(conanfile, cstd, gnu_extensions=False): + """ Validate if current cstd fits the minimal version required. + + :param conanfile: The current recipe object. Always use ``self``. + :param cstd: Minimal cstd version required + :param gnu_extensions: GNU extension is required (e.g gnu17). This option ONLY works on Linux. + :return: True, if current cstd matches the required cstd version. Otherwise, False. + """ + try: + check_min_cstd(conanfile, cstd, gnu_extensions) + except ConanInvalidConfiguration: + return False + return True + + +def valid_max_cstd(conanfile, cstd, gnu_extensions=False): + """ Validate if current cstd fits the maximum version required. + + :param conanfile: The current recipe object. Always use ``self``. + :param cstd: Maximum cstd version required + :param gnu_extensions: GNU extension is required (e.g gnu17). This option ONLY works on Linux. + :return: True, if current cstd matches the required cstd version. Otherwise, False. + """ + try: + check_max_cstd(conanfile, cstd, gnu_extensions) + except ConanInvalidConfiguration: + return False + return True + + +def supported_cstd(conanfile, compiler=None, compiler_version=None): + """ + Get a list of supported ``compiler.cstd`` 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 ``cstd`` 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_cstd with no compiler or no compiler.version") + + func = {"apple-clang": _apple_clang_supported_cstd, + "gcc": _gcc_supported_cstd, + "msvc": _msvc_supported_cstd, + "clang": _clang_supported_cstd, + }.get(compiler) + if func: + return func(Version(compiler_version)) + return None + + +def _check_cstd(conanfile, cstd, comparator, gnu_extensions): + """ Check if current cstd fits the version required according to a given comparator. + + In case the current cstd doesn't fit the maximum version required + by cstd, a ConanInvalidConfiguration exception will be raised. + + 1. If settings.compiler.cstd, the tool will use settings.compiler.cstd to compare + 2. It not settings.compiler.cstd, the tool will use compiler to compare (reading the + default from cstd_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 cstd for settings.compiler, a exception will be raised. + + :param conanfile: The current recipe object. Always use ``self``. + :param cstd: Required cstd version. + :param comparator: Operator to use to compare the detected and the required cstd versions. + :param gnu_extensions: GNU extension is required (e.g gnu17) + """ + if not str(cstd).isdigit(): + raise ConanException("cstd parameter must be a number") + + def compare(lhs, rhs, comp): + def extract_cpp_version(_cstd): + return str(_cstd).replace("gnu", "") + + def add_millennium(_cstd): + return "19%s" % _cstd if _cstd == "99" else "20%s" % _cstd + + lhs = add_millennium(extract_cpp_version(lhs)) + rhs = add_millennium(extract_cpp_version(rhs)) + return not comp(lhs, rhs) + + current_cstd = conanfile.settings.get_safe("compiler.cstd") + if current_cstd is None: + raise ConanInvalidConfiguration("The compiler.cstd is not defined for this configuration") + + if gnu_extensions and "gnu" not in current_cstd: + raise ConanInvalidConfiguration("The cstd GNU extension is required") + + if not compare(current_cstd, cstd, comparator): + raise ConanInvalidConfiguration( + "Current cstd ({}) is {} than the required C standard ({}).".format( + current_cstd, "higher" if comparator == operator.gt else "lower", cstd)) + + +def _apple_clang_supported_cstd(version): + # TODO: Per-version support + return ["99", "gnu99", "11", "gnu11", "17", "gnu17", "23", "gnu23"] + + +def _gcc_supported_cstd(version): + if version < "4.7": + return ["99", "gnu99"] + if version < "8": + return ["99", "gnu99", "11", "gnu11"] + if version < "14": + return ["99", "gnu99", "11", "gnu11", "17", "gnu17"] + return ["99", "gnu99", "11", "gnu11", "17", "gnu17", "23", "gnu23"] + + +def _msvc_supported_cstd(version): + if version < "192": + return [] + return ["11", "17"] + + +def _clang_supported_cstd(version): + if version < "3": + return ["99", "gnu99"] + if version < "6": + return ["99", "gnu99", "11", "gnu11"] + if version < "18": + return ["99", "gnu99", "11", "gnu11", "17", "gnu17"] + return ["99", "gnu99", "11", "gnu11", "17", "gnu17", "23", "gnu23"] diff --git a/conan/tools/build/flags.py b/conan/tools/build/flags.py index 7dbb1e06b2f..8b20fed3dfe 100644 --- a/conan/tools/build/flags.py +++ b/conan/tools/build/flags.py @@ -482,3 +482,76 @@ def _cppstd_intel_cc(_, cppstd): "20": v20, "gnu20": vgnu20, "23": v23, "gnu23": vgnu23}.get(cppstd) return f'-std={flag}' if flag else None + + +def cstd_flag(conanfile) -> str: + """ + Returns flags specific to the C+standard based on the ``conanfile.settings.compiler``, + ``conanfile.settings.compiler.version`` and ``conanfile.settings.compiler.cstd``. + + It also considers when using GNU extension in ``settings.compiler.cstd``, reflecting it in the + compiler flag. Currently, it supports GCC, Clang, AppleClang, MSVC, Intel, MCST-LCC. + + In case there is no ``settings.compiler`` or ``settings.cstd`` in the profile, the result will + be an **empty string**. + + :param conanfile: The current recipe object. Always use ``self``. + :return: ``str`` with the standard C flag used by the compiler. + """ + compiler = conanfile.settings.get_safe("compiler") + compiler_version = conanfile.settings.get_safe("compiler.version") + cstd = conanfile.settings.get_safe("compiler.cstd") + + if not compiler or not compiler_version or not cstd: + return "" + + func = {"gcc": _cstd_gcc, + "clang": _cstd_clang, + "apple-clang": _cstd_apple_clang, + "msvc": _cstd_msvc}.get(compiler) + flag = None + if func: + flag = func(Version(compiler_version), str(cstd)) + return flag + + +def _cstd_gcc(gcc_version, cstd): + # TODO: Verify flags per version + flag = {"99": "c99", + "11": "c11", + "17": "c17", + "23": "c23"}.get(cstd, cstd) + return f'-std={flag}' if flag else None + + +def _cstd_clang(gcc_version, cstd): + # TODO: Verify flags per version + flag = {"99": "c99", + "11": "c11", + "17": "c17", + "23": "c23"}.get(cstd, cstd) + return f'-std={flag}' if flag else None + + +def _cstd_apple_clang(gcc_version, cstd): + # TODO: Verify flags per version + flag = {"99": "c99", + "11": "c11", + "17": "c17", + "23": "c23"}.get(cstd, cstd) + return f'-std={flag}' if flag else None + + +def cstd_msvc_flag(visual_version, cstd): + if cstd == "17": + if visual_version >= "192": + return "c17" + elif cstd == "11": + if visual_version >= "192": + return "c11" + return None + + +def _cstd_msvc(visual_version, cstd): + flag = cstd_msvc_flag(visual_version, cstd) + return f'/std:{flag}' if flag else None diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py index 26cb7ba0707..34021ece8e5 100644 --- a/conan/tools/cmake/toolchain/blocks.py +++ b/conan/tools/cmake/toolchain/blocks.py @@ -249,24 +249,39 @@ def context(self): class CppStdBlock(Block): template = textwrap.dedent(""" + {% if cppstd %} message(STATUS "Conan toolchain: C++ Standard {{ cppstd }} with extensions {{ cppstd_extensions }}") set(CMAKE_CXX_STANDARD {{ cppstd }}) set(CMAKE_CXX_EXTENSIONS {{ cppstd_extensions }}) set(CMAKE_CXX_STANDARD_REQUIRED ON) + {% endif %} + {% if cstd %} + message(STATUS "Conan toolchain: C Standard {{ cstd }} with extensions {{ cstd_extensions }}") + set(CMAKE_C_STANDARD {{ cstd }}) + set(CMAKE_C_EXTENSIONS {{ cstd_extensions }}) + set(CMAKE_C_STANDARD_REQUIRED ON) + {% endif %} """) def context(self): compiler_cppstd = self._conanfile.settings.get_safe("compiler.cppstd") - if compiler_cppstd is None: - return None - - if compiler_cppstd.startswith("gnu"): - cppstd = compiler_cppstd[3:] - cppstd_extensions = "ON" - else: - cppstd = compiler_cppstd - cppstd_extensions = "OFF" - return {"cppstd": cppstd, "cppstd_extensions": cppstd_extensions} + compiler_cstd = self._conanfile.settings.get_safe("compiler.cstd") + result = {} + if compiler_cppstd is not None: + if compiler_cppstd.startswith("gnu"): + result["cppstd"] = compiler_cppstd[3:] + result["cppstd_extensions"] = "ON" + else: + result["cppstd"] = compiler_cppstd + result["cppstd_extensions"] = "OFF" + if compiler_cstd is not None: + if compiler_cstd.startswith("gnu"): + result["cstd"] = compiler_cstd[3:] + result["cstd_extensions"] = "ON" + else: + result["cstd"] = compiler_cstd + result["cstd_extensions"] = "OFF" + return result or None class SharedLibBock(Block): diff --git a/conan/tools/gnu/autotoolstoolchain.py b/conan/tools/gnu/autotoolstoolchain.py index c0a8655d317..f76edb7d1bb 100644 --- a/conan/tools/gnu/autotoolstoolchain.py +++ b/conan/tools/gnu/autotoolstoolchain.py @@ -5,8 +5,7 @@ from conan.tools.build import cmd_args_to_string, save_toolchain_args from conan.tools.build.cross_building import cross_building from conan.tools.build.flags import architecture_flag, build_type_flags, cppstd_flag, \ - build_type_link_flags, \ - libcxx_flags + build_type_link_flags, libcxx_flags, cstd_flag from conan.tools.env import Environment from conan.tools.gnu.get_gnu_triplet import _get_gnu_triplet from conan.tools.microsoft import VCVars, msvc_runtime_flag, unix_path, check_min_vs, is_msvc @@ -49,6 +48,7 @@ def __init__(self, conanfile, namespace=None, prefix="/"): self.build_type_link_flags = build_type_link_flags(self._conanfile.settings) self.cppstd = cppstd_flag(self._conanfile) + self.cstd = cstd_flag(self._conanfile) self.arch_flag = architecture_flag(self._conanfile.settings) self.libcxx, self.gcc_cxx11_abi = libcxx_flags(self._conanfile) self.fpic = self._conanfile.options.get_safe("fPIC") @@ -130,7 +130,7 @@ def cxxflags(self): @property def cflags(self): fpic = "-fPIC" if self.fpic else None - ret = [self.arch_flag, fpic, self.msvc_runtime_flag, self.sysroot_flag] + ret = [self.cstd, self.arch_flag, fpic, self.msvc_runtime_flag, self.sysroot_flag] apple_flags = [self.apple_isysroot_flag, self.apple_arch_flag, self.apple_min_version_flag] conf_flags = self._conanfile.conf.get("tools.build:cflags", default=[], check_type=list) vs_flag = self._add_msvc_flags(self.extra_cflags) diff --git a/conan/tools/meson/helpers.py b/conan/tools/meson/helpers.py index d0decdf8b1b..ba8e1eb8533 100644 --- a/conan/tools/meson/helpers.py +++ b/conan/tools/meson/helpers.py @@ -2,7 +2,7 @@ from conan.tools.build.flags import cppstd_msvc_flag from conans.model.options import _PackageOption -__all__ = ["to_meson_machine", "to_meson_value", "to_cppstd_flag"] +__all__ = ["to_meson_machine", "to_meson_value", "to_cppstd_flag", "to_cstd_flag"] # https://mesonbuild.com/Reference-tables.html#operating-system-names _meson_system_map = { @@ -126,3 +126,16 @@ def to_cppstd_flag(compiler, compiler_version, cppstd): return 'v%s' % flag if flag else None else: return _cppstd_map.get(cppstd) + + +def to_cstd_flag(cstd): + """ possible values + none, c89, c99, c11, c17, c18, c2x, c23, gnu89, gnu99, gnu11, gnu17, gnu18, gnu2x, gnu23 + """ + _cstd_map = { + '99': "c99", + '11': "c11", + '17': "c17", + '23': "c23", + } + return _cstd_map.get(cstd, cstd) diff --git a/conan/tools/meson/toolchain.py b/conan/tools/meson/toolchain.py index 127eaf77c0b..743d38cbb25 100644 --- a/conan/tools/meson/toolchain.py +++ b/conan/tools/meson/toolchain.py @@ -109,6 +109,9 @@ class MesonToolchain(object): {% if cpp_std %} cpp_std = '{{cpp_std}}' {% endif %} + {% if c_std %} + c_std = '{{c_std}}' + {% endif %} {% if backend %} backend = '{{backend}}' {% endif %} @@ -188,6 +191,9 @@ def __init__(self, conanfile, backend=None, native=False): cppstd = self._conanfile.settings.get_safe("compiler.cppstd") self._cpp_std = to_cppstd_flag(compiler, compiler_version, cppstd) + cstd = self._conanfile.settings.get_safe("compiler.cstd") + self._c_std = to_cstd_flag(cstd) + self._b_vscrt = None if compiler in ("msvc", "clang"): vscrt = msvc_runtime_flag(self._conanfile) @@ -505,6 +511,7 @@ def _context(self): "b_ndebug": to_meson_value(self._b_ndebug), # boolean as string # https://mesonbuild.com/Builtin-options.html#compiler-options "cpp_std": self._cpp_std, + "c_std": self._c_std, "c_args": to_meson_value(self._filter_list_empty_fields(self.c_args)), "c_link_args": to_meson_value(self._filter_list_empty_fields(self.c_link_args)), "cpp_args": to_meson_value(self._filter_list_empty_fields(self.cpp_args)), diff --git a/conan/tools/microsoft/toolchain.py b/conan/tools/microsoft/toolchain.py index e5427001cae..5e33d51030c 100644 --- a/conan/tools/microsoft/toolchain.py +++ b/conan/tools/microsoft/toolchain.py @@ -27,6 +27,7 @@ class MSBuildToolchain(object): {{ defines }}%(PreprocessorDefinitions) {{ compiler_flags }} %(AdditionalOptions) {{ runtime_library }} + {% if cstd %}{{ cstd }}{% endif %} {{ cppstd }}{{ parallel }}{{ compile_options }} @@ -69,6 +70,7 @@ def __init__(self, conanfile): self.runtime_library = self._runtime_library(conanfile.settings) #: cppstd value. By default, ``compiler.cppstd`` one. self.cppstd = conanfile.settings.get_safe("compiler.cppstd") + self.cstd = conanfile.settings.get_safe("compiler.cstd") #: VS IDE Toolset, e.g., ``"v140"``. If ``compiler=msvc``, you can use ``compiler.toolset`` #: setting, else, it'll be based on ``msvc`` version. self.toolset = msvs_toolset(conanfile) @@ -139,6 +141,7 @@ def format_macro(key, value): self.ldflags.extend(sharedlinkflags + exelinkflags) cppstd = "stdcpp%s" % self.cppstd if self.cppstd else "" + cstd = f"stdc{self.cstd}" if self.cstd else "" runtime_library = self.runtime_library toolset = self.toolset or "" compile_options = self._conanfile.conf.get("tools.microsoft.msbuildtoolchain:compile_options", @@ -161,6 +164,7 @@ def format_macro(key, value): 'compiler_flags': " ".join(self.cxxflags + self.cflags), 'linker_flags': " ".join(self.ldflags), "cppstd": cppstd, + "cstd": cstd, "runtime_library": runtime_library, "toolset": toolset, "compile_options": compile_options, diff --git a/conans/client/conanfile/configure.py b/conans/client/conanfile/configure.py index 116a879a820..4b0aa99f1eb 100644 --- a/conans/client/conanfile/configure.py +++ b/conans/client/conanfile/configure.py @@ -1,7 +1,8 @@ from conans.errors import conanfile_exception_formatter from conans.model.pkg_type import PackageType from conans.model.requires import BuildRequirements, TestRequirements, ToolRequirements -from conans.client.conanfile.implementations import auto_shared_fpic_config_options, auto_shared_fpic_configure +from conans.client.conanfile.implementations import auto_shared_fpic_config_options, \ + auto_shared_fpic_configure, auto_language def run_configure_method(conanfile, down_options, profile_options, ref): @@ -15,6 +16,8 @@ def run_configure_method(conanfile, down_options, profile_options, ref): elif "auto_shared_fpic" in conanfile.implements: auto_shared_fpic_config_options(conanfile) + auto_language(conanfile) # default implementation removes `compiler.cstd` + # Assign only the current package options values, but none of the dependencies is_consumer = conanfile._conan_is_consumer conanfile.options.apply_downstream(down_options, profile_options, ref, is_consumer) diff --git a/conans/client/conanfile/implementations.py b/conans/client/conanfile/implementations.py index 133ddcea7e5..14cabc76032 100644 --- a/conans/client/conanfile/implementations.py +++ b/conans/client/conanfile/implementations.py @@ -17,3 +17,14 @@ def auto_shared_fpic_configure(conanfile): def auto_header_only_package_id(conanfile): if conanfile.options.get_safe("header_only") or conanfile.package_type is PackageType.HEADER: conanfile.info.clear() + + +def auto_language(conanfile): + if not conanfile.languages: + conanfile.settings.rm_safe("compiler.cstd") + return + if "C" not in conanfile.languages: + conanfile.settings.rm_safe("compiler.cstd") + if "C++" not in conanfile.languages: + conanfile.settings.rm_safe("compiler.cppstd") + conanfile.settings.rm_safe("compiler.libcxx") diff --git a/conans/client/conf/__init__.py b/conans/client/conf/__init__.py index 9dfe94a1b04..23b5e28f533 100644 --- a/conans/client/conf/__init__.py +++ b/conans/client/conf/__init__.py @@ -100,6 +100,7 @@ threads: [null, posix, win32] # Windows MinGW exception: [null, dwarf2, sjlj, seh] # Windows MinGW cppstd: [null, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20, 23, gnu23] + cstd: [null, 99, gnu99, 11, gnu11, 17, gnu17, 23, gnu23] msvc: version: [170, 180, 190, 191, 192, 193, 194] update: [null, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] @@ -107,6 +108,7 @@ runtime_type: [Debug, Release] cppstd: [null, 14, 17, 20, 23] toolset: [null, v110_xp, v120_xp, v140_xp, v141_xp] + cstd: [null, 11, 17] clang: version: ["3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9", "4.0", "5.0", "6.0", "7.0", "7.1", @@ -116,11 +118,13 @@ runtime: [null, static, dynamic] runtime_type: [null, Debug, Release] runtime_version: [null, v140, v141, v142, v143, v144] + cstd: [null, 99, gnu99, 11, gnu11, 17, gnu17, 23, gnu23] apple-clang: version: ["5.0", "5.1", "6.0", "6.1", "7.0", "7.3", "8.0", "8.1", "9.0", "9.1", "10.0", "11.0", "12.0", "13", "13.0", "13.1", "14", "14.0", "15", "15.0"] libcxx: [libstdc++, libc++] cppstd: [null, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20, 23, gnu23] + cstd: [null, 99, gnu99, 11, gnu11, 17, gnu17, 23, gnu23] intel-cc: version: ["2021.1", "2021.2", "2021.3", "2021.4", "2022.1", "2022.2", "2022.3", "2023.0", "2023.1", "2023.2", "2024.0",] diff --git a/conans/client/graph/compatibility.py b/conans/client/graph/compatibility.py index 1d67e7cf5e6..a23669c8870 100644 --- a/conans/client/graph/compatibility.py +++ b/conans/client/graph/compatibility.py @@ -23,7 +23,7 @@ def compatibility(conanfile): _default_cppstd_compat = """\ # This file was generated by Conan. Remove this comment if you edit this file or Conan # will destroy your changes. -from conan.tools.build import supported_cppstd +from conan.tools.build import supported_cppstd, supported_cstd from conan.errors import ConanException @@ -44,6 +44,14 @@ def cppstd_compat(conanfile): else: # The current cppst must be included in case there is other factor factors.append([{"compiler.cppstd": v} for v in cppstd_possible_values]) + cstd = conanfile.settings.get_safe("compiler.cstd") + if cstd is not None and extension_properties.get("compatibility_cstd") is not False: + cstd_possible_values = supported_cstd(conanfile) + if cstd_possible_values is None: + conanfile.output.warning(f'No cstd compatibility defined for compiler "{compiler}"') + else: + factors.append([{"compiler.cstd": v} for v in cstd_possible_values if v != cstd]) + if compiler == "msvc": msvc_fallback = {"194": "193"}.get(compiler_version) if msvc_fallback: diff --git a/conans/client/profile_loader.py b/conans/client/profile_loader.py index 6b1ba23e81a..cc4763fba8e 100644 --- a/conans/client/profile_loader.py +++ b/conans/client/profile_loader.py @@ -39,6 +39,7 @@ def profile_plugin(profile): except ConanException: pass _check_correct_cppstd(settings) + _check_correct_cstd(settings) def _check_correct_cppstd(settings): from conan.tools.scm import Version @@ -76,6 +77,36 @@ def _error(compiler, cppstd, min_version, version): "14": "190"}.get(cppstd) if mver and version < mver: _error(compiler, cppstd, mver, version) + + +def _check_correct_cstd(settings): + from conan.tools.scm import Version + def _error(compiler, cstd, min_version, version): + from conan.errors import ConanException + raise ConanException(f"The provided compiler.cstd={cstd} requires at least {compiler}" + f">={min_version} but version {version} provided") + cstd = settings.get("compiler.cstd") + version = settings.get("compiler.version") + + if cstd and version: + cstd = cstd.replace("gnu", "") + version = Version(version) + mver = None + compiler = settings.get("compiler") + if compiler == "gcc": + # TODO: right versions + mver = {}.get(cstd) + elif compiler == "clang": + # TODO: right versions + mver = {}.get(cstd) + elif compiler == "apple-clang": + # TODO: Right versions + mver = {}.get(cstd) + elif compiler == "msvc": + mver = {"17": "192", + "11": "192"}.get(cstd) + if mver and version < mver: + _error(compiler, cppstd, mver, version) """ diff --git a/conans/model/conan_file.py b/conans/model/conan_file.py index 96e819ff128..7fcdbf8ac23 100644 --- a/conans/model/conan_file.py +++ b/conans/model/conan_file.py @@ -47,6 +47,7 @@ class ConanFile: default_options = None default_build_options = None package_type = None + languages = [] implements = [] @@ -91,6 +92,8 @@ def __init__(self, display_name=""): if isinstance(self.generators, str): self.generators = [self.generators] + if isinstance(self.languages, str): + self.languages = [self.languages] if isinstance(self.settings, str): self.settings = [self.settings] self.requires = Requirements(self.requires, self.build_requires, self.test_requires, @@ -134,6 +137,7 @@ def serialize(self): result["version"] = str(self.version) if self.version is not None else None result["topics"] = list(self.topics) if self.topics is not None else None result["package_type"] = str(self.package_type) + result["languages"] = self.languages settings = self.settings if settings is not None: diff --git a/conans/model/conanfile_interface.py b/conans/model/conanfile_interface.py index 0ebb1b1ffe2..2130ff85be2 100644 --- a/conans/model/conanfile_interface.py +++ b/conans/model/conanfile_interface.py @@ -100,6 +100,10 @@ def is_build_context(self): def package_type(self): return self._conanfile.package_type + @property + def languages(self): + return self._conanfile.languages + @property def info(self): return self._conanfile.info diff --git a/test/integration/command_v2/test_inspect.py b/test/integration/command_v2/test_inspect.py index 129ee376181..8de7c470ac1 100644 --- a/test/integration/command_v2/test_inspect.py +++ b/test/integration/command_v2/test_inspect.py @@ -14,6 +14,7 @@ def test_basic_inspect(): ' shared: False', 'generators: []', 'label: ', + 'languages: []', 'name: foo', 'options:', ' shared: False', @@ -83,6 +84,7 @@ def test_normal_inspect(): 'generators: []', 'homepage: ', 'label: ', + 'languages: []', 'license: ', 'name: pkg', 'options:', @@ -126,6 +128,7 @@ class Pkg(ConanFile): tc.run("inspect .") assert ['generators: []', 'label: ', + 'languages: []', "license: ['MIT', 'Apache']", 'options:', 'options_definitions:', diff --git a/test/integration/package_id/test_cache_compatibles.py b/test/integration/package_id/test_cache_compatibles.py index 35fe89e9295..74f1c364c46 100644 --- a/test/integration/package_id/test_cache_compatibles.py +++ b/test/integration/package_id/test_cache_compatibles.py @@ -311,6 +311,37 @@ def package_info(self): assert "valid standard!!" in c.out assert "pkg/0.1: CPPSTD: 17" in c.out + def test_check_min_cstd(self): + """ test that the check_min_cstd works fine wiht compatibility + """ + conanfile = textwrap.dedent(""" + from conan import ConanFile + from conan.tools.build import check_min_cstd + class Pkg(ConanFile): + name = "pkg" + version = "0.1" + settings = "os", "arch", "compiler", "build_type" + languages = "C" + def validate(self): + check_min_cstd(self, "17", False) + self.output.info("valid standard!!") + def package_info(self): + self.output.info("CSTD: {}".format(self.settings.compiler.cstd)) + """) + + c = TestClient() + c.save({"conanfile.py": conanfile}) + settings = "-s compiler=gcc -s compiler.version=9 -s compiler.libcxx=libstdc++11" + c.run(f"create . {settings} -s compiler.cstd=17") + assert "pkg/0.1: valid standard!!" in c.out + assert "pkg/0.1: CSTD: 17" in c.out + c.run(f"install {settings} --requires=pkg/0.1 -s compiler.cstd=11", assert_error=True) + assert "pkg/0.1: Invalid: Current cstd (11) is lower than the required C standard (17)."\ + in c.out + c.run(f"install {settings} --requires=pkg/0.1 -s compiler.cstd=23") + assert "valid standard!!" in c.out + assert "pkg/0.1: CSTD: 17" in c.out + def test_check_min_cppstd_interface(self): """ test that says that compatible binaries are ok, as long as the user defined cppstd>=14. The syntax is a bit forced, maybe we want to improve ``check_min_cppstd`` diff --git a/test/integration/toolchains/meson/test_mesontoolchain.py b/test/integration/toolchains/meson/test_mesontoolchain.py index 588873848a3..d9ca23ea9e4 100644 --- a/test/integration/toolchains/meson/test_mesontoolchain.py +++ b/test/integration/toolchains/meson/test_mesontoolchain.py @@ -196,6 +196,29 @@ def test_correct_quotes(): assert "buildtype = 'release'" in content +def test_c_std(): + profile = textwrap.dedent(""" + [settings] + os=Windows + arch=x86_64 + compiler=gcc + compiler.version=9 + compiler.cstd=11 + build_type=Release + """) + t = TestClient() + t.save({"conanfile.py": GenConanfile().with_settings("os", "compiler", "build_type", "arch") + .with_generator("MesonToolchain") + .with_class_attribute("languages='C'"), + "profile": profile}) + + t.run("install . -pr:h=profile -pr:b=profile") + content = t.load(MesonToolchain.native_filename) + assert "c_std = 'c11'" in content + assert "backend = 'ninja'" in content + assert "buildtype = 'release'" in content + + def test_deactivate_nowrap(): # https://github.com/conan-io/conan/issues/10671 t = TestClient() diff --git a/test/unittests/client/toolchain/autotools/autotools_toolchain_test.py b/test/unittests/client/toolchain/autotools/autotools_toolchain_test.py index 5ea435a3ffa..e5afa10d744 100644 --- a/test/unittests/client/toolchain/autotools/autotools_toolchain_test.py +++ b/test/unittests/client/toolchain/autotools/autotools_toolchain_test.py @@ -130,6 +130,21 @@ def test_cppstd(): assert "/std:c++17" in env["CXXFLAGS"] +def test_cstd(): + conanfile = ConanFileMock() + conanfile.settings = MockSettings( + {"build_type": "Release", + "arch": "x86", + "compiler": "gcc", + "compiler.libcxx": "libstdc++11", + "compiler.version": "7.1", + "compiler.cstd": "17"}) + conanfile.settings_build = MockSettings({"os": "Linux", "arch": "x86"}) + be = AutotoolsToolchain(conanfile) + env = be.vars() + assert "-std=c17" in env["CFLAGS"] + + def test_fpic(): conanfile = ConanFileMock() conanfile.settings = MockSettings({"os": "Linux"}) diff --git a/test/unittests/tools/microsoft/test_msbuild.py b/test/unittests/tools/microsoft/test_msbuild.py index 9f0e5b1bf8a..3fc98453183 100644 --- a/test/unittests/tools/microsoft/test_msbuild.py +++ b/test/unittests/tools/microsoft/test_msbuild.py @@ -104,7 +104,8 @@ def test_msbuild_standard(): conanfile.conf.define("tools.microsoft.msbuild:installation_path", ".") conanfile.settings = "os", "compiler", "build_type", "arch" conanfile.settings = Settings({"build_type": ["Release"], - "compiler": {"msvc": {"version": ["193"], "cppstd": ["20"]}}, + "compiler": {"msvc": {"version": ["193"], "cppstd": ["20"], + "cstd": ["17"]}}, "os": ["Windows"], "arch": ["x86_64"]}) conanfile.settings_build = conanfile.settings @@ -112,6 +113,7 @@ def test_msbuild_standard(): conanfile.settings.compiler = "msvc" conanfile.settings.compiler.version = "193" conanfile.settings.compiler.cppstd = "20" + conanfile.settings.compiler.cstd = "17" conanfile.settings.os = "Windows" conanfile.settings.arch = "x86_64" @@ -119,6 +121,7 @@ def test_msbuild_standard(): props_file = os.path.join(test_folder, 'conantoolchain_release_x64.props') msbuild.generate() assert 'stdcpp20' in load(props_file) + assert 'stdc17' in load(props_file) def test_resource_compile():