Repository: conan-io/conan
Pull request #16391 (2024-06-03 13:36:43)

[AutotoolsToolchain] Added [CC|CXX]_FOR_BUILD env var
Changelog: Feature: Added `CC_FOR_BUILD` and  `CXX_FOR_BUILD` environment variable to AutotoolsToolchain.
Docs: https://github.com/conan-io/docs/pull/XXXX
Closes: https://github.com/conan-io/conan/issues/15486



Patch:
diff --git a/.gitignore b/.gitignore
index cd3afe32d..87824d101 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,7 @@ venv/
 *.egg-info/
 .installed.cfg
 *.egg
+pip-wheel-metadata/
 
 # PyInstaller
 #  Usually these files are written by a python script from a template
diff --git a/conan/internal/api/detect_api.py b/conan/internal/api/detect_api.py
index aebad6e72..04bb3ab16 100644
--- a/conan/internal/api/detect_api.py
+++ b/conan/internal/api/detect_api.py
@@ -438,8 +438,15 @@ def _cc_compiler(compiler_exe="cc"):
         if ret != 0:
             return None, None, None
         compiler = "clang" if "clang" in out else "gcc"
-        installed_version = re.search(r"([0-9]+(\.[0-9])?)", out).group()
-        if installed_version:
+        # clang and gcc have version after a space, first try to find that to skip extra numbers
+        # that might appear in the first line of the output before the version
+        installed_version = re.search(r" ([0-9]+(\.[0-9])+)", out)
+        # Try only major but with spaces next
+        installed_version = installed_version or re.search(r" ([0-9]+(\.[0-9])?)", out)
+        # Fallback to the first number we find optionally followed by other version fields
+        installed_version = installed_version or re.search(r"([0-9]+(\.[0-9])?)", out)
+        if installed_version and installed_version.group():
+            installed_version = installed_version.group()
             ConanOutput(scope="detect_api").info("Found cc=%s-%s" % (compiler, installed_version))
             return compiler, Version(installed_version), compiler_exe
     except (Exception,):  # to disable broad-except
diff --git a/conan/tools/gnu/autotoolstoolchain.py b/conan/tools/gnu/autotoolstoolchain.py
index 6f961e442..c0a8655d3 100644
--- a/conan/tools/gnu/autotoolstoolchain.py
+++ b/conan/tools/gnu/autotoolstoolchain.py
@@ -60,8 +60,8 @@ class AutotoolsToolchain:
         self._build = self._conanfile.conf.get("tools.gnu:build_triplet")
         self._target = None
 
-        is_cross_building = cross_building(self._conanfile)
-        if is_cross_building:
+        self._is_cross_building = cross_building(self._conanfile)
+        if self._is_cross_building:
             os_host = conanfile.settings.get_safe("os")
             arch_host = conanfile.settings.get_safe("arch")
             os_build = conanfile.settings_build.get_safe('os')
@@ -84,11 +84,12 @@ class AutotoolsToolchain:
         self.autoreconf_args = self._default_autoreconf_flags()
         self.make_args = []
         # Apple stuff
-        is_cross_building_osx = (is_cross_building
+        is_cross_building_osx = (self._is_cross_building
                                  and conanfile.settings_build.get_safe('os') == "Macos"
                                  and is_apple_os(conanfile))
-        min_flag, arch_flag, isysroot_flag = resolve_apple_flags(conanfile,
-                                                                 is_cross_building=is_cross_building_osx)
+        min_flag, arch_flag, isysroot_flag = (
+            resolve_apple_flags(conanfile, is_cross_building=is_cross_building_osx)
+        )
         # https://man.archlinux.org/man/clang.1.en#Target_Selection_Options
         self.apple_arch_flag = arch_flag
         # -isysroot makes all includes for your library relative to the build directory
@@ -102,7 +103,8 @@ class AutotoolsToolchain:
         return flag
 
     def _msvc_extra_flags(self):
-        if is_msvc(self._conanfile) and check_min_vs(self._conanfile, "180", raise_invalid=False):
+        if is_msvc(self._conanfile) and check_min_vs(self._conanfile, "180",
+                                                     raise_invalid=False):
             return ["-FS"]
         return []
 
@@ -160,7 +162,8 @@ class AutotoolsToolchain:
         compilers_by_conf = self._conanfile.conf.get("tools.build:compiler_executables", default={},
                                                      check_type=dict)
         if compilers_by_conf:
-            compilers_mapping = {"c": "CC", "cpp": "CXX", "cuda": "NVCC", "fortran": "FC", "rc": "RC"}
+            compilers_mapping = {"c": "CC", "cpp": "CXX", "cuda": "NVCC", "fortran": "FC",
+                                 "rc": "RC"}
             for comp, env_var in compilers_mapping.items():
                 if comp in compilers_by_conf:
                     compiler = compilers_by_conf[comp]
@@ -172,6 +175,16 @@ class AutotoolsToolchain:
         env.append("CFLAGS", self.cflags)
         env.append("LDFLAGS", self.ldflags)
         env.prepend_path("PKG_CONFIG_PATH", self._conanfile.generators_folder)
+        # Issue related: https://github.com/conan-io/conan/issues/15486
+        if self._is_cross_building and self._conanfile.conf_build:
+            compilers_build_mapping = (
+                self._conanfile.conf_build.get("tools.build:compiler_executables", default={},
+                                               check_type=dict)
+            )
+            if "c" in compilers_build_mapping:
+                env.define("CC_FOR_BUILD", compilers_build_mapping["c"])
+            if "cpp" in compilers_build_mapping:
+                env.define("CXX_FOR_BUILD", compilers_build_mapping["cpp"])
         return env
 
     def vars(self):
diff --git a/conan/tools/meson/toolchain.py b/conan/tools/meson/toolchain.py
index d00f29145..1a47b382f 100644
--- a/conan/tools/meson/toolchain.py
+++ b/conan/tools/meson/toolchain.py
@@ -194,6 +194,17 @@ class MesonToolchain(object):
             if vscrt:
                 self._b_vscrt = str(vscrt).lower()
 
+        # Extra flags
+        #: List of extra CXX flags. Added to cpp_args
+        self.extra_cxxflags = []
+        #: List of extra C flags. Added to c_args
+        self.extra_cflags = []
+        #: List of extra linker flags. Added to c_link_args and cpp_link_args
+        self.extra_ldflags = []
+        #: List of extra preprocessor definitions. Added to c_args and cpp_args with the
+        #: format -DFLAG1
+        self.extra_defines = []
+
         #: Dict-like object that defines Meson``properties`` with ``key=value`` format
         self.properties = {}
         #: Dict-like object that defines Meson ``project options`` with ``key=value`` format
@@ -408,13 +419,14 @@ class MesonToolchain(object):
         exelinkflags = self._conanfile_conf.get("tools.build:exelinkflags", default=[], check_type=list)
         linker_scripts = self._conanfile_conf.get("tools.build:linker_scripts", default=[], check_type=list)
         linker_script_flags = ['-T"' + linker_script + '"' for linker_script in linker_scripts]
-        defines = [f"-D{d}" for d in self._conanfile.conf.get("tools.build:defines", default=[], check_type=list)]
+        defines = self._conanfile.conf.get("tools.build:defines", default=[], check_type=list)
         sys_root = [f"--sysroot={self._sys_root}"] if self._sys_root else [""]
         return {
-            "cxxflags": cxxflags + sys_root,
-            "cflags": cflags + sys_root,
-            "ldflags": sharedlinkflags + exelinkflags + linker_script_flags + sys_root,
-            "defines": defines
+            "cxxflags": cxxflags + sys_root + self.extra_cxxflags,
+            "cflags": cflags + sys_root + self.extra_cflags,
+            "ldflags": sharedlinkflags + exelinkflags + linker_script_flags
+                       + sys_root + self.extra_ldflags,
+            "defines": [f"-D{d}" for d in (defines + self.extra_defines)]
         }
 
     @staticmethod
diff --git a/conans/client/graph/graph_binaries.py b/conans/client/graph/graph_binaries.py
index db8a6bd13..c343ccd2b 100644
--- a/conans/client/graph/graph_binaries.py
+++ b/conans/client/graph/graph_binaries.py
@@ -1,7 +1,6 @@
 import json
 import os
 from collections import OrderedDict
-from multiprocessing.pool import ThreadPool
 
 from conan.api.output import ConanOutput
 from conan.internal.cache.home_paths import HomePaths
@@ -144,8 +143,8 @@ class GraphBinariesAnalyzer(object):
         conanfile.output.info(f"Checking {len(compatibles)} compatible configurations")
         for package_id, compatible_package in compatibles.items():
             if should_update_reference(node.ref, update):
-                conanfile.output.verbose(f"'{package_id}': "
-                                         f"{conanfile.info.dump_diff(compatible_package)}")
+                conanfile.output.info(f"'{package_id}': "
+                                      f"{conanfile.info.dump_diff(compatible_package)}")
             node._package_id = package_id  # Modifying package id under the hood, FIXME
             node.binary = None  # Invalidate it
             self._process_compatible_node(node, remotes, update)  # TODO: what if BINARY_BUILD
@@ -155,8 +154,8 @@ class GraphBinariesAnalyzer(object):
         if not should_update_reference(conanfile.ref, update):
             conanfile.output.info(f"Compatible configurations not found in cache, checking servers")
             for package_id, compatible_package in compatibles.items():
-                conanfile.output.verbose(f"'{package_id}': "
-                                         f"{conanfile.info.dump_diff(compatible_package)}")
+                conanfile.output.info(f"'{package_id}': "
+                                      f"{conanfile.info.dump_diff(compatible_package)}")
                 node._package_id = package_id  # Modifying package id under the hood, FIXME
                 node.binary = None  # Invalidate it
                 self._evaluate_download(node, remotes, update)
@@ -384,24 +383,21 @@ class GraphBinariesAnalyzer(object):
 
         levels = deps_graph.by_levels()
         config_version = self._config_version()
-        # This overcounts when there are multiple identical tool requires
-        max_level_length = max(len(level) for level in levels)
-        evaluate_pool = ThreadPool(min(16, max_level_length))
         for level in levels[:-1]:  # all levels but the last one, which is the single consumer
             for node in level:
                 self._evaluate_package_id(node, config_version)
-            # group by pref to parallelize, so evaluation is done only 1 per pref
+            # group by pref to paralelize, so evaluation is done only 1 per pref
             nodes = {}
             for node in level:
                 nodes.setdefault(node.pref, []).append(node)
-            # Evaluate the first node of each pref
-            evaluate_pool.map(_evaluate_single, [pref_nodes[0] for pref_nodes in nodes.values()])
-
+            # PARALLEL, this is the slow part that can query servers for packages, and compatibility
+            for pref, pref_nodes in nodes.items():
+                _evaluate_single(pref_nodes[0])
+            # END OF PARALLEL
             # Evaluate the possible nodes with repeated "prefs" that haven't been evaluated
             for pref, pref_nodes in nodes.items():
                 for n in pref_nodes[1:]:
                     _evaluate_single(n)
-        evaluate_pool.close()
 
         # Last level is always necessarily a consumer or a virtual
         assert len(levels[-1]) == 1


Test patch:
diff --git a/conan/test/utils/mocks.py b/conan/test/utils/mocks.py
index 6c1a160ef..bfed795b6 100644
--- a/conan/test/utils/mocks.py
+++ b/conan/test/utils/mocks.py
@@ -92,6 +92,7 @@ class ConanFileMock(ConanFile):
         self.options = options or Options()
         self.generators = []
         self.conf = Conf()
+        self.conf_build = Conf()
         self.folders = Folders()
         self.folders.set_base_source(".")
         self.folders.set_base_export_sources(".")
diff --git a/test/integration/toolchains/gnu/test_autotoolstoolchain.py b/test/integration/toolchains/gnu/test_autotoolstoolchain.py
index c80468a71..7364274ae 100644
--- a/test/integration/toolchains/gnu/test_autotoolstoolchain.py
+++ b/test/integration/toolchains/gnu/test_autotoolstoolchain.py
@@ -266,6 +266,9 @@ def test_toolchain_and_compilers_build_context():
             assert 'export CC="gcc"' in content
             assert 'export CXX="g++"' in content
             assert 'export RC="windres"' in content
+            # Issue: https://github.com/conan-io/conan/issues/15486
+            assert 'export CC_FOR_BUILD="clang"' in content
+            assert 'export CXX_FOR_BUILD="clang++"' in content
     """)
     client = TestClient()
     client.save({
diff --git a/test/integration/toolchains/meson/test_mesontoolchain.py b/test/integration/toolchains/meson/test_mesontoolchain.py
index 602b4bb9a..588873848 100644
--- a/test/integration/toolchains/meson/test_mesontoolchain.py
+++ b/test/integration/toolchains/meson/test_mesontoolchain.py
@@ -105,6 +105,46 @@ def test_extra_flags_via_conf():
     assert "cpp_link_args = ['-flag0', '-other=val', '-flag5', '-flag6']" in content
 
 
+def test_extra_flags_via_toolchain():
+    profile = textwrap.dedent("""
+        [settings]
+        os=Windows
+        arch=x86_64
+        compiler=gcc
+        compiler.version=9
+        compiler.cppstd=17
+        compiler.libcxx=libstdc++
+        build_type=Release
+
+        [buildenv]
+        CFLAGS=-flag0 -other=val
+        CXXFLAGS=-flag0 -other=val
+        LDFLAGS=-flag0 -other=val
+   """)
+    t = TestClient()
+    conanfile = textwrap.dedent("""
+    from conan import ConanFile
+    from conan.tools.meson import MesonToolchain
+    class Pkg(ConanFile):
+        settings = "os", "compiler", "arch", "build_type"
+        def generate(self):
+            tc = MesonToolchain(self)
+            tc.extra_cxxflags = ["-flag1", "-flag2"]
+            tc.extra_cflags = ["-flag3", "-flag4"]
+            tc.extra_ldflags = ["-flag5", "-flag6"]
+            tc.extra_defines = ["define1=0"]
+            tc.generate()
+    """)
+    t.save({"conanfile.py": conanfile,
+            "profile": profile})
+    t.run("install . -pr:h=profile -pr:b=profile")
+    content = t.load(MesonToolchain.native_filename)
+    assert "cpp_args = ['-flag0', '-other=val', '-flag1', '-flag2', '-Ddefine1=0', '-D_GLIBCXX_USE_CXX11_ABI=0']" in content
+    assert "c_args = ['-flag0', '-other=val', '-flag3', '-flag4', '-Ddefine1=0']" in content
+    assert "c_link_args = ['-flag0', '-other=val', '-flag5', '-flag6']" in content
+    assert "cpp_link_args = ['-flag0', '-other=val', '-flag5', '-flag6']" in content
+
+
 def test_linker_scripts_via_conf():
     profile = textwrap.dedent("""
         [settings]
diff --git a/test/unittests/util/detect_test.py b/test/unittests/util/detect_test.py
index 28b004230..1ac0e59a9 100644
--- a/test/unittests/util/detect_test.py
+++ b/test/unittests/util/detect_test.py
@@ -1,8 +1,11 @@
 import unittest
 
 import mock
+from unittest.mock import patch
+import pytest
 from parameterized import parameterized
 
+from conan.internal.api.detect_api import _cc_compiler
 from conans.client.conf.detect import detect_defaults_settings
 from conans.model.version import Version
 from conan.test.utils.mocks import RedirectedTestOutput
@@ -56,3 +59,18 @@ class DetectTest(unittest.TestCase):
             with environment_update({"CC": "clang-9 --gcc-toolchain=/usr/lib/gcc/x86_64-linux-gnu/9"}):
                 detect_defaults_settings()
                 self.assertIn("CC and CXX: clang-9 --gcc-toolchain", output)
+
+
+@pytest.mark.parametrize("version_return,expected_version", [
+    ["cc.exe (Rev3, Built by MSYS2 project) 14.1.0", "14.1.0"],
+    ["g++ (Conan-Build-gcc--binutils-2.42) 14.1.0", "14.1.0"],
+    ["clang version 18.1.0rc (https://github.com/llvm/llvm-project.git 461274b81d8641eab64d494accddc81d7db8a09e)", "18.1.0"],
+    ["cc.exe (Rev3, Built by MSYS2 project) 14.0", "14.0"],
+    ["clang version 18 (https://github.com/llvm/llvm-project.git 461274b81d8641eab64d494accddc81d7db8a09e)", "18"],
+    ["cc.exe (Rev3, Built by MSYS2 project) 14", "14"],
+])
+@patch("conan.internal.api.detect_api.detect_runner")
+def test_detect_cc_versionings(detect_runner_mock, version_return, expected_version):
+    detect_runner_mock.return_value = 0, version_return
+    compiler, installed_version, compiler_exe = _cc_compiler()
+    assert installed_version == Version(expected_version)