Repository: conan-io/conan
Pull request #12923 (2023-01-18 09:19:27)

MesonToolchain: guess Apple SDK path in case of cross-build on macOS if `tools.apple:sdk_path` not set, instead of raising an error
Changelog: (Fix): Guess Apple SDK path in `MesonToolchain` in case of cross-build on macOS if `tools.apple:sdk_path` is not set by user
Docs: https://github.com/conan-io/docs/pull/XXXX

closes https://github.com/conan-io/conan/issues/12921

- [x] Refer to the issue that supports this Pull Request.
- [x] If the issue has missing info, explain the purpose/use case/pain/need that covers this Pull Request.
- [x] I've read the [Contributing guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).
- [x] I've followed the PEP8 style guides for Python code.
- [ ] I've opened another PR in the Conan docs repo to the ``develop`` branch, documenting this one. 

<sup>**Note:** By default this PR will skip the slower tests and will use a limited set of python versions. Check [here](https://github.com/conan-io/conan/blob/develop/.github/PR_INCREASE_TESTING.md) how to increase the testing level by writing some tags in the current PR body text.</sup>



Patch:
diff --git a/conan/tools/cmake/layout.py b/conan/tools/cmake/layout.py
index abddc047f..959ae93e5 100644
--- a/conan/tools/cmake/layout.py
+++ b/conan/tools/cmake/layout.py
@@ -23,14 +23,11 @@ def cmake_layout(conanfile, generator=None, src_folder=".", build_folder="build"
 
     build_folder = build_folder if not subproject else os.path.join(subproject, build_folder)
     config_build_folder, user_defined_build = get_build_folder_custom_vars(conanfile)
-
     if config_build_folder:
-        conanfile.folders.build = os.path.join(build_folder, config_build_folder)
-    else:
-        if not multi and not user_defined_build:
-            conanfile.folders.build = os.path.join(build_folder, build_type)
-        else:
-            conanfile.folders.build = build_folder
+        build_folder = os.path.join(build_folder, config_build_folder)
+    if not multi and not user_defined_build:
+        build_folder = os.path.join(build_folder, build_type)
+    conanfile.folders.build = build_folder
 
     conanfile.folders.generators = os.path.join(conanfile.folders.build, "generators")
 
diff --git a/conan/tools/meson/toolchain.py b/conan/tools/meson/toolchain.py
index be0a53593..d4e808f16 100644
--- a/conan/tools/meson/toolchain.py
+++ b/conan/tools/meson/toolchain.py
@@ -5,7 +5,7 @@ from jinja2 import Template
 
 from conan.tools._check_build_profile import check_using_build_profile
 from conan.tools._compilers import libcxx_flags
-from conan.tools.apple.apple import to_apple_arch, is_apple_os, apple_min_version_flag
+from conan.tools.apple.apple import to_apple_arch, is_apple_os, apple_min_version_flag, apple_sdk_path
 from conan.tools.build.cross_building import cross_building, get_cross_building_settings
 from conan.tools.env import VirtualBuildEnv
 from conan.tools.meson.helpers import *
@@ -242,9 +242,12 @@ class MesonToolchain(object):
         if not self._is_apple_system:
             return
         # SDK path is mandatory for cross-building
-        sdk_path = self._conanfile.conf.get("tools.apple:sdk_path")
+        sdk_path = apple_sdk_path(self._conanfile)
         if not sdk_path and self.cross_build:
-            raise ConanException("You must provide a valid SDK path for cross-compilation.")
+            raise ConanException(
+                "Apple SDK path not found. For cross-compilation, you must "
+                "provide a valid SDK path in 'tools.apple:sdk_path' config."
+            )
 
         # TODO: Delete this os_sdk check whenever the _guess_apple_sdk_name() function disappears
         os_sdk = self._conanfile.settings.get_safe('os.sdk')
diff --git a/conan/tools/microsoft/nmakedeps.py b/conan/tools/microsoft/nmakedeps.py
index f8af5696e..fe77e9480 100644
--- a/conan/tools/microsoft/nmakedeps.py
+++ b/conan/tools/microsoft/nmakedeps.py
@@ -41,11 +41,23 @@ class NMakeDeps(object):
             ret.extend(cpp_info.exelinkflags or [])
             ret.extend(cpp_info.sharedlinkflags or [])
             ret.extend([format_lib(lib) for lib in cpp_info.libs or []])
+            ret.extend([format_lib(lib) for lib in cpp_info.system_libs or []])
             link_args = " ".join(ret)
 
+            def format_define(define):
+                if "=" in define:
+                    # CL env-var can't accept '=' sign in /D option, it can be replaced by '#' sign:
+                    # https://learn.microsoft.com/en-us/cpp/build/reference/cl-environment-variables
+                    macro, value = define.split("=", 1)
+                    if value and not value.isnumeric():
+                        value = f'\\"{value}\\"'
+                    define = f"{macro}#{value}"
+                return f"/D{define}"
+
             cl_flags = [f'-I"{p}"' for p in cpp_info.includedirs or []]
             cl_flags.extend(cpp_info.cflags or [])
             cl_flags.extend(cpp_info.cxxflags or [])
+            cl_flags.extend([format_define(define) for define in cpp_info.defines or []])
 
             env = Environment()
             env.append("CL", " ".join(cl_flags))
diff --git a/conan/tools/scm/__init__.py b/conan/tools/scm/__init__.py
index 59846cc18..7947ecae2 100644
--- a/conan/tools/scm/__init__.py
+++ b/conan/tools/scm/__init__.py
@@ -165,7 +165,7 @@ class Version:
     def __eq__(self, other):
         if other is None:
             return False
-        if isinstance(other, str):
+        if not isinstance(other, Version):
             other = Version(other)
 
         return (self._nonzero_items, self._pre, self._build) ==\


Test patch:
diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
index e172d3927..c4ae2e602 100644
--- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
+++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
@@ -587,7 +587,7 @@ def test_cmake_presets_multiple_settings_single_config():
                "-s compiler.version=12.0 -s compiler.cppstd=gnu17"
     client.run("install . {} {}".format(settings, settings_layout))
     assert os.path.exists(os.path.join(client.current_folder, "build", "apple-clang-12.0-gnu17",
-                                       "generators"))
+                                       "Release", "generators"))
     assert os.path.exists(user_presets_path)
     user_presets = json.loads(load(user_presets_path))
     assert len(user_presets["include"]) == 1
@@ -601,27 +601,33 @@ def test_cmake_presets_multiple_settings_single_config():
     assert presets["testPresets"][0]["name"] == "apple-clang-12.0-gnu17-release"
     assert presets["testPresets"][0]["configurePreset"] == "apple-clang-12.0-gnu17-release"
 
-    # If we create the "Debug" one, it has the same toolchain and preset file, that is
-    # always multiconfig
+    # If we create the "Debug" one, it will be appended
     client.run("install . {} -s build_type=Debug {}".format(settings, settings_layout))
-    assert os.path.exists(os.path.join(client.current_folder, "build", "apple-clang-12.0-gnu17", "generators"))
+    assert os.path.exists(os.path.join(client.current_folder, "build", "apple-clang-12.0-gnu17",
+                                       "Release", "generators"))
     assert os.path.exists(user_presets_path)
+    print(load(user_presets_path))
     user_presets = json.loads(load(user_presets_path))
-    assert len(user_presets["include"]) == 1
+    assert len(user_presets["include"]) == 2
     presets = json.loads(load(user_presets["include"][0]))
-    assert len(presets["configurePresets"]) == 2
-    assert len(presets["buildPresets"]) == 2
-    assert len(presets["testPresets"]) == 2
+    assert len(presets["configurePresets"]) == 1
+    assert len(presets["buildPresets"]) == 1
+    assert len(presets["testPresets"]) == 1
     assert presets["configurePresets"][0]["name"] == "apple-clang-12.0-gnu17-release"
-    assert presets["configurePresets"][1]["name"] == "apple-clang-12.0-gnu17-debug"
     assert presets["buildPresets"][0]["name"] == "apple-clang-12.0-gnu17-release"
-    assert presets["buildPresets"][1]["name"] == "apple-clang-12.0-gnu17-debug"
     assert presets["buildPresets"][0]["configurePreset"] == "apple-clang-12.0-gnu17-release"
-    assert presets["buildPresets"][1]["configurePreset"] == "apple-clang-12.0-gnu17-debug"
     assert presets["testPresets"][0]["name"] == "apple-clang-12.0-gnu17-release"
-    assert presets["testPresets"][1]["name"] == "apple-clang-12.0-gnu17-debug"
     assert presets["testPresets"][0]["configurePreset"] == "apple-clang-12.0-gnu17-release"
-    assert presets["testPresets"][1]["configurePreset"] == "apple-clang-12.0-gnu17-debug"
+
+    presets = json.loads(load(user_presets["include"][1]))
+    assert len(presets["configurePresets"]) == 1
+    assert len(presets["buildPresets"]) == 1
+    assert len(presets["testPresets"]) == 1
+    assert presets["configurePresets"][0]["name"] == "apple-clang-12.0-gnu17-debug"
+    assert presets["buildPresets"][0]["name"] == "apple-clang-12.0-gnu17-debug"
+    assert presets["buildPresets"][0]["configurePreset"] == "apple-clang-12.0-gnu17-debug"
+    assert presets["testPresets"][0]["name"] == "apple-clang-12.0-gnu17-debug"
+    assert presets["testPresets"][0]["configurePreset"] == "apple-clang-12.0-gnu17-debug"
 
     # But If we change, for example, the cppstd and the compiler version, the toolchain
     # and presets will be different, but it will be appended to the UserPresets.json
@@ -629,12 +635,12 @@ def test_cmake_presets_multiple_settings_single_config():
                "-s compiler.version=13 -s compiler.cppstd=gnu20"
     client.run("install . {} {}".format(settings, settings_layout))
     assert os.path.exists(os.path.join(client.current_folder, "build", "apple-clang-13-gnu20",
-                                       "generators"))
+                                       "Release", "generators"))
     assert os.path.exists(user_presets_path)
     user_presets = json.loads(load(user_presets_path))
     # The [0] is the apple-clang 12 the [1] is the apple-clang 13
-    assert len(user_presets["include"]) == 2
-    presets = json.loads(load(user_presets["include"][1]))
+    assert len(user_presets["include"]) == 3
+    presets = json.loads(load(user_presets["include"][2]))
     assert len(presets["configurePresets"]) == 1
     assert len(presets["buildPresets"]) == 1
     assert len(presets["testPresets"]) == 1
@@ -681,8 +687,12 @@ def test_cmake_presets_duplicated_install(multiconfig):
         settings += '-c tools.cmake.cmaketoolchain:generator="Multi-Config"'
     client.run("install . {}".format(settings))
     client.run("install . {}".format(settings))
-    presets_path = os.path.join(client.current_folder, "build", "gcc-5", "generators",
-                                "CMakePresets.json")
+    if multiconfig:
+        presets_path = os.path.join(client.current_folder, "build", "gcc-5", "generators",
+                                    "CMakePresets.json")
+    else:
+        presets_path = os.path.join(client.current_folder, "build", "gcc-5", "Release", "generators",
+                                    "CMakePresets.json")
     assert os.path.exists(presets_path)
     contents = json.loads(load(presets_path))
     assert len(contents["buildPresets"]) == 1
diff --git a/conans/test/functional/toolchains/meson/test_cross_compilation.py b/conans/test/functional/toolchains/meson/test_cross_compilation.py
index a03d9b971..89e4ad334 100644
--- a/conans/test/functional/toolchains/meson/test_cross_compilation.py
+++ b/conans/test/functional/toolchains/meson/test_cross_compilation.py
@@ -71,14 +71,8 @@ def test_apple_meson_toolchain_cross_compiling(arch, os_, os_version, sdk):
     compiler = apple-clang
     compiler.version = 12.0
     compiler.libcxx = libc++
-
-    [conf]
-    tools.apple:sdk_path={sdk_path}
     """)
 
-    xcrun = XCRun(None, sdk)
-    sdk_path = xcrun.sdk_path
-
     hello_h = gen_function_h(name="hello")
     hello_cpp = gen_function_cpp(name="hello", preprocessor=["STRING_DEFINITION"])
     app = gen_function_cpp(name="main", includes=["hello"], calls=["hello"])
@@ -86,8 +80,7 @@ def test_apple_meson_toolchain_cross_compiling(arch, os_, os_version, sdk):
         os=os_,
         os_version=os_version,
         os_sdk=sdk,
-        arch=arch,
-        sdk_path=sdk_path)
+        arch=arch)
 
     t = TestClient()
     t.save({"conanfile.py": _conanfile_py,
@@ -106,6 +99,7 @@ def test_apple_meson_toolchain_cross_compiling(arch, os_, os_version, sdk):
     demo = os.path.join(t.current_folder, "build", "demo")
     assert os.path.isfile(demo) is True
 
+    xcrun = XCRun(None, sdk)
     lipo = xcrun.find('lipo')
 
     t.run_command('"%s" -info "%s"' % (lipo, libhello))
diff --git a/conans/test/functional/toolchains/meson/test_meson_and_objc.py b/conans/test/functional/toolchains/meson/test_meson_and_objc.py
index 6f657a87b..c15080b82 100644
--- a/conans/test/functional/toolchains/meson/test_meson_and_objc.py
+++ b/conans/test/functional/toolchains/meson/test_meson_and_objc.py
@@ -98,13 +98,8 @@ def test_apple_meson_toolchain_cross_compiling_and_objective_c(arch, os_, os_ver
     compiler = apple-clang
     compiler.version = 12.0
     compiler.libcxx = libc++
-
-    [conf]
-    tools.apple:sdk_path={sdk_path}
     """)
 
-    xcrun = XCRun(None, sdk)
-    sdk_path = xcrun.sdk_path
     app = textwrap.dedent("""
     #import <Foundation/Foundation.h>
 
@@ -120,8 +115,7 @@ def test_apple_meson_toolchain_cross_compiling_and_objective_c(arch, os_, os_ver
         os=os_,
         os_version=os_version,
         os_sdk=sdk,
-        arch=arch,
-        sdk_path=sdk_path)
+        arch=arch)
 
     t = TestClient()
     t.save({"conanfile.py": _conanfile_py,
@@ -136,6 +130,7 @@ def test_apple_meson_toolchain_cross_compiling_and_objective_c(arch, os_, os_ver
     demo = os.path.join(t.current_folder, "build", "demo")
     assert os.path.isfile(demo) is True
 
+    xcrun = XCRun(None, sdk)
     lipo = xcrun.find('lipo')
     t.run_command('"%s" -info "%s"' % (lipo, demo))
     assert "architecture: %s" % to_apple_arch(arch) in t.out
diff --git a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
index 7f2bac7fb..c308a6279 100644
--- a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
+++ b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
@@ -583,7 +583,8 @@ def test_user_presets_version2():
 
             """)
     client.save({"conanfile.py": conanfile, "CMakeLists.txt": "foo"})
-    configs = ["-c tools.cmake.cmaketoolchain.presets:max_schema_version=2 ",
+    configs = ["-c tools.cmake.cmaketoolchain:generator=Ninja",
+               "-c tools.cmake.cmaketoolchain.presets:max_schema_version=2 ",
                "-c tools.cmake.cmake_layout:build_folder_vars='[\"settings.compiler.cppstd\"]'"]
     client.run("install . {} -s compiler.cppstd=14".format(" ".join(configs)))
     client.run("install . {} -s compiler.cppstd=17".format(" ".join(configs)))
@@ -591,10 +592,10 @@ def test_user_presets_version2():
     presets = json.loads(client.load("CMakeUserPresets.json"))
     assert len(presets["configurePresets"]) == 2
     assert presets["version"] == 2
-    assert "build/14/generators/conan_toolchain.cmake" \
+    assert "build/14/Release/generators/conan_toolchain.cmake" \
            in presets["configurePresets"][0]["cacheVariables"]["CMAKE_TOOLCHAIN_FILE"].replace("\\",
                                                                                                "/")
-    assert "build/17/generators/conan_toolchain.cmake" \
+    assert "build/17/Release/generators/conan_toolchain.cmake" \
            in presets["configurePresets"][1]["cacheVariables"]["CMAKE_TOOLCHAIN_FILE"].replace("\\",
                                                                                                "/")
 
@@ -751,7 +752,7 @@ def test_presets_ninja_msvc(arch, arch_toolset):
            "-s compiler.runtime_type=Release"
     client.run("install . {} -s compiler.cppstd=14 {} -s arch={}".format(" ".join(configs), msvc, arch))
 
-    presets = json.loads(client.load("build/14/generators/CMakePresets.json"))
+    presets = json.loads(client.load("build/14/Release/generators/CMakePresets.json"))
 
     toolset_value = {"x86_64": "host=x86_64", "x86": "x86"}.get(arch_toolset)
     arch_value = {"x86_64": "x64", "x86": "x86"}.get(arch)
@@ -779,7 +780,7 @@ def test_presets_ninja_msvc(arch, arch_toolset):
 
     client.run(
         "install . {} -s compiler.cppstd=14 {} -s arch={}".format(" ".join(configs), msvc, arch))
-    presets = json.loads(client.load("build/14/generators/CMakePresets.json"))
+    presets = json.loads(client.load("build/14/Release/generators/CMakePresets.json"))
     assert "architecture" in presets["configurePresets"][0]
     assert "toolset" not in presets["configurePresets"][0]
 
@@ -900,6 +901,11 @@ def test_cmake_layout_toolchain_folder():
           "-c tools.cmake.cmake_layout:build_folder_vars='[\"settings.arch\", \"settings.build_type\"]'")
     assert os.path.exists(os.path.join(c.current_folder,
                                        "build/x86-debug/generators/conan_toolchain.cmake"))
+    c.run("install . -s os=Linux -s compiler=gcc -s compiler.version=7 -s build_type=Debug "
+          "-s compiler.libcxx=libstdc++11 -s arch=x86 "
+          "-c tools.cmake.cmake_layout:build_folder_vars='[\"settings.os\"]'")
+    assert os.path.exists(os.path.join(c.current_folder,
+                                       "build/linux/Debug/generators/conan_toolchain.cmake"))
 
 
 def test_set_linker_scripts():
diff --git a/conans/test/integration/toolchains/microsoft/test_nmakedeps.py b/conans/test/integration/toolchains/microsoft/test_nmakedeps.py
new file mode 100644
index 000000000..3cf032512
--- /dev/null
+++ b/conans/test/integration/toolchains/microsoft/test_nmakedeps.py
@@ -0,0 +1,46 @@
+import platform
+import re
+import textwrap
+
+import pytest
+
+from conans.test.utils.tools import TestClient
+
+
+@pytest.mark.skipif(platform.system() != "Windows", reason="Only for windows")
+def test_nmakedeps():
+    client = TestClient()
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        class Pkg(ConanFile):
+            settings = "os", "arch", "compiler", "build_type"
+            name = "test-nmakedeps"
+            version = "1.0"
+
+            def package_info(self):
+                self.cpp_info.components["pkg-1"].libs = ["pkg-1"]
+                self.cpp_info.components["pkg-1"].defines = ["TEST_DEFINITION1"]
+                self.cpp_info.components["pkg-1"].system_libs = ["ws2_32"]
+                self.cpp_info.components["pkg-2"].libs = ["pkg-2"]
+                self.cpp_info.components["pkg-2"].defines = ["TEST_DEFINITION2=0"]
+                self.cpp_info.components["pkg-2"].requires = ["pkg-1"]
+                self.cpp_info.components["pkg-3"].libs = ["pkg-3"]
+                self.cpp_info.components["pkg-3"].defines = ["TEST_DEFINITION3="]
+                self.cpp_info.components["pkg-3"].requires = ["pkg-1", "pkg-2"]
+                self.cpp_info.components["pkg-4"].libs = ["pkg-4"]
+                self.cpp_info.components["pkg-4"].defines = ["TEST_DEFINITION4=foo"]
+    """)
+    client.save({"conanfile.py": conanfile})
+    client.run("create . -s arch=x86_64")
+    client.run("install test-nmakedeps/1.0@ -g NMakeDeps -s build_type=Release -s arch=x86_64")
+    # Checking that NMakeDeps builds correctly .bat file
+    bat_file = client.load("conannmakedeps.bat")
+    # Checking that defines are added to CL
+    for flag in (
+        r"/DTEST_DEFINITION1", r"/DTEST_DEFINITION2#0",
+        r"/DTEST_DEFINITION3#", r'/DTEST_DEFINITION4#\\"foo\\"',
+    ):
+        assert re.search(fr'set "CL=%CL%.*\s{flag}(?:\s|")', bat_file)
+    # Checking that libs and system libs are added to _LINK_
+    for flag in (r"pkg-1\.lib", r"pkg-2\.lib", r"pkg-3\.lib", r"pkg-4\.lib", r"ws2_32\.lib"):
+        assert re.search(fr'set "_LINK_=%_LINK_%.*\s{flag}(?:\s|")', bat_file)
diff --git a/conans/test/unittests/tools/scm/test_version.py b/conans/test/unittests/tools/scm/test_version.py
index 7c52bb49e..2f3ab2428 100644
--- a/conans/test/unittests/tools/scm/test_version.py
+++ b/conans/test/unittests/tools/scm/test_version.py
@@ -1,6 +1,7 @@
 import textwrap
 
 from conans.test.utils.tools import TestClient
+from conan.tools.scm import Version
 
 
 def test_version():
@@ -30,3 +31,18 @@ def test_version():
     assert "The patch of 1.2.3 is 3" in c.out
 
     c.run("create . {} -s compiler.version=9".format(settings), assert_error=True)
+
+
+def test_version_comparison():
+    v = Version("1.2")
+    assert v == "1.2"
+    assert v > 1
+    assert v > "1.1"
+    assert v > "1.1.1"
+    assert v >= 1
+    assert v >= "1.1"
+    assert v < 2
+    assert v < "1.3"
+    assert v < "1.2.2"
+    assert v <= 2
+    assert v <= "1.7"