Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/language #16028

Open
wants to merge 21 commits into
base: develop2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions conan/tools/build/__init__.py
Expand Up @@ -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
Expand Down
165 changes: 165 additions & 0 deletions conan/tools/build/cstd.py
@@ -0,0 +1,165 @@
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):
# TODO: Per-version support
return ["99", "gnu99", "11", "gnu11", "17", "gnu17", "23", "gnu23"]


def _msvc_supported_cstd(version):
# TODO: Per-version support
return ["11", "17"]


def _clang_supported_cstd(version):
# TODO: Per-version support
return ["99", "gnu99", "11", "gnu11", "17", "gnu17", "23", "gnu23"]
35 changes: 25 additions & 10 deletions conan/tools/cmake/toolchain/blocks.py
Expand Up @@ -244,24 +244,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):
Expand Down
5 changes: 4 additions & 1 deletion 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):
Expand All @@ -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)
Expand Down
11 changes: 11 additions & 0 deletions conans/client/conanfile/implementations.py
Expand Up @@ -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")
6 changes: 5 additions & 1 deletion conans/client/conf/__init__.py
Expand Up @@ -94,19 +94,21 @@
"10", "10.1", "10.2", "10.3", "10.4", "10.5",
"11", "11.1", "11.2", "11.3", "11.4",
"12", "12.1", "12.2", "12.3",
"13", "13.1", "13.2",
"13", "13.1", "13.2",
"14", "14.1"]
libcxx: [libstdc++, libstdc++11]
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]
update: [null, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
runtime: [static, dynamic]
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",
Expand All @@ -116,11 +118,13 @@
runtime: [null, static, dynamic]
runtime_type: [null, Debug, Release]
runtime_version: [null, v140, v141, v142, v143]
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",]
Expand Down
43 changes: 29 additions & 14 deletions conans/client/graph/compatibility.py
Expand Up @@ -23,32 +23,47 @@ 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


def cppstd_compat(conanfile):
# It will try to find packages with all the cppstd versions
extension_properties = getattr(conanfile, "extension_properties", {})
if extension_properties.get("compatibility_cppstd") is False:
return []
compiler = conanfile.settings.get_safe("compiler")
compiler_version = conanfile.settings.get_safe("compiler.version")
cppstd = conanfile.settings.get_safe("compiler.cppstd")
if not compiler or not compiler_version or not cppstd:
cstd = conanfile.settings.get_safe("compiler.cstd")
if not compiler or not compiler_version:
return []
base = dict(conanfile.settings.values_list)
cppstd_possible_values = supported_cppstd(conanfile)
if cppstd_possible_values is None:
conanfile.output.warning(f'No cppstd compatibility defined for compiler "{compiler}"')
return []
if cppstd is None or extension_properties.get("compatibility_cppstd") is False:
cppstd_possible_values = [None]
else:
cppstd_possible_values = supported_cppstd(conanfile)
if cppstd_possible_values is None:
conanfile.output.warning(f'No cppstd compatibility defined for compiler "{compiler}"')
cppstd_possible_values = [None]
if cstd is None or extension_properties.get("compatibility_cstd") is False:
cstd_possible_values = [None]
else:
cstd_possible_values = supported_cstd(conanfile)
if cstd_possible_values is None:
conanfile.output.warning(f'No cstd compatibility defined for compiler "{compiler}"')
cstd_possible_values = [None]

combinations = [(c1, c2) for c1 in cppstd_possible_values for c2 in cstd_possible_values]
ret = []
for _cppstd in cppstd_possible_values:
if _cppstd is None or _cppstd == cppstd:
continue
configuration = base.copy()
configuration["compiler.cppstd"] = _cppstd
ret.append({"settings": [(k, v) for k, v in configuration.items()]})
for _cppstd, _cstd in combinations:
conf_variation = {}
if _cppstd is not None and _cppstd != cppstd:
conf_variation["compiler.cppstd"] = _cppstd
if _cstd is not None and _cstd != cstd:
conf_variation["compiler.cstd"] = _cstd
if conf_variation:
configuration = base.copy()
configuration.update(conf_variation)
ret.append({"settings": [(k, v) for k, v in configuration.items()]})

return ret
"""
Expand Down
3 changes: 3 additions & 0 deletions conans/model/conan_file.py
Expand Up @@ -48,6 +48,7 @@ class ConanFile:
default_options = None
default_build_options = None
package_type = None
languages = []
memsharded marked this conversation as resolved.
Show resolved Hide resolved

implements = []

Expand Down Expand Up @@ -92,6 +93,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,
Expand Down